Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions .github/workflows/desktop-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Desktop CI — runs on desktop changes. Verifies the Go bridge, the frontend
# (typecheck + build), and a native macOS app bundle with a bridge smoke test.
name: Desktop CI

on:
push:
branches: [main]
paths:
- "apps/desktop/**"
- "cmd/neo-bridge/**"
- "internal/**"
- ".github/workflows/desktop-ci.yml"
pull_request:
paths:
- "apps/desktop/**"
- "cmd/neo-bridge/**"
- "internal/**"
- ".github/workflows/desktop-ci.yml"

concurrency:
group: desktop-ci-${{ github.ref }}
cancel-in-progress: true

jobs:
go:
name: Go (bridge builds + unit tests)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.23"
cache: true
- run: go build ./cmd/neo-bridge
- run: go test ./internal/... ./cmd/neo-bridge/... 2>/dev/null || go test ./...

frontend:
name: Frontend (typecheck + build)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: apps/desktop/package-lock.json
- working-directory: apps/desktop
run: npm ci
- working-directory: apps/desktop
run: npm run build

macos-build:
name: macOS build (fmt, clippy, bundle, smoke)
runs-on: macos-14
timeout-minutes: 40
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: apps/desktop/package-lock.json
- uses: actions/setup-go@v5
with:
go-version: "1.23"
cache: true
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
targets: aarch64-apple-darwin
- uses: Swatinem/rust-cache@v2
with:
workspaces: apps/desktop/src-tauri
- name: Build neo-bridge sidecar
run: |
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -trimpath \
-o apps/desktop/src-tauri/binaries/neo-bridge-aarch64-apple-darwin ./cmd/neo-bridge
- working-directory: apps/desktop
run: npm ci
- name: Rust formatting
run: cargo fmt --manifest-path apps/desktop/src-tauri/Cargo.toml --all --check
- name: Rust lint (clippy)
run: cargo clippy --manifest-path apps/desktop/src-tauri/Cargo.toml --all-targets -- -D warnings
- name: Build macOS app bundle (unsigned)
working-directory: apps/desktop
run: npm run tauri build -- --target aarch64-apple-darwin --bundles app
- name: Smoke test bundled bridge
shell: bash
run: |
set -euo pipefail
bridge="apps/desktop/src-tauri/target/aarch64-apple-darwin/release/bundle/macos/Neo Desktop.app/Contents/MacOS/neo-bridge"
test -x "$bridge"
"$bridge" bridge.hello | grep -q '"protocolVersion":1'
175 changes: 175 additions & 0 deletions .github/workflows/desktop-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# Neo Desktop release pipeline — triggered by `desktop-v*` tags (independent of
# the CLI's `v*` releases). Per target it builds the matching neo-bridge sidecar
# from the tag commit, packages the Tauri app + DMG, smoke-tests the bundled
# bridge, and publishes a draft GitHub Release with the DMGs.
#
# Signing is optional: with no APPLE_CERTIFICATE secret the macOS build is
# unsigned (ad-hoc) instead of failing at `security import`.
name: Desktop Release

on:
push:
tags:
- "desktop-v*"

permissions:
contents: write

concurrency:
group: desktop-release-${{ github.ref_name }}
cancel-in-progress: false

jobs:
verify:
name: Verify tag and versions
runs-on: ubuntu-latest
timeout-minutes: 10
outputs:
version: ${{ steps.meta.outputs.version }}
steps:
- uses: actions/checkout@v4
- id: meta
shell: bash
run: |
set -euo pipefail
version="${GITHUB_REF_NAME#desktop-v}"
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?$ ]]; then
echo "Desktop release tags must be desktop-v<semver>; got ${GITHUB_REF_NAME}" >&2
exit 1
fi
conf=$(node -p "require('./apps/desktop/src-tauri/tauri.conf.json').version")
pkg=$(node -p "require('./apps/desktop/package.json').version")
if [[ "$conf" != "$version" || "$pkg" != "$version" ]]; then
echo "Version mismatch: tag=$version tauri.conf.json=$conf package.json=$pkg" >&2
echo "Bump apps/desktop versions before tagging." >&2
exit 1
fi
echo "version=$version" >> "$GITHUB_OUTPUT"

