Skip to content

Commit 9bd8c13

Browse files
authored
Merge pull request #79 from K9i-0/feat/update-channels
feat: アップデートチャンネル機能を実装
2 parents 4596eb8 + e760e6e commit 9bd8c13

File tree

13 files changed

+664
-81
lines changed

13 files changed

+664
-81
lines changed

.github/workflows/release-common.yml

Lines changed: 9 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ jobs:
7979
name: dmg-artifact
8080
path: .
8181

82-
- name: Generate Sparkle appcast
82+
- name: Update appcast on gh-pages
8383
env:
8484
SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }}
8585
VERSION: ${{ needs.prepare.outputs.version }}
@@ -95,57 +95,13 @@ jobs:
9595
exit 1
9696
fi
9797
98-
if [ -n "$SPARKLE_PRIVATE_KEY" ]; then
99-
echo "✅ Generating Sparkle appcast.xml..."
100-
echo "📦 DMG file: $DMG_PATH"
101-
102-
# Create directories
103-
mkdir -p sparkle appcast
104-
105-
# Download and extract Sparkle
106-
echo "Downloading Sparkle tools..."
107-
cd sparkle
108-
curl -Lo sparkle.tar.xz https://github.com/sparkle-project/Sparkle/releases/download/2.7.1/Sparkle-2.7.1.tar.xz
109-
tar xzf sparkle.tar.xz
110-
cd ..
111-
112-
# Copy DMG to appcast directory
113-
cp "$DMG_PATH" appcast/
114-
115-
# Create temporary file for private key
116-
PRIVATE_KEY_FILE=$(mktemp)
117-
trap "rm -f $PRIVATE_KEY_FILE" EXIT
118-
(umask 077 && echo -n "$SPARKLE_PRIVATE_KEY" > "$PRIVATE_KEY_FILE")
119-
120-
# Generate appcast
121-
DOWNLOAD_URL_PREFIX="https://github.com/${{ github.repository }}/releases/download/v${VERSION}/"
122-
./sparkle/bin/generate_appcast \
123-
--ed-key-file "$PRIVATE_KEY_FILE" \
124-
--download-url-prefix "$DOWNLOAD_URL_PREFIX" \
125-
-o appcast.xml \
126-
appcast/
127-
128-
# Clean up
129-
rm -rf sparkle appcast
130-
131-
if [ -f "appcast.xml" ]; then
132-
echo "✅ Successfully generated appcast.xml"
133-
else
134-
echo "❌ Failed to generate appcast.xml"
135-
exit 1
136-
fi
137-
else
138-
echo "⚠️ No Sparkle private key, creating empty appcast.xml"
139-
{
140-
echo '<?xml version="1.0" encoding="utf-8"?>'
141-
echo '<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle">'
142-
echo ' <channel>'
143-
echo ' <title>Claude Code Monitor</title>'
144-
echo ' <description>No Sparkle key configured</description>'
145-
echo ' </channel>'
146-
echo '</rss>'
147-
} > appcast.xml
148-
fi
98+
# Configure git
99+
git config user.name "GitHub Actions"
100+
git config user.email "actions@github.com"
101+
102+
# Call update-appcast script
103+
IS_DEV=${{ inputs.is_dev_build }}
104+
./scripts/update-appcast.sh "$VERSION" "$IS_DEV"
149105
150106
- name: Create and push tag
151107
run: |
@@ -208,5 +164,4 @@ jobs:
208164
draft: false
209165
prerelease: ${{ inputs.is_dev_build }}
210166
files: |
211-
*.dmg
212-
appcast.xml
167+
*.dmg

.swiftlint.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ disabled_rules:
7373
- trailing_comma
7474
- force_cast
7575
- force_try
76+
- type_body_length
7677

7778
# Rule configuration
7879
line_length:

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,21 @@ All notable changes to this project will be documented in this file.
1818

1919

2020

