{"id":51145586,"url":"https://github.com/c4bo3l/tauri-dotnet-react-boilerplate","last_synced_at":"2026-06-26T02:30:30.302Z","repository":{"id":360383004,"uuid":"1249852731","full_name":"c4bo3l/tauri-dotnet-react-boilerplate","owner":"c4bo3l","description":"Tauri + .NET 10 + React desktop app boilerplate with CQRS, SQLCipher, licensing, and CI/CD","archived":false,"fork":false,"pushed_at":"2026-05-26T08:09:12.000Z","size":80540,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-26T08:19:35.917Z","etag":null,"topics":["boilerplate","cqrs","csharp","desktop-app","dotnet","mediator","react","sidecar","sqlchiper","tauri","template","vite"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/c4bo3l.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-26T05:02:42.000Z","updated_at":"2026-05-26T08:13:28.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/c4bo3l/tauri-dotnet-react-boilerplate","commit_stats":null,"previous_names":["c4bo3l/tauri-dotnet-react-boilerplate"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/c4bo3l/tauri-dotnet-react-boilerplate","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c4bo3l%2Ftauri-dotnet-react-boilerplate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c4bo3l%2Ftauri-dotnet-react-boilerplate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c4bo3l%2Ftauri-dotnet-react-boilerplate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c4bo3l%2Ftauri-dotnet-react-boilerplate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/c4bo3l","download_url":"https://codeload.github.com/c4bo3l/tauri-dotnet-react-boilerplate/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c4bo3l%2Ftauri-dotnet-react-boilerplate/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34801014,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-26T02:00:06.560Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["boilerplate","cqrs","csharp","desktop-app","dotnet","mediator","react","sidecar","sqlchiper","tauri","template","vite"],"created_at":"2026-06-26T02:30:26.036Z","updated_at":"2026-06-26T02:30:30.292Z","avatar_url":"https://github.com/c4bo3l.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Tauri + .NET + Vite App\n\nA desktop application built with **Tauri** (Rust shell), **.NET 10** (ASP.NET Core backend as sidecar), and **React + Vite** (frontend). The .NET backend is structured using CQRS via Mediator, with SQLite + SQLCipher for storage.\n\n\u003e This boilerplate was designed with the help of [opencode](https://opencode.ai), an AI-powered CLI coding assistant.\n\u003e\n\u003e **Note:** This template has only been tested on **macOS**. Windows and Linux commands are provided as a reference and may need adjustments.\n\n## Table of Contents\n\n- [Project Structure](#project-structure)\n- [Getting Started](#getting-started)\n- [Build](#build)\n  - [GitHub Actions](#github-actions)\n- [Cleaning](#cleaning)\n- [Database \u0026 Migrations](#database--migrations)\n- [API Endpoints](#api-endpoints)\n- [Code Splitting](#code-splitting)\n- [Security \u0026 Protection](#security--protection)\n- [Exception Handling](#exception-handling)\n- [Versioning](#versioning)\n- [Licensing \u0026 Unlock System](#licensing--unlock-system)\n- [Auto-Update](#auto-update)\n- [Removing Licensing](#removing-licensing)\n- [Renaming the Project](#renaming-the-project)\n- [App Icons](#app-icons)\n- [License](#license)\n- [Linting](#linting)\n- [Testing](#testing)\n- [Dev URLs](#dev-urls)\n\n## Project Structure\n\n```\n├── .gitignore                  # Root gitignore (dotnet, node, rust, macOS, etc.)\n├── backend/                    # .NET 10 ASP.NET Core backend\n│   ├── .config/\n│   │   └── dotnet-tools.json   # Local tool manifest (dotnet-ef, obfuscar)\n│   ├── dotnet-backend.csproj   # Web API entry point\n│   ├── dotnet-backend.slnx     # Solution file\n│   ├── dotnet-backend.http     # HTTP file for testing endpoints\n│   ├── Program.cs              # App startup, middleware, DI, APIs\n│   ├── appsettings.json        # Configuration (DB password, etc.)\n│   ├── appsettings.Development.json\n│   ├── obfuscar.xml            # Obfuscation config (reference)\n│   ├── Properties/\n│   │   └── launchSettings.json\n│   ├── src/\n│   │   ├── Infrastructure.Models/     # Entity models (TodoItem)\n│   │   ├── Infrastructure.Database/   # EF Core DbContext + Migrations\n│   │   ├── Infrastructure.Dtos/       # Request/response DTOs\n│   │   ├── Infrastructure.Commons/    # Shared types (Result\u003cT\u003e)\n│   │   ├── Infrastructure.Licensing/  # RSA license verification, machine ID\n│   │   └── Infrastructure.Services/   # CQRS handlers, pipeline behaviors\n│   └── tests/\n│       ├── Infrastructure.Services.Tests/  # 10 handler tests (InMemory EF)\n│       └── Infrastructure.Commons.Tests/   # 2 Result\u003cT\u003e tests\n├── frontend/                   # React + Vite + TypeScript\n│   ├── src/\n│   │   ├── main.tsx            # Entry point, lazy-loads App\n│   │   ├── App.tsx             # Todo list UI (lazy-loaded)\n│   │   ├── LicenseGate.tsx     # Unlock screen wrapper (eager)\n│   │   ├── index.css           # Global styles\n│   │   ├── App.css\n│   │   └── assets/\n│   ├── index.html\n│   ├── vite.config.ts          # Vite config (SWC plugin, manualChunks)\n│   ├── eslint.config.js        # ESLint flat config (type-aware, enforced semicolons)\n│   ├── tsconfig.json           # TS config (references app + node configs)\n│   ├── tsconfig.app.json\n│   ├── tsconfig.node.json\n│   └── package.json\n├── tauri/                      # Tauri Rust shell\n│   ├── .gitignore\n│   ├── src/\n│   │   ├── lib.rs              # Sidecar startup logic\n│   │   └── main.rs             # Tauri entry point\n│   ├── build.rs                # Tauri build script\n│   ├── capabilities/\n│   │   └── default.json        # Tauri capability permissions\n│   ├── icons/                  # App icons (icns, ico, png)\n│   ├── binaries/               # .NET sidecar binary (auto-copied by build-dotnet)\n│   ├── tauri.conf.json         # Tauri configuration\n│   └── Cargo.toml              # Rust dependencies, release profile (strip+LTO)\n├── tools/                      # Standalone CLI tools\n│   └── LicenseGenerator/       # .NET console app: generate RSA keys \u0026 licenses\n│       ├── Program.cs\n│       └── LicenseGenerator.csproj\n└── scripts/                    # Build \u0026 dev automation\n    ├── build-dotnet.mjs        # .NET publish + sidecar copy\n    ├── tauri-dev.mjs           # Dev launcher: backend + Vite + Tauri\n    ├── clean-all.mjs           # Cleans all build artifacts (dotnet, cargo, dist)\n    ├── db-reset.mjs            # Deletes app.db from build output dirs\n    ├── install-deps.mjs        # Install all deps (npm, dotnet restore, cargo fetch)\n    ├── migration.mjs           # EF Core migration helper\n    ├── remove-license.mjs      # Strips all licensing from the project\n    ├── rename.mjs              # Renames project from \"tauri-dotnet-app\" to a custom name\n    ├── version.mjs             # Version read/set/bump CLI\n    └── version.txt             # Single source of truth for version\n```\n\n## Prerequisites\n\n- [.NET 10 SDK](https://dotnet.microsoft.com/download)\n- [Node.js 20+](https://nodejs.org/)\n- [Rust](https://rustup.rs/) (for Tauri)\n- Platform-specific dependencies for Tauri (see [Tauri prerequisites](https://v2.tauri.app/start/prerequisites/))\n\n## Getting Started\n\n### 1. Install dependencies\n\n```bash\nnpm run setup                   # Installs everything: npm, dotnet restore, cargo fetch\n```\n\nOr install individually:\n\n```bash\nnpm install                    # Frontend + root dependencies\ndotnet restore backend/dotnet-backend.slnx   # .NET packages\n(cd backend \u0026\u0026 dotnet tool restore)          # Local .NET tools (dotnet-ef, obfuscar)\ncd tauri \u0026\u0026 cargo fetch        # Rust / Tauri dependencies\n```\n\n### 2. Configure the database password\n\nSet `DatabasePassword` and `LicensePublicKey` in `backend/appsettings.json` (and `backend/appsettings.Development.json` for dev). In CI/CD, override them via environment variables (`DatabasePassword`, `LicensePublicKey`) — .NET config reads env vars before `appsettings.json`.\n\n\u003e **Security warning:** `appsettings.json` is checked into version control. Never store real secrets there in plain text — use environment variables, CI/CD secrets, or a secret manager instead. The values shown are placeholders for local development only.\n\n### 3. Run in development\n\n```bash\nnpm run tauri:dev              # Starts backend (port 5199), Vite (port 5173), then Tauri\n```\n\nOr run components individually:\n\n```bash\nnpm run dev:backend            # .NET backend only (http://127.0.0.1:5199)\nnpm run dev                    # Vite frontend only (http://localhost:5173)\n```\n\n## Build\n\n### Release build\n\n```bash\nnpm run tauri:build            # Platform-native bundle (fast)\nnpm run tauri:build:dmg        # .app + .dmg installer (macOS only)\nnpm run tauri:build:msi        # .msi installer (Windows only)\nnpm run tauri:build:deb        # .deb package (Linux only)\n```\n\nOutput (varies by platform):\n\n- **macOS**: `tauri/target/release/bundle/macos/tauri-dotnet-app.app` / `.dmg`\n- **Windows**: `tauri/target/release/bundle/msi/tauri-dotnet-app_0.1.0_x64.msi`\n- **Linux**: `tauri/target/release/bundle/deb/tauri-dotnet-app_0.1.0_amd64.deb` / `.AppImage`\n\n### Platform cross-compilation\n\n| Part | Cross-compile from macOS? |\n|------|---------------------------|\n| .NET backend | ✅ Yes (see platform-specific commands below) |\n| Tauri (Rust shell) | ❌ No — requires native SDK per platform |\n| Frontend (Vite) | ✅ Yes (pure JavaScript) |\n\nThe .NET backend can be published for any platform from macOS, but **the final Tauri bundle must be built on the target OS** (the Tauri CLI needs the platform's native toolchain to compile Rust and package the app).\n\n### GitHub Actions\n\n#### PR checks\n\nCreate `.github/workflows/pr.yml`:\n\n```yaml\nname: PR\non: [pull_request]\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with: { node-version: \"20\" }\n      - run: npm install\n      - run: npm run lint\n\n  dotnet:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-dotnet@v4\n        with: { dotnet-version: \"10.0\" }\n      - run: dotnet restore backend/dotnet-backend.slnx\n      - run: dotnet build backend/dotnet-backend.csproj --no-restore\n      - run: dotnet test backend/dotnet-backend.slnx --no-restore\n```\n\n#### Release build\n\nCreate `.github/workflows/release.yml`:\n\n```yaml\nname: Release\non:\n  push:\n    tags: [\"v*\"]\n\njobs:\n  tauri:\n    strategy:\n      matrix:\n        include:\n          - os: macos-latest\n            target: aarch64-apple-darwin\n            bundle: \"app,dmg\"\n          - os: windows-latest\n            target: x86_64-pc-windows-msvc\n            bundle: msi\n          - os: ubuntu-latest\n            target: x86_64-unknown-linux-gnu\n            bundle: deb\n    runs-on: ${{ matrix.os }}\n    steps:\n      - uses: actions/checkout@v4\n        with: { fetch-depth: 0 }\n      - uses: actions/setup-node@v4\n        with: { node-version: \"20\" }\n      - uses: actions/setup-dotnet@v4\n        with: { dotnet-version: \"10.0\" }\n      - uses: dtolnay/rust-toolchain@stable\n\n      - run: npm install\n\n      - name: Build and bundle\n        run: |\n          node scripts/build-dotnet.mjs\n          cd tauri\n          ../frontend/node_modules/.bin/tauri build --bundles ${{ matrix.bundle }}\n\n      - name: Generate changelog\n        id: changelog\n        run: |\n          prev_tag=$(git describe --tags --abbrev=0 HEAD^ 2\u003e/dev/null || echo \"\")\n          echo \"## What's Changed\" \u003e /tmp/changelog.md\n          if [ -n \"$prev_tag\" ]; then\n            git log \"$prev_tag\"..HEAD --oneline --no-merges --format=\"%s\" | while read line; do\n              echo \"- $line\"\n            done\n          else\n            git log --oneline --no-merges --format=\"%s\" | while read line; do\n              echo \"- $line\"\n            done\n          fi \u003e\u003e /tmp/changelog.md\n\n      - uses: softprops/action-gh-release@v2\n        with:\n          body_path: /tmp/changelog.md\n          files: |\n            tauri/target/release/bundle/**/*\n```\n\n#### Auto-version on merge to `main`\n\nAdd a workflow that bumps the version in `scripts/version.txt` after every merge to `main`, using conventional commit prefixes to determine the bump type:\n\n```yaml\nname: Auto Version\non:\n  push:\n    branches: [main]\n\njobs:\n  bump:\n    if: github.event.head_commit.message != 'docs: update version'\n    runs-on: ubuntu-latest\n    permissions: { contents: write }\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with: { node-version: \"20\" }\n\n      - name: Determine bump type from commit messages\n        id: bump\n        run: |\n          # Collect messages of new commits since last version bump\n          last_version=$(git log --grep=\"^docs: update version\" --format=\"%H\" -1)\n          if [ -z \"$last_version\" ]; then\n            last_version=$(git rev-list --max-parents=0 HEAD)\n          fi\n          msgs=$(git log \"$last_version\"..HEAD --format=\"%s\" --no-merges)\n\n          bump=\"patch\"\n          echo \"$msgs\" | while read msg; do\n            case \"$msg\" in\n              BREAKING\\ CHANGE:*|*!:*) bump=\"major\" ;;\n              feat:*|feat\\(*\\):*) [ \"$bump\" != \"major\" ] \u0026\u0026 bump=\"minor\" ;;\n            esac\n          done\n          echo \"type=$bump\" \u003e\u003e \"$GITHUB_OUTPUT\"\n\n      - run: node scripts/version.mjs bump ${{ steps.bump.outputs.type }}\n\n      - name: Commit version bump and tag\n        run: |\n          git config user.name \"github-actions\"\n          git config user.email \"actions@github.com\"\n          NEW_VER=$(node scripts/version.mjs read)\n          git add scripts/version.txt\n          git commit -m \"docs: update version to $NEW_VER\"\n          git tag \"v$NEW_VER\"\n          git push --atomic origin main \"v$NEW_VER\"\n```\n\nThis approach requires **conventional commit** messages on `main`:\n- `fix:` → patch bump (0.1.0 → 0.1.1)\n- `feat:` → minor bump (0.1.0 → 0.2.0)\n- `BREAKING CHANGE:` or `!:` suffix → major bump (0.1.0 → 1.0.0)\n\n### Build individual components\n\n```bash\nnpm run build:dotnet           # Publish .NET backend (current platform)\nnpm run build:dotnet:mac       # Publish for macOS (osx-x64, cross-compile from any host)\nnpm run build:dotnet:win       # Publish for Windows (win-x64, cross-compile from any host)\nnpm run build:dotnet:linux     # Publish for Linux (linux-x64, cross-compile from any host)\nnpm run build:dotnet:all       # Publish for all three platforms at once\nnpm run build                  # Build frontend only\n```\n\u003e **Note:** The .NET backend uses **trimmed single-file publishing** (`PublishTrimmed` + `PublishSingleFile`), not Native AOT. EF Core and `Scalar.AspNetCore` rely on runtime reflection/code-gen that AOT doesn't support. To switch to AOT, you'd need to replace them with compatible alternatives (e.g., raw SQLite, manual JSON serialization).\n\n## Cleaning\n\nTo remove all build artifacts across the entire project:\n\n```bash\nnpm run clean                   # dotnet clean + rm frontend/dist + cargo clean\n```\n\nThis runs `dotnet clean` for the backend, deletes `frontend/dist/`, and runs `cargo clean` for the Tauri Rust target.\n\n## Database \u0026 Migrations\n\nThe app uses **EF Core** with **SQLite + SQLCipher** (encrypted). Migrations are managed via the helper script:\n\n```bash\nnpm run migration:create       # Create a new migration (prompts for name)\nnpm run migration:apply        # Apply pending migrations\nnpm run migration:remove       # Remove the last migration\n```\n\nThe database file is created at `app.db` in the same directory as the executable.\n\nIn development, you have two ways to reset the database to a clean slate:\n\n```bash\nnpm run db:reset                # Deletes app.db files from disk (no backend needed)\n# or\ncurl -s -X POST http://127.0.0.1:5199/api/db/reset   # Via API (backend must be running; use `curl` or `Invoke-WebRequest` on Windows)\n```\n\n## API Endpoints\n\n| Method | Path | Description |\n|--------|------|-------------|\n| GET | `/api/hello` | Health check |\n| GET | `/api/weather` | Sample weather forecast |\n| GET | `/api/todos` | List all todos |\n| GET | `/api/todos/{id}` | Get todo by ID |\n| POST | `/api/todos` | Create todo (`{ \"title\": \"...\" }`) |\n| PUT | `/api/todos/{id}` | Update todo (`{ \"title\": \"...\", \"isCompleted\": true/false }`) |\n| DELETE | `/api/todos/{id}` | Delete todo |\n| POST | `/api/db/reset` | Drop all tables and re-run migrations (dev only) |\n| GET | `/openapi/v1.json` | OpenAPI spec (dev only) |\n| GET | `/scalar/v1` | Scalar API reference UI (dev only) |\n\n## Code Splitting\n\nThe frontend uses two levels of code splitting:\n\n- **Lazy-loaded components**: `App.tsx` is loaded via `React.lazy()` + `Suspense`, so its code is fetched only after `LicenseGate` confirms the device is licensed\n- **Vendor chunking**: Vite's `manualChunks` splits React + ReactDOM into a separate `vendor-*.js` chunk (cached independently)\n\nBuild output:\n\n| Chunk | Size | Contents |\n|-------|------|----------|\n| `vendor-*.js` | ~190 kB | React, ReactDOM |\n| `index-*.js` | ~4.5 kB | Entry, LicenseGate (eager) |\n| `App-*.js` | ~2 kB | App component (lazy) |\n\n## Security \u0026 Protection\n\n- **.NET backend**: Obfuscated with Obfuscar (rename-only, skips JSON-exposed types)\n- **.NET publishing**: Trimmed, single-file, self-contained\n- **Rust binary**: Stripped symbols, LTO, single codegen unit\n- **Frontend**: Sourcemaps disabled, vendor chunk split, lazy-loaded App\n- **Database**: Encrypted via SQLCipher (password in `appsettings.json`; override via `DatabasePassword` env var for CI/CD)\n\n## Exception Handling\n\n- **HTTP errors**: Caught by `UseExceptionHandler` middleware → 500 JSON response\n- **Unobserved task exceptions**: Logged via `UnobservedTaskException`\n- **Fatal crashes**: Logged via `UnhandledException`, app auto-restarts up to 5 times\n- **SQLite transient errors** (BUSY/LOCKED): Retried up to 3 times via Polly pipeline behavior\n\n## Versioning\n\nAll version numbers are single-sourced from `scripts/version.txt`. Build scripts automatically inject it into the .NET assembly version and `tauri.conf.json`.\n\n```bash\nnpm run version                # Read current version\nnpm run version:set 1.2.3      # Set explicitly\nnpm run version:bump patch     # 0.1.0 → 0.1.1\nnpm run version:bump minor     # 0.1.0 → 0.2.0\nnpm run version:bump major     # 0.1.0 → 1.0.0\n```\n\n## Licensing \u0026 Unlock System\n\nThe app includes a device-locked license system (RSA-signed). The app will not show its content until a valid license is activated.\n\n### How it works\n\n1. On first launch, the app generates a **Machine ID** (SHA-256 of machine name + OS version)\n2. The user sends this Machine ID to you (the developer)\n3. You generate a signed license file using your private key\n4. The user pastes the license code into the unlock screen\n5. The app verifies the RSA signature and binds it to the machine ID\n\n### Generating licenses\n\n```bash\n# Generate a new key pair (one-time setup)\nnpm run license:keygen -- private-key.pem public-key.pem\n\n# Generate a license for a specific machine ID (no expiration)\nnpm run license:sign -- private-key.pem \u003cmachine-id\u003e license.lic\n\n# With expiration — relative duration (\"30d\", \"6m\", \"1y\")\nnpm run license:sign -- private-key.pem \u003cmachine-id\u003e license.lic \"30d\"\n\n# With expiration — absolute date\nnpm run license:sign -- private-key.pem \u003cmachine-id\u003e license.lic \"2027-06-01\"\n```\n\nThe public key is set in `appsettings.json` (`LicensePublicKey`). Override it via environment variable or CI/CD secret (`LicensePublicKey`) for different environments.\nKeep the **private key** secret and never commit it to the repository.\n\n### API endpoints\n\n| Method | Path | Description |\n|--------|------|-------------|\n| GET | `/api/license/status` | Returns `{ isLicensed, machineId, reason? }` |\n| POST | `/api/license/activate` | Accepts a raw license JSON body, saves and validates it |\n\n### Frontend unlock screen\n\nThe `LicenseGate` component (`frontend/src/LicenseGate.tsx`) wraps the entire app. It shows the machine ID and a text area for pasting the license code. The app content is only rendered once `isLicensed === true`.\n\n## Auto-Update\n\nThe app uses Tauri's built-in updater plugin (not yet configured — see steps below to enable it).\n\n### 1. Install the plugin\n\n```bash\ncd frontend \u0026\u0026 npm add @tauri-apps/plugin-updater\n```\n\nAdd to `tauri/Cargo.toml`:\n```toml\ntauri-plugin-updater = \"2\"\n```\n\n### 2. Generate signing keys\n\n```bash\ncd tauri \u0026\u0026 npx tauri signer generate -w ~/.tauri/tauri-dotnet-app.key\n# On Windows PowerShell: npx tauri signer generate -w $env:USERPROFILE\\.tauri\\tauri-dotnet-app.key\n```\n\nKeep the private key secret (used in CI). The public key goes in the config.\n\n### 3. Configure in `tauri/tauri.conf.json`\n\n```json\n\"plugins\": {\n  \"updater\": {\n    \"pubkey\": \"\u003cyour-public-key\u003e\",\n    \"endpoints\": [\"https://github.com/c4bo3l/tauri-dotnet-react-boilerplate/releases/latest/download/update.json\"],\n    \"windows\": {\n      \"installMode\": \"passive\"\n    }\n  }\n}\n```\n\n### 4. Check for updates from the frontend\n\n```typescript\nimport { check } from '@tauri-apps/plugin-updater'\nimport { relaunch } from '@tauri-apps/plugin-process'\n\nconst update = await check()\nif (update?.available) {\n  await update.downloadAndInstall()\n  await relaunch()\n}\n```\n\nThe updater replaces the entire `.app` bundle, so the .NET sidecar inside it is updated automatically.\n\n## Removing Licensing\n\nIf you don't need the licensing system, run the cleanup script to strip it entirely:\n\n```bash\nnode scripts/remove-license.mjs\n```\n\nThis removes `Infrastructure.Licensing/`, `LicenseGenerator/`, `LicenseGate.tsx`, license endpoints, npm scripts, `LicensePublicKey` from `appsettings.json`, and all README/solution/project references. Can be re-run safely if already clean.\n\n## Renaming the Project\n\nTo rename the project from \"tauri-dotnet-app\" to your own name:\n\n```bash\nnode scripts/rename.mjs my-app-name\n```\n\nThis updates the npm package names, HTML title, Tauri product name and bundle identifier (`com.my-app-name.dev`), and README paths.\n\n## App Icons\n\nReplace the icon files in `tauri/icons/`:\n\n```\ntauri/icons/\n├── 32x32.png\n├── 128x128.png\n├── 128x128@2x.png\n├── icon.icns    (macOS)\n└── icon.ico     (Windows)\n```\n\nRegenerate all sizes from a single source image (recommended: **1024×1024 PNG**):\n\n```bash\ncd tauri \u0026\u0026 npx tauri icon ../path/to/source.png\n```\n\nThis overwrites all icon files with properly scaled versions. The icons are referenced from `tauri/tauri.conf.json` under `bundle.icon`.\n\n## License\n\n[MIT](LICENSE)\n\n## Linting\n\n```bash\nnpm run lint                    # ESLint — type-aware (recommendedTypeChecked), enforced semicolons\n```\n\n## Testing\n\n```bash\ndotnet test backend/dotnet-backend.slnx\n```\n\n## Dev URLs\n\n| URL | Description |\n|-----|-------------|\n| `http://127.0.0.1:5199` | .NET backend |\n| `http://localhost:5173` | Vite frontend |\n| `http://localhost:5199/scalar/v1` | Scalar API reference (dev only) |\n| `http://localhost:5199/openapi/v1.json` | OpenAPI spec (dev only) |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fc4bo3l%2Ftauri-dotnet-react-boilerplate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fc4bo3l%2Ftauri-dotnet-react-boilerplate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fc4bo3l%2Ftauri-dotnet-react-boilerplate/lists"}