build:
name: Build ${{ matrix.triple }}
needs: verify
strategy:
fail-fast: false
matrix:
include:
- os: macos-14
triple: aarch64-apple-darwin
goos: darwin
goarch: arm64
- os: macos-13
triple: x86_64-apple-darwin
goos: darwin
goarch: amd64
runs-on: ${{ matrix.os }}
timeout-minutes: 40
env:
VERSION: ${{ needs.verify.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: apps/desktop/package-lock.json
- uses: actions/setup-go@v5
with:
go-version: "1.23"
cache: true
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.triple }}
- uses: Swatinem/rust-cache@v2
with:
workspaces: apps/desktop/src-tauri

# Build the target-specific bridge sidecar (version-stamped) before the
# Tauri bundle consumes it via externalBin.
- name: Build neo-bridge sidecar
shell: bash
run: |
set -euo pipefail
CGO_ENABLED=0 GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} \
go build -trimpath -ldflags "-s -w -X main.version=${VERSION}" \
-o "apps/desktop/src-tauri/binaries/neo-bridge-${{ matrix.triple }}" ./cmd/neo-bridge

- name: Install frontend deps
working-directory: apps/desktop
run: npm ci

- name: Signing preflight
id: signing
shell: bash
env:
HAS_APPLE_CERT: ${{ secrets.APPLE_CERTIFICATE != '' }}
run: |
echo "apple_cert=${HAS_APPLE_CERT}" >> "$GITHUB_OUTPUT"
[ "${HAS_APPLE_CERT}" = "true" ] || echo "::warning::APPLE_CERTIFICATE missing — macOS app will be unsigned"

# Signed build — only when a certificate secret exists (passing APPLE_*
# env with no cert makes Tauri abort at `security import`).
- name: Build and package (signed)
if: steps.signing.outputs.apple_cert == 'true'
working-directory: apps/desktop
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: npm run tauri build -- --target ${{ matrix.triple }}

- name: Build and package (unsigned)
if: steps.signing.outputs.apple_cert != 'true'
working-directory: apps/desktop
run: npm run tauri build -- --target ${{ matrix.triple }}

# The bundled bridge must be executable and report the tag version.
- name: Smoke test bundled bridge
shell: bash
run: |
set -euo pipefail
app="apps/desktop/src-tauri/target/${{ matrix.triple }}/release/bundle/macos/Neo Desktop.app"
bridge="$app/Contents/MacOS/neo-bridge"
test -x "$bridge" || { echo "bundled neo-bridge missing"; exit 1; }
"$bridge" bridge.hello > hello.json
cat hello.json
grep -q '"protocolVersion":1' hello.json
grep -q "\"bridgeVersion\":\"${VERSION}\"" hello.json

- name: Collect DMG
shell: bash
run: |
set -euo pipefail
mkdir -p "$RUNNER_TEMP/upload"
cp apps/desktop/src-tauri/target/${{ matrix.triple }}/release/bundle/dmg/*.dmg "$RUNNER_TEMP/upload/"

- uses: actions/upload-artifact@v4
with:
name: desktop-${{ matrix.triple }}
path: ${{ runner.temp }}/upload/*

publish:
name: Publish draft release
needs: [verify, build]
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/download-artifact@v4
with:
path: dist
- name: Create draft release
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
mapfile -t files < <(find dist -type f -name '*.dmg')
echo "Publishing: ${files[*]}"
gh release create "$GITHUB_REF_NAME" \
--repo "$GITHUB_REPOSITORY" \
--draft \
--title "Neo Desktop ${GITHUB_REF_NAME#desktop-v}" \
--notes "Automated build of the Neo Desktop app (our UI + integrated terminal). Unsigned — right-click the app and choose Open on first launch." \
"${files[@]}"
32 changes: 32 additions & 0 deletions apps/desktop/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Tauri / Rust build output
src-tauri/target
src-tauri/gen

# Bundled sidecar binaries (generated via `go build` — rebuildable)
src-tauri/binaries/*
!src-tauri/binaries/.gitkeep

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
3 changes: 3 additions & 0 deletions apps/desktop/.vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"]
}
7 changes: 7 additions & 0 deletions apps/desktop/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Tauri + React + Typescript

This template should help get you started developing with Tauri, React and Typescript in Vite.

## Recommended IDE Setup

- [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)
14 changes: 14 additions & 0 deletions apps/desktop/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tauri + React + Typescript</title>
</head>

<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Loading
Loading