diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b152cf2..4495157 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,9 +40,13 @@ jobs: mkdir -p "$temporary_directory/release" printf 'arm64 archive' >"$temporary_directory/release/Gloss-macos-arm64.zip" printf 'x86_64 archive' >"$temporary_directory/release/Gloss-macos-x86_64.zip" + printf 'arm64 Codex archive' >"$temporary_directory/release/Gloss-macos-arm64-with-codex.zip" + printf 'x86_64 Codex archive' >"$temporary_directory/release/Gloss-macos-x86_64-with-codex.zip" Scripts/generate_release_metadata.sh \ "$temporary_directory/release/Gloss-macos-arm64.zip" \ "$temporary_directory/release/Gloss-macos-x86_64.zip" \ + "$temporary_directory/release/Gloss-macos-arm64-with-codex.zip" \ + "$temporary_directory/release/Gloss-macos-x86_64-with-codex.zip" \ 0.0.0 \ "$temporary_directory/release" \ v0.0.0 \ @@ -53,6 +57,9 @@ jobs: grep -F \ 'https://github.com/SunChJ/gloss-releases/releases/download/v0.0.0/' \ "$temporary_directory/release/gloss.rb" + grep -F \ + 'Gloss-macos-arm64-with-codex.zip' \ + "$temporary_directory/release/gloss-with-codex.rb" ( cd "$temporary_directory/release" shasum -a 256 --check SHA256SUMS diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d165b9b..954b753 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,7 +53,7 @@ jobs: fi build: - name: Build macOS ${{ matrix.architecture }} + name: Build macOS ${{ matrix.architecture }} (${{ matrix.variant }}) needs: - preflight strategy: @@ -62,8 +62,24 @@ jobs: include: - architecture: arm64 runner: macos-15 + variant: standard + runtime_mode: cli + archive: Gloss-macos-arm64.zip + - architecture: arm64 + runner: macos-15 + variant: with-codex + runtime_mode: bundled + archive: Gloss-macos-arm64-with-codex.zip + - architecture: x86_64 + runner: macos-15-intel + variant: standard + runtime_mode: cli + archive: Gloss-macos-x86_64.zip - architecture: x86_64 runner: macos-15-intel + variant: with-codex + runtime_mode: bundled + archive: Gloss-macos-x86_64-with-codex.zip runs-on: ${{ matrix.runner }} timeout-minutes: 90 env: @@ -101,15 +117,27 @@ jobs: fi - name: Build Gloss.app working-directory: gloss + env: + GLOSS_CODEX_RUNTIME_MODE: ${{ matrix.runtime_mode }} run: Scripts/build_app.sh - - name: Verify app architecture and ad-hoc signature + - name: Verify app architecture, contents, and ad-hoc signature working-directory: gloss env: EXPECTED_ARCHITECTURE: ${{ matrix.architecture }} + RELEASE_VARIANT: ${{ matrix.variant }} run: | lipo dist/Gloss.app/Contents/MacOS/Gloss -verify_arch "$EXPECTED_ARCHITECTURE" test ! -e "dist/Gloss.app/Contents/PlugIns/Gloss Extension.appex" - test ! -e "dist/Gloss.app/Contents/Helpers/gloss-codex-app-server" + if [[ "$RELEASE_VARIANT" == "with-codex" ]]; then + test -x "dist/Gloss.app/Contents/Helpers/gloss-codex-app-server" + test -f "dist/Gloss.app/Contents/Resources/Codex-LICENSE.txt" + test -f "dist/Gloss.app/Contents/Resources/CodexRuntime.lock" + lipo dist/Gloss.app/Contents/Helpers/gloss-codex-app-server \ + -verify_arch "$EXPECTED_ARCHITECTURE" + else + test ! -e "dist/Gloss.app/Contents/Helpers/gloss-codex-app-server" + test ! -e "dist/Gloss.app/Contents/Resources/CodexRuntime.lock" + fi test "$( /usr/libexec/PlistBuddy \ -c 'Print :GlossSafariExtensionAvailable' \ @@ -122,12 +150,13 @@ jobs: working-directory: gloss env: GLOSS_RELEASE_ARCHITECTURE: ${{ matrix.architecture }} + GLOSS_RELEASE_VARIANT: ${{ matrix.variant }} run: Scripts/package_release.sh - - name: Upload architecture artifact + - name: Upload architecture and variant artifact uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: - name: Gloss-${{ matrix.architecture }} - path: gloss/dist/release/Gloss-macos-${{ matrix.architecture }}.zip + name: Gloss-${{ matrix.architecture }}-${{ matrix.variant }} + path: gloss/dist/release/${{ matrix.archive }} if-no-files-found: error assemble: @@ -157,9 +186,13 @@ jobs: mkdir -p dist/release cp ../release-input/Gloss-macos-arm64.zip dist/release/ cp ../release-input/Gloss-macos-x86_64.zip dist/release/ + cp ../release-input/Gloss-macos-arm64-with-codex.zip dist/release/ + cp ../release-input/Gloss-macos-x86_64-with-codex.zip dist/release/ Scripts/generate_release_metadata.sh \ dist/release/Gloss-macos-arm64.zip \ dist/release/Gloss-macos-x86_64.zip \ + dist/release/Gloss-macos-arm64-with-codex.zip \ + dist/release/Gloss-macos-x86_64-with-codex.zip \ "$version" \ dist/release \ "$RELEASE_TAG" \ @@ -206,10 +239,13 @@ jobs: path: | gloss/dist/release/Gloss-macos-arm64.zip gloss/dist/release/Gloss-macos-x86_64.zip + gloss/dist/release/Gloss-macos-arm64-with-codex.zip + gloss/dist/release/Gloss-macos-x86_64-with-codex.zip gloss/dist/release/SHA256SUMS gloss/dist/release/gloss-release-manifest.json gloss/dist/release/gloss-release-manifest.json.sig gloss/dist/release/gloss.rb + gloss/dist/release/gloss-with-codex.rb if-no-files-found: error - name: Publish GitHub release assets if: github.event_name == 'push' || inputs.publish_release @@ -250,10 +286,13 @@ jobs: --repo "$GLOSS_RELEASE_REPOSITORY" \ dist/release/Gloss-macos-arm64.zip \ dist/release/Gloss-macos-x86_64.zip \ + dist/release/Gloss-macos-arm64-with-codex.zip \ + dist/release/Gloss-macos-x86_64-with-codex.zip \ dist/release/SHA256SUMS \ dist/release/gloss-release-manifest.json \ dist/release/gloss-release-manifest.json.sig \ dist/release/gloss.rb \ + dist/release/gloss-with-codex.rb \ --clobber gh release edit "$RELEASE_TAG" \ --repo "$GLOSS_RELEASE_REPOSITORY" \ diff --git a/README.md b/README.md index 9907f15..402205f 100644 --- a/README.md +++ b/README.md @@ -38,10 +38,22 @@ browser bridge on `127.0.0.1` behind a per-device token. Gloss requires **macOS 14 or newer**. +The default installation does not embed Codex and uses a separately installed Codex CLI for GPT translation: + ```bash brew install --cask sunchj/tap/gloss ``` +Alternatively, install the full variant with a bundled Codex app-server runtime. +The two casks conflict and cannot be installed together: + +```bash +brew install --cask sunchj/tap/gloss-with-codex +``` + +GitHub Releases provide both the default `Gloss-macos-.zip` and the +bundled `Gloss-macos--with-codex.zip` archives. + Gloss checks the signed release channel after launch, at most once every 24 hours. When an update is available it presents an actionable reminder; official Homebrew installations can update and restart directly from that prompt. You @@ -49,7 +61,7 @@ can also update manually with: ```bash brew update -brew upgrade --cask sunchj/tap/gloss +brew upgrade --cask sunchj/tap/gloss # use gloss-with-codex for the bundled variant ``` The Homebrew build is ad-hoc signed rather than Apple-notarized. Its cask @@ -142,10 +154,16 @@ open dist/Gloss.app ``` The packaging script expects the companion browser-extension source in a -sibling checkout and deliberately does not bundle Codex. Install Codex first, -or set `GLOSS_CODEX_BIN`; `./Scripts/build_app_without_bundled_codex.sh` is the -explicit equivalent of the default lightweight build. If the extension is not -a sibling checkout, set `GLOSS_BROWSER_EXTENSION_SOURCE` to its directory. +sibling checkout. It does not bundle Codex by default; install Codex first or +set `GLOSS_CODEX_BIN`. To build the bundled variant, run: + +```bash +GLOSS_CODEX_RUNTIME_MODE=bundled ./Scripts/build_app.sh +``` + +`./Scripts/build_app_without_bundled_codex.sh` is the explicit equivalent of +the default lightweight build. If the extension is not a sibling checkout, set +`GLOSS_BROWSER_EXTENSION_SOURCE` to its directory. ## Repository layout diff --git a/README.zh-CN.md b/README.zh-CN.md index 9660979..839f7a9 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -35,16 +35,28 @@ Gloss 使用 Swift 开发,通过 Apple Vision 在本机执行 OCR;浏览器 Gloss 要求 **macOS 14 或更高版本**。 +默认安装不内置 Codex,运行 GPT 翻译时使用本机另外安装的 Codex CLI: + ```bash brew install --cask sunchj/tap/gloss ``` +也可以选择安装内置 Codex app-server runtime 的完整版本。两种 Cask 会互相冲突, +不能同时安装: + +```bash +brew install --cask sunchj/tap/gloss-with-codex +``` + +GitHub Release 同时提供默认的 `Gloss-macos-<架构>.zip` 和内置 Codex 的 +`Gloss-macos-<架构>-with-codex.zip`。 + Gloss 会在启动后检查签名发布通道,每 24 小时最多一次。发现新版本时会主动提醒; 官方 Homebrew 安装可以直接在提醒中点击“更新并重新启动”。也可以手动运行: ```bash brew update -brew upgrade --cask sunchj/tap/gloss +brew upgrade --cask sunchj/tap/gloss # 内置版使用 gloss-with-codex ``` Homebrew 版本使用 ad-hoc 签名,并未经过 Apple 公证。Cask 会在本机重新签名、移除 @@ -124,8 +136,13 @@ swift run gloss-cli capabilities --json open dist/Gloss.app ``` -打包脚本要求在相邻目录中提供配套的浏览器扩展源码,并且默认不再内置 Codex。 -请先安装 Codex CLI,或设置 `GLOSS_CODEX_BIN`; +打包脚本要求在相邻目录中提供配套的浏览器扩展源码。默认不内置 Codex;请先安装 +Codex CLI,或设置 `GLOSS_CODEX_BIN`。如需构建内置 Codex runtime 的版本,可以运行: + +```bash +GLOSS_CODEX_RUNTIME_MODE=bundled ./Scripts/build_app.sh +``` + `./Scripts/build_app_without_bundled_codex.sh` 是默认轻量构建的显式入口。扩展源码不在 相邻目录时,可通过 `GLOSS_BROWSER_EXTENSION_SOURCE` 指定其位置。 diff --git a/Resources/Info.plist b/Resources/Info.plist index e3b948e..0ac64fd 100644 --- a/Resources/Info.plist +++ b/Resources/Info.plist @@ -19,9 +19,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0.2 + 1.0.3 CFBundleVersion - 15 + 16 GlossSafariExtensionAvailable CFBundleDocumentTypes diff --git a/Scripts/generate_homebrew_cask.sh b/Scripts/generate_homebrew_cask.sh index dc2ec3a..b4bf785 100755 --- a/Scripts/generate_homebrew_cask.sh +++ b/Scripts/generate_homebrew_cask.sh @@ -15,6 +15,8 @@ REPOSITORY="${6:-${GLOSS_RELEASE_REPOSITORY:-SunChJ/gloss-releases}}" ARM64_ARCHIVE="${7:-Gloss-macos-arm64.zip}" X86_64_ARCHIVE="${8:-Gloss-macos-x86_64.zip}" DOWNLOAD_BASE_URL="${GLOSS_CASK_DOWNLOAD_BASE_URL:-}" +CASK_TOKEN="${GLOSS_CASK_TOKEN:-gloss}" +BUNDLES_CODEX="${GLOSS_CASK_BUNDLES_CODEX:-false}" if [[ -z "$DOWNLOAD_BASE_URL" ]]; then DOWNLOAD_BASE_URL="https://github.com/$REPOSITORY/releases/download/$RELEASE_TAG" fi @@ -23,6 +25,22 @@ if [[ ! "$VERSION" =~ ^[0-9]+(\.[0-9]+){2}([+-][0-9A-Za-z.-]+)?$ ]]; then echo "Invalid cask version: $VERSION" >&2 exit 65 fi +case "$CASK_TOKEN:$BUNDLES_CODEX" in + gloss:false) + CONFLICTING_CASK="gloss-with-codex" + CASK_NAME="Gloss" + CASK_DESCRIPTION="Context-aware text and document translation" + ;; + gloss-with-codex:true) + CONFLICTING_CASK="gloss" + CASK_NAME="Gloss with Codex" + CASK_DESCRIPTION="Context-aware translation with a bundled Codex runtime" + ;; + *) + echo "Unsupported Gloss cask variant: $CASK_TOKEN (bundles Codex: $BUNDLES_CODEX)" >&2 + exit 65 + ;; +esac for checksum in "$ARM64_SHA256" "$X86_64_SHA256"; do if [[ ! "$checksum" =~ ^[0-9a-fA-F]{64}$ ]]; then echo "Invalid cask SHA-256: $checksum" >&2 @@ -58,7 +76,7 @@ X86_64_SHA256="$(printf '%s' "$X86_64_SHA256" | tr '[:upper:]' '[:lower:]')" DOWNLOAD_BASE_URL="${DOWNLOAD_BASE_URL%/}" mkdir -p "$(dirname "$OUTPUT_PATH")" cat >"$OUTPUT_PATH" < [repository] [asset-base-url]" >&2 +if [[ $# -lt 7 || $# -gt 9 ]]; then + echo "Usage: $0 [repository] [asset-base-url]" >&2 exit 64 fi ARM64_ARCHIVE="$1" X86_64_ARCHIVE="$2" -VERSION="$3" -OUTPUT_DIRECTORY="$4" -RELEASE_TAG="$5" -REPOSITORY="${6:-${GLOSS_RELEASE_REPOSITORY:-SunChJ/gloss-releases}}" -ASSET_BASE_URL="${7:-https://github.com/$REPOSITORY/releases/download/$RELEASE_TAG}" +ARM64_CODEX_ARCHIVE="$3" +X86_64_CODEX_ARCHIVE="$4" +VERSION="$5" +OUTPUT_DIRECTORY="$6" +RELEASE_TAG="$7" +REPOSITORY="${8:-${GLOSS_RELEASE_REPOSITORY:-SunChJ/gloss-releases}}" +ASSET_BASE_URL="${9:-https://github.com/$REPOSITORY/releases/download/$RELEASE_TAG}" -for archive in "$ARM64_ARCHIVE" "$X86_64_ARCHIVE"; do +ARCHIVES=( + "$ARM64_ARCHIVE" + "$X86_64_ARCHIVE" + "$ARM64_CODEX_ARCHIVE" + "$X86_64_CODEX_ARCHIVE" +) +EXPECTED_NAMES=( + "Gloss-macos-arm64.zip" + "Gloss-macos-x86_64.zip" + "Gloss-macos-arm64-with-codex.zip" + "Gloss-macos-x86_64-with-codex.zip" +) +for index in "${!ARCHIVES[@]}"; do + archive="${ARCHIVES[$index]}" if [[ ! -f "$archive" ]]; then echo "Release archive not found: $archive" >&2 exit 66 fi + if [[ "$(basename "$archive")" != "${EXPECTED_NAMES[$index]}" ]]; then + echo "Unexpected release archive name: $(basename "$archive")" >&2 + exit 65 + fi done if [[ ! "$VERSION" =~ ^[0-9]+(\.[0-9]+){2}([+-][0-9A-Za-z.-]+)?$ ]]; then echo "Invalid release version: $VERSION" >&2 @@ -43,31 +62,29 @@ if [[ ! "$ASSET_BASE_URL" =~ $ASSET_URL_PATTERN ]]; then fi mkdir -p "$OUTPUT_DIRECTORY" -ARM64_NAME="$(basename "$ARM64_ARCHIVE")" -X86_64_NAME="$(basename "$X86_64_ARCHIVE")" -if [[ "$ARM64_NAME" != "Gloss-macos-arm64.zip" ]]; then - echo "Unexpected arm64 archive name: $ARM64_NAME" >&2 - exit 65 -fi -if [[ "$X86_64_NAME" != "Gloss-macos-x86_64.zip" ]]; then - echo "Unexpected x86_64 archive name: $X86_64_NAME" >&2 - exit 65 -fi - OUTPUT_DIRECTORY_ABSOLUTE="$(cd "$OUTPUT_DIRECTORY" && pwd -P)" -ARM64_SOURCE_ABSOLUTE="$(cd "$(dirname "$ARM64_ARCHIVE")" && pwd -P)/$ARM64_NAME" -X86_64_SOURCE_ABSOLUTE="$(cd "$(dirname "$X86_64_ARCHIVE")" && pwd -P)/$X86_64_NAME" +for index in "${!ARCHIVES[@]}"; do + archive="${ARCHIVES[$index]}" + name="${EXPECTED_NAMES[$index]}" + source_absolute="$(cd "$(dirname "$archive")" && pwd -P)/$name" + output="$OUTPUT_DIRECTORY_ABSOLUTE/$name" + if [[ "$source_absolute" != "$output" ]]; then + cp "$source_absolute" "$output" + fi +done + +ARM64_NAME="${EXPECTED_NAMES[0]}" +X86_64_NAME="${EXPECTED_NAMES[1]}" +ARM64_CODEX_NAME="${EXPECTED_NAMES[2]}" +X86_64_CODEX_NAME="${EXPECTED_NAMES[3]}" ARM64_OUTPUT="$OUTPUT_DIRECTORY_ABSOLUTE/$ARM64_NAME" X86_64_OUTPUT="$OUTPUT_DIRECTORY_ABSOLUTE/$X86_64_NAME" -if [[ "$ARM64_SOURCE_ABSOLUTE" != "$ARM64_OUTPUT" ]]; then - cp "$ARM64_SOURCE_ABSOLUTE" "$ARM64_OUTPUT" -fi -if [[ "$X86_64_SOURCE_ABSOLUTE" != "$X86_64_OUTPUT" ]]; then - cp "$X86_64_SOURCE_ABSOLUTE" "$X86_64_OUTPUT" -fi - +ARM64_CODEX_OUTPUT="$OUTPUT_DIRECTORY_ABSOLUTE/$ARM64_CODEX_NAME" +X86_64_CODEX_OUTPUT="$OUTPUT_DIRECTORY_ABSOLUTE/$X86_64_CODEX_NAME" ARM64_SHA256="$(shasum -a 256 "$ARM64_OUTPUT" | awk '{print $1}')" X86_64_SHA256="$(shasum -a 256 "$X86_64_OUTPUT" | awk '{print $1}')" +ARM64_CODEX_SHA256="$(shasum -a 256 "$ARM64_CODEX_OUTPUT" | awk '{print $1}')" +X86_64_CODEX_SHA256="$(shasum -a 256 "$X86_64_CODEX_OUTPUT" | awk '{print $1}')" ARM64_SIZE="$(stat -f '%z' "$ARM64_OUTPUT")" X86_64_SIZE="$(stat -f '%z' "$X86_64_OUTPUT")" PUBLISHED_AT="$( @@ -79,59 +96,49 @@ PUBLISHED_AT="$( )" MANIFEST_PATH="$OUTPUT_DIRECTORY/gloss-release-manifest.json" CHECKSUMS_PATH="$OUTPUT_DIRECTORY/SHA256SUMS" - ASSET_BASE_URL="${ASSET_BASE_URL%/}" + GLOSS_CASK_DOWNLOAD_BASE_URL="$ASSET_BASE_URL" \ +GLOSS_CASK_TOKEN="gloss" \ +GLOSS_CASK_BUNDLES_CODEX="false" \ bash "$(dirname "$0")/generate_homebrew_cask.sh" \ - "$VERSION" \ - "$ARM64_SHA256" \ - "$X86_64_SHA256" \ - "$OUTPUT_DIRECTORY/gloss.rb" \ - "$RELEASE_TAG" \ - "$REPOSITORY" \ - "$ARM64_NAME" \ - "$X86_64_NAME" + "$VERSION" "$ARM64_SHA256" "$X86_64_SHA256" \ + "$OUTPUT_DIRECTORY/gloss.rb" "$RELEASE_TAG" "$REPOSITORY" \ + "$ARM64_NAME" "$X86_64_NAME" bash "$(dirname "$0")/validate_homebrew_cask.sh" \ - "$OUTPUT_DIRECTORY/gloss.rb" + "$OUTPUT_DIRECTORY/gloss.rb" standard + +GLOSS_CASK_DOWNLOAD_BASE_URL="$ASSET_BASE_URL" \ +GLOSS_CASK_TOKEN="gloss-with-codex" \ +GLOSS_CASK_BUNDLES_CODEX="true" \ + bash "$(dirname "$0")/generate_homebrew_cask.sh" \ + "$VERSION" "$ARM64_CODEX_SHA256" "$X86_64_CODEX_SHA256" \ + "$OUTPUT_DIRECTORY/gloss-with-codex.rb" "$RELEASE_TAG" "$REPOSITORY" \ + "$ARM64_CODEX_NAME" "$X86_64_CODEX_NAME" +bash "$(dirname "$0")/validate_homebrew_cask.sh" \ + "$OUTPUT_DIRECTORY/gloss-with-codex.rb" with-codex + CASK_PATH="$OUTPUT_DIRECTORY/gloss.rb" CASK_SHA256="$(shasum -a 256 "$CASK_PATH" | awk '{print $1}')" CASK_SIZE="$(stat -f '%z' "$CASK_PATH")" CASK_URL="$ASSET_BASE_URL/gloss.rb" +CODEX_CASK_PATH="$OUTPUT_DIRECTORY/gloss-with-codex.rb" +CODEX_CASK_SHA256="$(shasum -a 256 "$CODEX_CASK_PATH" | awk '{print $1}')" python3 - \ - "$MANIFEST_PATH" \ - "$VERSION" \ - "$RELEASE_TAG" \ - "$PUBLISHED_AT" \ - "$ASSET_BASE_URL" \ - "$ARM64_NAME" \ - "$ARM64_SHA256" \ - "$ARM64_SIZE" \ - "$X86_64_NAME" \ - "$X86_64_SHA256" \ - "$X86_64_SIZE" \ - "$CASK_URL" \ - "$CASK_SHA256" \ - "$CASK_SIZE" <<'PY' + "$MANIFEST_PATH" "$VERSION" "$RELEASE_TAG" "$PUBLISHED_AT" "$ASSET_BASE_URL" \ + "$ARM64_NAME" "$ARM64_SHA256" "$ARM64_SIZE" \ + "$X86_64_NAME" "$X86_64_SHA256" "$X86_64_SIZE" \ + "$CASK_URL" "$CASK_SHA256" "$CASK_SIZE" <<'PY' import json import pathlib import sys ( - output, - version, - release_tag, - published_at, - asset_base_url, - arm64_name, - arm64_sha256, - arm64_size, - x86_64_name, - x86_64_sha256, - x86_64_size, - cask_url, - cask_sha256, - cask_size, + output, version, release_tag, published_at, asset_base_url, + arm64_name, arm64_sha256, arm64_size, + x86_64_name, x86_64_sha256, x86_64_size, + cask_url, cask_sha256, cask_size, ) = sys.argv[1:] manifest = { "schemaVersion": 2, @@ -173,7 +180,10 @@ MANIFEST_SHA256="$(shasum -a 256 "$MANIFEST_PATH" | awk '{print $1}')" { printf '%s %s\n' "$ARM64_SHA256" "$ARM64_NAME" printf '%s %s\n' "$X86_64_SHA256" "$X86_64_NAME" + printf '%s %s\n' "$ARM64_CODEX_SHA256" "$ARM64_CODEX_NAME" + printf '%s %s\n' "$X86_64_CODEX_SHA256" "$X86_64_CODEX_NAME" printf '%s %s\n' "$CASK_SHA256" "gloss.rb" + printf '%s %s\n' "$CODEX_CASK_SHA256" "gloss-with-codex.rb" printf '%s %s\n' "$MANIFEST_SHA256" "$(basename "$MANIFEST_PATH")" } >"$CHECKSUMS_PATH" diff --git a/Scripts/package_release.sh b/Scripts/package_release.sh index e095c7e..ab3f85b 100755 --- a/Scripts/package_release.sh +++ b/Scripts/package_release.sh @@ -12,6 +12,32 @@ if [[ ! -d "$APP_PATH" ]]; then fi VERSION="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$APP_PATH/Contents/Info.plist")" +VARIANT="${GLOSS_RELEASE_VARIANT:-standard}" +case "$VARIANT" in + standard) + ARCHIVE_SUFFIX="" + if [[ -e "$APP_PATH/Contents/Helpers/gloss-codex-app-server" ]]; then + echo "Standard release must not bundle the Codex app-server." >&2 + exit 65 + fi + ;; + with-codex) + ARCHIVE_SUFFIX="-with-codex" + for bundled_file in \ + "$APP_PATH/Contents/Helpers/gloss-codex-app-server" \ + "$APP_PATH/Contents/Resources/Codex-LICENSE.txt" \ + "$APP_PATH/Contents/Resources/CodexRuntime.lock"; do + if [[ ! -f "$bundled_file" ]]; then + echo "Codex release is missing bundled file: $bundled_file" >&2 + exit 65 + fi + done + ;; + *) + echo "Unsupported release variant: $VARIANT" >&2 + exit 65 + ;; +esac ARCHITECTURE="${GLOSS_RELEASE_ARCHITECTURE:-$(uname -m)}" case "$ARCHITECTURE" in arm64|aarch64) @@ -25,7 +51,7 @@ case "$ARCHITECTURE" in exit 65 ;; esac -ARCHIVE_PATH="$OUTPUT_DIRECTORY/Gloss-macos-$ARCHITECTURE.zip" +ARCHIVE_PATH="$OUTPUT_DIRECTORY/Gloss-macos-$ARCHITECTURE$ARCHIVE_SUFFIX.zip" mkdir -p "$OUTPUT_DIRECTORY" rm -f "$ARCHIVE_PATH" diff --git a/Scripts/validate_homebrew_cask.sh b/Scripts/validate_homebrew_cask.sh index 7d5440a..4fb6fc7 100755 --- a/Scripts/validate_homebrew_cask.sh +++ b/Scripts/validate_homebrew_cask.sh @@ -1,29 +1,47 @@ #!/usr/bin/env bash set -euo pipefail -if [[ $# -ne 1 ]]; then - echo "Usage: $0 " >&2 +if [[ $# -lt 1 || $# -gt 2 ]]; then + echo "Usage: $0 [standard|with-codex]" >&2 exit 64 fi CASK_PATH="$1" +VARIANT="${2:-standard}" if [[ ! -f "$CASK_PATH" ]]; then echo "Cask not found: $CASK_PATH" >&2 exit 66 fi +case "$VARIANT" in + standard) + CASK_TOKEN="gloss" + CONFLICTING_CASK="gloss-with-codex" + ARCHIVE_SUFFIX="" + ;; + with-codex) + CASK_TOKEN="gloss-with-codex" + CONFLICTING_CASK="gloss" + ARCHIVE_SUFFIX="-with-codex" + ;; + *) + echo "Unsupported cask variant: $VARIANT" >&2 + exit 65 + ;; +esac ruby -c "$CASK_PATH" -ruby - "$CASK_PATH" <<'RUBY' -path = ARGV.fetch(0) +ruby - "$CASK_PATH" "$CASK_TOKEN" "$CONFLICTING_CASK" "$ARCHIVE_SUFFIX" "$VARIANT" <<'RUBY' +path, cask_token, conflicting_cask, archive_suffix, variant = ARGV content = File.read(path, encoding: "UTF-8") required = [ - /^cask "gloss" do$/, + /^cask "#{Regexp.escape(cask_token)}" do$/, /^ version "[^"]+"$/, /^ on_arm do$/, /^ on_intel do$/, /^ sha256 "[0-9a-f]{64}"$/, - %r{^ url "https://[^"]+/Gloss-macos-arm64\.zip"$}, - %r{^ url "https://[^"]+/Gloss-macos-x86_64\.zip"$}, + %r{^ url "https://[^"]+/Gloss-macos-arm64#{Regexp.escape(archive_suffix)}\.zip"$}, + %r{^ url "https://[^"]+/Gloss-macos-x86_64#{Regexp.escape(archive_suffix)}\.zip"$}, + /^ conflicts_with cask: "#{Regexp.escape(conflicting_cask)}"$/, /^ app "Gloss\.app"$/, %r{^ binary "#\{appdir\}/Gloss\.app/Contents/Helpers/gloss-cli", target: "gloss-cli"$}, /^ postflight do$/, @@ -45,6 +63,11 @@ required = [ /^\s+must_succeed: true$/, /^ caveats <<~EOS$/, ] +required << if variant == "with-codex" + /This variant bundles the Codex app-server runtime/ +else + /Gloss does not bundle Codex/ +end missing = required.reject { |pattern| content.match?(pattern) } abort "Cask is missing required declarations: #{missing.join(", ")}" unless missing.empty? if content.include?("Gloss Extension.appex") diff --git a/docs/release-notes/v1.0.3.md b/docs/release-notes/v1.0.3.md new file mode 100644 index 0000000..71ee380 --- /dev/null +++ b/docs/release-notes/v1.0.3.md @@ -0,0 +1,13 @@ +# Gloss 1.0.3 + +Gloss 1.0.3 adds a choice between lightweight and bundled-Codex distributions. + +## Optional bundled Codex distribution + +- Keeps the default Homebrew and GitHub Release download lightweight and without Codex. +- Adds the mutually exclusive `sunchj/tap/gloss-with-codex` Cask for users who want the Codex app-server runtime bundled with Gloss. +- Publishes arm64 and x86_64 `-with-codex` archives alongside the default release archives. +- Verifies checksums, architecture, signatures, and the expected presence or absence of the Codex helper for both variants. +- Runs Homebrew installation smoke tests for both variants on Apple silicon and Intel runners. + +Gloss 1.0.3 requires macOS 14 or newer. diff --git a/docs/runtime-distribution.md b/docs/runtime-distribution.md index 07882d6..5042029 100644 --- a/docs/runtime-distribution.md +++ b/docs/runtime-distribution.md @@ -88,21 +88,24 @@ active runtime。 `.github/workflows/release.yml` 在 `v*` tag 上: -1. 分别在 `macos-15` arm64 和 `macos-15-intel` x86_64 runner 构建 `Gloss.app`,并显式使用 - `GLOSS_SIGN_IDENTITY=-` 对 App 与 helper 做 ad-hoc codesign;Homebrew 产物不包含需要 Apple - 身份配对的 Safari extension,也不执行 notarization 或 stapling。 -2. 生成 `Gloss-macos-arm64.zip`、`Gloss-macos-x86_64.zip`、`SHA256SUMS` 和包含两个 - architecture asset 的 `gloss-release-manifest.json`。 -3. 生成并校验使用 `on_arm` / `on_intel` URL 与 SHA-256 的根级 Release asset - `gloss.rb`。Manifest 还会签名绑定其固定 token、URL、SHA-256 与 size;tap workflow - 再将它落到 `Casks/gloss.rb`。Manifest 和 +1. 分别在 `macos-15` arm64 和 `macos-15-intel` x86_64 runner 构建默认轻量版与内置 + Codex runtime 版 `Gloss.app`,并显式使用 `GLOSS_SIGN_IDENTITY=-` 对 App 与 helper 做 + ad-hoc codesign;Homebrew 产物不包含需要 Apple 身份配对的 Safari extension,也不执行 + notarization 或 stapling。 +2. 生成默认的 `Gloss-macos-arm64.zip`、`Gloss-macos-x86_64.zip`,以及可选的 + `Gloss-macos-arm64-with-codex.zip`、`Gloss-macos-x86_64-with-codex.zip`。`SHA256SUMS` + 覆盖四个归档和两个 Cask;签名 `gloss-release-manifest.json` 继续把 App 内自动更新绑定到 + 默认轻量版的两个 architecture asset。 +3. 生成并校验使用 `on_arm` / `on_intel` URL 与 SHA-256 的根级 Release assets + `gloss.rb` 与 `gloss-with-codex.rb`。两个 Cask 显式互斥;Manifest 签名绑定默认 Cask 的 + 固定 token、URL、SHA-256 与 size;tap workflow 分别将它们落到同名 `Casks/` 文件。Manifest 和 Cask 中的下载地址固定指向公开仓库 `https://github.com/SunChJ/gloss-releases/releases/download//`。 4. 始终上传私有主仓中的 Actions artifact,便于内部验证。 -5. 仅在 tag 事件中使用跨仓库 token,把 app zip、校验和、manifest 与根级 `gloss.rb` 发布到 - 公开的 `SunChJ/gloss-releases` GitHub Release。 +5. 仅在 tag 事件中使用跨仓库 token,把两种 variant 的 app zip、校验和、manifest 与两个 + 根级 Cask 发布到公开的 `SunChJ/gloss-releases` GitHub Release。 6. Release 上传成功后,dispatch `SunChJ/homebrew-tap` 的 `update-cask.yml`,由公开 tap - 下载并二次校验 Release,再更新 `Casks/gloss.rb`。 + 下载并二次校验 Release,再更新 `Casks/gloss.rb` 与 `Casks/gloss-with-codex.rb`。 完整 App 会同时检出并构建私有浏览器扩展仓库。Release workflow 使用三个职责分离的凭据: @@ -150,10 +153,10 @@ key 添加为该仓库的 read-only deploy key,把 private key 保存为 `homebrew-tap` 的 `update-cask.yml` 必须声明两个 required `workflow_dispatch` inputs: `release_tag` 和 `release_repository`。它应只接受 -`release_repository == "SunChJ/gloss-releases"`,下载 -`Gloss-macos-arm64.zip`、`Gloss-macos-x86_64.zip`、`SHA256SUMS` 与 `gloss.rb`,执行 -SHA-256 校验并确认 Cask 内的版本、两个 checksum、公开 URL 和安全 `postflight` 后才更新 -`Casks/gloss.rb`。若 workflow 通过 PR 更新 `main`,还需在 tap 仓库 +`release_repository == "SunChJ/gloss-releases"`,下载默认版与 `-with-codex` 版的四个 +架构归档、`SHA256SUMS`、`gloss.rb` 与 `gloss-with-codex.rb`,执行 SHA-256 校验并确认 +两个 Cask 内的版本、checksum、公开 URL、互斥声明和安全 `postflight` 后才更新对应的 +`Casks/` 文件。若 workflow 通过 PR 更新 `main`,还需在 tap 仓库 Settings → Actions → General 启用 “Allow GitHub Actions to create and approve pull requests”,并给该 workflow `contents: write`、`pull-requests: write`。 @@ -162,12 +165,18 @@ Settings → Actions → General ```bash Scripts/build_app.sh -GLOSS_RELEASE_ARCHITECTURE="$(uname -m)" Scripts/package_release.sh +GLOSS_RELEASE_ARCHITECTURE="$(uname -m)" \ + GLOSS_RELEASE_VARIANT=standard Scripts/package_release.sh +GLOSS_CODEX_RUNTIME_MODE=bundled Scripts/build_app.sh +GLOSS_RELEASE_ARCHITECTURE="$(uname -m)" \ + GLOSS_RELEASE_VARIANT=with-codex Scripts/package_release.sh -# 收集在两类 Mac 上生成的 zip 后: +# 收集在两类 Mac 上生成的四个 zip 后: Scripts/generate_release_metadata.sh \ dist/release/Gloss-macos-arm64.zip \ dist/release/Gloss-macos-x86_64.zip \ + dist/release/Gloss-macos-arm64-with-codex.zip \ + dist/release/Gloss-macos-x86_64-with-codex.zip \ 0.8.0 \ dist/release \ v0.8.0 \ @@ -191,9 +200,14 @@ gh workflow run update-cask.yml \ ```bash brew tap sunchj/tap +# 默认轻量版(不内置 Codex) brew install --cask sunchj/tap/gloss + +# 可选完整版本(内置 Codex runtime;与默认版互斥) +brew install --cask sunchj/tap/gloss-with-codex + brew update -brew upgrade --cask sunchj/tap/gloss +brew upgrade --cask sunchj/tap/gloss # 或 gloss-with-codex ``` ### 正式发行顺序 @@ -206,12 +220,11 @@ brew upgrade --cask sunchj/tap/gloss 4. 在私有 Gloss 仓库的目标 commit 上创建并推送 tag,例如 `v0.8.0`。 5. 等待 Gloss Release workflow 完成 ad-hoc 签名;workflow 会先创建 draft Release,上传全部 资产后再发布,最后 dispatch tap 更新。 -6. 在 `SunChJ/gloss-releases` 验证两种架构 zip、`SHA256SUMS`、 - `gloss-release-manifest.json`、签名文件与根级 `gloss.rb` 均存在且 URL 指向该公开 Release; - tap 会把同一份 `gloss.rb` 落到 `Casks/gloss.rb`。 +6. 在 `SunChJ/gloss-releases` 验证默认版和 `-with-codex` 版的四个架构 zip、 + `SHA256SUMS`、`gloss-release-manifest.json`、签名文件与两个根级 Cask 均存在且 URL 指向 + 该公开 Release;tap 会将两个 Cask 落到对应的 `Casks/` 文件。 7. 等待 `SunChJ/homebrew-tap` 生成的 Cask PR 在 required checks 通过后自动合并;workflow - 会在 arm64 与 x86_64 Mac 上分别执行 `brew install --cask sunchj/tap/gloss` smoke test, - 不要求人工批准。 + 会在 arm64 与 x86_64 Mac 上分别安装测试默认版和内置 Codex 版,不要求人工批准。 `SunChJ/gloss-releases` 必须启用 GitHub release immutability。已发布 Release 的 tag 与资产 不可覆盖;相同 tag 的 workflow 重跑会 fail closed。上传中断时 Release 仍保持 draft,