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
10 changes: 0 additions & 10 deletions .claude/settings.local.json

This file was deleted.

19 changes: 19 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
build:
runs-on: macos-26
steps:
- uses: actions/checkout@v5

- name: Build
run: |
xcodebuild -project ActionBar.xcodeproj -scheme ActionBar -configuration Release \
-destination 'generic/platform=macOS' \
CODE_SIGNING_ALLOWED=NO \
build
163 changes: 163 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
name: Release

# Push a tag like `v2.0` to build, sign, notarize and publish a release.
# CHANGELOG.md must have a `## <version>` section; it becomes the release notes.
# Tags with a suffix (`v2.0-pre1`) are published as GitHub pre-releases and use
# the notes of the base version (`## 2.0`). Pre-releases never become "latest",
# so the public download link and update feed keep pointing at the last stable release.
#
# Required repository secrets:
# DEVELOPER_ID_CERT_P12 Base64-encoded "Developer ID Application" certificate (.p12)
# DEVELOPER_ID_CERT_PASSWORD Password for the .p12 file
# APPLE_ID Apple ID email used for notarization
# APPLE_APP_PASSWORD App-specific password for that Apple ID
# APPLE_TEAM_ID Apple Developer Team ID
# SPARKLE_PRIVATE_KEY Sparkle EdDSA private key (generate_keys -x)

on:
push:
tags: ['v*']

permissions:
contents: write

jobs:
release:
runs-on: macos-26
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0

- name: Set paths and version
run: |
{
echo "ARCHIVE_PATH=$RUNNER_TEMP/ActionBar.xcarchive"
echo "EXPORT_PATH=$RUNNER_TEMP/export"
echo "UPDATES_PATH=$RUNNER_TEMP/updates"
echo "SPM_PATH=$RUNNER_TEMP/spm"
echo "KEYCHAIN_PATH=$RUNNER_TEMP/signing.keychain-db"
} >> "$GITHUB_ENV"

VERSION="${GITHUB_REF_NAME#v}"
if ! [[ "$VERSION" =~ ^[0-9]+(\.[0-9]+)*(-[0-9A-Za-z.]+)?$ ]]; then
echo "Tag must look like v1.2, v1.2.3 or v1.2-pre1, got $GITHUB_REF_NAME" >&2
exit 1
fi
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
echo "NOTES_VERSION=${VERSION%%-*}" >> "$GITHUB_ENV"
if [[ "$VERSION" == *-* ]]; then
echo "PRERELEASE_FLAG=--prerelease" >> "$GITHUB_ENV"
fi
# Sparkle compares build numbers, so this must always increase.
echo "BUILD_NUMBER=$(git rev-list --count HEAD)" >> "$GITHUB_ENV"

- name: Extract release notes
run: |
mkdir -p "$UPDATES_PATH"
# Sparkle picks up notes named like the archive (ActionBar.md for ActionBar.dmg).
awk -v v="$NOTES_VERSION" '/^## / { if (found) exit; found = ($2 == v); next } found' CHANGELOG.md \
| sed -e '/./,$!d' > "$UPDATES_PATH/ActionBar.md"
if ! grep -q '[^[:space:]]' "$UPDATES_PATH/ActionBar.md"; then
echo "CHANGELOG.md has no notes for $NOTES_VERSION. Add a '## $NOTES_VERSION' section." >&2
exit 1
fi
cat "$UPDATES_PATH/ActionBar.md"

- name: Import signing certificate
env:
CERT_P12: ${{ secrets.DEVELOPER_ID_CERT_P12 }}
CERT_PASSWORD: ${{ secrets.DEVELOPER_ID_CERT_PASSWORD }}
run: |
KEYCHAIN_PASSWORD="$(uuidgen)"
echo "$CERT_P12" | base64 --decode > "$RUNNER_TEMP/cert.p12"
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security import "$RUNNER_TEMP/cert.p12" -P "$CERT_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security list-keychains -d user -s "$KEYCHAIN_PATH" "$HOME/Library/Keychains/login.keychain-db"
rm "$RUNNER_TEMP/cert.p12"

- name: Archive
run: |
xcodebuild -project ActionBar.xcodeproj -scheme ActionBar -configuration Release \
-destination 'generic/platform=macOS' \
-archivePath "$ARCHIVE_PATH" \
-clonedSourcePackagesDirPath "$SPM_PATH" \
DEVELOPMENT_TEAM="${{ secrets.APPLE_TEAM_ID }}" \
CODE_SIGN_STYLE=Manual \
CODE_SIGN_IDENTITY="Developer ID Application" \
MARKETING_VERSION="$VERSION" \
CURRENT_PROJECT_VERSION="$BUILD_NUMBER" \
archive

