chore: import upstream snapshot with attribution
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,430 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master]
|
||||
pull_request:
|
||||
branches: [main, master]
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
|
||||
jobs:
|
||||
quality:
|
||||
name: Quality Guardrails
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ssh-key: ${{ secrets.DEPLOY_KEY }}
|
||||
submodules: recursive
|
||||
|
||||
- name: Configure SSH for cargo git dependencies
|
||||
uses: webfactory/ssh-agent@v0.9.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.DEPLOY_KEY }}
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: clippy, rustfmt
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: quality-ubuntu
|
||||
cache-all-crates: "true"
|
||||
|
||||
- name: Check formatting
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
- name: Check all targets and all features
|
||||
run: cargo check --all-targets --all-features
|
||||
|
||||
- name: Run clippy with warnings denied
|
||||
run: cargo clippy --all-targets --all-features -- -D warnings
|
||||
|
||||
- name: Enforce warning budget
|
||||
shell: bash
|
||||
run: scripts/check_warning_budget.sh
|
||||
|
||||
- name: Enforce oversized-file ratchet
|
||||
shell: bash
|
||||
run: python3 scripts/check_code_size_budget.py
|
||||
|
||||
- name: Enforce oversized-test ratchet
|
||||
shell: bash
|
||||
run: python3 scripts/check_test_size_budget.py
|
||||
|
||||
- name: Enforce panic-prone usage ratchet
|
||||
shell: bash
|
||||
run: python3 scripts/check_panic_budget.py
|
||||
|
||||
- name: Enforce swallowed-error usage ratchet
|
||||
shell: bash
|
||||
run: python3 scripts/check_swallowed_error_budget.py
|
||||
|
||||
- name: Enforce crate dependency boundaries
|
||||
shell: bash
|
||||
run: python3 scripts/check_dependency_boundaries.py
|
||||
|
||||
- name: Enforce wildcard re-export ratchet
|
||||
shell: bash
|
||||
run: python3 scripts/check_wildcard_reexport_budget.py
|
||||
|
||||
- name: Enforce no unused dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
cargo install cargo-machete --locked
|
||||
cargo machete
|
||||
|
||||
build:
|
||||
name: Build & Test (${{ matrix.os }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 35
|
||||
# Some dependencies (e.g. convert_case 0.10.0 via derive_more/crossterm, and
|
||||
# proc-macro2/quote) accidentally ship a `rust-toolchain.toml` *inside their
|
||||
# published crate*. When cargo builds such a crate its CWD is that crate dir,
|
||||
# so the rustup proxy honours the file: convert_case pins `channel = "1.83.0"`
|
||||
# and proc-macro2 requests `components = ["rust-src"]`. That made every
|
||||
# Build & Test job (a) race two parallel `rust-src` downloads and (b) later
|
||||
# try to compile convert_case with rustc 1.83.0, failing with E0514/E0599.
|
||||
# RUSTUP_TOOLCHAIN takes precedence over any rust-toolchain.toml override, so
|
||||
# pinning it here makes every step use this job's installed stable toolchain.
|
||||
env:
|
||||
RUSTUP_TOOLCHAIN: stable
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-gnu
|
||||
- os: macos-latest
|
||||
target: aarch64-apple-darwin
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ssh-key: ${{ secrets.DEPLOY_KEY }}
|
||||
submodules: recursive
|
||||
|
||||
- name: Configure SSH for cargo git dependencies
|
||||
uses: webfactory/ssh-agent@v0.9.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.DEPLOY_KEY }}
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
# Pre-install rust-src in the (serial) toolchain step. Some build-script
|
||||
# / proc-macro units trigger an on-demand `rustup component add rust-src`,
|
||||
# and when several parallel cargo/rustc processes request it at once they
|
||||
# race on the shared `~/.rustup/downloads/*.partial` file and fail with
|
||||
# "could not rename ... No such file or directory". Fetching it once here
|
||||
# removes the race. (Was failing every Build & Test job.)
|
||||
components: rust-src
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
# Suffix bumped to evict caches that held 1.83.0 / <unknown rustc>
|
||||
# artifacts produced before RUSTUP_TOOLCHAIN=stable pinned the job.
|
||||
key: ${{ matrix.os }}-stablepin
|
||||
|
||||
- name: Install mold linker (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq mold
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p .cargo
|
||||
if [ "$RUNNER_OS" = "Linux" ]; then
|
||||
cat > .cargo/config.toml << 'EOF'
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
linker = "clang"
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
EOF
|
||||
fi
|
||||
# NOTE: intentionally NOT exporting RUSTC_WRAPPER=sccache here. Only this
|
||||
# step used sccache while the later `cargo test --no-run` steps did not,
|
||||
# so sccache emitted rlibs/rmetas stamped `<unknown rustc version>` that
|
||||
# the non-sccache test compile then rejected with E0514
|
||||
# ("compiled by an incompatible version of rustc"). rust-cache already
|
||||
# caches target/ across runs, so a clean, wrapper-consistent build is
|
||||
# both correct and fast enough.
|
||||
export RUSTC="$(rustup which rustc)"
|
||||
CARGO_BIN="$(rustup which cargo)"
|
||||
"$CARGO_BIN" build --release --target ${{ matrix.target }}
|
||||
|
||||
- name: Compile library and binary tests
|
||||
shell: bash
|
||||
run: |
|
||||
python3 .github/scripts/run_with_timeout.py 900 \
|
||||
"$(rustup which cargo)" test --target ${{ matrix.target }} --lib --bins --no-run
|
||||
|
||||
- name: Compile integration test binaries
|
||||
shell: bash
|
||||
# Build the integration-test binaries up front (not counted against the
|
||||
# run steps' execution budgets). The e2e suite in particular is heavy
|
||||
# (burst-spawn concurrency), and folding compilation into its 900s run
|
||||
# budget was pushing slower hosted ubuntu/macos runners over the limit.
|
||||
run: |
|
||||
python3 .github/scripts/run_with_timeout.py 900 \
|
||||
"$(rustup which cargo)" test --target ${{ matrix.target }} \
|
||||
--test provider_matrix --test e2e --no-run
|
||||
|
||||
- name: Run provider matrix tests
|
||||
shell: bash
|
||||
run: |
|
||||
python3 .github/scripts/run_with_timeout.py 600 \
|
||||
"$(rustup which cargo)" test --target ${{ matrix.target }} --test provider_matrix
|
||||
|
||||
- name: Run e2e tests
|
||||
shell: bash
|
||||
run: |
|
||||
# e2e is fast (~1-2 min of execution); the generous budget only guards
|
||||
# against an occasional slow hosted runner. Compilation is already done
|
||||
# in the dedicated compile step above.
|
||||
python3 .github/scripts/run_with_timeout.py 600 \
|
||||
"$(rustup which cargo)" test --target ${{ matrix.target }} --test e2e
|
||||
|
||||
- name: Check PowerShell script syntax (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
./scripts/check_powershell_syntax.ps1
|
||||
|
||||
- name: Enforce warning budget (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
shell: bash
|
||||
run: |
|
||||
scripts/check_warning_budget.sh
|
||||
|
||||
- name: Security preflight (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
shell: bash
|
||||
run: |
|
||||
cargo install cargo-audit --locked
|
||||
scripts/security_preflight.sh --strict
|
||||
|
||||
windows-build-test:
|
||||
name: Build & Test (windows-latest)
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 150
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ssh-key: ${{ secrets.DEPLOY_KEY }}
|
||||
submodules: recursive
|
||||
|
||||
- name: Configure SSH for cargo git dependencies
|
||||
uses: webfactory/ssh-agent@v0.9.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.DEPLOY_KEY }}
|
||||
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
with:
|
||||
arch: amd64
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: x86_64-pc-windows-msvc
|
||||
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.7
|
||||
continue-on-error: true
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: windows-latest
|
||||
cache-all-crates: "true"
|
||||
|
||||
- name: Build release binary
|
||||
shell: pwsh
|
||||
run: |
|
||||
if (Get-Command sccache -ErrorAction SilentlyContinue) {
|
||||
sccache --start-server *> $null
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
$env:RUSTC_WRAPPER = 'sccache'
|
||||
}
|
||||
}
|
||||
|
||||
& cargo build --locked --release --target x86_64-pc-windows-msvc
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw 'Windows release build failed'
|
||||
}
|
||||
|
||||
- name: Compile library and binary tests
|
||||
shell: pwsh
|
||||
run: |
|
||||
& cargo test --locked --target x86_64-pc-windows-msvc --lib --bins --no-run
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw 'Windows library/binary test compilation failed'
|
||||
}
|
||||
|
||||
- name: Run targeted Windows validation tests
|
||||
shell: pwsh
|
||||
run: |
|
||||
$tests = @(
|
||||
'command_candidates_adds_extension_on_windows',
|
||||
'command_exists_for_known_binary',
|
||||
'command_exists_absolute_path',
|
||||
'sibling_socket_path_roundtrip',
|
||||
'cleanup_socket_pair_removes_main_and_debug_files',
|
||||
'is_process_running_reports_exited_children_as_stopped',
|
||||
'spawn_replacement_process_returns_without_waiting_for_child_exit',
|
||||
'build_shell_command_uses_cmd_and_executes_command',
|
||||
'pipe_name_is_stable_and_normalizes_case_and_separators',
|
||||
'pipe_name_falls_back_when_stem_is_empty',
|
||||
'stream_pair_round_trips_bytes',
|
||||
'split_stream_supports_concurrent_read_and_write'
|
||||
)
|
||||
|
||||
foreach ($testName in $tests) {
|
||||
& ./scripts/invoke_cargo_with_timeout.ps1 `
|
||||
-Name "Windows targeted test: $testName" `
|
||||
-TimeoutSeconds 300 `
|
||||
-CargoArgs @('test', '--locked', '--target', 'x86_64-pc-windows-msvc', '--lib', $testName, '--', '--nocapture')
|
||||
}
|
||||
|
||||
- name: Run Windows e2e smoke tests
|
||||
shell: pwsh
|
||||
run: |
|
||||
$tests = @(
|
||||
'provider_behavior::test_socket_model_cycle_supported_models',
|
||||
'provider_behavior::test_model_switch_resets_provider_session'
|
||||
)
|
||||
|
||||
foreach ($testName in $tests) {
|
||||
& ./scripts/invoke_cargo_with_timeout.ps1 `
|
||||
-Name "Windows e2e smoke test: $testName" `
|
||||
-TimeoutSeconds 420 `
|
||||
-CargoArgs @('test', '--locked', '--target', 'x86_64-pc-windows-msvc', '--test', 'e2e', $testName, '--', '--exact', '--nocapture')
|
||||
}
|
||||
|
||||
- name: Run Windows lifecycle e2e tests
|
||||
shell: pwsh
|
||||
env:
|
||||
JCODE_E2E_ARTIFACT_DIR: ${{ runner.temp }}/jcode-windows-e2e-logs
|
||||
run: |
|
||||
New-Item -ItemType Directory -Force -Path $env:JCODE_E2E_ARTIFACT_DIR | Out-Null
|
||||
$tests = @(
|
||||
'windows_lifecycle::windows_binary_server_accepts_clients_and_debug_cli',
|
||||
'windows_lifecycle::windows_binary_server_rebinds_named_pipe_after_exit'
|
||||
)
|
||||
foreach ($testName in $tests) {
|
||||
& ./scripts/invoke_cargo_with_timeout.ps1 `
|
||||
-Name "Windows lifecycle e2e test: $testName" `
|
||||
-TimeoutSeconds 420 `
|
||||
-CargoArgs @('test', '--locked', '--target', 'x86_64-pc-windows-msvc', '--test', 'e2e', $testName, '--', '--exact', '--nocapture')
|
||||
}
|
||||
|
||||
- name: Upload Windows e2e diagnostics
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-e2e-diagnostics
|
||||
path: ${{ runner.temp }}/jcode-windows-e2e-logs
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Verify built binary launches
|
||||
shell: pwsh
|
||||
run: |
|
||||
& "target/x86_64-pc-windows-msvc/release/jcode.exe" --version
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw 'Built Windows binary failed to run --version'
|
||||
}
|
||||
|
||||
- name: Verify installer using local artifact
|
||||
shell: pwsh
|
||||
run: |
|
||||
$cargoVersion = Select-String -Path Cargo.toml -Pattern '^version\s*=\s*"([^"]+)"' | Select-Object -First 1
|
||||
if (-not $cargoVersion) {
|
||||
throw 'Could not determine Cargo.toml version'
|
||||
}
|
||||
|
||||
$version = 'v' + $cargoVersion.Matches[0].Groups[1].Value
|
||||
& ./.github/scripts/verify_windows_install.ps1 `
|
||||
-ArtifactExePath 'target/x86_64-pc-windows-msvc/release/jcode.exe' `
|
||||
-Version $version
|
||||
|
||||
fmt:
|
||||
name: Format
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt
|
||||
|
||||
- name: Check formatting
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
powershell-syntax:
|
||||
name: PowerShell Syntax
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Check PowerShell script syntax (Windows PowerShell 5.1)
|
||||
shell: powershell
|
||||
run: |
|
||||
& ./scripts/check_powershell_syntax.ps1
|
||||
|
||||
- name: Check PowerShell script syntax (PowerShell 7)
|
||||
shell: pwsh
|
||||
run: |
|
||||
& ./scripts/check_powershell_syntax.ps1
|
||||
|
||||
windows-cross-check:
|
||||
name: Windows Cross-Target Check (Linux)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 35
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ssh-key: ${{ secrets.DEPLOY_KEY }}
|
||||
submodules: recursive
|
||||
|
||||
- name: Configure SSH for cargo git dependencies
|
||||
uses: webfactory/ssh-agent@v0.9.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.DEPLOY_KEY }}
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: x86_64-pc-windows-msvc,aarch64-pc-windows-msvc
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: windows-cross-check
|
||||
cache-all-crates: "true"
|
||||
|
||||
- name: Install LLVM toolchain for cargo-xwin
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq clang lld llvm ninja-build
|
||||
|
||||
- name: Install cargo-xwin
|
||||
run: cargo install --git https://github.com/rust-cross/cargo-xwin cargo-xwin
|
||||
|
||||
- name: Check Windows x64 target
|
||||
run: cargo xwin check --locked --target x86_64-pc-windows-msvc
|
||||
|
||||
# cargo-xwin currently feeds clang-style ring builds MSVC /imsvc flags for
|
||||
# aarch64-pc-windows-msvc on Linux. Keep this advisory until upstream
|
||||
# cargo-xwin/ring interop is fixed; native Windows ARM64 smoke covers the
|
||||
# release artifact path.
|
||||
- name: Check Windows ARM64 target (advisory)
|
||||
continue-on-error: true
|
||||
run: cargo xwin check --locked --target aarch64-pc-windows-msvc --no-default-features --features pdf
|
||||
@@ -0,0 +1,88 @@
|
||||
name: FreeBSD Smoke
|
||||
|
||||
# Builds and smoke-tests jcode inside a real FreeBSD VM.
|
||||
#
|
||||
# GitHub does not offer native FreeBSD runners, so we boot a FreeBSD guest
|
||||
# (via QEMU) on a standard Ubuntu runner using vmactions/freebsd-vm. This runs
|
||||
# the actual FreeBSD kernel and userland, giving a true build + run check
|
||||
# instead of a cross-compile that can never link platform C dependencies
|
||||
# (e.g. aws-lc-sys). See issue #416.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release:
|
||||
description: Build in release mode (slower, matches shipping binary)
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
push:
|
||||
paths:
|
||||
- '.github/workflows/freebsd-smoke.yml'
|
||||
schedule:
|
||||
# Weekly drift check so FreeBSD breakage is caught even without a push.
|
||||
- cron: '0 7 * * 1'
|
||||
|
||||
concurrency:
|
||||
group: freebsd-smoke-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
smoke:
|
||||
name: FreeBSD Smoke (x86_64)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build and test in FreeBSD VM
|
||||
uses: vmactions/freebsd-vm@v1
|
||||
with:
|
||||
release: "15.1"
|
||||
usesh: true
|
||||
# Give the build VM enough room: aws-lc-sys + tract pull in heavy C/Rust.
|
||||
mem: 6144
|
||||
cpu: 4
|
||||
# Install the toolchain jcode needs. The project is OpenSSL-free
|
||||
# (rustls + aws-lc-rs), so we only need a C/C++ toolchain, cmake, and
|
||||
# rust. pkgconf is handy for any transitive pkg-config probes.
|
||||
prepare: |
|
||||
pkg install -y rust cmake gmake pkgconf bash git
|
||||
# CARGO_BUILD_JOBS keeps memory in check; aws-lc-sys is memory hungry.
|
||||
run: |
|
||||
set -e
|
||||
echo "::group::Toolchain versions"
|
||||
uname -a
|
||||
cc --version | head -1
|
||||
cargo --version
|
||||
rustc --version
|
||||
echo "::endgroup::"
|
||||
|
||||
export CARGO_TERM_COLOR=always
|
||||
export CARGO_BUILD_JOBS=3
|
||||
|
||||
if [ "${{ inputs.release }}" = "true" ]; then
|
||||
PROFILE_FLAG="--release"
|
||||
TARGET_DIR="release"
|
||||
else
|
||||
PROFILE_FLAG=""
|
||||
TARGET_DIR="debug"
|
||||
fi
|
||||
|
||||
echo "::group::cargo build (jcode binary)"
|
||||
cargo build --locked $PROFILE_FLAG -p jcode --bin jcode
|
||||
echo "::endgroup::"
|
||||
|
||||
echo "::group::Verify binary launches"
|
||||
./target/$TARGET_DIR/jcode --version
|
||||
echo "::endgroup::"
|
||||
|
||||
echo "::group::Compile platform unit tests"
|
||||
cargo test --locked $PROFILE_FLAG -p jcode-base --lib --no-run
|
||||
echo "::endgroup::"
|
||||
|
||||
echo "::group::Run platform unit tests"
|
||||
cargo test --locked $PROFILE_FLAG -p jcode-base --lib -- --nocapture platform
|
||||
echo "::endgroup::"
|
||||
@@ -0,0 +1,144 @@
|
||||
name: iOS TestFlight
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- "ios/**"
|
||||
- ".github/workflows/ios-testflight.yml"
|
||||
|
||||
concurrency:
|
||||
group: ios-testflight-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
BUNDLE_ID: com.jcode.mobile
|
||||
SCHEME: JCodeMobile
|
||||
TEAM_ID: TAS6ARKDN7
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: swift test (JCodeKit)
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Swift toolchain info
|
||||
run: |
|
||||
sw_vers
|
||||
xcodebuild -version
|
||||
swift --version
|
||||
|
||||
- name: Run JCodeKit tests on macOS
|
||||
working-directory: ios
|
||||
run: swift test
|
||||
|
||||
compile-check:
|
||||
name: xcodebuild (simulator, unsigned)
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install XcodeGen
|
||||
run: brew install xcodegen
|
||||
|
||||
- name: Generate Xcode project
|
||||
working-directory: ios
|
||||
run: xcodegen generate
|
||||
|
||||
- name: Build app target for iOS simulator (no signing)
|
||||
working-directory: ios
|
||||
run: |
|
||||
set -o pipefail
|
||||
xcodebuild build \
|
||||
-project JCodeMobile.xcodeproj \
|
||||
-scheme "$SCHEME" \
|
||||
-configuration Debug \
|
||||
-destination "generic/platform=iOS Simulator" \
|
||||
CODE_SIGNING_ALLOWED=NO \
|
||||
| tail -60
|
||||
|
||||
build-and-upload:
|
||||
name: Build, sign, upload to TestFlight
|
||||
needs: [test, compile-check]
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install XcodeGen
|
||||
run: brew install xcodegen
|
||||
|
||||
- name: Generate Xcode project
|
||||
working-directory: ios
|
||||
run: xcodegen generate
|
||||
|
||||
- name: Write App Store Connect API key
|
||||
env:
|
||||
APPSTORE_API_KEY_P8: ${{ secrets.APPSTORE_API_KEY_P8 }}
|
||||
run: |
|
||||
mkdir -p ~/private_keys
|
||||
printf '%s' "$APPSTORE_API_KEY_P8" > ~/private_keys/AuthKey_${{ secrets.APPSTORE_API_KEY_ID }}.p8
|
||||
chmod 600 ~/private_keys/AuthKey_${{ secrets.APPSTORE_API_KEY_ID }}.p8
|
||||
|
||||
- name: Archive (cloud-managed signing)
|
||||
working-directory: ios
|
||||
run: |
|
||||
set -o pipefail
|
||||
xcodebuild archive \
|
||||
-project JCodeMobile.xcodeproj \
|
||||
-scheme "$SCHEME" \
|
||||
-configuration Release \
|
||||
-destination "generic/platform=iOS" \
|
||||
-archivePath build/JCodeMobile.xcarchive \
|
||||
-allowProvisioningUpdates \
|
||||
-authenticationKeyPath "$HOME/private_keys/AuthKey_${{ secrets.APPSTORE_API_KEY_ID }}.p8" \
|
||||
-authenticationKeyID "${{ secrets.APPSTORE_API_KEY_ID }}" \
|
||||
-authenticationKeyIssuerID "${{ secrets.APPSTORE_ISSUER_ID }}" \
|
||||
CODE_SIGN_STYLE=Automatic \
|
||||
DEVELOPMENT_TEAM="$TEAM_ID" \
|
||||
CURRENT_PROJECT_VERSION="${{ github.run_number }}" \
|
||||
| tail -100
|
||||
|
||||
- name: Write export options
|
||||
working-directory: ios
|
||||
run: |
|
||||
cat > ExportOptions.plist <<'EOF'
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>method</key>
|
||||
<string>app-store-connect</string>
|
||||
<key>destination</key>
|
||||
<string>upload</string>
|
||||
<key>teamID</key>
|
||||
<string>TAS6ARKDN7</string>
|
||||
<key>uploadSymbols</key>
|
||||
<true/>
|
||||
<key>manageAppVersionAndBuildNumber</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
- name: Export and upload to App Store Connect
|
||||
working-directory: ios
|
||||
run: |
|
||||
set -o pipefail
|
||||
xcodebuild -exportArchive \
|
||||
-archivePath build/JCodeMobile.xcarchive \
|
||||
-exportOptionsPlist ExportOptions.plist \
|
||||
-exportPath build/export \
|
||||
-allowProvisioningUpdates \
|
||||
-authenticationKeyPath "$HOME/private_keys/AuthKey_${{ secrets.APPSTORE_API_KEY_ID }}.p8" \
|
||||
-authenticationKeyID "${{ secrets.APPSTORE_API_KEY_ID }}" \
|
||||
-authenticationKeyIssuerID "${{ secrets.APPSTORE_ISSUER_ID }}" \
|
||||
| tail -60
|
||||
|
||||
- name: Cleanup API key
|
||||
if: always()
|
||||
run: rm -rf ~/private_keys
|
||||
@@ -0,0 +1,575 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
concurrency:
|
||||
group: release-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
CARGO_INCREMENTAL: "0"
|
||||
|
||||
jobs:
|
||||
create-release:
|
||||
name: Create release
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# Render a human-readable changelog for the release body (issue #435):
|
||||
# changelog/v<version>.json when present, otherwise grouped commit
|
||||
# subjects since the previous tag, always ending with the compare link.
|
||||
- name: Generate release notes
|
||||
run: scripts/generate_release_notes.sh "${GITHUB_REF_NAME}" > release_notes.md
|
||||
|
||||
# Publish the release up front so each platform build can attach
|
||||
# its own asset the moment it finishes, instead of everyone waiting for
|
||||
# the slowest job. action-gh-release is idempotent: it creates the release
|
||||
# for this tag if missing and updates it otherwise.
|
||||
- name: Create release if missing
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
body_path: release_notes.md
|
||||
|
||||
build-linux-macos:
|
||||
name: Build (${{ matrix.target }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: create-release
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- # Build Linux x86_64 release assets on a CentOS 7 / manylinux2014
|
||||
# glibc 2.17 baseline so they run on older distros as well as newer
|
||||
# Debian/Ubuntu containers used by many TB tasks.
|
||||
os: ubuntu-22.04
|
||||
target: x86_64-unknown-linux-gnu
|
||||
artifact: jcode-linux-x86_64
|
||||
compat_container: true
|
||||
- os: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-gnu
|
||||
artifact: jcode-linux-aarch64
|
||||
- os: macos-latest
|
||||
target: aarch64-apple-darwin
|
||||
artifact: jcode-macos-aarch64
|
||||
- os: macos-15-intel
|
||||
target: x86_64-apple-darwin
|
||||
artifact: jcode-macos-x86_64
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ssh-key: ${{ secrets.DEPLOY_KEY }}
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Configure SSH for cargo git dependencies
|
||||
uses: webfactory/ssh-agent@v0.9.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.DEPLOY_KEY }}
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.7
|
||||
continue-on-error: true
|
||||
id: sccache
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: ${{ matrix.target }}
|
||||
cache-all-crates: "true"
|
||||
|
||||
- name: Install mold linker (Linux)
|
||||
if: runner.os == 'Linux' && matrix.compat_container != true
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq mold
|
||||
|
||||
- name: Build release binary
|
||||
if: matrix.compat_container != true
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p .cargo
|
||||
if [ "$RUNNER_OS" = "Linux" ]; then
|
||||
cat > .cargo/config.toml << 'EOF'
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
linker = "clang"
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
EOF
|
||||
fi
|
||||
if command -v sccache &>/dev/null && sccache --start-server 2>/dev/null; then
|
||||
export RUSTC_WRAPPER=sccache
|
||||
fi
|
||||
cargo build --release --target ${{ matrix.target }}
|
||||
env:
|
||||
JCODE_RELEASE_BUILD: "1"
|
||||
JCODE_BUILD_SEMVER: ${{ github.ref_name }}
|
||||
|
||||
- name: Build portable Linux x86_64 release binary
|
||||
if: matrix.compat_container == true
|
||||
shell: bash
|
||||
run: scripts/build_linux_compat.sh dist
|
||||
env:
|
||||
JCODE_RELEASE_BUILD: "1"
|
||||
JCODE_BUILD_SEMVER: ${{ github.ref_name }}
|
||||
JCODE_COMPAT_ARTIFACT: ${{ matrix.artifact }}
|
||||
|
||||
- name: Package binary
|
||||
if: matrix.compat_container != true
|
||||
run: |
|
||||
mkdir -p dist
|
||||
cp target/${{ matrix.target }}/release/jcode dist/${{ matrix.artifact }}
|
||||
chmod +x dist/${{ matrix.artifact }}
|
||||
cd dist && tar czf ${{ matrix.artifact }}.tar.gz ${{ matrix.artifact }}
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.artifact }}
|
||||
path: dist/${{ matrix.artifact }}.tar.gz
|
||||
|
||||
# Attach this platform's asset to the release immediately, so e.g. the
|
||||
# macOS binary is downloadable ~15 min in without waiting for the slower
|
||||
# Windows/Linux x86_64 jobs.
|
||||
- name: Publish asset to release
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
gh release upload "${GITHUB_REF_NAME}" "dist/${{ matrix.artifact }}.tar.gz" --clobber
|
||||
|
||||
build-windows:
|
||||
name: Build (${{ matrix.target }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: create-release
|
||||
# Windows x64 release smoke tests compile the e2e harness after the release
|
||||
# binary. GitHub-hosted Windows runners sometimes exceed 25 minutes, which
|
||||
# cancels otherwise healthy releases before artifacts can be uploaded.
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
artifact: jcode-windows-x86_64
|
||||
- os: windows-11-arm
|
||||
target: aarch64-pc-windows-msvc
|
||||
artifact: jcode-windows-aarch64
|
||||
cargo_args: "--no-default-features --features pdf"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Configure MSVC build environment (x64)
|
||||
if: matrix.target == 'x86_64-pc-windows-msvc'
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
with:
|
||||
arch: amd64
|
||||
|
||||
- name: Configure MSVC build environment (ARM64)
|
||||
if: matrix.target == 'aarch64-pc-windows-msvc'
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
with:
|
||||
arch: amd64_arm64
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.7
|
||||
continue-on-error: true
|
||||
id: sccache
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: ${{ matrix.target }}
|
||||
cache-all-crates: "true"
|
||||
|
||||
- name: Build release binary
|
||||
shell: pwsh
|
||||
run: |
|
||||
if (Get-Command sccache -ErrorAction SilentlyContinue) {
|
||||
sccache --start-server *> $null
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
$env:RUSTC_WRAPPER = "sccache"
|
||||
}
|
||||
}
|
||||
|
||||
$cargoArgs = @("build", "--release", "--target", "${{ matrix.target }}")
|
||||
$extraArgs = "${{ matrix.cargo_args }}"
|
||||
if (-not [string]::IsNullOrWhiteSpace($extraArgs)) {
|
||||
$cargoArgs += $extraArgs -split ' '
|
||||
}
|
||||
|
||||
& cargo @cargoArgs
|
||||
env:
|
||||
JCODE_RELEASE_BUILD: "1"
|
||||
JCODE_BUILD_SEMVER: ${{ github.ref_name }}
|
||||
|
||||
- name: Run Windows runtime smoke tests (x64)
|
||||
if: matrix.target == 'x86_64-pc-windows-msvc'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$tests = @(
|
||||
'provider_behavior::test_socket_model_cycle_supported_models',
|
||||
'provider_behavior::test_model_switch_resets_provider_session'
|
||||
)
|
||||
|
||||
foreach ($testName in $tests) {
|
||||
& cargo test --locked --target ${{ matrix.target }} --test e2e $testName -- --exact --nocapture
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Windows smoke test failed: $testName"
|
||||
}
|
||||
}
|
||||
|
||||
- name: Verify built Windows binary launches
|
||||
shell: pwsh
|
||||
run: |
|
||||
& "target/${{ matrix.target }}/release/jcode.exe" --version
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Built Windows binary failed to run --version"
|
||||
}
|
||||
|
||||
- name: Verify Windows installer with local artifact
|
||||
shell: pwsh
|
||||
run: |
|
||||
& ./.github/scripts/verify_windows_install.ps1 `
|
||||
-ArtifactExePath "target/${{ matrix.target }}/release/jcode.exe" `
|
||||
-Version "${{ github.ref_name }}"
|
||||
|
||||
- name: Package binary
|
||||
shell: pwsh
|
||||
run: |
|
||||
New-Item -ItemType Directory -Force -Path dist | Out-Null
|
||||
Copy-Item "target/${{ matrix.target }}/release/jcode.exe" "dist/${{ matrix.artifact }}.exe"
|
||||
tar -czf "dist/${{ matrix.artifact }}.tar.gz" -C dist "${{ matrix.artifact }}.exe"
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.artifact }}
|
||||
path: |
|
||||
dist/${{ matrix.artifact }}.tar.gz
|
||||
dist/${{ matrix.artifact }}.exe
|
||||
|
||||
- name: Publish asset to release
|
||||
shell: pwsh
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
gh release upload "${env:GITHUB_REF_NAME}" "dist/${{ matrix.artifact }}.tar.gz" "dist/${{ matrix.artifact }}.exe" --clobber
|
||||
|
||||
build-freebsd:
|
||||
name: Build (x86_64-unknown-freebsd)
|
||||
runs-on: ubuntu-latest
|
||||
needs: create-release
|
||||
# GitHub has no native FreeBSD runners, so build inside a FreeBSD VM
|
||||
# (QEMU via vmactions), same approach as freebsd-smoke.yml (issues #416,
|
||||
# #433). Best-effort: a flaky VM build must not block the rest of the
|
||||
# release, so failures are surfaced but non-fatal.
|
||||
continue-on-error: true
|
||||
timeout-minutes: 180
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build release binary in FreeBSD VM
|
||||
uses: vmactions/freebsd-vm@v1
|
||||
with:
|
||||
release: "15.1"
|
||||
usesh: true
|
||||
# Give the build VM enough room: aws-lc-sys + tract pull in heavy C/Rust.
|
||||
mem: 6144
|
||||
cpu: 4
|
||||
# The project is OpenSSL-free (rustls + aws-lc-rs): only a C/C++
|
||||
# toolchain, cmake, and rust are needed.
|
||||
prepare: |
|
||||
pkg install -y rust cmake gmake pkgconf bash git
|
||||
run: |
|
||||
set -e
|
||||
echo "::group::Toolchain versions"
|
||||
uname -a
|
||||
cc --version | head -1
|
||||
cargo --version
|
||||
rustc --version
|
||||
echo "::endgroup::"
|
||||
|
||||
export CARGO_TERM_COLOR=always
|
||||
# CARGO_BUILD_JOBS keeps memory in check; aws-lc-sys is memory hungry.
|
||||
export CARGO_BUILD_JOBS=3
|
||||
export JCODE_RELEASE_BUILD=1
|
||||
export JCODE_BUILD_SEMVER="${{ github.ref_name }}"
|
||||
|
||||
echo "::group::cargo build (jcode binary)"
|
||||
cargo build --locked --release -p jcode --bin jcode
|
||||
echo "::endgroup::"
|
||||
|
||||
echo "::group::Verify binary launches"
|
||||
./target/release/jcode --version
|
||||
echo "::endgroup::"
|
||||
|
||||
mkdir -p dist
|
||||
cp target/release/jcode dist/jcode-freebsd-x86_64
|
||||
chmod +x dist/jcode-freebsd-x86_64
|
||||
cd dist && tar czf jcode-freebsd-x86_64.tar.gz jcode-freebsd-x86_64
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: jcode-freebsd-x86_64
|
||||
path: dist/jcode-freebsd-x86_64.tar.gz
|
||||
|
||||
- name: Publish asset to release
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
gh release upload "${GITHUB_REF_NAME}" "dist/jcode-freebsd-x86_64.tar.gz" --clobber
|
||||
|
||||
release:
|
||||
name: Finalize release
|
||||
needs: [build-linux-macos, build-windows, build-freebsd]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
pattern: jcode-*
|
||||
|
||||
- name: Generate checksums
|
||||
shell: bash
|
||||
run: |
|
||||
python3 - << 'PY'
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
|
||||
files = sorted(
|
||||
p for p in Path("artifacts").rglob("*")
|
||||
if p.is_file() and (p.name.endswith(".tar.gz") or p.name.endswith(".exe"))
|
||||
)
|
||||
if not files:
|
||||
raise SystemExit("No release assets found for checksum generation")
|
||||
|
||||
with Path("SHA256SUMS").open("w", encoding="utf-8") as out:
|
||||
for path in files:
|
||||
digest = hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
out.write(f"{digest} {path.name}\n")
|
||||
PY
|
||||
cat SHA256SUMS
|
||||
|
||||
# Per-platform assets were already attached by their own build jobs as
|
||||
# they finished; here we only add the cross-cutting checksums file.
|
||||
- name: Upload checksums to release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
gh release upload "${GITHUB_REF_NAME}" SHA256SUMS --clobber
|
||||
|
||||
- name: Update Homebrew formula
|
||||
env:
|
||||
HOMEBREW_DEPLOY_KEY: ${{ secrets.HOMEBREW_DEPLOY_KEY }}
|
||||
if: env.HOMEBREW_DEPLOY_KEY != ''
|
||||
run: |
|
||||
VERSION="${GITHUB_REF_NAME}"
|
||||
VERSION_NUM="${VERSION#v}"
|
||||
|
||||
LINUX_SHA=$(sha256sum artifacts/jcode-linux-x86_64/jcode-linux-x86_64.tar.gz | cut -d' ' -f1)
|
||||
LINUX_ARM_SHA=$(sha256sum artifacts/jcode-linux-aarch64/jcode-linux-aarch64.tar.gz | cut -d' ' -f1)
|
||||
MACOS_ARM_SHA=$(sha256sum artifacts/jcode-macos-aarch64/jcode-macos-aarch64.tar.gz | cut -d' ' -f1)
|
||||
MACOS_INTEL_SHA=$(sha256sum artifacts/jcode-macos-x86_64/jcode-macos-x86_64.tar.gz | cut -d' ' -f1)
|
||||
|
||||
mkdir -p ~/.ssh
|
||||
echo "$HOMEBREW_DEPLOY_KEY" > ~/.ssh/deploy_key
|
||||
chmod 600 ~/.ssh/deploy_key
|
||||
export GIT_SSH_COMMAND="ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=no"
|
||||
|
||||
git clone git@github.com:1jehuang/homebrew-jcode.git /tmp/homebrew-jcode
|
||||
|
||||
cat > /tmp/homebrew-jcode/Formula/jcode.rb << FORMULA
|
||||
class Jcode < Formula
|
||||
desc "AI coding agent powered by Claude and ChatGPT"
|
||||
homepage "https://github.com/1jehuang/jcode"
|
||||
version "${VERSION_NUM}"
|
||||
license "MIT"
|
||||
|
||||
on_macos do
|
||||
on_arm do
|
||||
url "https://github.com/1jehuang/jcode/releases/download/${VERSION}/jcode-macos-aarch64.tar.gz"
|
||||
sha256 "${MACOS_ARM_SHA}"
|
||||
|
||||
def install
|
||||
bin.install "jcode-macos-aarch64" => "jcode"
|
||||
end
|
||||
end
|
||||
|
||||
on_intel do
|
||||
url "https://github.com/1jehuang/jcode/releases/download/${VERSION}/jcode-macos-x86_64.tar.gz"
|
||||
sha256 "${MACOS_INTEL_SHA}"
|
||||
|
||||
def install
|
||||
bin.install "jcode-macos-x86_64" => "jcode"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
on_linux do
|
||||
on_intel do
|
||||
url "https://github.com/1jehuang/jcode/releases/download/${VERSION}/jcode-linux-x86_64.tar.gz"
|
||||
sha256 "${LINUX_SHA}"
|
||||
|
||||
def install
|
||||
libexec.install "jcode-linux-x86_64", "jcode-linux-x86_64.bin"
|
||||
libexec.install Dir["libssl.so*"], Dir["libcrypto.so*"] unless Dir["libssl.so*", "libcrypto.so*"].empty?
|
||||
(bin/"jcode").write <<~SH
|
||||
#!/bin/sh
|
||||
exec "#{libexec}/jcode-linux-x86_64" "$@"
|
||||
SH
|
||||
end
|
||||
end
|
||||
|
||||
on_arm do
|
||||
url "https://github.com/1jehuang/jcode/releases/download/${VERSION}/jcode-linux-aarch64.tar.gz"
|
||||
sha256 "${LINUX_ARM_SHA}"
|
||||
|
||||
def install
|
||||
bin.install "jcode-linux-aarch64" => "jcode"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test do
|
||||
assert_match "jcode", shell_output("#{bin}/jcode --version")
|
||||
end
|
||||
end
|
||||
FORMULA
|
||||
|
||||
sed -i 's/^ //' /tmp/homebrew-jcode/Formula/jcode.rb
|
||||
|
||||
cd /tmp/homebrew-jcode
|
||||
git config user.name "jcode-release-bot"
|
||||
git config user.email "release@jcode.dev"
|
||||
git add Formula/jcode.rb
|
||||
git commit -m "Update to ${VERSION}" || echo "No changes"
|
||||
git push
|
||||
|
||||
- name: Update AUR package
|
||||
env:
|
||||
AUR_SSH_KEY: ${{ secrets.AUR_SSH_KEY }}
|
||||
if: env.AUR_SSH_KEY != ''
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
retry() {
|
||||
local attempts="$1"
|
||||
local delay="$2"
|
||||
shift 2
|
||||
local try=1
|
||||
|
||||
until "$@"; do
|
||||
local exit_code=$?
|
||||
if [ "$try" -ge "$attempts" ]; then
|
||||
return "$exit_code"
|
||||
fi
|
||||
echo "Attempt ${try}/${attempts} failed; retrying in ${delay}s..."
|
||||
sleep "$delay"
|
||||
try=$((try + 1))
|
||||
done
|
||||
}
|
||||
|
||||
VERSION="${GITHUB_REF_NAME}"
|
||||
VERSION_NUM="${VERSION#v}"
|
||||
|
||||
LINUX_SHA=$(sha256sum artifacts/jcode-linux-x86_64/jcode-linux-x86_64.tar.gz | cut -d' ' -f1)
|
||||
LINUX_URL="https://github.com/1jehuang/jcode/releases/download/${VERSION}/jcode-linux-x86_64.tar.gz"
|
||||
|
||||
mkdir -p ~/.ssh
|
||||
chmod 700 ~/.ssh
|
||||
printf '%s\n' "$AUR_SSH_KEY" > ~/.ssh/aur_key
|
||||
chmod 600 ~/.ssh/aur_key
|
||||
touch ~/.ssh/known_hosts
|
||||
chmod 644 ~/.ssh/known_hosts
|
||||
retry 3 5 bash -lc 'ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts'
|
||||
|
||||
export GIT_SSH_COMMAND="ssh -i ~/.ssh/aur_key -o BatchMode=yes -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile=$HOME/.ssh/known_hosts -o ConnectTimeout=10 -o ConnectionAttempts=3"
|
||||
|
||||
retry 3 5 bash -lc 'rm -rf /tmp/jcode-aur && git clone --depth 1 ssh://aur@aur.archlinux.org/jcode-bin.git /tmp/jcode-aur'
|
||||
cd /tmp/jcode-aur
|
||||
git remote set-url origin ssh://aur@aur.archlinux.org/jcode-bin.git
|
||||
|
||||
cat > PKGBUILD << 'PKGBUILD_END'
|
||||
# Maintainer: Jeremy Huang <jeremyhuang55555@gmail.com>
|
||||
pkgname=jcode-bin
|
||||
pkgver=VERSION_PLACEHOLDER
|
||||
pkgrel=1
|
||||
pkgdesc="AI coding agent powered by Claude and ChatGPT"
|
||||
arch=('x86_64')
|
||||
url="https://github.com/1jehuang/jcode"
|
||||
license=('MIT')
|
||||
provides=('jcode')
|
||||
conflicts=('jcode')
|
||||
source=("URL_PLACEHOLDER")
|
||||
sha256sums=('SHA_PLACEHOLDER')
|
||||
|
||||
package() {
|
||||
install -Dm755 "${srcdir}/jcode-linux-x86_64" "${pkgdir}/usr/lib/jcode/jcode-linux-x86_64"
|
||||
install -Dm755 "${srcdir}/jcode-linux-x86_64.bin" "${pkgdir}/usr/lib/jcode/jcode-linux-x86_64.bin"
|
||||
if compgen -G "${srcdir}/libssl.so*" >/dev/null; then
|
||||
install -Dm644 "${srcdir}"/libssl.so* "${pkgdir}/usr/lib/jcode/"
|
||||
fi
|
||||
if compgen -G "${srcdir}/libcrypto.so*" >/dev/null; then
|
||||
install -Dm644 "${srcdir}"/libcrypto.so* "${pkgdir}/usr/lib/jcode/"
|
||||
fi
|
||||
mkdir -p "${pkgdir}/usr/bin"
|
||||
ln -s /usr/lib/jcode/jcode-linux-x86_64 "${pkgdir}/usr/bin/jcode"
|
||||
}
|
||||
PKGBUILD_END
|
||||
|
||||
sed -i "s|VERSION_PLACEHOLDER|${VERSION_NUM}|" PKGBUILD
|
||||
sed -i "s|URL_PLACEHOLDER|${LINUX_URL}|" PKGBUILD
|
||||
sed -i "s|SHA_PLACEHOLDER|${LINUX_SHA}|" PKGBUILD
|
||||
sed -i 's/^ //' PKGBUILD
|
||||
|
||||
# Generate .SRCINFO without makepkg (AUR uses tab indentation)
|
||||
printf 'pkgbase = jcode-bin\n' > .SRCINFO
|
||||
printf '\tpkgdesc = AI coding agent powered by Claude and ChatGPT\n' >> .SRCINFO
|
||||
printf '\tpkgver = %s\n' "${VERSION_NUM}" >> .SRCINFO
|
||||
printf '\tpkgrel = 1\n' >> .SRCINFO
|
||||
printf '\turl = https://github.com/1jehuang/jcode\n' >> .SRCINFO
|
||||
printf '\tarch = x86_64\n' >> .SRCINFO
|
||||
printf '\tlicense = MIT\n' >> .SRCINFO
|
||||
printf '\tprovides = jcode\n' >> .SRCINFO
|
||||
printf '\tconflicts = jcode\n' >> .SRCINFO
|
||||
printf '\tsource = %s\n' "${LINUX_URL}" >> .SRCINFO
|
||||
printf '\tsha256sums = %s\n' "${LINUX_SHA}" >> .SRCINFO
|
||||
printf '\npkgname = jcode-bin\n' >> .SRCINFO
|
||||
|
||||
git config user.name "Jeremy Huang"
|
||||
git config user.email "jeremyhuang55555@gmail.com"
|
||||
git add PKGBUILD .SRCINFO
|
||||
git commit -m "Update to ${VERSION}" || echo "No changes"
|
||||
retry 3 5 git push origin master
|
||||
@@ -0,0 +1,111 @@
|
||||
name: Require Linked Issue
|
||||
|
||||
# Ensure every pull request is linked to a REAL GitHub issue in this repo.
|
||||
# A PR passes when it links an issue via any of:
|
||||
# * GitHub's "Development" sidebar (closing issue reference), or
|
||||
# * a closing keyword in the body/title (e.g. "Closes #123"), or
|
||||
# * a plain mention (#123 / owner/repo#123) or full issue URL.
|
||||
# Any referenced number is verified against the API: it must resolve to an
|
||||
# existing issue (not a pull request) in this repository.
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize, ready_for_review]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
issues: read
|
||||
|
||||
concurrency:
|
||||
group: require-issue-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
require-issue:
|
||||
name: Require Linked Issue
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Check for a linked, existing issue
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
if (!pr) {
|
||||
core.info('No pull_request payload; skipping.');
|
||||
return;
|
||||
}
|
||||
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
const text = `${pr.title || ''}\n\n${pr.body || ''}`;
|
||||
|
||||
// Collect candidate issue numbers that belong to THIS repo.
|
||||
const candidates = new Set();
|
||||
|
||||
// Full issue URLs: https://github.com/owner/repo/issues/123
|
||||
const urlRe = /https?:\/\/github\.com\/([\w.-]+)\/([\w.-]+)\/issues\/(\d+)/gi;
|
||||
for (const m of text.matchAll(urlRe)) {
|
||||
if (m[1].toLowerCase() === owner.toLowerCase() &&
|
||||
m[2].toLowerCase() === repo.toLowerCase()) {
|
||||
candidates.add(Number(m[3]));
|
||||
}
|
||||
}
|
||||
|
||||
// owner/repo#123 or bare #123 (bare assumed to be this repo).
|
||||
const refRe = /(?:([\w.-]+)\/([\w.-]+))?#(\d+)/g;
|
||||
for (const m of text.matchAll(refRe)) {
|
||||
const refOwner = m[1];
|
||||
const refRepo = m[2];
|
||||
const num = Number(m[3]);
|
||||
if (!refOwner && !refRepo) {
|
||||
candidates.add(num); // bare #123 -> this repo
|
||||
} else if (refOwner?.toLowerCase() === owner.toLowerCase() &&
|
||||
refRepo?.toLowerCase() === repo.toLowerCase()) {
|
||||
candidates.add(num);
|
||||
}
|
||||
}
|
||||
|
||||
// Issues linked via the Development sidebar (closing references).
|
||||
try {
|
||||
const query = `query($owner:String!, $repo:String!, $number:Int!) {
|
||||
repository(owner:$owner, name:$repo) {
|
||||
pullRequest(number:$number) {
|
||||
closingIssuesReferences(first: 10) { nodes { number } }
|
||||
}
|
||||
}
|
||||
}`;
|
||||
const result = await github.graphql(query, { owner, repo, number: pr.number });
|
||||
const nodes = result?.repository?.pullRequest?.closingIssuesReferences?.nodes || [];
|
||||
for (const n of nodes) candidates.add(Number(n.number));
|
||||
} catch (err) {
|
||||
core.warning(`Could not query closing issue references: ${err}`);
|
||||
}
|
||||
|
||||
// Verify at least one candidate is a real issue (not a PR, not this PR).
|
||||
let linkedIssue = null;
|
||||
for (const num of candidates) {
|
||||
if (num === pr.number) continue;
|
||||
try {
|
||||
const { data } = await github.rest.issues.get({ owner, repo, issue_number: num });
|
||||
if (!data.pull_request) { // issues have no pull_request field
|
||||
linkedIssue = num;
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
// 404 -> number doesn't exist; ignore and keep checking.
|
||||
}
|
||||
}
|
||||
|
||||
if (!linkedIssue) {
|
||||
core.setFailed(
|
||||
'This PR must be linked to an existing GitHub issue in this repository. ' +
|
||||
'Add a closing keyword (e.g. "Closes #123") to the PR description, link an ' +
|
||||
'issue via the Development sidebar, or reference an existing issue number ' +
|
||||
'(e.g. "#123"). If no issue exists yet, please open one first.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
core.info(`Linked to existing issue #${linkedIssue}. ✅`);
|
||||
@@ -0,0 +1,215 @@
|
||||
name: Windows Smoke
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
target:
|
||||
description: Windows target to verify
|
||||
required: true
|
||||
default: x64
|
||||
type: choice
|
||||
options:
|
||||
- x64
|
||||
- arm64
|
||||
- both
|
||||
|
||||
concurrency:
|
||||
group: windows-smoke-${{ github.ref }}-${{ github.event.inputs.target || 'x64' }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
|
||||
jobs:
|
||||
smoke-x64:
|
||||
name: Windows Smoke (x64)
|
||||
if: github.event.inputs.target == 'x64' || github.event.inputs.target == 'both'
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 150
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
with:
|
||||
arch: amd64
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: x86_64-pc-windows-msvc
|
||||
|
||||
- uses: mozilla-actions/sccache-action@v0.0.7
|
||||
continue-on-error: true
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: windows-smoke-x64
|
||||
cache-all-crates: "true"
|
||||
|
||||
- name: Build release binary
|
||||
shell: pwsh
|
||||
run: |
|
||||
if (Get-Command sccache -ErrorAction SilentlyContinue) {
|
||||
sccache --start-server *> $null
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
$env:RUSTC_WRAPPER = 'sccache'
|
||||
}
|
||||
}
|
||||
|
||||
& cargo build --locked --release --target x86_64-pc-windows-msvc
|
||||
|
||||
- name: Compile library and binary tests
|
||||
shell: pwsh
|
||||
run: |
|
||||
& cargo test --locked --target x86_64-pc-windows-msvc --lib --bins --no-run
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw 'Windows library/binary test compilation failed'
|
||||
}
|
||||
|
||||
- name: Run targeted Windows validation tests
|
||||
shell: pwsh
|
||||
run: |
|
||||
$tests = @(
|
||||
'command_candidates_adds_extension_on_windows',
|
||||
'command_exists_for_known_binary',
|
||||
'command_exists_absolute_path',
|
||||
'sibling_socket_path_roundtrip',
|
||||
'cleanup_socket_pair_removes_main_and_debug_files',
|
||||
'is_process_running_reports_exited_children_as_stopped',
|
||||
'spawn_replacement_process_returns_without_waiting_for_child_exit',
|
||||
'build_shell_command_uses_cmd_and_executes_command',
|
||||
'pipe_name_is_stable_and_normalizes_case_and_separators',
|
||||
'pipe_name_falls_back_when_stem_is_empty',
|
||||
'stream_pair_round_trips_bytes',
|
||||
'split_stream_supports_concurrent_read_and_write',
|
||||
'auto_provider_noninteractive_skips_untrusted_external_auth_instead_of_blocking'
|
||||
)
|
||||
|
||||
foreach ($testName in $tests) {
|
||||
& ./scripts/invoke_cargo_with_timeout.ps1 `
|
||||
-Name "Windows targeted test: $testName" `
|
||||
-TimeoutSeconds 300 `
|
||||
-CargoArgs @('test', '--locked', '--target', 'x86_64-pc-windows-msvc', '--lib', $testName, '--', '--nocapture')
|
||||
}
|
||||
|
||||
- name: Run Windows smoke tests
|
||||
shell: pwsh
|
||||
run: |
|
||||
$tests = @(
|
||||
'provider_behavior::test_socket_model_cycle_supported_models',
|
||||
'provider_behavior::test_model_switch_resets_provider_session'
|
||||
)
|
||||
|
||||
foreach ($testName in $tests) {
|
||||
& ./scripts/invoke_cargo_with_timeout.ps1 `
|
||||
-Name "Windows e2e smoke test: $testName" `
|
||||
-TimeoutSeconds 420 `
|
||||
-CargoArgs @('test', '--locked', '--target', 'x86_64-pc-windows-msvc', '--test', 'e2e', $testName, '--', '--exact', '--nocapture')
|
||||
}
|
||||
|
||||
- name: Run Windows lifecycle e2e tests
|
||||
shell: pwsh
|
||||
env:
|
||||
JCODE_E2E_ARTIFACT_DIR: ${{ runner.temp }}/jcode-windows-e2e-logs
|
||||
run: |
|
||||
New-Item -ItemType Directory -Force -Path $env:JCODE_E2E_ARTIFACT_DIR | Out-Null
|
||||
$tests = @(
|
||||
'windows_lifecycle::windows_binary_server_accepts_clients_and_debug_cli',
|
||||
'windows_lifecycle::windows_binary_server_rebinds_named_pipe_after_exit'
|
||||
)
|
||||
foreach ($testName in $tests) {
|
||||
& ./scripts/invoke_cargo_with_timeout.ps1 `
|
||||
-Name "Windows lifecycle e2e test: $testName" `
|
||||
-TimeoutSeconds 420 `
|
||||
-CargoArgs @('test', '--locked', '--target', 'x86_64-pc-windows-msvc', '--test', 'e2e', $testName, '--', '--exact', '--nocapture')
|
||||
}
|
||||
|
||||
- name: Upload Windows e2e diagnostics
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-smoke-e2e-diagnostics-x64
|
||||
path: ${{ runner.temp }}/jcode-windows-e2e-logs
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Verify built binary launches
|
||||
shell: pwsh
|
||||
run: |
|
||||
& "target/x86_64-pc-windows-msvc/release/jcode.exe" --version
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw 'Built Windows binary failed to run --version'
|
||||
}
|
||||
|
||||
- name: Verify installer using local artifact
|
||||
shell: pwsh
|
||||
run: |
|
||||
$cargoVersion = Select-String -Path Cargo.toml -Pattern '^version\s*=\s*"([^"]+)"' | Select-Object -First 1
|
||||
if (-not $cargoVersion) {
|
||||
throw 'Could not determine Cargo.toml version'
|
||||
}
|
||||
|
||||
$version = 'v' + $cargoVersion.Matches[0].Groups[1].Value
|
||||
& ./.github/scripts/verify_windows_install.ps1 `
|
||||
-ArtifactExePath 'target/x86_64-pc-windows-msvc/release/jcode.exe' `
|
||||
-Version $version
|
||||
|
||||
smoke-arm64:
|
||||
name: Windows Smoke (ARM64)
|
||||
if: github.event.inputs.target == 'arm64' || github.event.inputs.target == 'both'
|
||||
runs-on: windows-11-arm
|
||||
timeout-minutes: 35
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
with:
|
||||
arch: amd64_arm64
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: aarch64-pc-windows-msvc
|
||||
|
||||
- uses: mozilla-actions/sccache-action@v0.0.7
|
||||
continue-on-error: true
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: windows-smoke-arm64
|
||||
cache-all-crates: "true"
|
||||
|
||||
- name: Build release binary
|
||||
shell: pwsh
|
||||
run: |
|
||||
if (Get-Command sccache -ErrorAction SilentlyContinue) {
|
||||
sccache --start-server *> $null
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
$env:RUSTC_WRAPPER = 'sccache'
|
||||
}
|
||||
}
|
||||
|
||||
& cargo build --locked --release --target aarch64-pc-windows-msvc --no-default-features --features pdf
|
||||
|
||||
- name: Verify built binary launches
|
||||
shell: pwsh
|
||||
run: |
|
||||
& "target/aarch64-pc-windows-msvc/release/jcode.exe" --version
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw 'Built Windows ARM64 binary failed to run --version'
|
||||
}
|
||||
|
||||
- name: Verify installer using local artifact
|
||||
shell: pwsh
|
||||
run: |
|
||||
$cargoVersion = Select-String -Path Cargo.toml -Pattern '^version\s*=\s*"([^"]+)"' | Select-Object -First 1
|
||||
if (-not $cargoVersion) {
|
||||
throw 'Could not determine Cargo.toml version'
|
||||
}
|
||||
|
||||
$version = 'v' + $cargoVersion.Matches[0].Groups[1].Value
|
||||
& ./.github/scripts/verify_windows_install.ps1 `
|
||||
-ArtifactExePath 'target/aarch64-pc-windows-msvc/release/jcode.exe' `
|
||||
-Version $version
|
||||
Reference in New Issue
Block a user