Skip to content
Merged
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
60 changes: 55 additions & 5 deletions .github/workflows/desktop-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,53 @@ jobs:
quality:
name: Desktop quality gates
runs-on: ubuntu-24.04
outputs:
desktop_changed: ${{ steps.scope.outputs.desktop_changed }}

steps:
- name: Check out repository
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0

- name: Detect desktop-impacting changes
id: scope
env:
EVENT_NAME: ${{ github.event_name }}
BEFORE_SHA: ${{ github.event.before }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
run: |
if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then
echo "desktop_changed=true" >> "$GITHUB_OUTPUT"
exit 0
fi

if [[ "$EVENT_NAME" == "pull_request" ]]; then
compare_from="$BASE_SHA"
else
compare_from="$BEFORE_SHA"
fi

if [[ -z "$compare_from" || "$compare_from" =~ ^0+$ ]]; then
echo "desktop_changed=true" >> "$GITHUB_OUTPUT"
exit 0
fi

if ! git cat-file -e "${compare_from}^{commit}" 2>/dev/null; then
echo "desktop_changed=true" >> "$GITHUB_OUTPUT"
exit 0
fi

git diff --no-renames --name-only -z "$compare_from" "$HEAD_SHA" \
| python3 scripts/desktop_ci_scope.py >> "$GITHUB_OUTPUT"

- name: Skip unaffected Desktop checks
if: steps.scope.outputs.desktop_changed != 'true'
run: echo "No Desktop or bundled App Server inputs changed."

- name: Install Tauri Linux dependencies
if: steps.scope.outputs.desktop_changed == 'true'
run: |
sudo apt-get update
sudo apt-get install -y \
Expand All @@ -38,22 +79,26 @@ jobs:
librsvg2-dev

- name: Set up Node
uses: actions/setup-node@v4
if: steps.scope.outputs.desktop_changed == 'true'
uses: actions/setup-node@v5
with:
node-version: "22"
cache: npm
cache-dependency-path: desktop/package-lock.json

- name: Set up Rust
if: steps.scope.outputs.desktop_changed == 'true'
uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt

- name: Install frontend dependencies
if: steps.scope.outputs.desktop_changed == 'true'
working-directory: desktop
run: npm ci

- name: Verify frontend and protocol
if: steps.scope.outputs.desktop_changed == 'true'
working-directory: desktop
run: |
npm run check:version
Expand All @@ -64,10 +109,12 @@ jobs:
npm run build

- name: Prepare Tauri resources for Rust-only checks
if: steps.scope.outputs.desktop_changed == 'true'
working-directory: desktop
run: mkdir -p build/sidecar/dist/deepcode-app-server

- name: Verify Tauri crate
if: steps.scope.outputs.desktop_changed == 'true'
working-directory: desktop/src-tauri
run: |
cargo fmt --check
Expand All @@ -77,6 +124,9 @@ jobs:
bundle:
name: Bundle ${{ matrix.name }}
needs: quality
if: >-
needs.quality.result == 'success' &&
needs.quality.outputs.desktop_changed == 'true'
strategy:
fail-fast: false
matrix:
Expand All @@ -97,7 +147,7 @@ jobs:

steps:
- name: Check out repository
uses: actions/checkout@v4
uses: actions/checkout@v5

- name: Install Tauri Linux dependencies
if: runner.os == 'Linux'
Expand All @@ -116,14 +166,14 @@ jobs:
patchelf

- name: Set up Node
uses: actions/setup-node@v4
uses: actions/setup-node@v5
with:
node-version: "22"
cache: npm
cache-dependency-path: desktop/package-lock.json

- name: Set up Python for App Server sidecar
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: "3.12"
cache: pip
Expand Down
17 changes: 14 additions & 3 deletions desktop/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1470,9 +1470,20 @@ describe("desktop command center", () => {
await screen.findByRole("heading", { name: "Skills" });
fireEvent.click(await screen.findByRole("button", { name: "Create Skill" }));

const composer = (await screen.findByRole("textbox", {
name: "Task instruction",
})) as HTMLTextAreaElement;
// Creating a Skill crosses an async navigation and a thread/start RPC.
// Synchronize on that observable boundary before asserting the destination UI.
await waitFor(
() =>
expect(
runtime.requests.some((request) => request.method === "thread/start"),
).toBe(true),
{ timeout: 5_000 },
);
const composer = (await screen.findByRole(
"textbox",
{ name: "Task instruction" },
{ timeout: 5_000 },
)) as HTMLTextAreaElement;
await waitFor(() =>
expect(composer.value).toContain("$skill-creator"),
);
Expand Down
65 changes: 65 additions & 0 deletions scripts/desktop_ci_scope.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""Classify whether a Git change can affect the Desktop application.

Desktop bundles the Python App Server, so its CI scope includes both ``desktop``
and the Python runtime imported by the sidecar. Release-only scripts, tests, and
documentation intentionally stay outside this list.
"""

from __future__ import annotations

import os
import sys
from collections.abc import Iterable


DESKTOP_IMPACT_PREFIXES = (
".github/actions/",
"app_server/",
"cli/",
"core/",
"desktop/",
"prompts/",
"protocol/",
"schema/",
"tools/",
"utils/",
"workflows/",
)

DESKTOP_IMPACT_FILES = frozenset(
{
".github/workflows/desktop-ci.yml",
"__init__.py",
"deepcode.py",
"requirements.txt",
"rust-toolchain.toml",
"scripts/desktop_ci_scope.py",
}
)


def affects_desktop(paths: Iterable[str]) -> bool:
"""Return whether any repository-relative path affects Desktop artifacts."""

return any(
path in DESKTOP_IMPACT_FILES or path.startswith(DESKTOP_IMPACT_PREFIXES)
for path in paths
)


def _read_null_delimited_paths() -> list[str]:
return [
os.fsdecode(raw_path)
for raw_path in sys.stdin.buffer.read().split(b"\0")
if raw_path
]


def main() -> int:
changed = affects_desktop(_read_null_delimited_paths())
print(f"desktop_changed={'true' if changed else 'false'}")
return 0


if __name__ == "__main__":
raise SystemExit(main())
42 changes: 42 additions & 0 deletions tests/test_desktop_ci_scope.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from __future__ import annotations

import pytest

from scripts.desktop_ci_scope import affects_desktop


@pytest.mark.parametrize(
"path",
[
"desktop/src/App.tsx",
"desktop/src-tauri/src/main.rs",
"app_server/dispatcher.py",
"core/skills/service.py",
"protocol/app-server.schema.json",
"requirements.txt",
".github/workflows/desktop-ci.yml",
"scripts/desktop_ci_scope.py",
],
)
def test_desktop_ci_runs_for_desktop_and_sidecar_inputs(path: str) -> None:
assert affects_desktop([path])


@pytest.mark.parametrize(
"path",
[
"README.md",
"docs/HEADLESS_AND_AUTOMATION.md",
"tests/test_python_distribution_release.py",
"scripts/verify_python_distribution.py",
".github/workflows/python-ci.yml",
".github/workflows/pypi-publish.yml",
"desktop-notes/architecture.md",
],
)
def test_desktop_ci_skips_release_only_and_documentation_changes(path: str) -> None:
assert not affects_desktop([path])


def test_desktop_ci_runs_when_any_changed_path_has_desktop_impact() -> None:
assert affects_desktop(["README.md", "core/version.py"])
Loading