From e0094c3a9cffd51f86f6557c6895ac11601ae018 Mon Sep 17 00:00:00 2001 From: codewiththiha Date: Fri, 31 Jul 2026 04:52:11 +0000 Subject: [PATCH 1/2] release: v1.2.2 multi-arch builds, AppImage, Nix, and PowerShell - Bump version to 1.2.2 across package, Cargo, and Tauri config - Add PowerShell to supported slide languages - Split macOS releases into Apple Silicon and Intel targets - Ship Linux AppImage alongside deb/rpm with Wayland lib fix - Harden Tauri bundle metadata, CSP, and release profile - Add Nix flake packaging and post-release sources update workflow - Expand CI with Rust check/clippy on Linux --- .github/workflows/ci.yml | 32 ++++++++ .github/workflows/release.yml | 98 +++++++++++++++++++----- .github/workflows/update-nix-sources.yml | 87 +++++++++++++++++++++ README.md | 21 ++++- flake.lock | 27 +++++++ flake.nix | 24 ++++++ nix/package.nix | 78 +++++++++++++++++++ nix/sources.json | 8 ++ package.json | 2 +- release-notes/1.2.2.md | 26 +++++++ src-tauri/Cargo.lock | 2 +- src-tauri/Cargo.toml | 9 ++- src-tauri/src/commands/shared/naming.rs | 1 + src-tauri/tauri.conf.json | 50 ++++++++++-- src/shared/lib/language-meta.ts | 1 + 15 files changed, 434 insertions(+), 32 deletions(-) create mode 100644 .github/workflows/update-nix-sources.yml create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 nix/package.nix create mode 100644 nix/sources.json create mode 100644 release-notes/1.2.2.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b34f1df..628a28d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,3 +28,35 @@ jobs: bun run test:app-flow - name: Build run: bun run build + + rust: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + + - name: Install Linux build dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + libwebkit2gtk-4.1-dev \ + libgtk-3-dev \ + librsvg2-dev \ + libssl-dev \ + libappindicator3-dev \ + patchelf + + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + + - uses: swatinem/rust-cache@v2 + with: + workspaces: ./src-tauri -> target + + - name: cargo check + working-directory: src-tauri + run: cargo check --all-targets + + - name: cargo clippy + working-directory: src-tauri + run: cargo clippy --all-targets -- -D warnings diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ddfee31..cd5939e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,16 +1,17 @@ -name: Release Tauri App +name: Release on: push: tags: - "v*" + workflow_dispatch: permissions: contents: write jobs: extract-notes: - runs-on: macos-latest + runs-on: ubuntu-latest outputs: version: ${{ steps.notes.outputs.version }} payloadJson: ${{ steps.notes.outputs.payloadJson }} @@ -35,26 +36,32 @@ jobs: fail-fast: false matrix: include: - # ── macOS (universal binary) ── - - runner: macos-latest - args: --target universal-apple-darwin - rust_targets: aarch64-apple-darwin,x86_64-apple-darwin - - # ── Windows ── - - runner: windows-latest + # ── macOS Apple Silicon ── + - platform: macos-latest + args: --target aarch64-apple-darwin + rust_targets: aarch64-apple-darwin + + # ── macOS Intel ── + - platform: macos-latest + args: --target x86_64-apple-darwin + rust_targets: x86_64-apple-darwin + + # ── Windows x64 ── + - platform: windows-latest args: "" rust_targets: "" - - runner: windows-latest + # ── Windows ARM64 ── + - platform: windows-latest args: --target aarch64-pc-windows-msvc rust_targets: aarch64-pc-windows-msvc - # ── Linux (x86_64) ── - - runner: ubuntu-22.04 + # ── Linux x86_64 (deb, rpm, AppImage) ── + - platform: ubuntu-22.04 args: "" rust_targets: "" - runs-on: ${{ matrix.runner }} + runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v4 @@ -62,9 +69,8 @@ jobs: with: bun-version: latest - # ── Linux system dependencies ── - name: Install Linux dependencies - if: matrix.runner == 'ubuntu-22.04' + if: matrix.platform == 'ubuntu-22.04' run: | sudo apt-get update sudo apt-get install -y \ @@ -80,11 +86,18 @@ jobs: librsvg2-dev \ patchelf - - uses: dtolnay/rust-toolchain@stable + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.rust_targets }} - - run: bun install --frozen-lockfile + - uses: swatinem/rust-cache@v2 + with: + workspaces: ./src-tauri -> target + key: ${{ matrix.platform }}-${{ matrix.rust_targets }}-${{ matrix.args }} + + - name: Install frontend dependencies + run: bun install --frozen-lockfile - name: Build, bundle, and attach release artifacts uses: tauri-apps/tauri-action@v0.6.1 @@ -99,9 +112,58 @@ jobs: args: ${{ matrix.args }} tauriScript: bun run tauri + # Strip the bundled libwayland-* libs from the AppImage so it loads the + # host copies at runtime. Ubuntu 22.04 ships older libwayland than recent + # Mesa needs; AppImage puts bundled libs first on the search path, so the + # stale copy wins and EGL can fail. Removing them redirects resolution to + # the host copy present on every GTK-capable system. + - name: Fix AppImage wayland libs + if: matrix.platform == 'ubuntu-22.04' && startsWith(github.ref, 'refs/tags/') + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + + APPIMAGE=$(ls src-tauri/target/release/bundle/appimage/*.AppImage | head -n1) + BASENAME=$(basename "$APPIMAGE") + echo "Patching: $APPIMAGE" + + WORK=$(mktemp -d) + cp "$APPIMAGE" "$WORK/" + pushd "$WORK" >/dev/null + chmod +x "./$BASENAME" + "./$BASENAME" --appimage-extract >/dev/null + + for lib in libwayland-client.so.0 libwayland-egl.so.1 libwayland-cursor.so.0; do + if [ -e "squashfs-root/usr/lib/$lib" ]; then + rm -f "squashfs-root/usr/lib/$lib" + fi + done + rm -f squashfs-root/usr/lib/libwayland-server.so.0 || true + + curl -fsSL -o appimagetool.AppImage \ + https://github.com/AppImage/appimagetool/releases/download/1.9.1/appimagetool-x86_64.AppImage + chmod +x appimagetool.AppImage + ARCH=x86_64 ./appimagetool.AppImage --appimage-extract-and-run squashfs-root "$BASENAME" + + rm -rf squashfs-root + chmod +x "./$BASENAME" + "./$BASENAME" --appimage-extract >/dev/null + test -x squashfs-root/usr/bin/openslides \ + || test -x squashfs-root/usr/bin/OpenSlides \ + || { echo "openslides binary missing from repacked AppImage"; ls -la squashfs-root/usr/bin/ || true; exit 1; } + popd >/dev/null + + cp "$WORK/$BASENAME" "$APPIMAGE" + + TAG="${GITHUB_REF_NAME}" + gh release upload "$TAG" "$APPIMAGE" \ + --clobber --repo "$GITHUB_REPOSITORY" + update-marketing-site: needs: [extract-notes, build-tauri] - runs-on: macos-latest + if: startsWith(github.ref, 'refs/tags/') + runs-on: ubuntu-latest steps: - name: Send release notes to the marketing site uses: actions/github-script@v7 diff --git a/.github/workflows/update-nix-sources.yml b/.github/workflows/update-nix-sources.yml new file mode 100644 index 0000000..59a25a7 --- /dev/null +++ b/.github/workflows/update-nix-sources.yml @@ -0,0 +1,87 @@ +name: Update Nix sources + +on: + release: + types: [published] + workflow_dispatch: + inputs: + tag: + description: "Release tag (e.g. v1.2.2)" + required: true + +jobs: + update: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + # On a `release` event the default checkout lands on the tag (detached + # HEAD); create-pull-request needs a branch base, so check out main. + - uses: actions/checkout@v4 + with: + ref: main + + - name: Normalize tag + id: tag + env: + RAW_TAG: ${{ github.event.release.tag_name || github.event.inputs.tag }} + run: | + case "$RAW_TAG" in + v*) TAG="$RAW_TAG" ;; + *) TAG="v$RAW_TAG" ;; + esac + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + echo "version=${TAG#v}" >> "$GITHUB_OUTPUT" + + - name: Download release assets + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ steps.tag.outputs.tag }} + VERSION: ${{ steps.tag.outputs.version }} + run: | + gh release download "$TAG" \ + --repo codewiththiha/OpenSlides \ + --pattern "OpenSlides_${VERSION}_amd64.deb" \ + --pattern "OpenSlides_x64.app.tar.gz" \ + --pattern "OpenSlides_aarch64.app.tar.gz" + + - name: Compute SRI hashes + id: hashes + env: + VERSION: ${{ steps.tag.outputs.version }} + run: | + hash_sri() { + openssl dgst -sha256 -binary "$1" | base64 | tr -d '\n' + } + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "linux=sha256-$(hash_sri "OpenSlides_${VERSION}_amd64.deb")" >> "$GITHUB_OUTPUT" + echo "x64-darwin=sha256-$(hash_sri "OpenSlides_x64.app.tar.gz")" >> "$GITHUB_OUTPUT" + echo "aarch64-darwin=sha256-$(hash_sri "OpenSlides_aarch64.app.tar.gz")" >> "$GITHUB_OUTPUT" + + - name: Update nix/sources.json + run: | + cat > nix/sources.json <<- EOF + { + "version": "${{ steps.hashes.outputs.version }}", + "hashes": { + "x86_64-linux": "${{ steps.hashes.outputs.linux }}", + "x86_64-darwin": "${{ steps.hashes.outputs.x64-darwin }}", + "aarch64-darwin": "${{ steps.hashes.outputs.aarch64-darwin }}" + } + } + EOF + + - name: Create PR + uses: peter-evans/create-pull-request@v7 + with: + commit-message: "chore: update nix sources to ${{ steps.hashes.outputs.version }}" + title: "nix: update sources to ${{ steps.hashes.outputs.version }}" + body: "Automated update of `nix/sources.json` for release ${{ steps.tag.outputs.tag }}." + base: main + # Only commit the manifest; the downloaded release assets sit in the + # work tree and must not be committed. + add-paths: nix/sources.json + branch: update-nix-sources/${{ steps.hashes.outputs.version }} + delete-branch: true diff --git a/README.md b/README.md index 1dc1d18..c5b2751 100644 --- a/README.md +++ b/README.md @@ -48,13 +48,14 @@ Prebuilt installers for macOS, Windows, and Linux are available from the [OpenSl ### Platform packages -- **macOS:** Apple Silicon, Intel, and Universal builds +- **macOS:** separate Apple Silicon (`aarch64`) and Intel (`x64`) builds - **Windows:** x64 and ARM64 builds -- **Linux:** `.deb` and `.rpm` packages +- **Linux:** `.deb`, `.rpm`, and AppImage +- **Nix:** flake package for `x86_64-linux`, `x86_64-darwin`, and `aarch64-darwin` ### Linux installation -OpenSlides release builds include Linux packages in `.deb` and `.rpm` formats. +OpenSlides release builds include Linux packages in `.deb`, `.rpm`, and AppImage formats. - **Debian / Ubuntu / Linux Mint / Pop!\_OS** @@ -76,7 +77,19 @@ sudo dnf install -y webkit2gtk4.1 gtk3 libappindicator-gtk3 librsvg2 sudo dnf install -y ./OpenSlides--1.x86_64.rpm ``` -If your distribution prefers a different workflow, read the package manager guidance in your distro docs first. AppImage is intentionally not documented yet. +- **AppImage** + +Needs FUSE. Without it: `./OpenSlides_*.AppImage --appimage-extract-and-run`. On Wayland with rendering glitches, try `WEBKIT_DISABLE_DMABUF_RENDERER=1`. Otherwise the `.deb` / `.rpm` packages link against the system GTK stack and tend to be smoother. + +- **NixOS / Nix** + +```bash +nix profile install github:codewiththiha/OpenSlides +``` + +On NixOS you can also import the flake and add `inputs.openslides.packages.${pkgs.system}.openslides` to `environment.systemPackages`, or use the `nixosModules.openslides` output. After each published release, `nix/sources.json` is refreshed automatically so the flake points at the matching installer hashes. + +If your distribution prefers a different workflow, read the package manager guidance in your distro docs first. ## Tech stack diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..08dc0f4 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1778869304, + "narHash": "sha256-30sZNZoA1cqF5JNO9fVX+wgiQYjB7HJqqJ4ztCDeBZE=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "d233902339c02a9c334e7e593de68855ad26c4cb", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..5963f89 --- /dev/null +++ b/flake.nix @@ -0,0 +1,24 @@ +{ + description = "OpenSlides — offline-first code presentation desktop app"; + + inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + + outputs = { self, nixpkgs }: let + forAllSystems = nixpkgs.lib.genAttrs [ "x86_64-linux" "x86_64-darwin" "aarch64-darwin" ]; + in { + packages = forAllSystems (system: let + pkgs = nixpkgs.legacyPackages.${system}; + in { + openslides = pkgs.callPackage ./nix/package.nix { }; + default = self.packages.${system}.openslides; + }); + + nixosModules.openslides = { pkgs, ... }: { + environment.systemPackages = [ self.packages.${pkgs.system}.openslides ]; + }; + + darwinModules.openslides = { pkgs, ... }: { + environment.systemPackages = [ self.packages.${pkgs.system}.openslides ]; + }; + }; +} diff --git a/nix/package.nix b/nix/package.nix new file mode 100644 index 0000000..be0499d --- /dev/null +++ b/nix/package.nix @@ -0,0 +1,78 @@ +{ lib, stdenv, fetchurl +, dpkg, autoPatchelfHook, makeWrapper, wrapGAppsHook3 +, gtk3, gdk-pixbuf, cairo, glib, webkitgtk_4_1, libsoup_3, libgcc, gst_all_1 +}: + +let + sources = builtins.fromJSON (builtins.readFile ./sources.json); + version = sources.version; + + srcMap = { + x86_64-linux = fetchurl { + url = "https://github.com/codewiththiha/OpenSlides/releases/download/v${version}/OpenSlides_${version}_amd64.deb"; + hash = sources.hashes.x86_64-linux; + }; + x86_64-darwin = fetchurl { + url = "https://github.com/codewiththiha/OpenSlides/releases/download/v${version}/OpenSlides_x64.app.tar.gz"; + hash = sources.hashes.x86_64-darwin; + }; + aarch64-darwin = fetchurl { + url = "https://github.com/codewiththiha/OpenSlides/releases/download/v${version}/OpenSlides_aarch64.app.tar.gz"; + hash = sources.hashes.aarch64-darwin; + }; + }; + + sys = stdenv.hostPlatform.system; + +in + +assert lib.assertMsg (builtins.hasAttr sys srcMap) + "openslides: unsupported platform ${sys}"; + +stdenv.mkDerivation { + pname = "openslides"; + inherit version; + + src = srcMap.${sys}; + + nativeBuildInputs = lib.optionals stdenv.hostPlatform.isLinux [ + dpkg autoPatchelfHook makeWrapper wrapGAppsHook3 + ]; + + buildInputs = lib.optionals stdenv.hostPlatform.isLinux [ + gtk3 gdk-pixbuf cairo glib webkitgtk_4_1 libsoup_3 libgcc + gst_all_1.gstreamer gst_all_1.gst-plugins-base + gst_all_1.gst-plugins-good gst_all_1.gst-plugins-bad + ]; + + GST_PLUGIN_SYSTEM_PATH = lib.optionalString stdenv.hostPlatform.isLinux + "${gst_all_1.gst-plugins-base}/lib/gstreamer-1.0:${gst_all_1.gst-plugins-good}/lib/gstreamer-1.0:${gst_all_1.gst-plugins-bad}/lib/gstreamer-1.0"; + + # wrapGAppsHook3 would auto-wrap the binary in postFixup; we wrap it manually + # to add the GStreamer path, so disable the auto-wrap and splice the hook's + # GApps args into our single wrapper instead of nesting two wrappers. + dontWrapGApps = true; + + unpackPhase = if stdenv.hostPlatform.isLinux then "dpkg -x $src ." else "tar xzf $src"; + + installPhase = if stdenv.hostPlatform.isLinux then '' + mkdir -p $out/bin $out/share + cp -r usr/share/* $out/share/ + install -Dm755 usr/bin/openslides $out/bin/openslides + + wrapProgram $out/bin/openslides \ + "''${gappsWrapperArgs[@]}" \ + --prefix GST_PLUGIN_SYSTEM_PATH : "$GST_PLUGIN_SYSTEM_PATH" + '' else '' + mkdir -p $out/Applications + cp -r *.app $out/Applications/ + ''; + + meta = with lib; { + description = "Offline-first code presentation desktop app"; + homepage = "https://github.com/codewiththiha/OpenSlides"; + license = licenses.mit; + platforms = [ "x86_64-linux" "x86_64-darwin" "aarch64-darwin" ]; + mainProgram = "openslides"; + }; +} diff --git a/nix/sources.json b/nix/sources.json new file mode 100644 index 0000000..0636301 --- /dev/null +++ b/nix/sources.json @@ -0,0 +1,8 @@ +{ + "version": "1.2.2", + "hashes": { + "x86_64-linux": "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "x86_64-darwin": "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "aarch64-darwin": "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + } +} diff --git a/package.json b/package.json index fa6ccbd..72372b8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "openslides", "private": true, - "version": "1.2.1", + "version": "1.2.2", "type": "module", "scripts": { "dev": "vite", diff --git a/release-notes/1.2.2.md b/release-notes/1.2.2.md new file mode 100644 index 0000000..be204a5 --- /dev/null +++ b/release-notes/1.2.2.md @@ -0,0 +1,26 @@ +--- +version: 1.2.2 +date: 2026-07-31 +whatsNew: | + - Added PowerShell language support for code slides. + - Separate macOS Apple Silicon and Intel release builds (no more universal-only package). + - Linux releases now ship AppImage alongside .deb and .rpm. + - Added Nix flake packaging for Linux and macOS. + - Hardened Tauri bundle configuration for production-ready multi-platform builds. +--- + +## What's New + +- Added PowerShell language support for code slides. +- Separate macOS Apple Silicon and Intel release builds (no more universal-only package). +- Linux releases now ship AppImage alongside `.deb` and `.rpm`. +- Added Nix flake packaging for Linux and macOS. +- Hardened Tauri bundle configuration for production-ready multi-platform builds. + +## Changes + +- Release matrix builds dedicated `aarch64-apple-darwin` and `x86_64-apple-darwin` artifacts. +- Linux packages include AppImage with a Wayland library fix for newer Mesa stacks. +- Windows continues to ship x64 and ARM64 installers. +- Nix users can install via `nix profile install github:codewiththiha/OpenSlides` after sources are published. +- Content Security Policy and Linux/Windows bundle metadata tightened for release builds. diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index cdfe38f..a05815f 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -2395,7 +2395,7 @@ dependencies = [ [[package]] name = "openslides" -version = "1.2.1" +version = "1.2.2" dependencies = [ "aho-corasick", "chrono", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index e577620..cd2fa8a 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "openslides" -version = "1.2.1" +version = "1.2.2" description = "OpenSlides — offline-first code presentation desktop app" authors = ["codewiththiha"] edition = "2024" @@ -13,7 +13,7 @@ crate-type = ["staticlib", "cdylib", "rlib"] tauri-build = { version = "2", features = [] } [dependencies] -tauri = { version = "2", features = [] } +tauri = { version = "2", features = ["protocol-asset"] } tauri-plugin-opener = "2" tauri-plugin-dialog = "2" serde = { version = "1", features = ["derive"] } @@ -32,9 +32,12 @@ thiserror = "2" once_cell = "1" aho-corasick = "1" +[profile.dev] +incremental = true + [profile.release] opt-level = "z" # Optimize strictly for binary size -lto = true # Link-Time Optimization across dependencies +lto = "fat" # Link-Time Optimization across dependencies codegen-units = 1 # Maximize LTO effectiveness panic = "abort" # Strip stack unwinding logic strip = true # Strip debug symbols and tables diff --git a/src-tauri/src/commands/shared/naming.rs b/src-tauri/src/commands/shared/naming.rs index cace125..9762edb 100644 --- a/src-tauri/src/commands/shared/naming.rs +++ b/src-tauri/src/commands/shared/naming.rs @@ -43,6 +43,7 @@ const SUPPORTED_LANGUAGES: &[SupportedLanguageOption] = &[ SupportedLanguageOption { value: "yaml", label: "YAML" }, SupportedLanguageOption { value: "sql", label: "SQL" }, SupportedLanguageOption { value: "bash", label: "Bash/Shell" }, + SupportedLanguageOption { value: "powershell", label: "PowerShell" }, SupportedLanguageOption { value: "markdown", label: "Markdown" }, SupportedLanguageOption { value: "merustmar", label: "Merustmar" }, ]; diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 07e3768..ab2355d 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,17 +1,19 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "OpenSlides", - "version": "1.2.1", + "version": "1.2.2", "identifier": "com.codewiththiha.openslides", "build": { "beforeDevCommand": "bun run dev", "devUrl": "http://localhost:1420", "beforeBuildCommand": "bun run build", - "frontendDist": "../dist" + "frontendDist": "../dist", + "removeUnusedCommands": true }, "app": { "windows": [ { + "label": "main", "title": "OpenSlides", "width": 1400, "height": 900, @@ -24,14 +26,23 @@ } ], "security": { - "csp": null + "csp": "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' ipc: http://ipc.localhost; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; form-action 'self'", + "assetProtocol": { + "enable": true, + "scope": ["**"] + } }, "macOSPrivateApi": false }, "bundle": { "active": true, - "publisher": "codewiththiha", "targets": "all", + "copyright": "Copyright 2026 CodeWithThiha", + "license": "MIT", + "publisher": "codewiththiha", + "category": "Education", + "shortDescription": "Offline-first code presentation desktop app", + "longDescription": "OpenSlides — a free, open-source, offline desktop app for turning code into polished slides with smooth step-by-step transitions.", "icon": [ "icons/32x32.png", "icons/128x128.png", @@ -40,7 +51,36 @@ "icons/icon.ico" ], "macOS": { - "minimumSystemVersion": "10.15" + "minimumSystemVersion": "11.0" + }, + "linux": { + "deb": { + "section": "education", + "depends": [ + "libwebkit2gtk-4.1-0", + "libgtk-3-0" + ] + }, + "rpm": { + "depends": [ + "webkit2gtk4.1", + "gtk3" + ] + }, + "appimage": { + "bundleMediaFramework": true + } + }, + "windows": { + "webviewInstallMode": { + "type": "downloadBootstrapper" + }, + "nsis": { + "installMode": "currentUser", + "languages": ["English"], + "displayLanguageSelector": false, + "installerIcon": "icons/icon.ico" + } } } } diff --git a/src/shared/lib/language-meta.ts b/src/shared/lib/language-meta.ts index e81fbde..38a5c4b 100644 --- a/src/shared/lib/language-meta.ts +++ b/src/shared/lib/language-meta.ts @@ -24,6 +24,7 @@ export const FALLBACK_SUPPORTED_LANGUAGES: readonly LanguageOption[] = [ { value: "yaml", label: "YAML" }, { value: "sql", label: "SQL" }, { value: "bash", label: "Bash/Shell" }, + { value: "powershell", label: "PowerShell" }, { value: "markdown", label: "Markdown" }, { value: "merustmar", label: "Merustmar" }, ] as const; From 886096850415b22a1a7665c4cf9d847090d5ca9d Mon Sep 17 00:00:00 2001 From: codewiththiha Date: Fri, 31 Jul 2026 05:09:10 +0000 Subject: [PATCH 2/2] chore: improve packaging reliability and code quality --- .github/workflows/release.yml | 59 ++++++++++++++++++++--- README.md | 14 ++++++ src-tauri/src/commands/io.rs | 54 ++++++++++++--------- src-tauri/src/commands/projects.rs | 6 ++- src-tauri/src/commands/search.rs | 13 ++--- src-tauri/src/commands/stacks/projects.rs | 4 +- src-tauri/src/commands/stacks/slides.rs | 4 +- src-tauri/src/lib.rs | 25 +++++----- src-tauri/tauri.conf.json | 3 +- 9 files changed, 125 insertions(+), 57 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cd5939e..d70032a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,7 +4,14 @@ on: push: tags: - "v*" + branches: + - "build/**" workflow_dispatch: + inputs: + prerelease: + description: "Publish as a prerelease" + type: boolean + default: true permissions: contents: write @@ -15,6 +22,9 @@ jobs: outputs: version: ${{ steps.notes.outputs.version }} payloadJson: ${{ steps.notes.outputs.payloadJson }} + isPrerelease: ${{ steps.meta.outputs.isPrerelease }} + releaseTag: ${{ steps.meta.outputs.releaseTag }} + releaseName: ${{ steps.meta.outputs.releaseName }} steps: - uses: actions/checkout@v4 @@ -24,12 +34,45 @@ jobs: - run: bun install --frozen-lockfile + - name: Resolve release version + id: version + shell: bash + run: | + if [[ "${GITHUB_REF}" == refs/tags/* ]]; then + echo "version=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT" + else + VER=$(node -p "require('./package.json').version") + echo "version=v${VER}" >> "$GITHUB_OUTPUT" + fi + - name: Extract release notes id: notes env: - RELEASE_VERSION: ${{ github.ref_name }} + RELEASE_VERSION: ${{ steps.version.outputs.version }} run: bun scripts/extract-release-note.mjs + - name: Release metadata + id: meta + shell: bash + env: + INPUT_PRERELEASE: ${{ github.event.inputs.prerelease }} + VERSION: ${{ steps.notes.outputs.version }} + run: | + if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then + echo "isPrerelease=false" >> "$GITHUB_OUTPUT" + echo "releaseTag=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT" + echo "releaseName=OpenSlides ${VERSION}" >> "$GITHUB_OUTPUT" + else + # Branch / manual builds never create a v* tag. + if [[ "${INPUT_PRERELEASE}" == "false" ]]; then + echo "isPrerelease=false" >> "$GITHUB_OUTPUT" + else + echo "isPrerelease=true" >> "$GITHUB_OUTPUT" + fi + echo "releaseTag=build-${VERSION}-${GITHUB_RUN_NUMBER}" >> "$GITHUB_OUTPUT" + echo "releaseName=OpenSlides ${VERSION} (build ${GITHUB_RUN_NUMBER})" >> "$GITHUB_OUTPUT" + fi + build-tauri: needs: extract-notes strategy: @@ -104,11 +147,11 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - tagName: ${{ github.ref_name }} - releaseName: OpenSlides ${{ needs.extract-notes.outputs.version }} + tagName: ${{ needs.extract-notes.outputs.releaseTag }} + releaseName: ${{ needs.extract-notes.outputs.releaseName }} releaseBody: ${{ fromJSON(needs.extract-notes.outputs.payloadJson).fullNotes }} releaseDraft: false - prerelease: false + prerelease: ${{ needs.extract-notes.outputs.isPrerelease }} args: ${{ matrix.args }} tauriScript: bun run tauri @@ -118,9 +161,10 @@ jobs: # stale copy wins and EGL can fail. Removing them redirects resolution to # the host copy present on every GTK-capable system. - name: Fix AppImage wayland libs - if: matrix.platform == 'ubuntu-22.04' && startsWith(github.ref, 'refs/tags/') + if: matrix.platform == 'ubuntu-22.04' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_TAG: ${{ needs.extract-notes.outputs.releaseTag }} run: | set -euo pipefail @@ -156,13 +200,12 @@ jobs: cp "$WORK/$BASENAME" "$APPIMAGE" - TAG="${GITHUB_REF_NAME}" - gh release upload "$TAG" "$APPIMAGE" \ + gh release upload "$RELEASE_TAG" "$APPIMAGE" \ --clobber --repo "$GITHUB_REPOSITORY" update-marketing-site: needs: [extract-notes, build-tauri] - if: startsWith(github.ref, 'refs/tags/') + if: startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest steps: - name: Send release notes to the marketing site diff --git a/README.md b/README.md index c5b2751..754544e 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,20 @@ Prebuilt installers for macOS, Windows, and Linux are available from the [OpenSl - **Linux:** `.deb`, `.rpm`, and AppImage - **Nix:** flake package for `x86_64-linux`, `x86_64-darwin`, and `aarch64-darwin` +### macOS installation + +OpenSlides macOS builds are ad-hoc signed. That is enough for Apple Silicon packages to launch after download; a paid Apple Developer certificate is still required for full notarization later. + +1. Open the `.dmg` and drag **OpenSlides** into **Applications**. +2. First launch: right-click the app → **Open**, then confirm. +3. If macOS still says the app is damaged, clear the download quarantine flag: + +```bash +xattr -dr com.apple.quarantine /Applications/OpenSlides.app +``` + +Then open the app again from Applications. + ### Linux installation OpenSlides release builds include Linux packages in `.deb`, `.rpm`, and AppImage formats. diff --git a/src-tauri/src/commands/io.rs b/src-tauri/src/commands/io.rs index 54ded46..2c3ab4a 100644 --- a/src-tauri/src/commands/io.rs +++ b/src-tauri/src/commands/io.rs @@ -15,6 +15,17 @@ use std::collections::HashMap; use tauri::{AppHandle, State}; use uuid::Uuid; +struct ParsedImportSlide { + id: String, + code: String, + duration: i64, + transition_duration: i64, + stagger: i64, + name: String, + highlights_json: String, + section_id: Option, +} + #[tauri::command] pub async fn export_project_to_json( app: AppHandle, @@ -187,8 +198,7 @@ pub async fn import_project_from_json( // collide with the original slides table rows. let mut imported_slide_ids: HashMap = HashMap::new(); let mut imported_section_ids: HashMap = HashMap::new(); - let mut parsed_slides: Vec<(String, String, i64, i64, i64, String, String, Option)> = - Vec::new(); + let mut parsed_slides: Vec = Vec::new(); for (i, slide_value) in slides_val.iter().enumerate() { let slide: ImportSlidePayload = serde_json::from_value(slide_value.clone()) .map_err(|_| CommandError::Failed("That file contains an invalid slide entry".to_string()))?; @@ -207,23 +217,23 @@ pub async fn import_project_from_json( settings.current_slide_id = Some(id.clone()); } let section_id = remap_section_id(&mut imported_section_ids, slide.section_id.clone()); - parsed_slides.push(( + parsed_slides.push(ParsedImportSlide { id, - slide.code, - slide.duration, - slide.transition_duration, - slide.stagger, - sname, + code: slide.code, + duration: slide.duration, + transition_duration: slide.transition_duration, + stagger: slide.stagger, + name: sname, highlights_json, section_id, - )); + }); } // Remap the exported current slide ID to the fresh imported slide ID. - if let Some(cid) = imported_current_slide_id.as_deref() { - if let Some(imported_id) = imported_slide_ids.get(cid) { - settings.current_slide_id = Some(imported_id.clone()); - } + if let Some(cid) = imported_current_slide_id.as_deref() + && let Some(imported_id) = imported_slide_ids.get(cid) + { + settings.current_slide_id = Some(imported_id.clone()); } let settings_json = settings_to_json(&settings).map_err(CommandError::Failed)?; @@ -250,7 +260,7 @@ pub async fn import_project_from_json( .await .map_err(|e| CommandError::Failed(format!("Failed to insert project: {e}")))?; - for (i, (id, code, duration, transition, stagger, sname, highlights_json, section_id)) in parsed_slides.iter().enumerate() { + for (i, slide) in parsed_slides.iter().enumerate() { sqlx::query( r#" INSERT INTO slides @@ -258,16 +268,16 @@ pub async fn import_project_from_json( VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) "#, ) - .bind(id) + .bind(&slide.id) .bind(&project_id) .bind(i as i64) - .bind(code) - .bind(transition) - .bind(stagger) - .bind(duration) - .bind(sname) - .bind(highlights_json) - .bind(section_id) + .bind(&slide.code) + .bind(slide.transition_duration) + .bind(slide.stagger) + .bind(slide.duration) + .bind(&slide.name) + .bind(&slide.highlights_json) + .bind(&slide.section_id) .execute(&mut *tx) .await .map_err(|e| CommandError::Failed(format!("Failed to insert slide: {e}")))?; diff --git a/src-tauri/src/commands/projects.rs b/src-tauri/src/commands/projects.rs index e253be2..98edc6a 100644 --- a/src-tauri/src/commands/projects.rs +++ b/src-tauri/src/commands/projects.rs @@ -89,8 +89,10 @@ pub async fn create_project(pool: State<'_, DbPool>, name: String) -> CommandRes name.trim().to_string() }; - let mut settings = ProjectSettings::default(); - settings.current_slide_id = Some(slide_id.clone()); + let settings = ProjectSettings { + current_slide_id: Some(slide_id.clone()), + ..Default::default() + }; let settings_json = settings_to_json(&settings)?; let mut tx = pool diff --git a/src-tauri/src/commands/search.rs b/src-tauri/src/commands/search.rs index 4a94ad3..c5da2d4 100644 --- a/src-tauri/src/commands/search.rs +++ b/src-tauri/src/commands/search.rs @@ -129,12 +129,13 @@ pub async fn search_slides( } Err(_) => { let safe_query = safe_prefix_query(query); - if !safe_query.is_empty() && safe_query != primary_query { - if let Ok(rows) = run_fts_search(pool.inner(), &project_id, &safe_query).await { - if !rows.is_empty() || !has_literal_punctuation(query) { - return Ok(rows); - } - } + if !safe_query.is_empty() + && safe_query != primary_query + && let Ok(rows) = + run_fts_search(pool.inner(), &project_id, &safe_query).await + && (!rows.is_empty() || !has_literal_punctuation(query)) + { + return Ok(rows); } } } diff --git a/src-tauri/src/commands/stacks/projects.rs b/src-tauri/src/commands/stacks/projects.rs index 805a8da..a6e64f8 100644 --- a/src-tauri/src/commands/stacks/projects.rs +++ b/src-tauri/src/commands/stacks/projects.rs @@ -74,7 +74,7 @@ pub async fn stack_projects( next_order += 1; } - clean_up_single_item_project_groups(&mut *tx).await?; + clean_up_single_item_project_groups(&mut tx).await?; tx.commit() .await @@ -106,7 +106,7 @@ pub async fn unstack_projects( .map_err(|e| format!("Failed to unstack project {id}: {e}"))?; } - clean_up_single_item_project_groups(&mut *tx).await?; + clean_up_single_item_project_groups(&mut tx).await?; tx.commit() .await diff --git a/src-tauri/src/commands/stacks/slides.rs b/src-tauri/src/commands/stacks/slides.rs index 2399963..be3121e 100644 --- a/src-tauri/src/commands/stacks/slides.rs +++ b/src-tauri/src/commands/stacks/slides.rs @@ -110,7 +110,7 @@ pub async fn stack_slides( .map_err(|e| format!("Failed to position source {sid}: {e}"))?; } - clean_up_single_item_slide_sections(&mut *tx, &project_id).await?; + clean_up_single_item_slide_sections(&mut tx, &project_id).await?; tx.commit() .await @@ -152,7 +152,7 @@ pub async fn unstack_slides( .map_err(|e| format!("Failed to unstack slide {id}: {e}"))?; } - clean_up_single_item_slide_sections(&mut *tx, &project_id).await?; + clean_up_single_item_slide_sections(&mut tx, &project_id).await?; tx.commit() .await diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index d701c5c..5301eb1 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -20,10 +20,7 @@ pub fn run() { let handle = app.handle().clone(); let pool = tauri::async_runtime::block_on(async move { init_db(&handle).await }) .map_err(|error| { - std::io::Error::new( - std::io::ErrorKind::Other, - format!("failed to initialize database: {error}"), - ) + std::io::Error::other(format!("failed to initialize database: {error}")) })?; app.handle().manage(pool); Ok(()) @@ -61,11 +58,11 @@ pub fn run() { .on_window_event(|window, event| { // Window X / native close: flush the debounced auto-save first — // without this, the last <500ms of keystrokes could vanish. - if let tauri::WindowEvent::CloseRequested { api, .. } = event { - if !QUIT_FLUSHED.load(Ordering::SeqCst) { - api.prevent_close(); - request_flush_before_quit(window); - } + if let tauri::WindowEvent::CloseRequested { api, .. } = event + && !QUIT_FLUSHED.load(Ordering::SeqCst) + { + api.prevent_close(); + request_flush_before_quit(window); } }) .build(tauri::generate_context!()) @@ -73,11 +70,11 @@ pub fn run() { app.run(|app_handle, event| { // Cmd+Q / OS-level quit goes through the same flush handshake. - if let tauri::RunEvent::ExitRequested { api, .. } = event { - if !QUIT_FLUSHED.load(Ordering::SeqCst) { - api.prevent_exit(); - request_flush_before_quit(app_handle); - } + if let tauri::RunEvent::ExitRequested { api, .. } = event + && !QUIT_FLUSHED.load(Ordering::SeqCst) + { + api.prevent_exit(); + request_flush_before_quit(app_handle); } }); } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index ab2355d..5ea40c0 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -51,7 +51,8 @@ "icons/icon.ico" ], "macOS": { - "minimumSystemVersion": "11.0" + "minimumSystemVersion": "11.0", + "signingIdentity": "-" }, "linux": { "deb": {