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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Scripts/check_ci_release_workflows.rb
Original file line number Diff line number Diff line change
Expand Up @@ -475,9 +475,9 @@

releasing_docs = File.file?(releasing_docs_path) ? File.read(releasing_docs_path) : ""
check.call(releasing_docs.include?("environment protection rule must allow only protected tags matching `v*`"), "Release guide must require exact environment tag restrictions")
check.call(releasing_docs.include?("`workflow_dispatch` reruns must use `--ref v4.0.5b45`"), "Release guide must document tag-ref manual dispatch")
check.call(releasing_docs.include?("`workflow_dispatch` reruns must use `--ref v4.0.5b46`"), "Release guide must document tag-ref manual dispatch")
check.call(
releasing_docs.include?("gh workflow run appcast.yml --ref v4.0.5b45 -f tag=v4.0.5b45"),
releasing_docs.include?("gh workflow run appcast.yml --ref v4.0.5b46 -f tag=v4.0.5b46"),
"Release guide must document exact tag-bound appcast dispatch"
)
check.call(
Expand Down
21 changes: 15 additions & 6 deletions Scripts/check_fork_identity.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ readonly bug_template="$repo_root/.github/ISSUE_TEMPLATE/bug_report.md"
readonly feature_template="$repo_root/.github/ISSUE_TEMPLATE/feature_request.md"
readonly release_drafter="$repo_root/.github/release-drafter.yml"
readonly app_source="$repo_root/Xcodes/XcodesApp.swift"
readonly environment_source="$repo_root/Xcodes/Backend/Environment.swift"
readonly about_source="$repo_root/Xcodes/Frontend/About/AboutView.swift"
readonly bottom_status_source="$repo_root/Xcodes/Frontend/XcodeList/BottomStatusBar.swift"
readonly main_window_source="$repo_root/Xcodes/Frontend/MainWindow.swift"
readonly updates_source="$repo_root/Xcodes/Frontend/Preferences/UpdatesPreferencePane.swift"
readonly appcast_config="$repo_root/AppCast/_config.yml"
readonly appcast_template="$repo_root/AppCast/_includes/appcast.inc"
Expand All @@ -43,7 +44,7 @@ readonly tests_id="dev.jacobcx.Xcodes.Tests"
readonly helper_id="dev.jacobcx.Xcodes.Helper"
readonly team_id="K2648T24P4"
readonly marketing_version="4.0.5"
readonly build_number="45"
readonly build_number="46"
readonly app_copyright="Fork contributions © 2026 JacobCXDev. Upstream contributors retain their copyrights."
# shellcheck disable=SC2016 # Xcode expands this build-setting literal, not the shell.
readonly app_requirement='identifier "dev.jacobcx.Xcodes" and info [CFBundleShortVersionString] >= "1.0.0" and anchor apple generic and certificate leaf[subject.OU] = "$(CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT)"'
Expand Down Expand Up @@ -111,6 +112,7 @@ for required_file in \
"$uninstall_script" \
"$app_info_plist" \
"$helper_scheme" \
"$environment_source" \
"$updates_source" \
"$appcast_config" \
"$appcast_template" \
Expand Down Expand Up @@ -145,7 +147,7 @@ for ownership_file in \
"$release_drafter" \
"$app_source" \
"$about_source" \
"$bottom_status_source"; do
"$main_window_source"; do
require_file "$ownership_file"
done

Expand All @@ -162,6 +164,7 @@ require_literal "jacobcxdev/<topic>" "$contributing"
require_trimmed_line 'let xcodesRepoURL = URL(string: "https://github.com/jacobcxdev/XcodesApp/")!' "$app_source"
require_trimmed_line 'let bugReportURL = URL(string: "https://github.com/jacobcxdev/XcodesApp/issues/new?assignees=&labels=bug&template=bug_report.md&title=")!' "$app_source"
require_trimmed_line 'let featureRequestURL = URL(string: "https://github.com/jacobcxdev/XcodesApp/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=")!' "$app_source"
require_trimmed_line 'static let service = "dev.jacobcx.Xcodes.apple-account"' "$environment_source"
require_trimmed_line 'openURL(URL(string: "https://github.com/jacobcxdev/XcodesApp/")!)' "$about_source"
require_trimmed_line 'HStack(alignment: .top, spacing: 24) {' "$about_source"
require_trimmed_line 'VStack(alignment: .leading, spacing: 16) {' "$about_source"
Expand All @@ -173,8 +176,14 @@ require_literal "https://github.com/jacobcxdev/XcodesApp/issues" "$feature_templ
require_literal "* @jacobcxdev" "$codeowners"
require_literal "Copyright (c) 2026 Jacob Clayden" "$license"
require_literal "docs/RELEASING.md" "$readme"

if [[ -e "$repo_root/Xcodes/Frontend/XcodeList/BottomStatusBar.swift" ]] \
|| grep -n -F -- '.bottomStatusBar()' "$main_window_source" \
|| grep -n -F -- 'BottomStatusBar.swift' "$project_file"; then
fail "Obsolete main-window footer remains"
fi
require_literal "DEVELOPER_ID_APPLICATION_P12_BASE64" "$release_documentation"
require_literal "v4.0.5b45" "$release_documentation"
require_literal "v4.0.5b46" "$release_documentation"

if grep -n -F -- 'Jacob Clayden' "$readme" "$about_source" "$app_info_plist"; then
fail "Legal name leaked into public-facing fork branding"
Expand Down Expand Up @@ -223,12 +232,12 @@ fi
if grep -R -n -i -E -- \
'github\.com/(XcodesOrg|RobotsAndPencils)/XcodesApp|github\.com/robotsandpencils/xcodesapp|opencollective\.com/xcodesapp' \
"$codeowners" "$bug_template" "$feature_template" \
"$release_drafter" "$app_source" "$about_source" "$bottom_status_source"; then
"$release_drafter" "$app_source" "$about_source" "$main_window_source"; then
fail "Upstream-owned operational link remains in fork-facing metadata"
fi

if grep -n -E -- 'Support\.Xcodes|heart\.circle|opencollective\.com/xcodesapp|github\.com/jacobcxdev/XcodesApp/issues' \
"$about_source" "$bottom_status_source"; then
"$about_source" "$main_window_source"; then
fail "Donation-labelled support control remains without a fork donation destination"
fi

Expand Down
2 changes: 1 addition & 1 deletion Scripts/test_ci_release_workflows.sh
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ mutate_and_reject publish_unexpected_control \
mutate_and_reject missing_release_drafter_timeout \
'path = File.join(ARGV.fetch(0), ".github/workflows/release-drafter.yml"); data = YAML.safe_load_file(path, aliases: false); data["jobs"]["update_release_draft"].delete("timeout-minutes"); File.write(path, YAML.dump(data) + "# bounded timeout\n")'
mutate_and_reject missing_appcast_dispatch_docs \
'path = File.join(ARGV.fetch(0), "docs/RELEASING.md"); text = File.read(path).sub("gh workflow run appcast.yml --ref v4.0.5b45 -f tag=v4.0.5b45", "gh workflow run appcast.yml --ref main -f tag=latest"); File.write(path, text)'
'path = File.join(ARGV.fetch(0), "docs/RELEASING.md"); text = File.read(path).sub("gh workflow run appcast.yml --ref v4.0.5b46 -f tag=v4.0.5b46", "gh workflow run appcast.yml --ref main -f tag=latest"); File.write(path, text)'
# shellcheck disable=SC2016 # Documentation code spans must remain literal in the mutation.
mutate_and_reject missing_reusable_ref_docs \
'path = File.join(ARGV.fetch(0), "docs/RELEASING.md"); text = File.read(path).sub("Reusable workflows receive the caller'\''s `github.ref`; the appcast build requires that ref to equal `refs/tags/<tag>`", "Reusable workflows are called after release publication"); File.write(path, text)'
Expand Down
27 changes: 23 additions & 4 deletions Scripts/test_fork_identity_guard.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ mkdir -p \
"$fixture_root/AppCast/_includes" \
"$fixture_root/AppCast/_plugins" \
"$fixture_root/Xcodes/Frontend/About" \
"$fixture_root/Xcodes/Backend" \
"$fixture_root/Xcodes/Frontend/Preferences" \
"$fixture_root/Xcodes/Frontend/XcodeList" \
"$fixture_root/Xcodes/Resources" \
"$fixture_root/docs" \
"$fixture_root/Scripts"
Expand Down Expand Up @@ -76,13 +76,12 @@ cp \
cp "$repo_root/AppCast/_includes/appcast.inc" "$fixture_root/AppCast/_includes/"
cp "$repo_root/AppCast/_plugins/signature_filter.rb" "$fixture_root/AppCast/_plugins/"
cp "$repo_root/Xcodes/XcodesApp.swift" "$fixture_root/Xcodes/"
cp "$repo_root/Xcodes/Frontend/MainWindow.swift" "$fixture_root/Xcodes/Frontend/"
cp "$repo_root/Xcodes/Frontend/About/AboutView.swift" "$fixture_root/Xcodes/Frontend/About/"
cp \
"$repo_root/Xcodes/Frontend/Preferences/UpdatesPreferencePane.swift" \
"$fixture_root/Xcodes/Frontend/Preferences/"
cp \
"$repo_root/Xcodes/Frontend/XcodeList/BottomStatusBar.swift" \
"$fixture_root/Xcodes/Frontend/XcodeList/"
cp "$repo_root/Xcodes/Backend/Environment.swift" "$fixture_root/Xcodes/Backend/"
cp "$repo_root/Xcodes/Resources/Info.plist" "$fixture_root/Xcodes/Resources/Info.plist"

"$fixture_root/Scripts/check_fork_identity.sh" >/dev/null
Expand Down Expand Up @@ -141,4 +140,24 @@ if "$fixture_root/Scripts/check_fork_identity.sh" >/dev/null 2>&1; then
exit 1
fi

cp "$repo_root/Xcodes/Frontend/About/AboutView.swift" "$fixture_root/Xcodes/Frontend/About/"
perl -0pi -e \
's/dev\.jacobcx\.Xcodes\.apple-account/dev.jacobcx.Xcodes/' \
"$fixture_root/Xcodes/Backend/Environment.swift"

if "$fixture_root/Scripts/check_fork_identity.sh" >/dev/null 2>&1; then
echo "Identity guard accepted generic app Keychain service" >&2
exit 1
fi

cp "$repo_root/Xcodes/Backend/Environment.swift" "$fixture_root/Xcodes/Backend/"
perl -0pi -e \
's/\.padding\(\[\.top\], 0\)/.bottomStatusBar()\n .padding([.top], 0)/' \
"$fixture_root/Xcodes/Frontend/MainWindow.swift"

if "$fixture_root/Scripts/check_fork_identity.sh" >/dev/null 2>&1; then
echo "Identity guard accepted obsolete main-window footer" >&2
exit 1
fi

echo "Fork identity guard mutation test passed"
10 changes: 3 additions & 7 deletions Xcodes.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@
E8C0EB1C291EF9A10081528A /* AppState+Runtimes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8C0EB1B291EF9A10081528A /* AppState+Runtimes.swift */; };
E8CBDB8927ADE32300B22292 /* unxip in Copy aria2c */ = {isa = PBXBuildFile; fileRef = E8CBDB8627ADD92000B22292 /* unxip */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
E8CBDB8B27AE02FF00B22292 /* ExperiementsPreferencePane.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8CBDB8A27AE02FF00B22292 /* ExperiementsPreferencePane.swift */; };
E8D0296F284B029800647641 /* BottomStatusBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8D0296E284B029800647641 /* BottomStatusBar.swift */; };
E8D655C0288DD04700A139C2 /* SelectedActionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8D655BF288DD04700A139C2 /* SelectedActionType.swift */; };
E8DA461125FAF7FB002E85EF /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8DA461025FAF7FB002E85EF /* NotificationsView.swift */; };
E8E98A9025D8631800EC89A0 /* InstallationStepRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFBC3FF259AC17F00E2A3D8 /* InstallationStepRowView.swift */; };
Expand Down Expand Up @@ -310,7 +309,6 @@
E8C0EB1B291EF9A10081528A /* AppState+Runtimes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppState+Runtimes.swift"; sourceTree = "<group>"; };
E8CBDB8627ADD92000B22292 /* unxip */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = unxip; sourceTree = "<group>"; };
E8CBDB8A27AE02FF00B22292 /* ExperiementsPreferencePane.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExperiementsPreferencePane.swift; sourceTree = "<group>"; };
E8D0296E284B029800647641 /* BottomStatusBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomStatusBar.swift; sourceTree = "<group>"; };
E8D655BF288DD04700A139C2 /* SelectedActionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedActionType.swift; sourceTree = "<group>"; };
E8DA461025FAF7FB002E85EF /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = "<group>"; };
E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallationStepDetailView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -462,7 +460,6 @@
CAE42486259A68A300B8B246 /* XcodeListCategory.swift */,
CAD2E7A32449574E00113D76 /* XcodeListView.swift */,
CAFFFED7259CDA5000903F81 /* XcodeListViewRow.swift */,
E8D0296E284B029800647641 /* BottomStatusBar.swift */,
);
path = XcodeList;
sourceTree = "<group>";
Expand Down Expand Up @@ -950,7 +947,6 @@
E8977EA325C11E1500835F80 /* PreferencesView.swift in Sources */,
CA9FF87B2595293E00E47BAF /* DataSource.swift in Sources */,
CABFAA432593104F00380FEE /* AboutView.swift in Sources */,
E8D0296F284B029800647641 /* BottomStatusBar.swift in Sources */,
E8C0EB1C291EF9A10081528A /* AppState+Runtimes.swift in Sources */,
E8E98A9025D8631800EC89A0 /* InstallationStepRowView.swift in Sources */,
CABFA9CC2592EEEA00380FEE /* Path+.swift in Sources */,
Expand Down Expand Up @@ -1064,7 +1060,7 @@
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 45;
CURRENT_PROJECT_VERSION = 46;
DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\"";
DEVELOPMENT_TEAM = K2648T24P4;
ENABLE_HARDENED_RUNTIME = NO;
Expand Down Expand Up @@ -1317,7 +1313,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 45;
CURRENT_PROJECT_VERSION = 46;
DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\"";
DEVELOPMENT_TEAM = K2648T24P4;
ENABLE_HARDENED_RUNTIME = YES;
Expand Down Expand Up @@ -1346,7 +1342,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 45;
CURRENT_PROJECT_VERSION = 46;
DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\"";
DEVELOPMENT_TEAM = K2648T24P4;
ENABLE_HARDENED_RUNTIME = YES;
Expand Down
11 changes: 0 additions & 11 deletions Xcodes/Backend/AppState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -353,17 +353,6 @@ class AppState: ObservableObject {
savedUsername != nil
}

