Skip to content

Commit c48b1a0

Browse files
authored
Merge pull request #34 from MxIris-Reverse-Engineering/fix/ci-release-workflow
2 parents c218269 + 5f5e253 commit c48b1a0

4 files changed

Lines changed: 65 additions & 18 deletions

File tree

.github/workflows/release.yml

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,25 @@
1-
name: Release
1+
name: Build & Release
22

33
on:
44
push:
55
tags: ['v*']
66
workflow_dispatch:
77
inputs:
8+
tag:
9+
description: 'Tag to release (e.g. v2.0.0-RC.3)'
10+
required: true
11+
type: string
812
runner:
913
description: 'Runner to use'
1014
type: choice
1115
default: 'macos-latest'
1216
options:
1317
- macos-latest
1418
- self-hosted
19+
create_release:
20+
description: 'Create GitHub Release'
21+
type: boolean
22+
default: true
1523

1624
# Required GitHub Secrets:
1725
# DEVELOPER_ID_P12 - Base64-encoded Developer ID Application certificate (.p12)
@@ -29,12 +37,14 @@ jobs:
2937
contents: write
3038

3139
env:
32-
WORKSPACE: RuntimeViewer.xcworkspace
40+
WORKSPACE: RuntimeViewer-arm64e.xcworkspace
3341
BUILD_PATH: ./Products/Archives
3442

3543
steps:
3644
- name: Checkout
3745
uses: actions/checkout@v4
46+
with:
47+
ref: ${{ inputs.tag || github.ref }}
3848

3949
- name: Clone sibling dependencies
4050
run: |
@@ -80,12 +90,13 @@ jobs:
8090
-k "$KEYCHAIN_PATH"
8191
8292
# Import Apple intermediate certificates (required on self-hosted runners)
93+
# Use || true because these may already exist on GitHub-hosted runners
8394
for cert_url in \
8495
https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer \
8596
https://www.apple.com/certificateauthority/DeveloperIDG2CA.cer; do
8697
cert_file="$RUNNER_TEMP/$(basename "$cert_url")"
8798
curl -sL "$cert_url" -o "$cert_file"
88-
security import "$cert_file" -k "$KEYCHAIN_PATH" -T /usr/bin/codesign
99+
security import "$cert_file" -k "$KEYCHAIN_PATH" -T /usr/bin/codesign || true
89100
done
90101
91102
security list-keychain -d user -s \
@@ -213,7 +224,7 @@ jobs:
213224
-skipMacroValidation \
214225
CODE_SIGNING_ALLOWED=NO
215226
216-
# ── Package & Release ──
227+
# ── Package ──
217228

218229
- name: Package artifacts
219230
run: |
@@ -226,11 +237,22 @@ jobs:
226237
/usr/bin/ditto -c -k --keepParent RuntimeViewer.app \
227238
"$GITHUB_WORKSPACE/RuntimeViewer-iOS-Simulator.zip"
228239
240+
- name: Upload artifacts
241+
uses: actions/upload-artifact@v4
242+
with:
243+
name: release-artifacts
244+
path: |
245+
RuntimeViewer-macOS.zip
246+
RuntimeViewer-iOS-Simulator.zip
247+
248+
# ── Release (manual only) ──
249+
229250
- name: Create Release
251+
if: ${{ github.event_name == 'workflow_dispatch' && inputs.create_release }}
230252
env:
231253
GH_TOKEN: ${{ github.token }}
232254
run: |
233-
TAG="${{ github.ref_name }}"
255+
TAG="${{ inputs.tag }}"
234256
CHANGELOG_FILE="Changelogs/${TAG}.md"
235257
236258
NOTES_ARGS=()

