diff --git a/.github/actions/prepare-tofu/action.yml b/.github/actions/prepare-tofu/action.yml index c64332e3cd..09e5f92c75 100644 --- a/.github/actions/prepare-tofu/action.yml +++ b/.github/actions/prepare-tofu/action.yml @@ -2,10 +2,6 @@ name: Prepare OpenTofu Deployment description: Prepares OpenTofu in infra/web. inputs: - opentofu-version: - description: The OpenTofu version to install. - required: false - default: '1.11.5' is_release_tag: description: Whether the build is for a release tag. Passed through to VITE_IS_RELEASE_TAG. required: false @@ -27,22 +23,20 @@ runs: SENTRY_ORG: ${{ env.SENTRY_ORG }} SENTRY_PROJECT: ${{ env.SENTRY_PROJECT }} - - name: Setup OpenTofu - uses: opentofu/setup-opentofu@9d84900f3238fab8cd84ce47d658d25dd008be2f # v1.0.8 - with: - tofu_version: ${{ inputs.opentofu-version }} + - name: Setup OpenTofu via mise + uses: jdx/mise-action@dad1bfd3df957f44999b559dd69dc1671cb4e9ea # v4.2.1 - name: Check formatting shell: bash working-directory: ${{ github.workspace }}/infra/web - run: tofu fmt -check + run: mise run opentofu -- fmt -check - name: Initialize OpenTofu shell: bash working-directory: ${{ github.workspace }}/infra/web - run: tofu init -input=false -reconfigure + run: mise run opentofu -- init -input=false -reconfigure - name: Validate configuration shell: bash working-directory: ${{ github.workspace }}/infra/web - run: tofu validate -no-color + run: mise run opentofu -- validate -no-color diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 9b4c9acbb3..c3464dc0b2 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -6,6 +6,10 @@ inputs: description: Command used to install dependencies. required: false default: pnpm install --frozen-lockfile + tauri: + description: Whether to install Tauri build dependencies via mise. + required: false + default: 'false' build: description: Whether to build the app after installing dependencies. required: false @@ -18,14 +22,10 @@ inputs: runs: using: composite steps: - - name: Setup pnpm - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0 - - - name: Setup node - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - name: Setup tools (mise) + uses: jdx/mise-action@dad1bfd3df957f44999b559dd69dc1671cb4e9ea # v4.2.1 with: - node-version-file: '.node-version' - cache: 'pnpm' + cache: true - name: Install dependencies shell: bash @@ -34,6 +34,12 @@ runs: env: INPUTS_INSTALL_COMMAND: ${{ inputs.install-command }} + - name: Install Tauri build dependencies + if: ${{ inputs.tauri == 'true' }} + shell: bash + working-directory: ${{ github.workspace }} + run: mise run tauri:setup + - name: Build app if: ${{ inputs.build == 'true' }} shell: bash diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3c4f0f7ef7..60224d4efb 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -53,3 +53,19 @@ updates: update-types: - minor - patch + + - package-ecosystem: cargo + cooldown: + default-days: 7 + directory: /src-tauri + schedule: + interval: daily + open-pull-requests-limit: 5 + labels: + - internal + commit-message: + prefix: chore + groups: + docker: + patterns: + - '*' diff --git a/.github/workflows/cloudflare-web-deploy.yml b/.github/workflows/cloudflare-web-deploy.yml index 14d65c869e..5bba6a672e 100644 --- a/.github/workflows/cloudflare-web-deploy.yml +++ b/.github/workflows/cloudflare-web-deploy.yml @@ -55,8 +55,16 @@ jobs: - name: Resolve release version id: version shell: bash + env: + GIT_TAG: ${{ inputs.git_tag }} + REF_NAME: ${{ github.ref_name }} + EVENT_NAME: ${{ github.event_name }} run: | - TAG="${{ github.event_name == 'workflow_dispatch' && inputs.git_tag || github.ref_name }}" + if [[ "$EVENT_NAME" == "workflow_dispatch" && -n "$GIT_TAG" ]]; then + TAG="$GIT_TAG" + else + TAG="$REF_NAME" + fi echo "version=${TAG#v}" >> "$GITHUB_OUTPUT" - name: Prepare OpenTofu deployment @@ -95,8 +103,16 @@ jobs: - name: Resolve release version id: version shell: bash + env: + GIT_TAG: ${{ inputs.git_tag }} + REF_NAME: ${{ github.ref_name }} + EVENT_NAME: ${{ github.event_name }} run: | - TAG="${{ github.event_name == 'workflow_dispatch' && inputs.git_tag || github.ref_name }}" + if [[ "$EVENT_NAME" == "workflow_dispatch" && -n "$GIT_TAG" ]]; then + TAG="$GIT_TAG" + else + TAG="$REF_NAME" + fi echo "version=${TAG#v}" >> "$GITHUB_OUTPUT" - name: Prepare OpenTofu deployment @@ -112,7 +128,7 @@ jobs: SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} - name: Plan infrastructure - run: tofu plan -input=false -no-color + run: mise run opentofu -- plan -input=false -no-color - name: Apply infrastructure - run: tofu apply -input=false -auto-approve + run: mise run opentofu -- apply -input=false -auto-approve diff --git a/.github/workflows/cloudflare-web-dev.yml b/.github/workflows/cloudflare-web-dev.yml index ef2f711a94..e21bf8388d 100644 --- a/.github/workflows/cloudflare-web-dev.yml +++ b/.github/workflows/cloudflare-web-dev.yml @@ -62,7 +62,7 @@ jobs: SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} - name: Plan infrastructure - run: tofu plan -input=false -no-color + run: mise run opentofu -- plan -input=false -no-color - name: Apply infrastructure - run: tofu apply -input=false -auto-approve + run: mise run opentofu -- apply -input=false -auto-approve diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index bdb1d0bd5e..07e14f5034 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -58,6 +58,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false # Add any setup steps before running the `github/codeql-action/init` action. # This includes steps like installing compilers or runtimes (`actions/setup-node` diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 7b6ed21bc9..b52dd86b2b 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -49,8 +49,8 @@ jobs: id: release_tag shell: bash run: | - if [[ "${{ github.event_name }}" == "workflow_dispatch" && -n "${{ inputs.git_tag }}" ]]; then - TAG="${{ inputs.git_tag }}" + if [[ "${{ github.event_name }}" == "workflow_dispatch" && -n "${INPUTS_GIT_TAG}" ]]; then + TAG="${INPUTS_GIT_TAG}" else TAG="${GITHUB_REF#refs/tags/}" fi @@ -65,6 +65,8 @@ jobs: echo "value=" >> "$GITHUB_OUTPUT" echo "is_release=false" >> "$GITHUB_OUTPUT" fi + env: + INPUTS_GIT_TAG: ${{ inputs.git_tag }} - name: Extract metadata id: meta diff --git a/.github/workflows/flake-lock-fix.yml b/.github/workflows/flake-lock-fix.yml index c7b844e721..fdf800a849 100644 --- a/.github/workflows/flake-lock-fix.yml +++ b/.github/workflows/flake-lock-fix.yml @@ -42,17 +42,20 @@ jobs: - name: Get GitHub App User ID id: get-user-id - run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT" + run: echo "user-id=$(gh api "/users/${STEPS_APP_TOKEN_OUTPUTS_APP_SLUG}[bot]" --jq .id)" >> "$GITHUB_OUTPUT" env: GH_TOKEN: ${{ steps.app-token.outputs.token }} + STEPS_APP_TOKEN_OUTPUTS_APP_SLUG: ${{ steps.app-token.outputs.app-slug }} - name: Configure bot git identity run: | - git config --global user.name '${{ steps.app-token.outputs.app-slug }}[bot]' - git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com' + git config --global user.name '${STEPS_APP_TOKEN_OUTPUTS_APP_SLUG}[bot]' + git config --global user.email '${STEPS_GET_USER_ID_OUTPUTS_USER_ID}+${STEPS_APP_TOKEN_OUTPUTS_APP_SLUG}[bot]@users.noreply.github.com' gh auth setup-git env: GH_TOKEN: ${{ steps.app-token.outputs.token }} + STEPS_APP_TOKEN_OUTPUTS_APP_SLUG: ${{ steps.app-token.outputs.app-slug }} + STEPS_GET_USER_ID_OUTPUTS_USER_ID: ${{ steps.get-user-id.outputs.user-id }} - name: Commit and push if changed if: steps.check.outcome == 'failure' diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 60573416bf..29ae03c5d1 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -20,6 +20,8 @@ jobs: with: app-id: ${{ secrets.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} + permission-contents: write + permission-pull-requests: write - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -29,12 +31,8 @@ jobs: - uses: fregante/setup-git-user@024bc0b8e177d7e77203b48dab6fb45666854b35 # v2.0.2 - - uses: knope-dev/action@19617851f9f13ab2f27a05989c55efb18aca3675 # v2.1.2 - with: - version: 0.22.3 - - name: Prepare Release - run: knope prepare-release --verbose + run: mise run knope -- prepare-release --verbose env: GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} diff --git a/.github/workflows/quality-checks.yml b/.github/workflows/quality-checks.yml index a9ac437082..265ecafc67 100644 --- a/.github/workflows/quality-checks.yml +++ b/.github/workflows/quality-checks.yml @@ -40,7 +40,7 @@ jobs: - name: Setup app uses: ./.github/actions/setup - - name: Run ESLint + - name: Run Lint run: pnpm run lint typecheck: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e6e5f9d7f1..6eca8284bc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,6 +20,7 @@ jobs: with: app-id: ${{ secrets.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} + permission-contents: write - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -27,11 +28,7 @@ jobs: persist-credentials: true token: ${{ steps.generate-token.outputs.token }} - - uses: knope-dev/action@19617851f9f13ab2f27a05989c55efb18aca3675 # v2.1.2 - with: - version: 0.22.3 - - name: Create Release - run: knope release --verbose + run: mise run knope -- release --verbose env: GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} diff --git a/.github/workflows/rust-quality.yml b/.github/workflows/rust-quality.yml index f7fdd08f00..98679c0943 100644 --- a/.github/workflows/rust-quality.yml +++ b/.github/workflows/rust-quality.yml @@ -20,17 +20,19 @@ jobs: runs-on: ubuntu-latest permissions: contents: read + env: + CARGO_INCREMENTAL: 0 + CARGO_TERM_COLOR: always steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - name: Setup Rust - uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c # 2c7215f132e9ebf062739d9130488b56d53c060c + - name: Setup mise + uses: jdx/mise-action@dad1bfd3df957f44999b559dd69dc1671cb4e9ea # v4.2.1 with: - toolchain: stable - components: rustfmt + cache: true - name: Check Rust formatting run: cargo fmt --manifest-path src-tauri/Cargo.toml -- --check @@ -40,6 +42,9 @@ jobs: runs-on: ubuntu-22.04 permissions: contents: read + env: + CARGO_INCREMENTAL: 0 + CARGO_TERM_COLOR: always steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -60,11 +65,10 @@ jobs: libxdo-dev \ pkg-config - - name: Setup Rust - uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c # 2c7215f132e9ebf062739d9130488b56d53c060c + - name: Setup mise + uses: jdx/mise-action@dad1bfd3df957f44999b559dd69dc1671cb4e9ea # v4.2.1 with: - toolchain: stable - components: clippy + cache: true - name: Cache Rust build uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 diff --git a/.github/workflows/tauri-build.yml b/.github/workflows/tauri-build.yml index a427044e72..2104fd6926 100644 --- a/.github/workflows/tauri-build.yml +++ b/.github/workflows/tauri-build.yml @@ -120,7 +120,7 @@ jobs: - name: macOS platform: macos-latest asset-platform: macos-universal - rust-targets: aarch64-apple-darwin,x86_64-apple-darwin + rust-targets: aarch64-apple-darwin x86_64-apple-darwin args: --target universal-apple-darwin bundle_path: src-tauri/target/universal-apple-darwin/release/bundle - name: Windows @@ -140,6 +140,8 @@ jobs: VERSION: ${{ needs.setup-release.outputs.version }} VITE_APP_VERSION: ${{ needs.setup-release.outputs.version }} VITE_IS_RELEASE_TAG: ${{ needs.setup-release.outputs.nightly == 'true' && 'true' || '' }} + CARGO_INCREMENTAL: 0 + CARGO_TERM_COLOR: always NODE_OPTIONS: --max-old-space-size=8192 TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} @@ -150,34 +152,15 @@ jobs: ref: ${{ needs.setup-release.outputs.ref }} persist-credentials: false - - name: Install Linux build dependencies - if: matrix.cef - shell: bash - run: | - sudo apt-get update - sudo apt-get install -y \ - libwebkit2gtk-4.1-dev \ - libgtk-3-dev \ - libayatana-appindicator3-dev \ - librsvg2-dev \ - libsoup-3.0-dev \ - libssl-dev \ - libxdo-dev \ - build-essential \ - file \ - rpm \ - ruby \ - ruby-dev - sudo gem install --no-document fpm - - name: Setup app uses: ./.github/actions/setup - - - name: Setup Rust - uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c # 2c7215f132e9ebf062739d9130488b56d53c060c with: - toolchain: stable - targets: ${{ matrix.rust-targets }} + tauri: 'true' + + - name: Add Rust targets + if: ${{ matrix.rust-targets != '' }} + shell: bash + run: rustup target add ${{ matrix.rust-targets }} - name: Cache Rust build uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 @@ -190,8 +173,7 @@ jobs: UPDATER_ENDPOINT: ${{ needs.setup-release.outputs.nightly == 'true' && format('https://github.com/{0}/releases/download/nightly/latest.json', github.repository) || '' }} run: node .github/scripts/set-tauri-version.mjs "$VERSION" "$UPDATER_ENDPOINT" - # --- macOS / Windows: standard wry build via tauri-action --- - - name: Configure desktop build + - name: Configure build args shell: bash env: IS_NIGHTLY: ${{ needs.setup-release.outputs.nightly }} @@ -210,11 +192,22 @@ jobs: echo "TAURI_BUILD_ARGS=${ARGS# }" >> "$GITHUB_ENV" - name: Build desktop bundles - id: tauri if: ${{ !matrix.cef }} - uses: tauri-apps/tauri-action@1deb371b0cd8bd54025b384f1cd735e725c4060f # v1.0.0 - with: - args: ${{ matrix.args }} ${{ env.TAURI_BUILD_ARGS }} + shell: bash + run: pnpm tauri build ${{ matrix.args }} $TAURI_BUILD_ARGS + + - name: Package macOS .app for updater + if: ${{ matrix.name == 'macOS' }} + shell: bash + run: | + macos_dir="${{ matrix.bundle_path }}/macos" + if [ -d "$macos_dir" ]; then + for app in "$macos_dir"/*.app; do + [ -e "$app" ] || continue + echo "Packaging $(basename "$app") into .tar.gz" + tar czf "$app.tar.gz" -C "$macos_dir" "$(basename "$app")" + done + fi - name: Normalize desktop artifact names if: ${{ !matrix.cef }} @@ -275,7 +268,7 @@ jobs: - name: Build native packages if: matrix.cef shell: bash - run: bash scripts/cef-package.sh "$VERSION" + run: mise run cef:package "$VERSION" - name: Sign the AppImage for the updater if: ${{ matrix.cef && env.TAURI_SIGNING_PRIVATE_KEY != '' }} @@ -329,7 +322,11 @@ jobs: VERSION: ${{ needs.setup-release.outputs.version }} VITE_APP_VERSION: ${{ needs.setup-release.outputs.version }} VITE_IS_RELEASE_TAG: ${{ needs.setup-release.outputs.nightly == 'true' && 'true' || '' }} + CARGO_INCREMENTAL: 0 + CARGO_TERM_COLOR: always + MISE_ENV: tauri NODE_OPTIONS: --max-old-space-size=8192 + RUST_TARGETS: aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android ANDROID_KEY_BASE64: ${{ secrets.ANDROID_KEY_BASE64 }} ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} @@ -342,31 +339,29 @@ jobs: - name: Setup app uses: ./.github/actions/setup - - - name: Setup Java - uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 with: - distribution: temurin - java-version: '17' + tauri: 'true' - - name: Setup Android SDK - uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1 + - name: Setup Android SDK + NDK + shell: bash + run: mise run tauri:setup:android - - name: Setup Rust - uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c # 2c7215f132e9ebf062739d9130488b56d53c060c - with: - toolchain: stable - targets: aarch64-linux-android,armv7-linux-androideabi,i686-linux-android,x86_64-linux-android + - name: Add Rust targets + shell: bash + run: rustup target add $RUST_TARGETS - name: Cache Rust build uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 with: workspaces: src-tauri + - name: Setup Gradle + uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0 + - name: Resolve NDK shell: bash run: | - echo "NDK_HOME=$ANDROID_HOME/ndk/$(ls -1 "$ANDROID_HOME/ndk" | sort -V | tail -n1)" >> "$GITHUB_ENV" + echo "NDK_HOME=$ANDROID_HOME/ndk/$ANDROID_NDK_VERSION" >> "$GITHUB_ENV" - name: Stamp release version into tauri.conf.json shell: bash @@ -435,6 +430,9 @@ jobs: VERSION: ${{ needs.setup-release.outputs.version }} VITE_APP_VERSION: ${{ needs.setup-release.outputs.version }} VITE_IS_RELEASE_TAG: ${{ needs.setup-release.outputs.nightly == 'true' && 'true' || '' }} + CARGO_INCREMENTAL: 0 + CARGO_TERM_COLOR: always + RUST_TARGETS: aarch64-apple-ios NODE_OPTIONS: --max-old-space-size=8192 steps: - name: Checkout repository @@ -445,12 +443,12 @@ jobs: - name: Setup app uses: ./.github/actions/setup - - - name: Setup Rust - uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c # 2c7215f132e9ebf062739d9130488b56d53c060c with: - toolchain: stable - targets: aarch64-apple-ios + tauri: 'true' + + - name: Add Rust targets + shell: bash + run: rustup target add $RUST_TARGETS - name: Cache Rust build uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 @@ -461,13 +459,13 @@ jobs: shell: bash run: node .github/scripts/set-tauri-version.mjs "$VERSION" - - name: Setup Xcode + - name: Set up iOS shell: bash - run: pnpm tauri ios init + run: mise -y run tauri:setup:ios - name: Symlink icons to the ios folder shell: bash - run: pnpm run setup:tauri-icons --write --force + run: mise run icons:symlink --write --force - name: Build unsigned IPA id: ipa @@ -504,7 +502,8 @@ jobs: shell: bash env: GH_TOKEN: ${{ github.token }} - run: gh release upload "$TAG" "${{ steps.ipa.outputs.path }}" --clobber + STEPS_IPA_OUTPUTS_PATH: ${{ steps.ipa.outputs.path }} + run: gh release upload "$TAG" "${STEPS_IPA_OUTPUTS_PATH}" --clobber - name: Update AltStore/SideStore source shell: bash @@ -513,8 +512,10 @@ jobs: REPO: ${{ github.repository }} ALTSTORE_VERSION: ${{ steps.ipa.outputs.ipa_version }} ALTSTORE_VERSION_BUILD: ${{ steps.ipa.outputs.ipa_build }} + STEPS_IPA_OUTPUTS_NAME: ${{ steps.ipa.outputs.name }} + STEPS_IPA_OUTPUTS_SIZE: ${{ steps.ipa.outputs.size }} run: | - DOWNLOAD_URL="https://github.com/$REPO/releases/download/$TAG/${{ steps.ipa.outputs.name }}" + DOWNLOAD_URL="https://github.com/$REPO/releases/download/$TAG/${STEPS_IPA_OUTPUTS_NAME}" DATE="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" if gh release download "$TAG" --pattern 'altstore-source.json' --dir . --clobber 2>/dev/null; then echo "Using existing altstore-source.json from release $TAG" @@ -522,7 +523,7 @@ jobs: echo "Using repo altstore-source.json" fi node .github/scripts/update-altstore-source.mjs \ - altstore-source.json "${ALTSTORE_VERSION:-$VERSION}" "${ALTSTORE_VERSION_BUILD:-$VERSION}" "${{ steps.ipa.outputs.size }}" "$DOWNLOAD_URL" "$DATE" "Sable $VERSION" + altstore-source.json "${ALTSTORE_VERSION:-$VERSION}" "${ALTSTORE_VERSION_BUILD:-$VERSION}" "${STEPS_IPA_OUTPUTS_SIZE}" "$DOWNLOAD_URL" "$DATE" "Sable $VERSION" gh release upload "$TAG" altstore-source.json --clobber updater-manifest: @@ -554,8 +555,9 @@ jobs: shell: bash env: GH_TOKEN: ${{ github.token }} + NEEDS_SETUP_RELEASE_OUTPUTS_NIGHTLY: ${{ needs.setup-release.outputs.nightly }} run: | - if [ "${{ needs.setup-release.outputs.nightly }}" = "true" ]; then + if [ "${NEEDS_SETUP_RELEASE_OUTPUTS_NIGHTLY}" = "true" ]; then gh release view "$TAG" --json assets \ | jq -r --arg cutoff "$STARTED_AT" \ '.assets[] | select(.updatedAt <= $cutoff and (.name | endswith(".sig"))) | .name' \ diff --git a/.github/zizmor.yml b/.github/zizmor.yml new file mode 100644 index 0000000000..191a75c29d --- /dev/null +++ b/.github/zizmor.yml @@ -0,0 +1,4 @@ +rules: + cache-poisoning: + remap: + severity: informational diff --git a/.node-version b/.node-version deleted file mode 100644 index 32f8c50de0..0000000000 --- a/.node-version +++ /dev/null @@ -1 +0,0 @@ -24.13.1 diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.nvmrc b/.nvmrc deleted file mode 100644 index a4a7a41bca..0000000000 --- a/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -v24.14.0 diff --git a/README.md b/README.md index 1246d7b64b..afff172ae9 100644 --- a/README.md +++ b/README.md @@ -102,23 +102,38 @@ Invalid or unknown keys are ignored. * For example, if you want to deploy on `https://sable.moe/app`, then set `base: '/app'`. ## Local development + > [!TIP] -> We recommend using a version manager as versions change quickly. [fnm](https://github.com/Schniz/fnm) is a great cross-platform option (Windows, macOS, and Linux). [NVM on Windows](https://github.com/coreybutler/nvm-windows#installation--upgrades) and [nvm](https://github.com/nvm-sh/nvm) on Linux/macOS are also good choices. Use the version defined in [`.node-version`](.node-version). +> The easiest way to get started is with [mise](https://mise.jdx.dev/getting-started.html), it manages node, pnpm, rust, and other tooling. -Execute the following commands to start a development server: -```sh -fnm use --corepack-enabled # Activates the Node version and enables corepack -# If you not using fnm, install corepack manually: npm install --global corepack@latest -corepack install # Installs the pnpm version specified in package.json -pnpm i # Installs all dependencies -pnpm run dev # Serve a development version +```bash +mise install # Install all required tools +mise run setup # Install dependencies (pnpm install) +mise run dev # Start the Vite dev server ``` +Run `mise tasks` to list all available tasks (build, test, lint, etc.). + To build the app: ```sh -pnpm run build # Compiles the app into the dist/ directory +mise run build ``` +### Desktop & Mobile (Tauri) + +Sable uses [Tauri](https://v2.tauri.app) for native desktop and mobile builds. + +```bash +mise run tauri:setup # Install Rust toolchain + system packages +mise run tauri:setup:macos # Install Xcode (macOS only) +mise run tauri:setup:windows # Install VS Build Tools + WebView2 (Windows only) +mise run tauri wry dev # Dev server with system webview (WebKit/WebView2) +mise run tauri cef build # Production build with Chromium Embedded Framework +mise run tauri --help # Any other args pass through to the Tauri CLI +``` + +When the first argument is `wry` or `cef` and the second is `dev` or `build`, the wrapper injects `--features ,updater --no-default-features`. Everything else is forwarded to `tauri` as-is. + ## Deployment and infrastructure Deployment workflows and infrastructure details live in [`infra/README.md`](infra/README.md). diff --git a/knip.json b/knip.json index 5075b01ce2..6b0e9046d5 100644 --- a/knip.json +++ b/knip.json @@ -13,7 +13,7 @@ "@matrix-org/matrix-sdk-crypto-wasm", "@sableclient/twemoji-font" ], - "ignoreBinaries": ["knope"], + "ignoreBinaries": ["knope", "mise"], "rules": { "exports": "off", "types": "off", diff --git a/knope.toml b/knope.toml index 04cc4e690e..24ed9d44cb 100644 --- a/knope.toml +++ b/knope.toml @@ -1,5 +1,5 @@ [package] -versioned_files = ["package.json", "src-tauri/tauri.conf.json"] +versioned_files = ["package.json", "src-tauri/tauri.conf.json", "src-tauri/Cargo.toml"] changelog = "CHANGELOG.md" extra_changelog_sections = [ { name = "Security", types = [ diff --git a/mise.tauri.toml b/mise.tauri.toml new file mode 100644 index 0000000000..9362bb0b01 --- /dev/null +++ b/mise.tauri.toml @@ -0,0 +1,66 @@ +# Tauri-specific dev tools and system dependencies. +# Loaded via `MISE_ENV=tauri` (set by `mise run setup:tauri` or CI). + +[env] +ANDROID_NDK_VERSION = "29.0.14206865" + +[settings] +idiomatic_version_file_enable_tools = ["pnpm", "node", "rust"] +experimental = true + +[tools] +# Android +java = "temurin-21" +android-sdk = "22.0" + +[bootstrap.packages] +# Debian/Ubuntu +"apt:build-essential" = "latest" +"apt:file" = "latest" +"apt:libayatana-appindicator3-dev" = "latest" +"apt:libgtk-3-dev" = "latest" +"apt:librsvg2-dev" = "latest" +"apt:libsoup-3.0-dev" = "latest" +"apt:libssl-dev" = "latest" +"apt:libwebkit2gtk-4.1-dev" = "latest" +"apt:libxdo-dev" = "latest" +"apt:pkg-config" = "latest" +"apt:curl" = "latest" +"apt:wget" = "latest" + +# Fedora/RHEL +"dnf:webkit2gtk4.1-devel" = "latest" +"dnf:openssl-devel" = "latest" +"dnf:libappindicator-gtk3-devel" = "latest" +"dnf:librsvg2-devel" = "latest" +"dnf:libxdo-devel" = "latest" +"dnf:curl" = "latest" +"dnf:wget" = "latest" +"dnf:file" = "latest" + +# Arch +"pacman:webkit2gtk-4.1" = "latest" +"pacman:base-devel" = "latest" +"pacman:openssl" = "latest" +"pacman:librsvg" = "latest" +"pacman:xdotool" = "latest" +"pacman:curl" = "latest" +"pacman:wget" = "latest" +"pacman:file" = "latest" + +# Alpine +"apk:webkit2gtk-4.1-dev" = "latest" +"apk:build-base" = "latest" +"apk:openssl" = "latest" +"apk:libayatana-appindicator-dev" = "latest" +"apk:librsvg" = "latest" +"apk:curl" = "latest" +"apk:wget" = "latest" +"apk:file" = "latest" + +# macOS +"brew:curl" = "latest" +"brew:wget" = "latest" + +# iOS +"brew:cocoapods" = "latest" diff --git a/mise.toml b/mise.toml new file mode 100644 index 0000000000..a951554dba --- /dev/null +++ b/mise.toml @@ -0,0 +1,134 @@ +min_version = '2026.7.11' + +[env] +_.path = ["{{config_root}}/node_modules/.bin"] + +[settings] +idiomatic_version_file_enable_tools = ["pnpm", "node"] +minimum_release_age = "1d" +experimental = true + +[task_config] +includes = ["scripts"] + +[tasks.setup] +description = "Install dependencies with pnpm" +run = 'pnpm install' +sources = ['package.json', 'pnpm-lock.yaml', 'pnpm-workspace.yaml', 'mise.toml'] +outputs = ['node_modules/.pnpm/lock.yaml'] + +[tasks.dev] +description = "Start Vite dev server" +alias = "d" +run = "vite dev" +raw = true + +[tasks.build] +description = "Build frontend with Vite" +alias = "b" +run = "vite build" + +[tasks.preview] +description = "Preview the production build" +alias = "p" +run = "vite preview" + +[tasks.lint] +description = "Check lint, types, formatting, and dead code" +alias = "l" +run = ["tsc", "oxlint .", "oxfmt --check .", "knip"] + +[tasks."lint:fix"] +description = "Fix lint, formatting, and dead code issues" +alias = "lf" +run = ["oxlint . --fix", "oxfmt .", "knip --fix"] + +[tasks.test] +description = "Run tests" +alias = "t" +run = "vitest" +raw = true + +[tasks.knope] +run = "knope" +raw_args = true +hide = true +tools = { "cargo:knope" = "0.23.0", cargo-binstall = "1.20.1" } + +[tasks."knope:document-change"] +description = "Document a change with knope" +alias = ["doc", "doc"] +run = { task = "knope", args = ["document-change"] } + +[tasks."tauri:setup"] +description = "Install Tauri tools and system packages" +run = ["mise install", "mise bootstrap packages apply --yes"] +env = { MISE_ENV = "tauri" } + +[tasks."tauri:setup:android"] +description = "Install Android SDK packages + NDK (no Android Studio needed)" +depends = ["tauri:setup"] +env = { MISE_ENV = "tauri" } +run = [ + "yes | sdkmanager --licenses", + 'sdkmanager "platform-tools" "platforms;android-36" "build-tools;35.0.0" "ndk;{{env.ANDROID_NDK_VERSION}}"', +] + +[tasks."tauri:setup:macos"] +description = "Install Xcode" +confirm = { message = "Install Xcode? (required for Tauri)", default = "no" } +run = "xcode-select --install" + +[tasks."tauri:setup:ios"] +description = "Set up iOS Xcode project" +depends = ["tauri:setup:macos"] +confirm = { message = "Set up iOS Xcode project? (required for Tauri)", default = "no" } +run = [{ task = "tauri", args = ["ios", "init"] }] + +[tasks."tauri:setup:windows"] +description = "Install Visual Studio Build Tools + WebView2 runtime" +confirm = { message = "Install Visual Studio Build Tools and WebView2 runtime? (required for Tauri)", default = "no" } +run = [ + "winget install --id Microsoft.VisualStudio.BuildTools --source winget --force --override '--add Microsoft.VisualStudio.Workload.VCTools --includeRecommended --add Microsoft.VisualStudio.Component.Windows11SDK.26100 --addProductLang En-us'", + "winget install --id Microsoft.EdgeWebView2Runtime --source winget", +] + +[tasks."cargo:lint"] +description = "Check Rust formatting and Clippy lints" +alias = ["cl", "cargo-lint"] +dir = "src-tauri" +run = ["cargo fmt --check", "cargo clippy --locked --all-targets --all-features -- -D warnings"] + +[tasks."cargo:lint:fix"] +description = "Fix Rust formatting and Clippy issues" +alias = ["clf", "cargo-lint-fix"] +dir = "src-tauri" +run = ["cargo fmt", "cargo clippy --fix --allow-dirty --all-targets --all-features -- -D warnings"] + +[tasks."cargo:test"] +description = "Run Rust tests" +alias = ["ct", "cargo-test"] +dir = "src-tauri" +run = "cargo test --locked" +raw = true + +[tasks."actions:pin"] +description = "Pin GitHub Actions to SHA commit hashes" +run = "pinact run" +tools = { "pinact" = "latest" } + +[tasks."actions:audit"] +description = "Audit GitHub Actions for security issues" +run = "zizmor .github/workflows" +tools = { "zizmor" = "latest" } + +[tasks."actions:update"] +description = "Update pinned GitHub Actions to latest SHAs" +run = "pinact run -u" +tools = { "pinact" = "latest" } + +[tasks.opentofu] +run = "tofu" +raw_args = true +hide = true +tools = { "opentofu" = "1.11.5" } diff --git a/nfpm.yaml b/nfpm.yaml new file mode 100644 index 0000000000..8f44eab705 --- /dev/null +++ b/nfpm.yaml @@ -0,0 +1,48 @@ +# yaml-language-server: $schema=https://nfpm.goreleaser.com/schema.json + +name: sable +arch: amd64 +platform: linux +version: ${PKG_VERSION} +release: ${PKG_RELEASE} +version_schema: semver +section: net +maintainer: SableClient +description: Sable, a Matrix client +homepage: https://sable.moe + +contents: + - src: ${PKGROOT}/opt/sable/ + dst: /opt/sable + type: tree + - src: ${PKGROOT}/usr/ + dst: /usr + type: tree + +overrides: + deb: + depends: + - libgtk-3-0 + - libnss3 + - libnspr4 + - libgbm1 + - libdrm2 + - libxkbcommon0 + - libasound2 + - libcups2 + - xdg-utils + - xwayland + - libayatana-appindicator3-1 + rpm: + depends: + - gtk3 + - nss + - nspr + - mesa-libgbm + - libdrm + - libxkbcommon + - alsa-lib + - cups-libs + - xdg-utils + - xorg-x11-server-Xwayland + - libayatana-appindicator3.so.1()(64bit) diff --git a/package.json b/package.json index 9eff87f136..85b4c0a8c0 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "document-change": "knope document-change", "postinstall": "node scripts/install-knope.js", "setup:knope": "node scripts/install-knope.js", - "setup:tauri-icons": "node scripts/symlink-tauri-icons.js", + "setup:tauri-icons": "node scripts/icons/symlink.js", "lint:normalize-imports": "node scripts/normalize-imports.js" }, "dependencies": { @@ -150,14 +150,11 @@ "vitest": "^4.1.9", "wrangler": "^4.106.0" }, - "engines": { - "node": "24.x", - "pnpm": ">=10" - }, - "packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be", - "pnpm": { - "overrides": { - "jsdom>undici": "^7.28.0" + "devEngines": { + "runtime": { + "name": "node", + "version": "24.x" } - } + }, + "packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9f8fc1757b..4ac5e2a67f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,7 +5,15 @@ settings: excludeLinksFromLockfile: false overrides: + brace-expansion: '>=1.1.12' + esbuild: '>=0.25.0' + flatted: '>=3.4.2' jsdom>undici: ^7.28.0 + lodash: '>=4.17.23' + minimatch: '>=10.2.3' + rollup: '>=4.59.0' + serialize-javascript: '>=7.0.3' + undici: '>=7.24.0' importers: @@ -254,16 +262,16 @@ importers: version: 0.2.3(esbuild@0.28.1) '@rollup/plugin-inject': specifier: ^5.0.5 - version: 5.0.5(rollup@2.80.0) + version: 5.0.5(rollup@4.62.0) '@rollup/plugin-wasm': specifier: ^6.2.2 - version: 6.2.2(rollup@2.80.0) + version: 6.2.2(rollup@4.62.0) '@sableclient/sable-call-embedded': specifier: 1.1.7 version: 1.1.7 '@sentry/vite-plugin': specifier: ^5.3.0 - version: 5.3.0(rollup@2.80.0) + version: 5.3.0(rollup@4.62.0) '@tauri-apps/cli': specifier: 2.11.4 version: 2.11.4 @@ -338,7 +346,7 @@ importers: version: 7.3.5(@types/node@24.13.2)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0) vite-plugin-compression2: specifier: ^2.5.3 - version: 2.5.3(rollup@2.80.0) + version: 2.5.3(rollup@4.62.0) vite-plugin-pwa: specifier: ^1.3.0 version: 1.3.0(vite@7.3.5(@types/node@24.13.2)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0))(workbox-build@7.4.0(@types/babel__core@7.20.5))(workbox-window@7.4.0) @@ -347,10 +355,10 @@ importers: version: 3.4.0(vite@7.3.5(@types/node@24.13.2)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0)) vite-plugin-svgr: specifier: 4.5.0 - version: 4.5.0(rollup@2.80.0)(typescript@5.9.3)(vite@7.3.5(@types/node@24.13.2)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0)) + version: 4.5.0(rollup@4.62.0)(typescript@5.9.3)(vite@7.3.5(@types/node@24.13.2)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0)) vite-plugin-top-level-await: specifier: ^1.6.0 - version: 1.6.0(@swc/helpers@0.5.23)(rollup@2.80.0)(vite@7.3.5(@types/node@24.13.2)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0)) + version: 1.6.0(@swc/helpers@0.5.23)(rollup@4.62.0)(vite@7.3.5(@types/node@24.13.2)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0)) vitest: specifier: ^4.1.9 version: 4.1.9(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@7.3.5(@types/node@24.13.2)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0)) @@ -1052,7 +1060,7 @@ packages: '@esbuild-plugins/node-globals-polyfill@0.2.3': resolution: {integrity: sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==} peerDependencies: - esbuild: '*' + esbuild: '>=0.25.0' '@esbuild/aix-ppc64@0.27.7': resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} @@ -2313,7 +2321,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0 '@types/babel__core': ^7.1.9 - rollup: ^1.20.0||^2.0.0 + rollup: '>=4.59.0' peerDependenciesMeta: '@types/babel__core': optional: true @@ -2322,7 +2330,7 @@ packages: resolution: {integrity: sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + rollup: '>=4.59.0' peerDependenciesMeta: rollup: optional: true @@ -2331,7 +2339,7 @@ packages: resolution: {integrity: sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: ^2.78.0||^3.0.0||^4.0.0 + rollup: '>=4.59.0' peerDependenciesMeta: rollup: optional: true @@ -2339,13 +2347,13 @@ packages: '@rollup/plugin-replace@2.4.2': resolution: {integrity: sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==} peerDependencies: - rollup: ^1.20.0 || ^2.0.0 + rollup: '>=4.59.0' '@rollup/plugin-terser@0.4.4': resolution: {integrity: sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: ^2.0.0||^3.0.0||^4.0.0 + rollup: '>=4.59.0' peerDependenciesMeta: rollup: optional: true @@ -2354,7 +2362,7 @@ packages: resolution: {integrity: sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + rollup: '>=4.59.0' peerDependenciesMeta: rollup: optional: true @@ -2363,7 +2371,7 @@ packages: resolution: {integrity: sha512-gpC4R1G9Ni92ZIRTexqbhX7U+9estZrbhP+9SRb0DW9xpB9g7j34r+J2hqrcW/lRI7dJaU84MxZM0Rt82tqYPQ==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + rollup: '>=4.59.0' peerDependenciesMeta: rollup: optional: true @@ -2372,13 +2380,13 @@ packages: resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==} engines: {node: '>= 8.0.0'} peerDependencies: - rollup: ^1.20.0||^2.0.0 + rollup: '>=4.59.0' '@rollup/pluginutils@5.4.0': resolution: {integrity: sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + rollup: '>=4.59.0' peerDependenciesMeta: rollup: optional: true @@ -2621,7 +2629,7 @@ packages: resolution: {integrity: sha512-hgPGPYdQJ/G1cGYOxAb7d4z3V+/k/E5/P/5TFPEEBLuIbFFk+JG0CISUDJdzXJjO382Lb99PBJuXGbueBmO79w==} engines: {node: '>= 18'} peerDependencies: - rollup: '>=3.2.0' + rollup: '>=4.59.0' peerDependenciesMeta: rollup: optional: true @@ -3214,9 +3222,6 @@ packages: badwords-list@2.0.1-4: resolution: {integrity: sha512-FxfZUp7B9yCnesNtFQS9v6PvZdxTYa14Q60JR6vhjdQdWI4naTjJIyx22JzoER8ooeT8SAAKoHLjKfCV7XgYUQ==} - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - balanced-match@4.0.4: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} @@ -3248,9 +3253,6 @@ packages: blurhash@2.0.5: resolution: {integrity: sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w==} - brace-expansion@2.1.1: - resolution: {integrity: sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==} - brace-expansion@5.0.6: resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} engines: {node: 18 || 20 || >=22} @@ -4291,10 +4293,6 @@ packages: resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} engines: {node: 18 || 20 || >=22} - minimatch@5.1.9: - resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} - engines: {node: '>=10'} - minipass@7.1.3: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} @@ -4514,9 +4512,6 @@ packages: raf-schd@4.0.3: resolution: {integrity: sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==} - randombytes@2.1.0: - resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} - react-aria@3.50.0: resolution: {integrity: sha512-S0Os6QZk33fzUAKu1QLT9afoUaCBt1ZNdoiq0n2YMVgKIdNIQS8zxiZ8O9hYE6QyDkHKjD6q39LQZ+qaSAIgjw==} peerDependencies: @@ -4672,11 +4667,6 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - rollup@2.80.0: - resolution: {integrity: sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==} - engines: {node: '>=10.0.0'} - hasBin: true - rollup@4.62.0: resolution: {integrity: sha512-nc72Wgq62I7rtDV4izT5/aaS0zxy3kttkinf9586ApknY3jZO9NYsmtc24fUckA0X7Q2v+ML4a15pdUlV5V/jA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -4686,9 +4676,6 @@ packages: resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} engines: {node: '>=0.4'} - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - safe-push-apply@1.0.0: resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} engines: {node: '>= 0.4'} @@ -4720,8 +4707,9 @@ packages: engines: {node: '>=10'} hasBin: true - serialize-javascript@6.0.2: - resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + serialize-javascript@7.0.7: + resolution: {integrity: sha512-YAy8Od6KV+uuwUuU50np8fGB/Aues6Y0nAhA9y/hId74PlKUcme4pXcBD46NWKr1Q4osN/iseZ17YqO1XfmI8g==} + engines: {node: '>=20.0.0'} set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} @@ -5171,7 +5159,7 @@ packages: peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 '@vitejs/devtools': ^0.1.18 - esbuild: ^0.27.0 || ^0.28.0 + esbuild: '>=0.25.0' jiti: '>=1.21.0' less: ^4.0.0 sass: ^1.70.0 @@ -7004,73 +6992,73 @@ snapshots: '@rolldown/pluginutils@1.0.1': {} - '@rollup/plugin-babel@5.3.1(@babel/core@7.29.7)(@types/babel__core@7.20.5)(rollup@2.80.0)': + '@rollup/plugin-babel@5.3.1(@babel/core@7.29.7)(@types/babel__core@7.20.5)(rollup@4.62.0)': dependencies: '@babel/core': 7.29.7 '@babel/helper-module-imports': 7.29.7 - '@rollup/pluginutils': 3.1.0(rollup@2.80.0) - rollup: 2.80.0 + '@rollup/pluginutils': 3.1.0(rollup@4.62.0) + rollup: 4.62.0 optionalDependencies: '@types/babel__core': 7.20.5 transitivePeerDependencies: - supports-color - '@rollup/plugin-inject@5.0.5(rollup@2.80.0)': + '@rollup/plugin-inject@5.0.5(rollup@4.62.0)': dependencies: - '@rollup/pluginutils': 5.4.0(rollup@2.80.0) + '@rollup/pluginutils': 5.4.0(rollup@4.62.0) estree-walker: 2.0.2 magic-string: 0.30.21 optionalDependencies: - rollup: 2.80.0 + rollup: 4.62.0 - '@rollup/plugin-node-resolve@15.3.1(rollup@2.80.0)': + '@rollup/plugin-node-resolve@15.3.1(rollup@4.62.0)': dependencies: - '@rollup/pluginutils': 5.4.0(rollup@2.80.0) + '@rollup/pluginutils': 5.4.0(rollup@4.62.0) '@types/resolve': 1.20.2 deepmerge: 4.3.1 is-module: 1.0.0 resolve: 1.22.12 optionalDependencies: - rollup: 2.80.0 + rollup: 4.62.0 - '@rollup/plugin-replace@2.4.2(rollup@2.80.0)': + '@rollup/plugin-replace@2.4.2(rollup@4.62.0)': dependencies: - '@rollup/pluginutils': 3.1.0(rollup@2.80.0) + '@rollup/pluginutils': 3.1.0(rollup@4.62.0) magic-string: 0.25.9 - rollup: 2.80.0 + rollup: 4.62.0 - '@rollup/plugin-terser@0.4.4(rollup@2.80.0)': + '@rollup/plugin-terser@0.4.4(rollup@4.62.0)': dependencies: - serialize-javascript: 6.0.2 + serialize-javascript: 7.0.7 smob: 1.6.2 terser: 5.48.0 optionalDependencies: - rollup: 2.80.0 + rollup: 4.62.0 - '@rollup/plugin-virtual@3.0.2(rollup@2.80.0)': + '@rollup/plugin-virtual@3.0.2(rollup@4.62.0)': optionalDependencies: - rollup: 2.80.0 + rollup: 4.62.0 - '@rollup/plugin-wasm@6.2.2(rollup@2.80.0)': + '@rollup/plugin-wasm@6.2.2(rollup@4.62.0)': dependencies: - '@rollup/pluginutils': 5.4.0(rollup@2.80.0) + '@rollup/pluginutils': 5.4.0(rollup@4.62.0) optionalDependencies: - rollup: 2.80.0 + rollup: 4.62.0 - '@rollup/pluginutils@3.1.0(rollup@2.80.0)': + '@rollup/pluginutils@3.1.0(rollup@4.62.0)': dependencies: '@types/estree': 0.0.39 estree-walker: 1.0.1 picomatch: 2.3.2 - rollup: 2.80.0 + rollup: 4.62.0 - '@rollup/pluginutils@5.4.0(rollup@2.80.0)': + '@rollup/pluginutils@5.4.0(rollup@4.62.0)': dependencies: '@types/estree': 1.0.9 estree-walker: 2.0.2 picomatch: 4.0.4 optionalDependencies: - rollup: 2.80.0 + rollup: 4.62.0 '@rollup/rollup-android-arm-eabi@4.62.0': optional: true @@ -7244,20 +7232,20 @@ snapshots: '@sentry/browser-utils': 10.63.0 '@sentry/core': 10.63.0 - '@sentry/rollup-plugin@5.3.0(rollup@2.80.0)': + '@sentry/rollup-plugin@5.3.0(rollup@4.62.0)': dependencies: '@sentry/bundler-plugin-core': 5.3.0 magic-string: 0.30.21 optionalDependencies: - rollup: 2.80.0 + rollup: 4.62.0 transitivePeerDependencies: - encoding - supports-color - '@sentry/vite-plugin@5.3.0(rollup@2.80.0)': + '@sentry/vite-plugin@5.3.0(rollup@4.62.0)': dependencies: '@sentry/bundler-plugin-core': 5.3.0 - '@sentry/rollup-plugin': 5.3.0(rollup@2.80.0) + '@sentry/rollup-plugin': 5.3.0(rollup@4.62.0) transitivePeerDependencies: - encoding - rollup @@ -7900,8 +7888,6 @@ snapshots: badwords-list@2.0.1-4: {} - balanced-match@1.0.2: {} - balanced-match@4.0.4: {} base-x@5.0.1: {} @@ -7922,10 +7908,6 @@ snapshots: blurhash@2.0.5: {} - brace-expansion@2.1.1: - dependencies: - balanced-match: 1.0.2 - brace-expansion@5.0.6: dependencies: balanced-match: 4.0.4 @@ -8368,7 +8350,7 @@ snapshots: filelist@1.0.6: dependencies: - minimatch: 5.1.9 + minimatch: 10.2.5 fill-range@7.1.1: dependencies: @@ -9021,10 +9003,6 @@ snapshots: dependencies: brace-expansion: 5.0.6 - minimatch@5.1.9: - dependencies: - brace-expansion: 2.1.1 - minipass@7.1.3: {} mlly@1.8.2: @@ -9283,10 +9261,6 @@ snapshots: raf-schd@4.0.3: {} - randombytes@2.1.0: - dependencies: - safe-buffer: 5.2.1 - react-aria@3.50.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@internationalized/date': 3.12.2 @@ -9472,10 +9446,6 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.3 '@rolldown/binding-win32-x64-msvc': 1.0.3 - rollup@2.80.0: - optionalDependencies: - fsevents: 2.3.3 - rollup@4.62.0: dependencies: '@types/estree': 1.0.9 @@ -9515,8 +9485,6 @@ snapshots: has-symbols: 1.1.0 isarray: 2.0.5 - safe-buffer@5.2.1: {} - safe-push-apply@1.0.0: dependencies: es-errors: 1.3.0 @@ -9546,9 +9514,7 @@ snapshots: semver@7.8.4: {} - serialize-javascript@6.0.2: - dependencies: - randombytes: 2.1.0 + serialize-javascript@7.0.7: {} set-function-length@1.2.2: dependencies: @@ -9976,9 +9942,9 @@ snapshots: - tsx - yaml - vite-plugin-compression2@2.5.3(rollup@2.80.0): + vite-plugin-compression2@2.5.3(rollup@4.62.0): dependencies: - '@rollup/pluginutils': 5.4.0(rollup@2.80.0) + '@rollup/pluginutils': 5.4.0(rollup@4.62.0) tar-mini: 0.2.0 transitivePeerDependencies: - rollup @@ -10002,9 +9968,9 @@ snapshots: tinyglobby: 0.2.17 vite: 7.3.5(@types/node@24.13.2)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0) - vite-plugin-svgr@4.5.0(rollup@2.80.0)(typescript@5.9.3)(vite@7.3.5(@types/node@24.13.2)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0)): + vite-plugin-svgr@4.5.0(rollup@4.62.0)(typescript@5.9.3)(vite@7.3.5(@types/node@24.13.2)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0)): dependencies: - '@rollup/pluginutils': 5.4.0(rollup@2.80.0) + '@rollup/pluginutils': 5.4.0(rollup@4.62.0) '@svgr/core': 8.1.0(typescript@5.9.3) '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.9.3)) vite: 7.3.5(@types/node@24.13.2)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0) @@ -10013,9 +9979,9 @@ snapshots: - supports-color - typescript - vite-plugin-top-level-await@1.6.0(@swc/helpers@0.5.23)(rollup@2.80.0)(vite@7.3.5(@types/node@24.13.2)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0)): + vite-plugin-top-level-await@1.6.0(@swc/helpers@0.5.23)(rollup@4.62.0)(vite@7.3.5(@types/node@24.13.2)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0)): dependencies: - '@rollup/plugin-virtual': 3.0.2(rollup@2.80.0) + '@rollup/plugin-virtual': 3.0.2(rollup@4.62.0) '@swc/core': 1.15.41(@swc/helpers@0.5.23) '@swc/wasm': 1.15.41 uuid: 10.0.0 @@ -10185,10 +10151,10 @@ snapshots: '@babel/core': 7.29.7 '@babel/preset-env': 7.29.7(@babel/core@7.29.7) '@babel/runtime': 7.29.7 - '@rollup/plugin-babel': 5.3.1(@babel/core@7.29.7)(@types/babel__core@7.20.5)(rollup@2.80.0) - '@rollup/plugin-node-resolve': 15.3.1(rollup@2.80.0) - '@rollup/plugin-replace': 2.4.2(rollup@2.80.0) - '@rollup/plugin-terser': 0.4.4(rollup@2.80.0) + '@rollup/plugin-babel': 5.3.1(@babel/core@7.29.7)(@types/babel__core@7.20.5)(rollup@4.62.0) + '@rollup/plugin-node-resolve': 15.3.1(rollup@4.62.0) + '@rollup/plugin-replace': 2.4.2(rollup@4.62.0) + '@rollup/plugin-terser': 0.4.4(rollup@4.62.0) '@surma/rollup-plugin-off-main-thread': 2.2.3 ajv: 8.20.0 common-tags: 1.8.2 @@ -10197,7 +10163,7 @@ snapshots: glob: 11.1.0 lodash: 4.18.1 pretty-bytes: 5.6.0 - rollup: 2.80.0 + rollup: 4.62.0 source-map: 0.8.0-beta.0 stringify-object: 3.3.0 strip-comments: 2.0.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index dd46996060..0b5e9b21ec 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -18,6 +18,7 @@ overrides: brace-expansion: '>=1.1.12' esbuild: '>=0.25.0' flatted: '>=3.4.2' + jsdom>undici: '^7.28.0' lodash: '>=4.17.23' minimatch: '>=10.2.3' rollup: '>=4.59.0' diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000000..59277fee77 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "1.97.1" +components = ["clippy", "rustfmt"] diff --git a/scripts/cef-copy-libs.sh b/scripts/cef/copy-libs.sh similarity index 96% rename from scripts/cef-copy-libs.sh rename to scripts/cef/copy-libs.sh index 99e0261266..30603f8170 100644 --- a/scripts/cef-copy-libs.sh +++ b/scripts/cef/copy-libs.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +#MISE description="Copy CEF runtime libs next to the built binary" # Copy the CEF runtime (libcef.so + resources) next to a built binary so the app # is self-contained: works standalone, when launched by the OS via a sable:// # deep link, or from a release package. Pair with the rpath=$ORIGIN that diff --git a/scripts/cef-package.sh b/scripts/cef/package.sh old mode 100755 new mode 100644 similarity index 61% rename from scripts/cef-package.sh rename to scripts/cef/package.sh index aeff0fd11f..0523d777f5 --- a/scripts/cef-package.sh +++ b/scripts/cef/package.sh @@ -1,12 +1,15 @@ #!/usr/bin/env bash +#MISE description="Package CEF build into deb/rpm/AppImage" +#MISE tools={nfpm="2.47.0", "github:AppImage/appimagetool" = {version = "1.9.1", matching = ".AppImage"}} # Package the Linux CEF build into deb/rpm/AppImage. Used by CI and locally. # Run after the binary is built (pnpm tauri:cef build). -# Usage: scripts/cef-package.sh [version] +# Usage: scripts/cef/package.sh [version] set -euo pipefail -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" cd "$ROOT" VERSION="${1:-$(grep -m1 '"version":' src-tauri/tauri.conf.json | sed 's/.*: *"\(.*\)".*/\1/')}" +: "${VERSION:?version not found in src-tauri/tauri.conf.json}" DEB_VERSION="$VERSION" RPM_VERSION="$VERSION" RPM_ITERATION=1 @@ -44,7 +47,7 @@ stage_runtime() { local dest="$1" mkdir -p "$dest" cp "$STAGE/$BIN_NAME" "$dest/sable" - bash scripts/cef-copy-libs.sh release "$dest" + bash scripts/cef/copy-libs.sh release "$dest" } # Bundle the system-tray libraries (Tauri's linuxdeploy path normally does this, @@ -86,8 +89,10 @@ MimeType=x-scheme-handler/sable;x-scheme-handler/moe.sable.app; EOF } -if command -v fpm >/dev/null 2>&1; then +CONFIG="$ROOT/nfpm.yaml" +if command -v nfpm >/dev/null 2>&1; then PKGROOT="$WORK/pkgroot" + export PKGROOT stage_runtime "$PKGROOT/opt/sable" mkdir -p "$PKGROOT/usr/bin" "$PKGROOT/usr/share/applications" cat > "$PKGROOT/usr/bin/sable" <<'EOF' @@ -101,57 +106,39 @@ EOF cp "src-tauri/icons/${size}.png" \ "$PKGROOT/usr/share/icons/hicolor/${size}/apps/sable.png" done - # 128x128@2x is 256x256 mkdir -p "$PKGROOT/usr/share/icons/hicolor/256x256/apps" cp "src-tauri/icons/128x128@2x.png" \ "$PKGROOT/usr/share/icons/hicolor/256x256/apps/sable.png" - COMMON=(-s dir -n sable - --description "Sable, a Matrix client" - --url "https://sable.moe" --maintainer "SableClient" - --category net) - # CEF deps: no WebKitGTK. ALSA/CUPS are NEEDED by libcef.so; - # xwayland because main.rs forces GDK_BACKEND=x11. - DEB_DEPS=(--depends libgtk-3-0 --depends libnss3 --depends libnspr4 - --depends libgbm1 --depends libdrm2 --depends libxkbcommon0 - --depends libasound2 --depends libcups2 --depends xdg-utils - --depends xwayland --depends libayatana-appindicator3-1) - (cd "$OUT/deb" && fpm "${COMMON[@]}" -v "$DEB_VERSION" --iteration 1 \ - -t deb -a amd64 -p "Sable-${VERSION}-linux-x86_64.deb" "${DEB_DEPS[@]}" -C "$PKGROOT" .) + PKG_VERSION="$DEB_VERSION" PKG_RELEASE=1 nfpm pkg -f "$CONFIG" -p deb \ + -t "$OUT/deb/Sable-${VERSION}-linux-x86_64.deb" - RPM_DEPS=(--depends gtk3 --depends nss --depends nspr - --depends mesa-libgbm --depends libdrm --depends libxkbcommon - --depends alsa-lib --depends cups-libs --depends xdg-utils - --depends xorg-x11-server-Xwayland - --depends 'libayatana-appindicator3.so.1()(64bit)') - (cd "$OUT/rpm" && fpm "${COMMON[@]}" -v "$RPM_VERSION" --iteration "$RPM_ITERATION" \ - -t rpm -a x86_64 -p "Sable-${VERSION}-linux-x86_64.rpm" "${RPM_DEPS[@]}" -C "$PKGROOT" .) + PKG_VERSION="$RPM_VERSION" PKG_RELEASE="$RPM_ITERATION" nfpm pkg -f "$CONFIG" -p rpm \ + -t "$OUT/rpm/Sable-${VERSION}-linux-x86_64.rpm" else - echo "fpm not found; skipping deb/rpm (building AppImage only)" + echo "nfpm not found; skipping deb/rpm" fi -APPDIR="$WORK/Sable.AppDir" -stage_runtime "$APPDIR/usr/bin" -stage_appindicator "$APPDIR/usr/bin" -# nosuid AppImage mount: drop setuid chrome-sandbox, use the namespace sandbox. -rm -f "$APPDIR/usr/bin/chrome-sandbox" -write_desktop "$APPDIR/sable.desktop" -cp src-tauri/icons/128x128.png "$APPDIR/sable.png" -cat > "$APPDIR/AppRun" <<'EOF' +if command -v appimagetool >/dev/null 2>&1; then + APPDIR="$WORK/Sable.AppDir" + stage_runtime "$APPDIR/usr/bin" + stage_appindicator "$APPDIR/usr/bin" + # nosuid AppImage mount: drop setuid chrome-sandbox, use the namespace sandbox. + rm -f "$APPDIR/usr/bin/chrome-sandbox" + write_desktop "$APPDIR/sable.desktop" + cp src-tauri/icons/128x128.png "$APPDIR/sable.png" + cat > "$APPDIR/AppRun" <<'EOF' #!/bin/sh HERE="$(dirname "$(readlink -f "$0")")" export LD_LIBRARY_PATH="$HERE/usr/bin${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" exec "$HERE/usr/bin/sable" "$@" EOF -chmod 755 "$APPDIR/AppRun" + chmod 755 "$APPDIR/AppRun" -APPIMAGETOOL_SHA256=ed4ce84f0d9caff66f50bcca6ff6f35aae54ce8135408b3fa33abfc3cb384eb0 -APPIMAGETOOL="$WORK/appimagetool" -curl -fsSL -o "$APPIMAGETOOL" \ - https://github.com/AppImage/appimagetool/releases/download/1.9.1/appimagetool-x86_64.AppImage -echo "${APPIMAGETOOL_SHA256} $APPIMAGETOOL" | sha256sum -c - -chmod +x "$APPIMAGETOOL" -APPIMAGE_EXTRACT_AND_RUN=1 ARCH=x86_64 "$APPIMAGETOOL" "$APPDIR" \ - "$OUT/appimage/Sable-${VERSION}-linux-x86_64.AppImage" + ARCH=x86_64 appimagetool "$APPDIR" \ + "$OUT/appimage/Sable-${VERSION}-linux-x86_64.AppImage" +else + echo "appimagetool not found; skipping AppImage" >&2 +fi echo "Packages in: $OUT" diff --git a/scripts/icons/replace.js b/scripts/icons/replace.js new file mode 100644 index 0000000000..ba35bec148 --- /dev/null +++ b/scripts/icons/replace.js @@ -0,0 +1,183 @@ +#!/usr/bin/env node +//MISE description="Replace icon assets from SVG inputs" +//MISE tools={ffmpeg="7.1.1"} + +import { execFileSync } from 'node:child_process'; +import { existsSync, mkdirSync, copyFileSync } from 'node:fs'; +import { dirname, resolve, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { createInterface } from 'node:readline'; +import { PrefixedLogger, createTextHelpers } from '../utils/console-style.js'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const BASE_DIR = resolve(__dirname, '..'); + +function resolveBinary(name) { + try { + const root = execFileSync('mise', ['where', name], { encoding: 'utf-8' }).trim(); + const candidates = [ + join(root, 'Library', 'bin', `${name}.exe`), + join(root, 'bin', `${name}.exe`), + join(root, `${name}.exe`), + ]; + for (const exe of candidates) { + if (existsSync(exe)) return exe; + } + } catch { + /* fallback to path */ + } + return name; +} + +const FFMPEG = resolveBinary('ffmpeg'); + +const logger = new PrefixedLogger('[replace-logo]'); +const { red } = createTextHelpers({ useColor: logger.useColor }); + +/** + * Display a question on stdin and return the user's trimmed response. + * @param {string} question - The prompt text to display. + * @returns {Promise} The user's input with surrounding whitespace removed. + */ +function prompt(question) { + const rl = createInterface({ input: process.stdin, output: process.stdout }); + return new Promise((resolve_) => { + rl.question(question, (answer) => { + rl.close(); + resolve_(answer.trim()); + }); + }); +} + +/** Print CLI usage instructions to stdout. */ +function usage() { + logger.info(`Usage: run this script and provide four file paths when prompted. +It will replace icons in the repository public/ tree using ffmpeg. + +You will be prompted for: + - maskless (borderless) logo (used for maskable icons) + - circular logo (used for circular icons) + - highlight variant (icon for highlight indicator) + - unread variant (icon for unread indicator) + +Requires: ffmpeg, base64`); +} + +/** + * Render an SVG to a PNG at the given square dimension using ffmpeg. + * Creates the output directory if it doesn't already exist. + * @param {string} input - Absolute path to the source SVG file. + * @param {string} output - Absolute path for the generated PNG. + * @param {string | number} size - Width and height in pixels. + */ +function ffmpegCmd(input, output, size) { + mkdirSync(dirname(output), { recursive: true }); + logger.info(`Generating ${output} (${size} x ${size})`); + execFileSync( + FFMPEG, + ['-y', '-width', String(size), '-height', String(size), '-i', input, output], + { + stdio: 'pipe', + } + ); +} + +/** + * Check whether a file path has an SVG extension (case-insensitive). + * @param {string} filePath + * @returns {boolean} + */ +function isSvg(filePath) { + return filePath.toLowerCase().endsWith('.svg'); +} + +/** + * Entry point. Prompts for four SVG logo variants, validates them, + * then renders PNGs at every required size and copies SVGs into public/res/svg/. + * @param {string[]} args - CLI arguments (supports --help / -h). + */ +async function main(args) { + if (args.includes('--help') || args.includes('-h')) { + usage(); + process.exit(0); + } + + const maskless = await prompt('Path to maskless (borderless) logo: '); + const circular = await prompt('Path to circular logo: '); + const highlight = await prompt('Path to highlight variant: '); + const unread = await prompt('Path to unread variant: '); + + for (const f of [maskless, circular, highlight, unread]) { + if (!existsSync(f)) { + logger.error(`${red('File not found:')} ${f}`); + process.exit(2); + } + if (!isSvg(f)) { + logger.error(`${red('Input must be an SVG:')} ${f}`); + process.exit(3); + } + } + + /** @type {[number, string][]} */ + const CIRCULAR_TARGETS = [ + [512, 'public/favicon.png'], + [2560, 'public/full_res_sable.png'], + [144, 'public/res/logo/logo-144x144.png'], + [192, 'public/res/logo/logo-192x192.png'], + [256, 'public/res/logo/logo-256x256.png'], + [36, 'public/res/logo/logo-36x36.png'], + [384, 'public/res/logo/logo-384x384.png'], + [48, 'public/res/logo/logo-48x48.png'], + [512, 'public/res/logo/logo-512x512.png'], + [72, 'public/res/logo/logo-72x72.png'], + [96, 'public/res/logo/logo-96x96.png'], + ]; + + logger.info('Replacing circular icons...'); + for (const [size, out] of CIRCULAR_TARGETS) { + ffmpegCmd(circular, resolve(BASE_DIR, out), size); + } + + /** @type {[number, string][]} */ + const MASKABLE_TARGETS = [ + [114, 'public/res/logo-maskable/logo-maskable-114x114.png'], + [120, 'public/res/logo-maskable/logo-maskable-120x120.png'], + [144, 'public/res/logo-maskable/logo-maskable-144x144.png'], + [152, 'public/res/logo-maskable/logo-maskable-152x152.png'], + [167, 'public/res/logo-maskable/logo-maskable-167x167.png'], + [180, 'public/res/logo-maskable/logo-maskable-180x180.png'], + [192, 'public/res/logo-maskable/logo-maskable-192x192.png'], + [256, 'public/res/logo-maskable/logo-maskable-256x256.png'], + [36, 'public/res/logo-maskable/logo-maskable-36x36.png'], + [384, 'public/res/logo-maskable/logo-maskable-384x384.png'], + [48, 'public/res/logo-maskable/logo-maskable-48x48.png'], + [512, 'public/res/logo-maskable/logo-maskable-512x512.png'], + [57, 'public/res/logo-maskable/logo-maskable-57x57.png'], + [60, 'public/res/logo-maskable/logo-maskable-60x60.png'], + [72, 'public/res/logo-maskable/logo-maskable-72x72.png'], + [76, 'public/res/logo-maskable/logo-maskable-76x76.png'], + [96, 'public/res/logo-maskable/logo-maskable-96x96.png'], + ]; + + logger.info('Replacing maskable icons (using maskless input)...'); + for (const [size, out] of MASKABLE_TARGETS) { + ffmpegCmd(maskless, resolve(BASE_DIR, out), size); + } + + logger.info('Handling SVG assets in public/res/svg/...'); + const svgDir = resolve(BASE_DIR, 'public/res/svg'); + mkdirSync(svgDir, { recursive: true }); + + logger.info(`Copying circular SVG to ${svgDir}/logo.svg`); + copyFileSync(circular, resolve(svgDir, 'logo.svg')); + + logger.info(`Copying maskable SVG to ${svgDir}/logo-maskable.svg`); + copyFileSync(maskless, resolve(svgDir, 'logo-maskable.svg')); + + copyFileSync(highlight, resolve(svgDir, 'highlight.svg')); + copyFileSync(unread, resolve(svgDir, 'unread.svg')); + + logger.info('All done. Please review the generated files.'); +} + +main(process.argv.slice(2)); diff --git a/scripts/symlink-tauri-icons.js b/scripts/icons/symlink.js similarity index 96% rename from scripts/symlink-tauri-icons.js rename to scripts/icons/symlink.js index 25674f335c..1f0c4ae8f2 100644 --- a/scripts/symlink-tauri-icons.js +++ b/scripts/icons/symlink.js @@ -1,10 +1,12 @@ #!/usr/bin/env node +//MISE description="Create Tauri icon symlinks" +//MISE raw_args=true /* eslint-disable no-console */ import fs from 'node:fs'; import path from 'node:path'; import process from 'node:process'; -import { createTextHelpers } from './utils/console-style.js'; +import { createTextHelpers } from '../utils/console-style.js'; const ANDROID_ICONS = [ 'mipmap-hdpi', diff --git a/scripts/install-knope.js b/scripts/install-knope.js index 74fd9c2643..a3bb04045f 100644 --- a/scripts/install-knope.js +++ b/scripts/install-knope.js @@ -1,4 +1,6 @@ #!/usr/bin/env node +//MISE hide=true +//MISE description="Install knope" import { spawnSync } from 'node:child_process'; import process from 'node:process'; import { chmodSync, existsSync, mkdirSync, realpathSync, writeFileSync } from 'node:fs'; diff --git a/scripts/normalize-imports.js b/scripts/normalize-imports.js index 77af442c38..415e366967 100644 --- a/scripts/normalize-imports.js +++ b/scripts/normalize-imports.js @@ -1,4 +1,6 @@ #!/usr/bin/env node +//MISE hide=true +//MISE description="Normalize import paths across the codebase" /* oxlint-disable no-console */ import fs from 'node:fs/promises'; diff --git a/scripts/replace-logo.sh b/scripts/replace-logo.sh deleted file mode 100755 index 5d92318512..0000000000 --- a/scripts/replace-logo.sh +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -readonly BASE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" - -usage(){ - cat <&2 - exit 2 - fi - if [[ "${f,,}" != *.svg ]]; then - echo "Input must be an SVG: $f" >&2 - exit 3 - fi -done - -ffmpeg_cmd(){ - local in="$1" out="$2" size="$3" - mkdir -p "$(dirname "$out")" - echo "Generating $out ($size x $size)" - ffmpeg -y -width "$size" -height "$size" -i "$in" "$out" 2>/dev/null -} - -# All inputs are required to be SVG. Use ffmpeg to render SVGs to PNG targets -# and copy SVGs directly to the svg assets directory. - -is_svg(){ - local f="$1" - [[ "${f,,}" == *.svg ]] -} - -echo "Replacing circular icons..." - -declare -a CIRCULAR_TARGETS=( - "512:$BASE_DIR/public/favicon.png" - "2560:$BASE_DIR/public/full_res_sable.png" - "144:$BASE_DIR/public/res/logo/logo-144x144.png" - "192:$BASE_DIR/public/res/logo/logo-192x192.png" - "256:$BASE_DIR/public/res/logo/logo-256x256.png" - "36:$BASE_DIR/public/res/logo/logo-36x36.png" - "384:$BASE_DIR/public/res/logo/logo-384x384.png" - "48:$BASE_DIR/public/res/logo/logo-48x48.png" - "512:$BASE_DIR/public/res/logo/logo-512x512.png" - "72:$BASE_DIR/public/res/logo/logo-72x72.png" - "96:$BASE_DIR/public/res/logo/logo-96x96.png" -) - -for entry in "${CIRCULAR_TARGETS[@]}"; do - size=${entry%%:*} - out=${entry#*:} - ffmpeg_cmd "$CIRCULAR" "$out" "$size" -done - -echo "Replacing maskable icons (using maskless input)..." - -declare -a MASKABLE_TARGETS=( - "114:$BASE_DIR/public/res/logo-maskable/logo-maskable-114x114.png" - "120:$BASE_DIR/public/res/logo-maskable/logo-maskable-120x120.png" - "144:$BASE_DIR/public/res/logo-maskable/logo-maskable-144x144.png" - "152:$BASE_DIR/public/res/logo-maskable/logo-maskable-152x152.png" - "167:$BASE_DIR/public/res/logo-maskable/logo-maskable-167x167.png" - "180:$BASE_DIR/public/res/logo-maskable/logo-maskable-180x180.png" - "192:$BASE_DIR/public/res/logo-maskable/logo-maskable-192x192.png" - "256:$BASE_DIR/public/res/logo-maskable/logo-maskable-256x256.png" - "36:$BASE_DIR/public/res/logo-maskable/logo-maskable-36x36.png" - "384:$BASE_DIR/public/res/logo-maskable/logo-maskable-384x384.png" - "48:$BASE_DIR/public/res/logo-maskable/logo-maskable-48x48.png" - "512:$BASE_DIR/public/res/logo-maskable/logo-maskable-512x512.png" - "57:$BASE_DIR/public/res/logo-maskable/logo-maskable-57x57.png" - "60:$BASE_DIR/public/res/logo-maskable/logo-maskable-60x60.png" - "72:$BASE_DIR/public/res/logo-maskable/logo-maskable-72x72.png" - "76:$BASE_DIR/public/res/logo-maskable/logo-maskable-76x76.png" - "96:$BASE_DIR/public/res/logo-maskable/logo-maskable-96x96.png" -) - -for entry in "${MASKABLE_TARGETS[@]}"; do - size=${entry%%:*} - out=${entry#*:} - ffmpeg_cmd "$MASKLESS" "$out" "$size" -done - -echo "Handling SVG assets in public/res/svg/..." - -SVG_DIR="$BASE_DIR/public/res/svg" -mkdir -p "$SVG_DIR" - -# logo.svg from circular input (inputs are SVG so copy directly) -echo "Copying circular SVG to $SVG_DIR/logo.svg" -cp "$CIRCULAR" "$SVG_DIR/logo.svg" - -# logo-maskable.svg from maskless input -echo "Copying maskable SVG to $SVG_DIR/logo-maskable.svg" -cp "$MASKLESS" "$SVG_DIR/logo-maskable.svg" - -# highlight.svg -cp "$HIGHLIGHT" "$SVG_DIR/highlight.svg" - -# unread.svg -cp "$UNREAD" "$SVG_DIR/unread.svg" - -echo "All done. Please review the generated files." diff --git a/scripts/tauri.js b/scripts/tauri.js index 4ed2803ced..3d89ef1119 100644 --- a/scripts/tauri.js +++ b/scripts/tauri.js @@ -1,15 +1,16 @@ #!/usr/bin/env node +//MISE description="Run Tauri CLI" +//MISE depends="tauri:setup" +//MISE raw_args=true /** - * Script to run tauri commands for a given runtime (wry or cef) with prepended CLI args. + * Passes through to the Tauri CLI. When the first argument is a desktop runtime + * (`wry` or `cef`) and the second is `dev` or `build`, injects the appropriate + * Cargo feature flags (`--features ,updater --no-default-features`). + * Everything else is forwarded to `tauri` as-is. * - * Usage: - * script/tauri [prepended-args...] - * - * Examples: * script/tauri cef dev --verbose - * script/tauri cef dev -- --verbose - * Both will run: tauri dev --features cef -- --verbose --no-default-features + * → tauri dev --features cef,updater -- --verbose --no-default-features */ import { run } from '@tauri-apps/cli'; @@ -25,45 +26,46 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const cmdlineArgs = process.argv.slice(2); +process.chdir(join(__dirname, '..')); + +const DESKTOP = new Set(['wry', 'cef']); -if (cmdlineArgs.length < 2) { - logger.error('Usage: node scripts/tauri [prepended-args...]'); - logger.error(` ${dim('command:')} dev or build`); - logger.error(` ${dim('runtime:')} wry or cef`); - logger.error( - ` ${dim('prepended-args:')} Arguments to prepend to the cargo args (-- separator is optional)` - ); - process.exit(1); +async function runTauri(args) { + logger.info(`${dim('Running:')} tauri ${args.join(' ')}`); + try { + await run(args, 'tauri'); + } catch (error) { + logger.error(`Failed to run tauri: ${error?.message ?? error}`); + process.exit(1); + } } -const [runtime, cmd, ...tauriArgs] = cmdlineArgs; +async function main() { + if (cmdlineArgs.length === 0 || !DESKTOP.has(cmdlineArgs[0])) { + return runTauri(cmdlineArgs); + } -if (!['wry', 'cef'].includes(runtime)) { - logger.error(`Invalid runtime: ${runtime}. Must be 'wry' or 'cef'`); - process.exit(1); -} -if (!['dev', 'build'].includes(cmd)) { - logger.error(`Invalid command: ${cmd}. Must be 'dev' or 'build'`); - process.exit(1); -} + const [platform, cmd, ...tauriArgs] = cmdlineArgs; -tauriArgs.unshift('--features', `${runtime},updater`); -// tauri's Linux bundler can't package CEF; scripts/cef-package.sh does that. -if (runtime === 'cef' && cmd === 'build' && !tauriArgs.includes('--no-bundle')) { - tauriArgs.unshift('--no-bundle'); -} -if (!tauriArgs.includes('--')) { - tauriArgs.push('--'); -} -tauriArgs.push('--no-default-features'); + if (!cmd) { + return runTauri(cmdlineArgs); + } -const args = [cmd, ...tauriArgs]; + if (!['dev', 'build'].includes(cmd)) { + return runTauri([cmd, ...tauriArgs]); + } -process.chdir(join(__dirname, '..')); + const args = [cmd, '--features', `${platform},updater`, ...tauriArgs]; + if (!tauriArgs.includes('--')) { + args.push('--'); + } + args.push('--no-default-features'); -logger.info(`${dim('Running:')} tauri ${args.join(' ')}`); + if (platform === 'cef' && cmd === 'build' && !tauriArgs.includes('--no-bundle')) { + args.splice(1, 0, '--no-bundle'); + } + + return runTauri(args); +} -run(args, 'tauri').catch((error) => { - logger.error(`Failed to run tauri: ${error?.message ?? error}`); - process.exit(1); -}); +main(); diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index fd5d34206a..e849fd22f3 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -5247,7 +5247,7 @@ checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "sable" -version = "0.1.0" +version = "1.20.0" dependencies = [ "async-stream", "base64 0.22.1", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index aedc31083f..0ecbb65e96 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,12 +1,11 @@ [package] name = "sable" -version = "0.1.0" +version = "1.20.0" description = "Yet another matrix client but better" -authors = ["you"] -license = "" -repository = "" +authors = ["Sable Maintainers"] +license = "AGPL-3.0-or-later" +repository = "https://github.com/SableClient/Sable" edition = "2021" -rust-version = "1.88.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src-tauri/build.rs b/src-tauri/build.rs index 704ebff1be..923c16181b 100644 --- a/src-tauri/build.rs +++ b/src-tauri/build.rs @@ -13,6 +13,11 @@ fn main() { println!("cargo:rustc-link-arg-bins=-Wl,-rpath,/usr/lib/swift"); } + // Use the NDK's lld linker for faster Android linking. + if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("android") { + println!("cargo:rustc-link-arg=-fuse-ld=lld"); + } + tauri_typegen::BuildSystem::generate_at_build_time() .expect("Failed to generate TypeScript bindings"); diff --git a/src-tauri/gen/android/gradle.properties b/src-tauri/gen/android/gradle.properties index 2a7ec6959d..119e060525 100644 --- a/src-tauri/gen/android/gradle.properties +++ b/src-tauri/gen/android/gradle.properties @@ -10,7 +10,9 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true +org.gradle.parallel=true +# Enable the Gradle build cache so task outputs are reused across runs. +org.gradle.caching=true # AndroidX package structure to make it clearer which packages are bundled with the # Android operating system, and which are packaged with your app"s APK # https://developer.android.com/topic/libraries/support-library/androidx-rn