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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 31 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,33 @@ VERSION ?=
BUILD ?=
XCODEBUILD_FLAGS ?=

# Toolchain pin. macOS 26.4+ SDKs dropped the arm64-macos slice from
# libSystem.tbd, which zig 0.15.2 (pinned by Ghostty) can't link against
# (ziglang/zig#31658, fixed in zig 0.16). Xcode 26.5's Swift 6.3 also fails to
# compile the pinned swift-composable-architecture. Xcode 26.3 satisfies both,
# so resolve a developer dir and export it for every recipe: `tuist generate`
# builds Ghostty, and `xcodebuild` builds zmx (a pre-script) plus the app.
# Override with SUPACODE_DEVELOPER_DIR=... (set it empty to use the system
# default); it auto-disables on machines/CI without Xcode 26.3 installed.
SUPACODE_DEVELOPER_DIR ?= /Applications/Xcode_26.3.app/Contents/Developer
ifneq ($(wildcard $(SUPACODE_DEVELOPER_DIR)),)
export DEVELOPER_DIR := $(SUPACODE_DEVELOPER_DIR)
endif

# Pretty-print xcodebuild output with xcbeautify when it's available (via mise
# or on PATH), otherwise pass the raw log through `cat`. xcbeautify is not
# declared in mise.toml, so the build must not hard-depend on it.
ifneq ($(shell mise which xcbeautify 2>/dev/null),)
XCBEAUTIFY := mise exec -- xcbeautify --disable-logging
XCBEAUTIFY_QUIET := mise exec -- xcbeautify --quiet --disable-logging
else ifneq ($(shell command -v xcbeautify 2>/dev/null),)
XCBEAUTIFY := xcbeautify --disable-logging
XCBEAUTIFY_QUIET := xcbeautify --quiet --disable-logging
else
XCBEAUTIFY := cat
XCBEAUTIFY_QUIET := cat
endif

.DEFAULT_GOAL := help
.PHONY: build-ghostty-xcframework build-zmx generate-project generate-project-sources inspect-dependencies warm-cache build-app run-app install-dev-build archive export-archive format lint check test bump-version bump-and-release log-stream

Expand Down Expand Up @@ -82,7 +109,7 @@ warm-cache: $(TUIST_INSTALL_STAMP) # Warm the full Tuist cacheable graph
mise exec -- tuist cache warm --configuration $(TUIST_CACHE_CONFIGURATION)

build-app: $(TUIST_DEVELOPMENT_GENERATION_STAMP) # Build the macOS app (Debug)
bash -o pipefail -c 'xcodebuild -workspace "$(PROJECT_WORKSPACE)" -scheme "$(APP_SCHEME)" -configuration Debug build -skipMacroValidation 2>&1 | mise exec -- xcbeautify --disable-logging'
bash -o pipefail -c 'xcodebuild -workspace "$(PROJECT_WORKSPACE)" -scheme "$(APP_SCHEME)" -configuration Debug build -skipMacroValidation 2>&1 | $(XCBEAUTIFY)'

run-app: build-app # Build then launch (Debug) with log streaming
@settings="$$(xcodebuild -workspace "$(PROJECT_WORKSPACE)" -scheme "$(APP_SCHEME)" -configuration Debug -showBuildSettings -json 2>/dev/null)"; \
Expand All @@ -108,14 +135,14 @@ install-dev-build: build-app # install dev build to /Applications

archive: $(TUIST_SOURCE_RELEASE_GENERATION_STAMP) # Archive Release build for distribution
mkdir -p build
bash -o pipefail -c 'xcodebuild -workspace "$(PROJECT_WORKSPACE)" -scheme "$(APP_SCHEME)" -configuration Release -destination "generic/platform=macOS" -archivePath build/supacode.xcarchive archive CODE_SIGN_STYLE=Manual DEVELOPMENT_TEAM="$$APPLE_TEAM_ID" CODE_SIGN_IDENTITY="$$DEVELOPER_ID_IDENTITY_SHA" OTHER_CODE_SIGN_FLAGS="--timestamp" -skipMacroValidation $(XCODEBUILD_FLAGS) 2>&1 | mise exec -- xcbeautify --quiet --disable-logging'
bash -o pipefail -c 'xcodebuild -workspace "$(PROJECT_WORKSPACE)" -scheme "$(APP_SCHEME)" -configuration Release -destination "generic/platform=macOS" -archivePath build/supacode.xcarchive archive CODE_SIGN_STYLE=Manual DEVELOPMENT_TEAM="$$APPLE_TEAM_ID" CODE_SIGN_IDENTITY="$$DEVELOPER_ID_IDENTITY_SHA" OTHER_CODE_SIGN_FLAGS="--timestamp" -skipMacroValidation $(XCODEBUILD_FLAGS) 2>&1 | $(XCBEAUTIFY_QUIET)'