- name: Export app
run: |
xcodebuild -exportArchive \
-archivePath "$ARCHIVE_PATH" \
-exportPath "$EXPORT_PATH" \
-exportOptionsPlist Config/ExportOptions.plist

- name: Notarize app
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
ditto -c -k --keepParent "$EXPORT_PATH/ActionBar.app" "$RUNNER_TEMP/ActionBar.zip"
xcrun notarytool submit "$RUNNER_TEMP/ActionBar.zip" \
--apple-id "$APPLE_ID" \
--password "$APPLE_APP_PASSWORD" \
--team-id "$APPLE_TEAM_ID" \
--wait
xcrun stapler staple "$EXPORT_PATH/ActionBar.app"

- name: Create DMG
run: |
mkdir -p "$RUNNER_TEMP/dmg"
cp -R "$EXPORT_PATH/ActionBar.app" "$RUNNER_TEMP/dmg/"
ln -s /Applications "$RUNNER_TEMP/dmg/Applications"
hdiutil create -volname ActionBar -srcfolder "$RUNNER_TEMP/dmg" -ov -format UDZO "$UPDATES_PATH/ActionBar.dmg"
codesign --sign "Developer ID Application" --timestamp "$UPDATES_PATH/ActionBar.dmg"

- name: Notarize DMG
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
xcrun notarytool submit "$UPDATES_PATH/ActionBar.dmg" \
--apple-id "$APPLE_ID" \
--password "$APPLE_APP_PASSWORD" \
--team-id "$APPLE_TEAM_ID" \
--wait
xcrun stapler staple "$UPDATES_PATH/ActionBar.dmg"

- name: Generate appcast
env:
SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }}
run: |
echo "$SPARKLE_PRIVATE_KEY" | "$SPM_PATH/artifacts/sparkle/Sparkle/bin/generate_appcast" \
--ed-key-file - \
--embed-release-notes \
--download-url-prefix "https://github.com/$GITHUB_REPOSITORY/releases/download/$GITHUB_REF_NAME/" \
--full-release-notes-url "https://github.com/$GITHUB_REPOSITORY/releases/tag/$GITHUB_REF_NAME" \
"$UPDATES_PATH"
cat "$UPDATES_PATH/appcast.xml"

- name: Publish release
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "$GITHUB_REF_NAME" \
"$UPDATES_PATH/ActionBar.dmg" \
"$UPDATES_PATH/appcast.xml" \
--title "ActionBar $VERSION" \
--notes-file "$UPDATES_PATH/ActionBar.md" \
${PRERELEASE_FLAG:+"$PRERELEASE_FLAG"}

- name: Clean up
if: always()
run: |
security delete-keychain "$KEYCHAIN_PATH" || true
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@
.swiftpm/
*.app
xcuserdata/
*.xcodeproj/project.xcworkspace/
*.xcodeproj/project.xcworkspace/xcuserdata/
*.xcodeproj/xcuserdata/
DerivedData/
.DS_Store

# Local agent config
.claude/settings.local.json
CLAUDE.md

