mirror of
https://github.com/Alishahryar1/free-claude-code.git
synced 2026-11-01 11:21:06 +00:00
Some checks are pending
Post merge / Warm dependency cache (ubuntu-latest) (push) Waiting to run
Post merge / Warm dependency cache (windows-latest) (push) Waiting to run
Post merge / Warm dependency cache (macos-26) (push) Waiting to run
Post merge / Warm dependency cache (macos-26-intel) (push) Waiting to run
Post merge / Warm dependency cache (macos-latest) (push) Waiting to run
Post merge / Publish to PyPI (push) Waiting to run
## Why
Fixes #2092. Launching the macOS app through its shell wrapper replaces
the app executable with Python. AppKit then sees Python's bundle instead
of Free Claude Code, and the menu-bar item can have a zero-height window
even though pystray reports it visible.
## How
- Run the installed uv-managed Python runtime inside a native app
executable, preserving the app's bundle identity and main thread. Enable
UTF-8 and retain the tool environment for imports and child processes.
- Build arm64 and x86_64 launchers during release staging and include
both in the existing wheel and source distribution. Select the launcher
matching the installed Python architecture.
- Create and ad-hoc sign the app through a Python installer helper.
Validate its runtime, stage replacements, preserve foreign apps and
Desktop entries, and recognize existing FCC ownership markers during
upgrades and uninstall.
- Add native macOS coverage for bundle identity, visible menu-bar
geometry, server lifecycle, duplicate launches, child processes, and
reinstall. Add Windows and Linux source-distribution rebuild checks and
require these jobs in CI.
<!-- greptile_comment -->
<!-- greptile_summary -->
<h2><a
href="https://app.greptile.com/api/retrigger?id=81926736"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://greptile-static-assets.s3.amazonaws.com/badges/RetriggerDark.svg?v=2"><source
media="(prefers-color-scheme: light)"
srcset="https://greptile-static-assets.s3.amazonaws.com/badges/Retrigger.svg?v=2"><img
alt="Retrigger"
src="https://greptile-static-assets.s3.amazonaws.com/badges/Retrigger.svg?v=2"
align="right"></picture></a><a
href="https://app.greptile.com/alishahryar1/github/Alishahryar1/free-claude-code/-/pull/2095"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewInGreptileDark.svg?v=2"><source
media="(prefers-color-scheme: light)"
srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewInGreptile.svg?v=2"><img
alt="View in Greptile"
src="https://greptile-static-assets.s3.amazonaws.com/badges/ViewInGreptile.svg?v=2"
align="right"></picture></a>Confidence Score: 5/5</h2>
<!-- greptile-impact-verdict --> No outstanding findings prevent
merging.
<details open><summary><h3>Summary</h3></summary>
The PR replaces the macOS shell launcher with a native executable that
keeps Free Claude Code's app identity while running its installed Python
environment.
- The macOS app keeps its identity while running the desktop app.
- Release packages carry native launchers for both Mac architectures.
- CI checks the native Mac app on Intel and Apple silicon.
</details>
<!-- greptile_confidence_score:5 -->
<sub>Reviews (2) · Last reviewed commit: ["Restore POSIX uv reuse
regression
covera..."](b50bb8449e)
· [Reviewed by
Greptile](https://www.greptile.com/?utm_source=greptile_review&utm_medium=github&utm_campaign=review_public&utm_content=alishahryar1/free-claude-code)</sub>
<!-- /greptile_comment -->
232 lines
5.4 KiB
Bash
Executable file
232 lines
5.4 KiB
Bash
Executable file
#!/bin/sh
|
|
set -eu
|
|
|
|
CHECK_ORDER="suppressions ruff-format ruff-check ty pytest playwright macos-desktop"
|
|
|
|
dry_run=0
|
|
only_checks=""
|
|
skip_checks=""
|
|
|
|
show_usage() {
|
|
cat <<'USAGE'
|
|
Usage: ci.sh [options]
|
|
|
|
Runs the local sequence for the same check IDs enforced by GitHub CI.
|
|
Requires uv on PATH when running ruff, ty, pytest, or Playwright checks.
|
|
Local ruff checks repair formatting and autofixable lint before later checks.
|
|
|
|
Checks (in order):
|
|
suppressions Ban type ignores and legacy future annotations
|
|
ruff-format uv run ruff format
|
|
ruff-check uv run ruff check --fix
|
|
ty uv run ty check
|
|
pytest uv run pytest -v --tb=short
|
|
playwright Install Chromium and run deterministic Admin browser tests
|
|
macos-desktop Build and test the native app (macOS GUI required, --only)
|
|
|
|
Options:
|
|
--only ID Run only the given check (repeatable)
|
|
--skip ID Skip the given check (repeatable)
|
|
--dry-run Print commands without running them.
|
|
--help Show this help text.
|
|
USAGE
|
|
}
|
|
|
|
fail() {
|
|
printf 'error: %s\n' "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
step() {
|
|
printf '\n==> %s\n' "$1"
|
|
}
|
|
|
|
quote_arg() {
|
|
case "$1" in
|
|
*[!A-Za-z0-9_./:@%+=,-]*|"")
|
|
escaped=$(printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
|
printf '"%s"' "$escaped"
|
|
;;
|
|
*)
|
|
printf '%s' "$1"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
print_command() {
|
|
printf '+'
|
|
for arg in "$@"; do
|
|
printf ' '
|
|
quote_arg "$arg"
|
|
done
|
|
printf '\n'
|
|
}
|
|
|
|
run() {
|
|
print_command "$@"
|
|
if [ "$dry_run" -eq 0 ]; then
|
|
"$@"
|
|
fi
|
|
}
|
|
|
|
valid_check_id() {
|
|
case "$1" in
|
|
suppressions | ruff-format | ruff-check | ty | pytest | playwright | macos-desktop) return 0 ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
validate_check_id() {
|
|
if ! valid_check_id "$1"; then
|
|
fail "unknown check id: $1 (expected one of: $CHECK_ORDER)"
|
|
fi
|
|
}
|
|
|
|
contains_check_id() {
|
|
list=$1
|
|
id=$2
|
|
case " $list " in
|
|
*" $id "*) return 0 ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
should_run_check() {
|
|
check_id=$1
|
|
|
|
if [ "$check_id" = "macos-desktop" ] && [ -z "$only_checks" ]; then
|
|
return 1
|
|
fi
|
|
|
|
if [ -n "$only_checks" ] && ! contains_check_id "$only_checks" "$check_id"; then
|
|
return 1
|
|
fi
|
|
|
|
if contains_check_id "$skip_checks" "$check_id"; then
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
assert_uv_available() {
|
|
if ! command -v uv >/dev/null 2>&1; then
|
|
fail "uv is required but was not found on PATH. Install uv first (see README or scripts/install.sh)."
|
|
fi
|
|
}
|
|
|
|
selected_checks_need_uv() {
|
|
if [ "$dry_run" -ne 0 ]; then
|
|
return 1
|
|
fi
|
|
|
|
for check_id in $CHECK_ORDER; do
|
|
if should_run_check "$check_id" && [ "$check_id" != "suppressions" ]; then
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
run_suppressions() {
|
|
step "Ban suppressions and legacy annotations"
|
|
pattern='# type: ignore|# ty: ignore|from __future__ import annotations'
|
|
print_command grep -rE "$pattern" --include='*.py' . \
|
|
--exclude-dir=.venv --exclude-dir=.git
|
|
if [ "$dry_run" -eq 0 ]; then
|
|
if grep -rE "$pattern" --include='*.py' . \
|
|
--exclude-dir=.venv --exclude-dir=.git; then
|
|
fail "type: ignore / ty: ignore comments and legacy future annotations are not allowed. Fix the underlying type/import issue instead."
|
|
fi
|
|
fi
|
|
}
|
|
|
|
run_ruff_format() {
|
|
step "ruff format"
|
|
run uv run ruff format
|
|
}
|
|
|
|
run_ruff_check() {
|
|
step "ruff check --fix"
|
|
run uv run ruff check --fix
|
|
}
|
|
|
|
run_ty() {
|
|
step "ty check"
|
|
run uv run ty check
|
|
}
|
|
|
|
run_pytest() {
|
|
step "pytest"
|
|
run uv run pytest -v --tb=short
|
|
}
|
|
|
|
run_playwright() {
|
|
step "playwright"
|
|
run uv run playwright install chromium
|
|
run uv run pytest e2e -v --tb=short \
|
|
--tracing=retain-on-failure --screenshot=only-on-failure \
|
|
--full-page-screenshot --output=test-results
|
|
}
|
|
|
|
run_check() {
|
|
case "$1" in
|
|
suppressions) run_suppressions ;;
|
|
ruff-format) run_ruff_format ;;
|
|
ruff-check) run_ruff_check ;;
|
|
ty) run_ty ;;
|
|
pytest) run_pytest ;;
|
|
playwright) run_playwright ;;
|
|
macos-desktop)
|
|
step "macOS desktop"
|
|
run uv run --locked python scripts/build_macos_launcher.py
|
|
run uv run --locked pytest --confcutdir=smoke/desktop -n 0 -s -v smoke/desktop
|
|
;;
|
|
*) fail "unknown check id: $1" ;;
|
|
esac
|
|
}
|
|
|
|
parse_args() {
|
|
while [ "$#" -gt 0 ]; do
|
|
case "$1" in
|
|
--only)
|
|
shift
|
|
[ "$#" -gt 0 ] || fail "--only requires a check id."
|
|
validate_check_id "$1"
|
|
only_checks="${only_checks} $1"
|
|
;;
|
|
--skip)
|
|
shift
|
|
[ "$#" -gt 0 ] || fail "--skip requires a check id."
|
|
validate_check_id "$1"
|
|
skip_checks="${skip_checks} $1"
|
|
;;
|
|
--dry-run)
|
|
dry_run=1
|
|
;;
|
|
--help|-h)
|
|
show_usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
show_usage >&2
|
|
fail "unknown option: $1"
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
}
|
|
|
|
parse_args "$@"
|
|
if selected_checks_need_uv; then
|
|
assert_uv_available
|
|
fi
|
|
|
|
for check_id in $CHECK_ORDER; do
|
|
if should_run_check "$check_id"; then
|
|
run_check "$check_id"
|
|
fi
|
|
done
|
|
|
|
printf '\nAll selected CI checks passed.\n'
|