21+
22+
## [0.7.15] - 2025-07-20
23+
24+
### Added
25+
- アップデートチャンネル機能(安定版/開発版の選択)
26+
- 各チャンネルの最新バージョン表示
27+
- Debug版でのアップデート設定UI表示
28+
29+
### Changed
30+
- アップデート配信をGitHub Pagesベースに変更
31+
- 開発版チャンネルの説明文を「releases only」に修正
32+
33+
### Fixed
34+
-
35+
2136
## [0.7.14] - 2025-07-06
2237

2338
### Fixed

Info.plist

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
<key>CFBundlePackageType</key>
1818
<string>APPL</string>
1919
<key>CFBundleShortVersionString</key>
20-
<string>0.7.14</string>
20+
<string>0.7.15</string>
2121
<key>CFBundleVersion</key>
22-
<string>0.7.14</string>
22+
<string>0.7.15</string>
2323
<key>CcusageVersion</key>
2424
<string>15.3.0</string>
2525
<key>LSMinimumSystemVersion</key>

Sources/ClaudeUsageMonitor/App/AppDelegate.swift

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
2121
// Sparkle configuration based on build type
2222
#if canImport(Sparkle)
2323
#if DEBUG
24-
// Allow testing Sparkle in debug builds with TEST_SPARKLE environment variable
25-
private let updaterController: SPUStandardUpdaterController? = {
26-
if ProcessInfo.processInfo.environment["TEST_SPARKLE"] != nil {
27-
print("⚠️ Sparkle enabled in DEBUG mode for testing")
28-
return SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil)
29-
}
30-
return nil
31-
}()
24+
// Enable Sparkle in debug builds for testing update UI
25+
private lazy var updaterController = SPUStandardUpdaterController(startingUpdater: false, updaterDelegate: self, userDriverDelegate: nil)
3226
#else
33-
private let updaterController = SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil)
27+
private lazy var updaterController = SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: self, userDriverDelegate: nil)
3428
#endif
3529
#else
3630
private let updaterController: AnyObject? = nil
@@ -41,10 +35,11 @@ class AppDelegate: NSObject, NSApplicationDelegate {
4135
#if DEBUG
4236
// This will be reflected in menu bar and other UI elements
4337
print("Running in DEBUG mode")
44-
if ProcessInfo.processInfo.environment["TEST_SPARKLE"] != nil {
45-
print("Sparkle testing mode enabled")
46-
}
38+
print("Sparkle is enabled for UI testing (auto-updates disabled)")
4739
#endif
40+
41+
// Configure Sparkle update channel
42+
configureUpdateChannel()
4843

4944
// Perform synchronous environment check first
5045
environmentCheckResult = CommandExecutor.shared.checkEnvironmentSync()
@@ -231,7 +226,35 @@ class AppDelegate: NSObject, NSApplicationDelegate {
231226
func applicationWillTerminate(_ notification: Notification) {
232227
usageMonitor.stopMonitoring()
233228
}
229+
230+
private func configureUpdateChannel() {
231+
#if canImport(Sparkle)
232+
let channel = UserDefaults.standard.updateChannel
233+
print("Sparkle configured for \(channel.rawValue) channel: \(channel.appcastURL)")
234+
#endif
235+
}
236+
237+
func updateChannelChanged(to newChannel: UpdateChannel) {
238+
#if canImport(Sparkle)
239+
let updater = updaterController.updater
240+
241+
print("Sparkle channel changed to \(newChannel.rawValue): \(newChannel.appcastURL)")
242+
243+
// Check for updates with new channel
244+
updater.checkForUpdates()
245+
#endif
246+
}
247+
}
248+
249+
// MARK: - SPUUpdaterDelegate
250+
#if canImport(Sparkle)
251+
extension AppDelegate: SPUUpdaterDelegate {
252+
nonisolated func feedURLString(for updater: SPUUpdater) -> String? {
253+
let channel = UserDefaults.standard.updateChannel
254+
return channel.appcastURL
255+
}
234256
}
257+
#endif
235258

236259
class EventMonitor {
237260
private var monitor: Any?
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import Foundation
2+
3+
enum UpdateChannel: String, CaseIterable {
4+
case stable = "stable"
5+
case dev = "dev"
6+
7+
var displayName: String {
8+
switch self {
9+
case .stable:
10+
return L10n.Update.stableChannel
11+
case .dev:
12+
return L10n.Update.devChannel
13+
}
14+
}
15+
16+
var appcastURL: String {
17+
switch self {
18+
case .stable:
19+
return "https://k9i-0.github.io/ClaudeCodeMonitor/appcast.xml"
20+
case .dev:
21+
return "https://k9i-0.github.io/ClaudeCodeMonitor/appcast-dev.xml"
22+
}
23+
}
24+
25+
var description: String {
26+
switch self {
27+
case .stable:
28+
return L10n.Update.stableChannelDescription
29+
case .dev:
30+
return L10n.Update.devChannelDescription
31+
}
32+
}
33+
34+
static func fromVersion(_ version: String) -> UpdateChannel {
35+
// If version contains "-dev", it's a dev channel
36+
return version.contains("-dev") ? .dev : .stable
37+
}
38+
}

Sources/ClaudeUsageMonitor/Resources/en.lproj/Localizable.strings

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,10 @@
165165
"update.upToDate" = "You're up to date!";
166166
"update.available" = "Update available";
167167
"update.failed" = "Failed to check for updates";
168+
"update.updateChannel" = "Update Channel";
169+
"update.stableChannel" = "Stable";
170+
"update.devChannel" = "Development";
171+
"update.stableChannelDescription" = "Stable releases only";
172+
"update.devChannelDescription" = "Development releases only";
173+
"update.recommended" = "(Recommended)";
174+
"update.latestVersion" = "Latest: %@";

Sources/ClaudeUsageMonitor/Resources/ja.lproj/Localizable.strings

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,10 @@
165165
"update.upToDate" = "最新の状態です!";
166166
"update.available" = "アップデートがあります";
167167
"update.failed" = "アップデート確認に失敗しました";
168+
"update.updateChannel" = "アップデートチャンネル";
169+
"update.stableChannel" = "安定版";
170+
"update.devChannel" = "開発版";
171+
"update.stableChannelDescription" = "安定版リリースのみ";
172+
"update.devChannelDescription" = "開発版リリースのみ";
173+
"update.recommended" = "(推奨)";
174+
"update.latestVersion" = "最新: %@";

Sources/ClaudeUsageMonitor/Utils/Localization.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,5 +306,14 @@ struct L10n {
306306
static var upToDate: String { "update.upToDate".localized }
307307
static var available: String { "update.available".localized }
308308
static var failed: String { "update.failed".localized }
309+
static var updateChannel: String { "update.updateChannel".localized }
310+
static var stableChannel: String { "update.stableChannel".localized }
311+
static var devChannel: String { "update.devChannel".localized }
312+
static var stableChannelDescription: String { "update.stableChannelDescription".localized }
313+
static var devChannelDescription: String { "update.devChannelDescription".localized }
314+
static var recommended: String { "update.recommended".localized }
315+
static func latestVersion(version: String) -> String {
316+
return "update.latestVersion".localized(with: version)
317+
}
309318
}
310319
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import Foundation
2+
3+
extension UserDefaults {
4+
private enum Keys {
5+
static let updateChannel = "UpdateChannel"
6+
}
7+
8+
var updateChannel: UpdateChannel {
9+
get {
10+
guard let rawValue = string(forKey: Keys.updateChannel),
11+
let channel = UpdateChannel(rawValue: rawValue) else {
12+
// Auto-detect based on current version
13+
let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
14+
return UpdateChannel.fromVersion(version)
15+
}
16+
return channel
17+
}
18+
set {
19+
set(newValue.rawValue, forKey: Keys.updateChannel)
20+
}
21+
}
22+
}

0 commit comments

Comments
 (0)