# Local signing config
Config/Local.xcconfig
63 changes: 41 additions & 22 deletions ActionBar.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@
A10001000000000000000015 /* MenuBarIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = A10002000000000000000015 /* MenuBarIcon.swift */; };
A10001000000000000000016 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A10002000000000000000016 /* Assets.xcassets */; };
A10001000000000000000017 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A10002000000000000000017 /* SettingsView.swift */; };
A10001000000000000000018 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = A1000C000000000000000001 /* Sparkle */; };
A10001000000000000000019 /* UpdaterService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A10002000000000000000019 /* UpdaterService.swift */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
24E6B8522F2D684B00884177 /* MyIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = MyIcon.icon; sourceTree = "<group>"; };
24F874A92F2DF07C00553174 /* ActionBarRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ActionBarRelease.entitlements; sourceTree = "<group>"; };
A10002000000000000000001 /* ActionBarApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionBarApp.swift; sourceTree = "<group>"; };
A10002000000000000000002 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
A10002000000000000000003 /* WorkflowRun.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkflowRun.swift; sourceTree = "<group>"; };
Expand All @@ -48,13 +49,17 @@
A10002000000000000000016 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
A10002000000000000000017 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
A10003000000000000000001 /* ActionBar.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ActionBar.app; sourceTree = BUILT_PRODUCTS_DIR; };
A10002000000000000000018 /* Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Base.xcconfig; path = Config/Base.xcconfig; sourceTree = "<group>"; };
A10002000000000000000019 /* UpdaterService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdaterService.swift; sourceTree = "<group>"; };
A10002000000000000000020 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
A10004000000000000000001 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
A10001000000000000000018 /* Sparkle in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -64,7 +69,7 @@
A10005000000000000000001 = {
isa = PBXGroup;
children = (
24F874A92F2DF07C00553174 /* ActionBarRelease.entitlements */,
A10002000000000000000018 /* Base.xcconfig */,
A10005000000000000000003 /* Sources */,
A10005000000000000000008 /* Resources */,
A10005000000000000000002 /* Products */,
Expand Down Expand Up @@ -117,6 +122,7 @@
A10002000000000000000006 /* KeychainService.swift */,
A10002000000000000000007 /* GitHubAPIClient.swift */,
A10002000000000000000008 /* OAuthService.swift */,
A10002000000000000000019 /* UpdaterService.swift */,
);
path = Services;
sourceTree = "<group>";
Expand All @@ -139,6 +145,7 @@
children = (
A10002000000000000000016 /* Assets.xcassets */,
24E6B8522F2D684B00884177 /* MyIcon.icon */,
A10002000000000000000020 /* Info.plist */,
);
path = Resources;
sourceTree = "<group>";
Expand Down Expand Up @@ -168,6 +175,9 @@
dependencies = (
);
name = ActionBar;
packageProductDependencies = (
A1000C000000000000000001 /* Sparkle */,
);
productName = GitHubActionsMenu;
productReference = A10003000000000000000001 /* ActionBar.app */;
productType = "com.apple.product-type.application";
Expand All @@ -191,6 +201,9 @@
Base,
);
mainGroup = A10005000000000000000001;
packageReferences = (
A1000B000000000000000001 /* XCRemoteSwiftPackageReference "Sparkle" */,
);
productRefGroup = A10005000000000000000002 /* Products */;
projectDirPath = "";
projectRoot = "";
Expand Down Expand Up @@ -219,6 +232,7 @@
files = (
A10001000000000000000001 /* ActionBarApp.swift in Sources */,
A10001000000000000000002 /* AppDelegate.swift in Sources */,
A10001000000000000000019 /* UpdaterService.swift in Sources */,
A10001000000000000000003 /* WorkflowRun.swift in Sources */,
A10001000000000000000004 /* AppState.swift in Sources */,
A10001000000000000000005 /* NotificationService.swift in Sources */,
Expand Down Expand Up @@ -269,7 +283,6 @@
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = ZX8SV23YKJ;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
Expand Down Expand Up @@ -323,7 +336,6 @@
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = ZX8SV23YKJ;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
Expand All @@ -344,6 +356,7 @@
};
A1000A000000000000000003 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = A10002000000000000000018 /* Base.xcconfig */;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD)";
ASSETCATALOG_COMPILER_APPICON_NAME = MyIcon;
Expand All @@ -355,12 +368,11 @@
DEAD_CODE_STRIPPING = YES;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Resources/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = ActionBar;
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
INFOPLIST_KEY_LSUIElement = YES;
MARKETING_VERSION = 1.7;
PRODUCT_BUNDLE_IDENTIFIER = com.ptrchm.actionbar;
MARKETING_VERSION = 2.0;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_EMIT_LOC_STRINGS = YES;
Expand All @@ -370,35 +382,23 @@
};
A1000A000000000000000004 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = A10002000000000000000018 /* Base.xcconfig */;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD)";
ASSETCATALOG_COMPILER_APPICON_NAME = MyIcon;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CODE_SIGN_ENTITLEMENTS = ActionBarRelease.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 9;
DEAD_CODE_STRIPPING = YES;
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
ENABLE_RESOURCE_ACCESS_CAMERA = NO;
ENABLE_RESOURCE_ACCESS_CONTACTS = NO;
ENABLE_RESOURCE_ACCESS_LOCATION = NO;
ENABLE_RESOURCE_ACCESS_PRINTING = NO;
ENABLE_RESOURCE_ACCESS_USB = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Resources/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = ActionBar;
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
INFOPLIST_KEY_LSUIElement = YES;
MARKETING_VERSION = 1.7;
PRODUCT_BUNDLE_IDENTIFIER = com.ptrchm.actionbar;
MARKETING_VERSION = 2.0;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_EMIT_LOC_STRINGS = YES;
Expand Down Expand Up @@ -428,6 +428,25 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */

/* Begin XCRemoteSwiftPackageReference section */
A1000B000000000000000001 /* XCRemoteSwiftPackageReference "Sparkle" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/sparkle-project/Sparkle";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.7.0;
};
};
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
A1000C000000000000000001 /* Sparkle */ = {
isa = XCSwiftPackageProductDependency;
package = A1000B000000000000000001 /* XCRemoteSwiftPackageReference "Sparkle" */;
productName = Sparkle;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = A10009000000000000000001 /* Project object */;
}
Loading
Loading