export-archive: # Export xarchive
bash -o pipefail -c 'xcodebuild -exportArchive -archivePath build/supacode.xcarchive -exportPath build/export -exportOptionsPlist build/ExportOptions.plist 2>&1 | mise exec -- xcbeautify --quiet --disable-logging'
bash -o pipefail -c 'xcodebuild -exportArchive -archivePath build/supacode.xcarchive -exportPath build/export -exportOptionsPlist build/ExportOptions.plist 2>&1 | $(XCBEAUTIFY_QUIET)'

test: $(TUIST_DEVELOPMENT_GENERATION_STAMP) # Run all tests
@if [ -t 1 ]; then \
bash -o pipefail -c 'xcodebuild test -workspace "$(PROJECT_WORKSPACE)" -scheme "$(APP_SCHEME)" -destination "platform=macOS" CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY="" -skipMacroValidation -parallel-testing-enabled NO 2>&1 | mise exec -- xcbeautify --disable-logging'; \
bash -o pipefail -c 'xcodebuild test -workspace "$(PROJECT_WORKSPACE)" -scheme "$(APP_SCHEME)" -destination "platform=macOS" CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY="" -skipMacroValidation -parallel-testing-enabled NO 2>&1 | $(XCBEAUTIFY)'; \
else \
xcodebuild test -workspace "$(PROJECT_WORKSPACE)" -scheme "$(APP_SCHEME)" -destination "platform=macOS" CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY="" -skipMacroValidation -parallel-testing-enabled NO; \
fi
Expand Down
4 changes: 4 additions & 0 deletions SupacodeSettingsFeature/Reducer/SettingsFeature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public struct SettingsFeature {
public var analyticsEnabled: Bool
public var crashReportsEnabled: Bool
public var githubIntegrationEnabled: Bool
public var gitlabIntegrationEnabled: Bool
public var deleteBranchOnDeleteWorktree: Bool
public var mergedWorktreeAction: MergedWorktreeAction?
public var promptForWorktreeCreation: Bool
Expand Down Expand Up @@ -95,6 +96,7 @@ public struct SettingsFeature {
analyticsEnabled = settings.analyticsEnabled
crashReportsEnabled = settings.crashReportsEnabled
githubIntegrationEnabled = settings.githubIntegrationEnabled
gitlabIntegrationEnabled = settings.gitlabIntegrationEnabled
deleteBranchOnDeleteWorktree = settings.deleteBranchOnDeleteWorktree
mergedWorktreeAction = settings.mergedWorktreeAction
promptForWorktreeCreation = settings.promptForWorktreeCreation
Expand Down Expand Up @@ -131,6 +133,7 @@ public struct SettingsFeature {
analyticsEnabled: analyticsEnabled,
crashReportsEnabled: crashReportsEnabled,
githubIntegrationEnabled: githubIntegrationEnabled,
gitlabIntegrationEnabled: gitlabIntegrationEnabled,
deleteBranchOnDeleteWorktree: deleteBranchOnDeleteWorktree,
mergedWorktreeAction: mergedWorktreeAction,
promptForWorktreeCreation: promptForWorktreeCreation,
Expand Down Expand Up @@ -271,6 +274,7 @@ public struct SettingsFeature {
state.analyticsEnabled = normalizedSettings.analyticsEnabled
state.crashReportsEnabled = normalizedSettings.crashReportsEnabled
state.githubIntegrationEnabled = normalizedSettings.githubIntegrationEnabled
state.gitlabIntegrationEnabled = normalizedSettings.gitlabIntegrationEnabled
state.deleteBranchOnDeleteWorktree = normalizedSettings.deleteBranchOnDeleteWorktree
state.mergedWorktreeAction = normalizedSettings.mergedWorktreeAction
state.promptForWorktreeCreation = normalizedSettings.promptForWorktreeCreation
Expand Down
2 changes: 1 addition & 1 deletion SupacodeSettingsFeature/Views/SettingsSection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public enum SettingsSection: Hashable {
case developer
case shortcuts
case updates
case github
case forges
case scripts
case repository(String)
case repositoryScripts(String)
Expand Down
7 changes: 7 additions & 0 deletions SupacodeSettingsShared/Models/GlobalSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public nonisolated struct GlobalSettings: Codable, Equatable, Sendable {
public var analyticsEnabled: Bool
public var crashReportsEnabled: Bool
public var githubIntegrationEnabled: Bool
public var gitlabIntegrationEnabled: Bool
public var deleteBranchOnDeleteWorktree: Bool
public var mergedWorktreeAction: MergedWorktreeAction?
public var promptForWorktreeCreation: Bool
Expand Down Expand Up @@ -80,6 +81,7 @@ public nonisolated struct GlobalSettings: Codable, Equatable, Sendable {
analyticsEnabled: true,
crashReportsEnabled: true,
githubIntegrationEnabled: true,
gitlabIntegrationEnabled: true,
deleteBranchOnDeleteWorktree: true,
mergedWorktreeAction: nil,
promptForWorktreeCreation: true,
Expand Down Expand Up @@ -114,6 +116,7 @@ public nonisolated struct GlobalSettings: Codable, Equatable, Sendable {
analyticsEnabled: Bool,
crashReportsEnabled: Bool,
githubIntegrationEnabled: Bool,
gitlabIntegrationEnabled: Bool = true,
deleteBranchOnDeleteWorktree: Bool,
mergedWorktreeAction: MergedWorktreeAction? = nil,
promptForWorktreeCreation: Bool,
Expand Down Expand Up @@ -146,6 +149,7 @@ public nonisolated struct GlobalSettings: Codable, Equatable, Sendable {
self.analyticsEnabled = analyticsEnabled
self.crashReportsEnabled = crashReportsEnabled
self.githubIntegrationEnabled = githubIntegrationEnabled
self.gitlabIntegrationEnabled = gitlabIntegrationEnabled
self.deleteBranchOnDeleteWorktree = deleteBranchOnDeleteWorktree
self.mergedWorktreeAction = mergedWorktreeAction
self.promptForWorktreeCreation = promptForWorktreeCreation
Expand Down Expand Up @@ -210,6 +214,9 @@ public nonisolated struct GlobalSettings: Codable, Equatable, Sendable {
githubIntegrationEnabled =
try container.decodeIfPresent(Bool.self, forKey: .githubIntegrationEnabled)
?? Self.default.githubIntegrationEnabled
gitlabIntegrationEnabled =
try container.decodeIfPresent(Bool.self, forKey: .gitlabIntegrationEnabled)
?? Self.default.gitlabIntegrationEnabled
deleteBranchOnDeleteWorktree =
try container.decodeIfPresent(Bool.self, forKey: .deleteBranchOnDeleteWorktree)
?? Self.default.deleteBranchOnDeleteWorktree
Expand Down
14 changes: 14 additions & 0 deletions scripts/build-ghostty.sh
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,20 @@ if [ -f "${ghostty_fingerprint_path}" ] &&
fi

cd "${ghostty_dir}"

# macOS 26.4+ SDKs dropped plain `arm64-macos` from libSystem.tbd, leaving only
# `arm64e-macos`. Zig 0.15.2's Mach-O linker won't match the two, so every
# libSystem symbol comes up undefined when it compiles its own build runner
# (ziglang/zig#31658, fixed in 0.16 but Ghostty pins minimum 0.15.2). Build
# against an Xcode whose SDK still advertises arm64-macos. The dir must be a
# *full* Xcode (Command Line Tools lack the iOS SDK and the `metal` compiler the
# xcframework build needs). Respect a DEVELOPER_DIR the caller already chose
# (the Makefile exports one); only fall back to Xcode 26.3 when nothing is set,
# e.g. when this script is run directly. No-op when 26.3 is absent.
if [ -z "${DEVELOPER_DIR:-}" ] && [ -d "/Applications/Xcode_26.3.app/Contents/Developer" ]; then
export DEVELOPER_DIR="/Applications/Xcode_26.3.app/Contents/Developer"
fi

mise exec -- zig build -Doptimize=ReleaseFast -Demit-xcframework=true -Dsentry=false --prefix "${ghostty_build_root}" --cache-dir "${ghostty_local_cache_dir}" --global-cache-dir "${ghostty_global_cache_dir}"
rsync -a --delete "${ghostty_dir}/macos/GhosttyKit.xcframework/" "${xcframework_path}/"
prepare_xcframework
Expand Down
9 changes: 9 additions & 0 deletions scripts/build-zmx.sh
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@ fi

cd "${zmx_dir}"

# zig compiles its build runner for the native target, which won't link against
# macOS 26.4+ SDKs (arm64e-only libSystem.tbd; ziglang/zig#31658). Respect a
# DEVELOPER_DIR the caller already chose (the Makefile exports one); only fall
# back to Xcode 26.3 — whose SDK still has arm64-macos — when nothing is set,
# e.g. when this script is run directly. No-op when 26.3 is absent.
if [ -z "${DEVELOPER_DIR:-}" ] && [ -d "/Applications/Xcode_26.3.app/Contents/Developer" ]; then
export DEVELOPER_DIR="/Applications/Xcode_26.3.app/Contents/Developer"
fi

slice_paths=()
for target in "${zmx_targets[@]}"; do
slice_prefix="${zmx_build_root}/slices/${target}"
Expand Down
17 changes: 17 additions & 0 deletions supacode/Clients/Forge/Forge.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Foundation

nonisolated enum Forge: String, Equatable, Hashable, Sendable, Codable, CaseIterable {
case github
case gitlab

static func detect(host: String) -> Forge? {
let normalized = host.lowercased()
if normalized.contains("github") {
return .github
}
if normalized.contains("gitlab") {
return .gitlab
}
return nil
}
}
130 changes: 130 additions & 0 deletions supacode/Clients/Forge/ForgePullRequest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import Foundation

// Forge-tagged pull/merge request. Views and reducers that don't care which forge the request came
// from read the shared projection (number, title, isDraft, ...). Views that need forge-specific
// fields pull out the underlying case via `.github` / `.gitlab`.
nonisolated enum ForgePullRequest: Equatable, Hashable, Sendable {
case github(GithubPullRequest)
case gitlab(GitLabMergeRequest)
}

extension ForgePullRequest {
var forge: Forge {
switch self {
case .github: return .github
case .gitlab: return .gitlab
}
}

// GitHub: PR number. GitLab: MR iid.
var number: Int {
switch self {
case .github(let pr): return pr.number
case .gitlab(let mr): return mr.iid
}
}

var title: String {
switch self {
case .github(let pr): return pr.title
case .gitlab(let mr): return mr.title
}
}

var url: String {
switch self {
case .github(let pr): return pr.url
case .gitlab(let mr): return mr.url
}
}

var isDraft: Bool {
switch self {
case .github(let pr): return pr.isDraft
case .gitlab(let mr): return mr.isDraft
}
}

var additions: Int {
switch self {
case .github(let pr): return pr.additions
case .gitlab(let mr): return mr.additions
}
}

var deletions: Int {
switch self {
case .github(let pr): return pr.deletions
case .gitlab(let mr): return mr.deletions
}
}

var updatedAt: Date? {
switch self {
case .github(let pr): return pr.updatedAt
case .gitlab(let mr): return mr.updatedAt
}
}

var headRefName: String? {
switch self {
case .github(let pr): return pr.headRefName
case .gitlab(let mr): return mr.sourceBranch
}
}

var baseRefName: String? {
switch self {
case .github(let pr): return pr.baseRefName
case .gitlab(let mr): return mr.targetBranch
}
}

var authorLogin: String? {
switch self {
case .github(let pr): return pr.authorLogin
case .gitlab(let mr): return mr.authorUsername
}
}

var isMerged: Bool {
switch self {
case .github(let pr): return pr.state == "MERGED"
case .gitlab(let mr): return mr.state == .merged
}
}

var isOpen: Bool {
switch self {
case .github(let pr): return pr.state == "OPEN"
case .gitlab(let mr): return mr.state == .opened
}
}

// Uppercase state string compatible with the existing `PullRequestBadgeStyle.style(state:...)`
// vocabulary ("OPEN" / "MERGED" / "CLOSED"). GitLab states are mapped to the nearest GitHub
// equivalent so the badge color picker keeps working without per-forge branching.
var displayStateBadge: String? {
switch self {
case .github(let pr):
return pr.state.uppercased()
case .gitlab(let mr):
switch mr.state {
case .opened: return "OPEN"
case .merged: return "MERGED"
case .closed, .locked: return "CLOSED"
case .unknown: return nil
}
}
}

var github: GithubPullRequest? {
if case .github(let pr) = self { return pr }
return nil
}

var gitlab: GitLabMergeRequest? {
if case .gitlab(let mr) = self { return mr }
return nil
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Foundation

struct GithubRemoteInfo: Equatable, Sendable {
struct ForgeRemoteInfo: Equatable, Sendable {
let forge: Forge
let host: String
let owner: String
let repo: String
Expand Down
Loading