RuntimeViewerMCP/Sources/RuntimeViewerMCPBridge/MCPService.swift

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ public final class MCPService {
4242

4343
private var transport: HTTPSSETransport?
4444

45+
private var transportTask: Task<Void, Never>?
46+
4547
private var startTask: Task<Void, Never>?
4648

4749
private var observeToken: ObserveToken?
@@ -61,7 +63,11 @@ public final class MCPService {
6163
private init() {
6264
let appSupportURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
6365
let runtimeViewerDir = appSupportURL.appendingPathComponent("RuntimeViewer")
64-
try? FileManager.default.createDirectory(at: runtimeViewerDir, withIntermediateDirectories: true)
66+
do {
67+
try FileManager.default.createDirectory(at: runtimeViewerDir, withIntermediateDirectories: true)
68+
} catch {
69+
logger.error("Failed to create app support directory: \(error)")
70+
}
6571
self.portFilePath = runtimeViewerDir.appendingPathComponent(Settings.MCP.portFileName).path
6672
}
6773

@@ -70,9 +76,17 @@ public final class MCPService {
7076
}
7177

7278
public func start(for documentProvider: some MCPBridgeDocumentProvider) {
73-
let mcpSettings = settings.mcp
74-
guard mcpSettings.isEnabled else {
79+
self.documentProvider = documentProvider
80+
81+
// Initialize previous values before observing to avoid a spurious restart
82+
let currentMCP = settings.mcp
83+
previousMCPEnabled = currentMCP.isEnabled
84+
previousMCPUsesFixedPort = currentMCP.useFixedPort
85+
previousMCPFixedPort = currentMCP.fixedPort
86+
87+
guard currentMCP.isEnabled else {
7588
serverState = .disabled
89+
observe()
7690
return
7791
}
7892
startTask = Task {
@@ -82,10 +96,9 @@ public final class MCPService {
8296
let mcpServer = MCPBridgeServer(documentProvider: documentProvider)
8397
let transport = HTTPSSETransport(server: mcpServer, host: "127.0.0.1", port: Int(port))
8498
self.transport = transport
85-
self.documentProvider = documentProvider
8699

87100
// Run transport in a detached task (run() blocks on the NIO event loop)
88-
Task.detached {
101+
self.transportTask = Task.detached {
89102
do {
90103
try await transport.run()
91104
} catch {
@@ -103,11 +116,6 @@ public final class MCPService {
103116
logger.error("Failed to start MCP server: \(error)")
104117
self.serverState = .stopped
105118
}
106-
// Initialize previous values before observing to avoid a spurious restart
107-
let currentMCP = settings.mcp
108-
previousMCPEnabled = currentMCP.isEnabled
109-
previousMCPUsesFixedPort = currentMCP.useFixedPort
110-
previousMCPFixedPort = currentMCP.fixedPort
111119
observe()
112120
}
113121
}
@@ -117,6 +125,13 @@ public final class MCPService {
117125
startTask = nil
118126
restartTask?.cancel()
119127
restartTask = nil
128+
transportTask?.cancel()
129+
transportTask = nil
130+
if let transport {
131+
Task.detached {
132+
try? await transport.stop()
133+
}
134+
}
120135
transport = nil
121136
let isEnabled = settings.mcp.isEnabled
122137
serverState = isEnabled ? .stopped : .disabled
@@ -178,6 +193,12 @@ public final class MCPService {
178193
}
179194

180195
private nonisolated func removePortFile() {
181-
try? FileManager.default.removeItem(atPath: portFilePath)
196+
do {
197+
try FileManager.default.removeItem(atPath: portFilePath)
198+
} catch let error as NSError where error.domain == NSCocoaErrorDomain && error.code == NSFileNoSuchFileError {
199+
// File already removed, ignore
200+
} catch {
201+
logger.error("Failed to remove port file: \(error)")
202+
}
182203
}
183204
}

RuntimeViewerPackages/Sources/RuntimeViewerService/RuntimeViewerService.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,12 @@ public final class RuntimeViewerService {
9191

9292
guard let app = notification.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication,
9393
let bundleID = app.bundleIdentifier,
94-
let launchedApps = service.launchedApplicationsByCallerBundleID.removeValue(forKey: bundleID) else { continue }
94+
let launchedApps = service.launchedApplicationsByCallerBundleID[bundleID] else { continue }
9595

9696
// Check if there's still another instance of the caller app running
97-
if NSWorkspace.shared.runningApplications.contains(where: { $0.bundleIdentifier == bundleID }) { continue }
97+
if NSWorkspace.shared.runningApplications.contains(where: { $0.bundleIdentifier == bundleID && $0 != app }) { continue }
98+
99+
service.launchedApplicationsByCallerBundleID.removeValue(forKey: bundleID)
98100

99101
for launchedApp in launchedApps {
100102
if !launchedApp.isTerminated {

RuntimeViewerUsingAppKit/RuntimeViewerUsingAppKit/MCP/MCPStatusPopoverViewController.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,8 @@ final class MCPStatusPopoverViewController: AppKitViewController<MCPStatusPopove
168168
portValueLabel.stringValue = "\(port)"
169169
actionButton.title = "Stop Server"
170170
}
171+
172+
preferredContentSize = view.fittingSize
171173
}
172174

173175
// MARK: - Bindings

0 commit comments

Comments
 (0)