var bottomStatusBarMessage: String {
let formatter = DateFormatter()
formatter.dateFormat = "dd/MM/yyyy"
let finishDate = formatter.date(from: "11/06/2022")

if Date().compare(finishDate!) == .orderedAscending {
return String(format: localizeString("WWDC.Message"), "2022")
}
return ""
}

// MARK: - Init

init(runtimeService: RuntimeService = RuntimeService()) {
Expand Down
4 changes: 3 additions & 1 deletion Xcodes/Backend/Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,10 @@ public struct Network: Sendable {
}

public struct Keychain: Sendable {
static let service = "dev.jacobcx.Xcodes.apple-account"

private static var keychain: KeychainAccess.Keychain {
KeychainAccess.Keychain(service: "dev.jacobcx.Xcodes")
KeychainAccess.Keychain(service: service)
}

public var getString: @Sendable (String) throws -> String? = { try keychain.getString($0) }
Expand Down
1 change: 0 additions & 1 deletion Xcodes/Frontend/MainWindow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ struct MainWindow: View {
}
}
}
.bottomStatusBar()
.padding([.top], 0)
.navigationSubtitle(subtitleText)
.frame(minWidth: 600, maxWidth: .infinity, minHeight: 300, maxHeight: .infinity)
Expand Down
55 changes: 0 additions & 55 deletions Xcodes/Frontend/XcodeList/BottomStatusBar.swift

