From 1f3dcd3663b8b1c3bd3f7b82cc2297e1fd777467 Mon Sep 17 00:00:00 2001 From: Jacob Clayden Date: Tue, 11 Aug 2026 18:14:39 +0100 Subject: [PATCH] fix: persisted Apple account login - separated Apple credentials into a purpose-specific Keychain service - removed obsolete main-window version footer - advanced maintained build and locked regression contracts --- Scripts/check_ci_release_workflows.rb | 4 +- Scripts/check_fork_identity.sh | 21 +++++-- Scripts/test_ci_release_workflows.sh | 2 +- Scripts/test_fork_identity_guard.sh | 27 +++++++-- Xcodes.xcodeproj/project.pbxproj | 10 +--- Xcodes/Backend/AppState.swift | 11 ---- Xcodes/Backend/Environment.swift | 4 +- Xcodes/Frontend/MainWindow.swift | 1 - .../Frontend/XcodeList/BottomStatusBar.swift | 55 ------------------- XcodesTests/AppStateTests.swift | 4 ++ docs/RELEASING.md | 14 ++--- .../2026-08-10-owned-fork-productisation.md | 2 +- ...-08-10-owned-fork-productisation-design.md | 4 +- 13 files changed, 61 insertions(+), 98 deletions(-) delete mode 100644 Xcodes/Frontend/XcodeList/BottomStatusBar.swift diff --git a/Scripts/check_ci_release_workflows.rb b/Scripts/check_ci_release_workflows.rb index 893db134..ceebc805 100755 --- a/Scripts/check_ci_release_workflows.rb +++ b/Scripts/check_ci_release_workflows.rb @@ -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( diff --git a/Scripts/check_fork_identity.sh b/Scripts/check_fork_identity.sh index d87391b5..8cacbd88 100755 --- a/Scripts/check_fork_identity.sh +++ b/Scripts/check_fork_identity.sh @@ -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" @@ -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)"' @@ -111,6 +112,7 @@ for required_file in \ "$uninstall_script" \ "$app_info_plist" \ "$helper_scheme" \ + "$environment_source" \ "$updates_source" \ "$appcast_config" \ "$appcast_template" \ @@ -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 @@ -162,6 +164,7 @@ require_literal "jacobcxdev/" "$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" @@ -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" @@ -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 diff --git a/Scripts/test_ci_release_workflows.sh b/Scripts/test_ci_release_workflows.sh index f3b285be..51c835a7 100755 --- a/Scripts/test_ci_release_workflows.sh +++ b/Scripts/test_ci_release_workflows.sh @@ -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/`", "Reusable workflows are called after release publication"); File.write(path, text)' diff --git a/Scripts/test_fork_identity_guard.sh b/Scripts/test_fork_identity_guard.sh index b6db13e1..b583eebf 100755 --- a/Scripts/test_fork_identity_guard.sh +++ b/Scripts/test_fork_identity_guard.sh @@ -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" @@ -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 @@ -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" diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index 8516ddcb..5fd75d5b 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -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 */; }; @@ -310,7 +309,6 @@ E8C0EB1B291EF9A10081528A /* AppState+Runtimes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppState+Runtimes.swift"; sourceTree = ""; }; E8CBDB8627ADD92000B22292 /* unxip */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = unxip; sourceTree = ""; }; E8CBDB8A27AE02FF00B22292 /* ExperiementsPreferencePane.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExperiementsPreferencePane.swift; sourceTree = ""; }; - E8D0296E284B029800647641 /* BottomStatusBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomStatusBar.swift; sourceTree = ""; }; E8D655BF288DD04700A139C2 /* SelectedActionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedActionType.swift; sourceTree = ""; }; E8DA461025FAF7FB002E85EF /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = ""; }; E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallationStepDetailView.swift; sourceTree = ""; }; @@ -462,7 +460,6 @@ CAE42486259A68A300B8B246 /* XcodeListCategory.swift */, CAD2E7A32449574E00113D76 /* XcodeListView.swift */, CAFFFED7259CDA5000903F81 /* XcodeListViewRow.swift */, - E8D0296E284B029800647641 /* BottomStatusBar.swift */, ); path = XcodeList; sourceTree = ""; @@ -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 */, @@ -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; @@ -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; @@ -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; diff --git a/Xcodes/Backend/AppState.swift b/Xcodes/Backend/AppState.swift index 35112423..970da3c5 100644 --- a/Xcodes/Backend/AppState.swift +++ b/Xcodes/Backend/AppState.swift @@ -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()) { diff --git a/Xcodes/Backend/Environment.swift b/Xcodes/Backend/Environment.swift index 0651707d..49973225 100644 --- a/Xcodes/Backend/Environment.swift +++ b/Xcodes/Backend/Environment.swift @@ -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) } diff --git a/Xcodes/Frontend/MainWindow.swift b/Xcodes/Frontend/MainWindow.swift index 0de18b5c..7f8e16b0 100644 --- a/Xcodes/Frontend/MainWindow.swift +++ b/Xcodes/Frontend/MainWindow.swift @@ -65,7 +65,6 @@ struct MainWindow: View { } } } - .bottomStatusBar() .padding([.top], 0) .navigationSubtitle(subtitleText) .frame(minWidth: 600, maxWidth: .infinity, minHeight: 300, maxHeight: .infinity) diff --git a/Xcodes/Frontend/XcodeList/BottomStatusBar.swift b/Xcodes/Frontend/XcodeList/BottomStatusBar.swift deleted file mode 100644 index d657734f..00000000 --- a/Xcodes/Frontend/XcodeList/BottomStatusBar.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// BottomStatusBar.swift -// Xcodes -// -// Created by Matt Kiazyk on 2022-06-03. -// Copyright © 2022 Robots and Pencils. All rights reserved. -// - -import Foundation -import SwiftUI - -struct BottomStatusModifier: ViewModifier { - @EnvironmentObject var appState: AppState - - func body(content: Content) -> some View { - VStack(spacing: 0) { - content - VStack(spacing: 0) { - Divider() - HStack { - Text(appState.bottomStatusBarMessage) - .font(.subheadline) - Spacer() - Text(verbatim: "\(Bundle.main.shortVersion!) (\(Bundle.main.version!))") - .font(.subheadline) - } - .frame(maxWidth: .infinity, maxHeight: 30, alignment: .leading) - .padding([.leading, .trailing], 10) - } - .frame(maxWidth: .infinity, maxHeight: 30, alignment: .leading) - } - } -} - -extension View { - func bottomStatusBar() -> some View { - self.modifier( - BottomStatusModifier() - ) - } -} - -struct Previews_BottomStatusBar_Previews: PreviewProvider { - @MainActor - static var previews: some View { - HStack { - - } - .bottomStatusBar() - .environmentObject({ () -> AppState in - let a = AppState() - return a }() - ) - } -} diff --git a/XcodesTests/AppStateTests.swift b/XcodesTests/AppStateTests.swift index 79cfecb4..f928c4de 100644 --- a/XcodesTests/AppStateTests.swift +++ b/XcodesTests/AppStateTests.swift @@ -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 diff --git a/docs/RELEASING.md b/docs/RELEASING.md index ed0d365f..3260b195 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -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. @@ -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 -f 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 @@ -77,8 +77,8 @@ export NOTARY_KEY_ID='' export NOTARY_ISSUER_ID='' 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. diff --git a/docs/superpowers/plans/2026-08-10-owned-fork-productisation.md b/docs/superpowers/plans/2026-08-10-owned-fork-productisation.md index 674e4466..79aaab1e 100644 --- a/docs/superpowers/plans/2026-08-10-owned-fork-productisation.md +++ b/docs/superpowers/plans/2026-08-10-owned-fork-productisation.md @@ -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" diff --git a/docs/superpowers/specs/2026-08-10-owned-fork-productisation-design.md b/docs/superpowers/specs/2026-08-10-owned-fork-productisation-design.md index 6a673388..41a60387 100644 --- a/docs/superpowers/specs/2026-08-10-owned-fork-productisation-design.md +++ b/docs/superpowers/specs/2026-08-10-owned-fork-productisation-design.md @@ -20,7 +20,7 @@ Visible product remains `Xcodes` and installs as `/Applications/Xcodes.app`. For - application bundle ID: `dev.jacobcx.Xcodes` - privileged helper ID and Mach service: `dev.jacobcx.Xcodes.Helper` - test bundle ID: `dev.jacobcx.Xcodes.Tests` -- Keychain service: `dev.jacobcx.Xcodes` +- Apple-account Keychain service: `dev.jacobcx.Xcodes.apple-account` - application-support directory: `~/Library/Application Support/dev.jacobcx.Xcodes` - cache directory: `~/Library/Caches/dev.jacobcx.Xcodes` - Apple Developer Team ID: `K2648T24P4` @@ -34,7 +34,7 @@ First launch under fork bundle ID performs an idempotent, allowlisted migration Migrated values are non-secret preferences only: install/cache paths, download/data-source choices, runtime and list presentation, selection actions, update preferences, and automatic-install settings. `username`, cookies, Apple credentials, updater state, and arbitrary unknown keys are excluded. -If upstream has no saved `localPath` but its legacy application-support directory exists, migration records that directory as `localPath` so downloaded archives and metadata remain reusable. Fresh installs use fork-owned support and cache directories. New Keychain service starts empty, requiring Apple sign-in. Helper installation remains explicit because bundle ID, Mach service, and signing requirement changed. +If upstream has no saved `localPath` but its legacy application-support directory exists, migration records that directory as `localPath` so downloaded archives and metadata remain reusable. Fresh installs use fork-owned support and cache directories. Purpose-specific Apple-account Keychain service starts empty, requiring Apple sign-in, without reusing app-wide bundle identity as credential label. Helper installation remains explicit because bundle ID, Mach service, and signing requirement changed. Migration writes a versioned marker only after copied values are committed. Existing fork values win over legacy values. Failures leave marker unset so a later launch can retry.