chore(deps): pin brace-expansion to patched versions for audit gate (… #211
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Nightly Release | |
| # Fires on every push to main and on manual re-trigger. | |
| # Produces a single rolling "nightly" prerelease DMG: one tag, one release, | |
| # always pointing to the latest successful build on main. | |
| on: | |
| push: | |
| branches: | |
| - main | |
| # workflow_dispatch lets you force a rebuild without a no-op commit, | |
| # useful when a previous run failed due to a transient CI flake. | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| jobs: | |
| guard: | |
| name: Check if build needed | |
| # Runs on a cheap ubuntu runner to avoid spinning up a macOS runner | |
| # when the nightly tag already points to HEAD (duplicate push, bot commit, etc.). | |
| runs-on: ubuntu-latest | |
| outputs: | |
| should_build: ${{ steps.check.outputs.should_build }} | |
| # head_sha pins the exact commit this guard evaluated. The nightly job | |
| # checks out this SHA explicitly so a concurrent push between jobs cannot | |
| # cause the build and the tag to diverge. | |
| head_sha: ${{ steps.check.outputs.head_sha }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check nightly tag vs HEAD | |
| id: check | |
| run: | | |
| NIGHTLY_SHA=$(git rev-parse nightly 2>/dev/null || echo "none") | |
| HEAD_SHA=$(git rev-parse HEAD) | |
| echo "head_sha=$HEAD_SHA" >> "$GITHUB_OUTPUT" | |
| # Manual triggers always build, regardless of tag state. | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| echo "manual trigger, forcing build" | |
| echo "should_build=true" >> "$GITHUB_OUTPUT" | |
| elif [ "$NIGHTLY_SHA" = "$HEAD_SHA" ]; then | |
| echo "nightly tag already at HEAD ($HEAD_SHA), skipping build" | |
| echo "should_build=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "should_build=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| nightly: | |
| name: Build and publish nightly DMG | |
| needs: guard | |
| if: needs.guard.outputs.should_build == 'true' | |
| runs-on: macos-15 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| fetch-depth: 0 | |
| # Pin to the SHA guard evaluated, not the live HEAD. Without this, a | |
| # second push landing between guard and this checkout would build a | |
| # different commit than the one guard approved. | |
| ref: ${{ needs.guard.outputs.head_sha }} | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 | |
| with: | |
| bun-version: 1.3.11 | |
| - name: Install stable Rust toolchain | |
| run: rustup toolchain install stable --no-self-update | |
| # The nightly toolchain with llvm-tools is required by cargo-llvm-cov | |
| # to instrument binaries and enforce the 100% coverage gate. | |
| - name: Install nightly Rust toolchain | |
| run: rustup toolchain install nightly-2026-03-30 --component llvm-tools --no-self-update | |
| - name: Install cargo-llvm-cov | |
| uses: taiki-e/install-action@43aecc8d72668fbcfe75c31400bc4f890f1c5853 # v2.83.2 | |
| with: | |
| tool: cargo-llvm-cov | |
| - name: Install frontend dependencies | |
| run: bun install --frozen-lockfile | |
| # The sidecar must be present before any cargo invocation. lint:backend | |
| # (cargo clippy) and test:all:coverage (cargo llvm-cov) both run build.rs, | |
| # which fails if binaries/llama-server-<triple> is missing. Restore the | |
| # cache and build on miss before the first lint step, not after. | |
| - name: Cache llama.cpp sidecar | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: src-tauri/binaries | |
| key: llama-cpp-${{ runner.os }}-${{ hashFiles('scripts/ensure-llama-server.ts') }} | |
| - name: Build llama-server sidecar | |
| run: bun run engine:ensure | |
| - name: Lint and format check | |
| run: bun run lint && bun run format:check | |
| - name: Typecheck | |
| run: bun run typecheck | |
| - name: Run all tests with coverage enforcement | |
| run: bun run test:all:coverage | |
| # Give nightly a separate product identity so it coexists with stable: | |
| # different productName/identifier → distinct /Applications path, TCC | |
| # grant, Keychain service, and app-data dir. Updater stays off so the | |
| # rolling "nightly" tag cannot try to self-update from latest.json. | |
| - name: Patch tauri.conf.json for nightly identity | |
| run: | | |
| command -v jq >/dev/null 2>&1 || brew install jq | |
| tmp="$(mktemp)" | |
| jq ' | |
| .productName = "Thuki Nightly" | |
| | .identifier = "com.quietnode.thuki.nightly" | |
| | .plugins.updater.active = false | |
| | .app.windows |= map( | |
| if .title == "Thuki" then .title = "Thuki Nightly" | |
| elif .title == "Thuki Settings" then .title = "Thuki Nightly Settings" | |
| else . | |
| end | |
| ) | |
| ' src-tauri/tauri.conf.json > "$tmp" | |
| mv "$tmp" src-tauri/tauri.conf.json | |
| jq '{productName, identifier, updater: .plugins.updater.active, titles: [.app.windows[].title]}' \ | |
| src-tauri/tauri.conf.json | |
| - name: Build Tauri app | |
| # VITE_GIT_COMMIT_SHA is set here, not on a separate frontend step, because | |
| # tauri build runs beforeBuildCommand (bun run build:frontend) internally. | |
| # A separate frontend build step would be overwritten by that second pass. | |
| # AboutTab slices the SHA to 7 chars: v0.6.1+nightly.abc1234 | |
| env: | |
| VITE_GIT_COMMIT_SHA: ${{ needs.guard.outputs.head_sha }} | |
| run: bun run build:backend | |
| # Ad-hoc signing with "-" satisfies Gatekeeper for drag-install without | |
| # requiring an Apple Developer certificate in CI secrets. The app is not | |
| # notarized, which is acceptable for a nightly/dev artifact. | |
| # productName "Thuki Nightly" → bundle path has a space; quote carefully. | |
| - name: Ad-hoc sign the app | |
| run: | | |
| APP_BUNDLE='src-tauri/target/release/bundle/macos/Thuki Nightly.app' | |
| codesign --deep --force --sign - "$APP_BUNDLE" | |
| codesign --verify --verbose "$APP_BUNDLE" | |
| - name: Install create-dmg | |
| run: brew install create-dmg | |
| - name: Create DMG installer | |
| run: | | |
| # Stage only the .app. Asset filenames stay Thuki.dmg / Thuki.dmg.sig | |
| # so install.sh can use the same names for stable and nightly. | |
| mkdir -p /tmp/thuki-dmg-src | |
| cp -R "src-tauri/target/release/bundle/macos/Thuki Nightly.app" /tmp/thuki-dmg-src/ | |
| mkdir -p src-tauri/target/release/bundle/dmg | |
| create-dmg \ | |
| --volname "Thuki Nightly" \ | |
| --background "src-tauri/assets/dmg-background.png" \ | |
| --window-pos 200 120 \ | |
| --window-size 600 380 \ | |
| --icon-size 128 \ | |
| --icon "Thuki Nightly.app" 170 170 \ | |
| --hide-extension "Thuki Nightly.app" \ | |
| --app-drop-link 430 170 \ | |
| "src-tauri/target/release/bundle/dmg/Thuki.dmg" \ | |
| "/tmp/thuki-dmg-src" | |
| rm -rf /tmp/thuki-dmg-src | |
| # Same detached RSA scheme as release-please "Sign DMG for install script". | |
| # Private key never leaves the secret; public key is pinned in install.sh. | |
| - name: Sign DMG for install script | |
| env: | |
| INSTALLER_SIGNING_KEY: ${{ secrets.THUKI_INSTALLER_RSA_PRIVATE_KEY }} | |
| run: | | |
| KEYFILE="$(mktemp)" | |
| trap 'rm -f "$KEYFILE"' EXIT | |
| printf '%s\n' "$INSTALLER_SIGNING_KEY" > "$KEYFILE" | |
| openssl dgst -sha256 -sign "$KEYFILE" \ | |
| -out src-tauri/target/release/bundle/dmg/Thuki.dmg.sig \ | |
| src-tauri/target/release/bundle/dmg/Thuki.dmg | |
| # refs/tags/ is explicit to prevent ambiguity if a "nightly" branch | |
| # is ever created alongside the tag. | |
| - name: Force-push nightly tag | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git tag -f nightly | |
| git push origin refs/tags/nightly --force | |
| # Floating release: delete the previous nightly release (if it exists) | |
| # then recreate it. There is always exactly one nightly prerelease, | |
| # always pointing to the latest build. Upload both DMG and signature. | |
| - name: Publish nightly prerelease | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| # "|| true" tolerates the first run when no nightly release exists yet. | |
| gh release delete nightly --yes || true | |
| SHORT_SHA="$(git rev-parse --short HEAD)" | |
| NOTES="Automated build from main (${SHORT_SHA}). | |
| Thuki Nightly is a separate app (Thuki Nightly.app, bundle id com.quietnode.thuki.nightly) that installs alongside stable Thuki. Not for production use. | |
| Notes: | |
| - Accessibility (and other TCC grants) may need to be re-granted for the nightly identity. | |
| - Do not run stable and nightly at the same time: both claim double-tap Control and will fight over the hotkey. | |
| - Auto-update is disabled for nightly; reinstall via the nightly channel to refresh." | |
| gh release create nightly \ | |
| --title "Thuki Nightly" \ | |
| --notes "$NOTES" \ | |
| --prerelease \ | |
| src-tauri/target/release/bundle/dmg/Thuki.dmg \ | |
| src-tauri/target/release/bundle/dmg/Thuki.dmg.sig |