This file was deleted.

4 changes: 4 additions & 0 deletions XcodesTests/AppStateTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ class AppStateTests: XCTestCase {
subject = AppState()
}

func test_KeychainUsesPurposeSpecificAppleAccountService() {
XCTAssertEqual(Keychain.service, "dev.jacobcx.Xcodes.apple-account")
}

func test_AutoInstallWaitsForInitialInstalledXcodeScan() {
Current.defaults.get = { key in
key == "autoInstallation" ? AutoInstallationType.newestBeta.rawValue : nil
Expand Down
14 changes: 7 additions & 7 deletions docs/RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ Configure GitHub Actions to allow selected pinned actions. Protect the `v*` tag
3. Commit the version change. Create an annotated tag whose values exactly match the project, for example:

```sh
git tag -a v4.0.5b45 -m 'Xcodes 4.0.5 build 45'
git push origin v4.0.5b45
git tag -a v4.0.5b46 -m 'Xcodes 4.0.5 build 46'
git push origin v4.0.5b46
```

4. Approve the protected `release` environment deployment after confirming the tag and commit.
Expand All @@ -56,16 +56,16 @@ Configure GitHub Actions to allow selected pinned actions. Protect the `v*` tag

Tags using this contract are stable releases. The workflow does not infer prerelease status from the build-number suffix. Add an explicit, reviewed tag grammar and matching appcast policy before publishing prereleases.

For a manual rerun, `workflow_dispatch` reruns must use `--ref v4.0.5b45` and the same `release_tag`; selecting a branch is rejected before credential files are written:
For a manual rerun, `workflow_dispatch` reruns must use `--ref v4.0.5b46` and the same `release_tag`; selecting a branch is rejected before credential files are written:

```sh
gh workflow run release.yml --ref v4.0.5b45 -f release_tag=v4.0.5b45
gh workflow run release.yml --ref v4.0.5b46 -f release_tag=v4.0.5b46
```

A manual appcast rerun must likewise use the published tag for both the workflow ref and input (`--ref <tag> -f tag=<same tag>`):

```sh
gh workflow run appcast.yml --ref v4.0.5b45 -f tag=v4.0.5b45
gh workflow run appcast.yml --ref v4.0.5b46 -f tag=v4.0.5b46
```

## Local packaging dry run
Expand All @@ -77,8 +77,8 @@ export NOTARY_KEY_ID='<key identifier>'
export NOTARY_ISSUER_ID='<issuer UUID>'
export NOTARY_KEY_PATH='/absolute/path/AuthKey_ID.p8'
export SPARKLE_PRIVATE_KEY_FILE='/absolute/path/sparkle-private-key'
bash Scripts/package_release.sh v4.0.5b45
bash Scripts/validate_release_artifacts.sh Product/v4.0.5b45 v4.0.5b45
bash Scripts/package_release.sh v4.0.5b46
bash Scripts/validate_release_artifacts.sh Product/v4.0.5b46 v4.0.5b46
```

Do not paste secret values into shell history on shared machines. Prefer a local secret manager or a short-lived protected shell environment.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ Expected: compile failure because `ForkPreferenceMigration` does not exist.

Add `ForkPreferenceMigration` with marker `dev.jacobcx.Xcodes.preferenceMigrationVersion`, version `1`, and explicit keys from `PreferenceKey` plus `terminateAfterLastWindowClosed`. Exclude `username` and unknown values. Existing destination values win. When no `localPath` exists and legacy support exists, use `~/Library/Application Support/com.robotsandpencils.XcodesApp`.

Call migration at start of production `AppState.init` before loading caches. Change Keychain service to `dev.jacobcx.Xcodes`. Fresh defaults become:
Call migration at start of production `AppState.init` before loading caches. Change Apple-account Keychain service to `dev.jacobcx.Xcodes.apple-account`. Fresh defaults become:

```swift
Path.applicationSupport/"dev.jacobcx.Xcodes"
Expand Down
Loading
Loading