Skip to content

Commit 8791517

Browse files
authored
Merge pull request #255 from datlechin/feat/multi-arch-plugin-registry
feat: support per-architecture binaries in plugin registry
2 parents c87a6af + 907bbe8 commit 8791517

File tree

6 files changed

+91
-29
lines changed

6 files changed

+91
-29
lines changed

.github/workflows/build-plugin.yml

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ jobs:
8181
run: ./scripts/build-plugin.sh "${{ steps.plugin-info.outputs.target }}" x86_64
8282

8383
- name: Capture SHA-256 hashes
84+
# Hashes are captured before notarize intentionally: notarytool does not
85+
# modify the ZIP on disk, and stapling applies to app bundles, not ZIPs.
8486
id: sha256
8587
run: |
8688
BUNDLE_NAME="${{ steps.plugin-info.outputs.bundle_name }}"
@@ -125,9 +127,8 @@ jobs:
125127
run: |
126128
TAG="${GITHUB_REF#refs/tags/}"
127129
128-
# Registry entry uses arm64 ZIP (Apple Silicon, vast majority of users).
129-
# x86_64 ZIP is available on the release page for manual install.
130-
DOWNLOAD_URL="https://github.com/${{ github.repository }}/releases/download/${TAG}/${{ steps.plugin-info.outputs.bundle_name }}-arm64.zip"
130+
ARM64_URL="https://github.com/${{ github.repository }}/releases/download/${TAG}/${{ steps.plugin-info.outputs.bundle_name }}-arm64.zip"
131+
X86_64_URL="https://github.com/${{ github.repository }}/releases/download/${TAG}/${{ steps.plugin-info.outputs.bundle_name }}-x86_64.zip"
131132
132133
# Get current app version as minAppVersion
133134
MIN_APP_VERSION=$(sed -n 's/.*MARKETING_VERSION = \(.*\);/\1/p' \
@@ -147,8 +148,10 @@ jobs:
147148
"${{ steps.plugin-info.outputs.version }}" \
148149
"${{ steps.plugin-info.outputs.summary }}" \
149150
'${{ steps.plugin-info.outputs.db_type_ids }}' \
150-
"$DOWNLOAD_URL" \
151+
"$ARM64_URL" \
151152
"${{ steps.sha256.outputs.arm64 }}" \
153+
"$X86_64_URL" \
154+
"${{ steps.sha256.outputs.x86_64 }}" \
152155
"$MIN_APP_VERSION" \
153156
"${{ steps.plugin-info.outputs.icon }}" \
154157
"${{ steps.plugin-info.outputs.homepage }}" \
@@ -160,11 +163,13 @@ jobs:
160163
version = sys.argv[3]
161164
summary = sys.argv[4]
162165
db_type_ids = json.loads(sys.argv[5])
163-
download_url = sys.argv[6]
164-
sha256 = sys.argv[7]
165-
min_app_version = sys.argv[8]
166-
icon = sys.argv[9]
167-
homepage = sys.argv[10]
166+
arm64_url = sys.argv[6]
167+
arm64_sha = sys.argv[7]
168+
x86_64_url = sys.argv[8]
169+
x86_64_sha = sys.argv[9]
170+
min_app_version = sys.argv[10]
171+
icon = sys.argv[11]
172+
homepage = sys.argv[12]
168173
169174
with open("plugins.json", "r") as f:
170175
manifest = json.load(f)
@@ -178,8 +183,12 @@ jobs:
178183
"homepage": homepage,
179184
"category": "database-driver",
180185
"databaseTypeIds": db_type_ids,
181-
"downloadURL": download_url,
182-
"sha256": sha256,
186+
"downloadURL": arm64_url,
187+
"sha256": arm64_sha,
188+
"binaries": [
189+
{"architecture": "arm64", "downloadURL": arm64_url, "sha256": arm64_sha},
190+
{"architecture": "x86_64", "downloadURL": x86_64_url, "sha256": x86_64_sha}
191+
],
183192
"minAppVersion": min_app_version,
184193
"minPluginKitVersion": 1,
185194
"iconName": icon,

.swiftlint.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ identifier_name:
145145
- protocol_tcp
146146
- where_
147147
- TableProTabSmart
148+
- x86_64
148149

149150
nesting:
150151
type_level:

TablePro/Core/Plugins/Registry/PluginManager+Registry.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ extension PluginManager {
2626
throw PluginError.pluginConflict(existingName: registryPlugin.name)
2727
}
2828

29-
guard let downloadURL = URL(string: registryPlugin.downloadURL) else {
29+
let resolved = try registryPlugin.resolvedBinary()
30+
31+
guard let downloadURL = URL(string: resolved.url) else {
3032
throw PluginError.downloadFailed("Invalid download URL")
3133
}
3234

@@ -57,7 +59,7 @@ extension PluginManager {
5759
let digest = SHA256.hash(data: downloadedData)
5860
let hexChecksum = digest.map { String(format: "%02x", $0) }.joined()
5961

60-
if hexChecksum != registryPlugin.sha256.lowercased() {
62+
if hexChecksum != resolved.sha256.lowercased() {
6163
throw PluginError.checksumMismatch
6264
}
6365

TablePro/Core/Plugins/Registry/RegistryModels.swift

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,25 @@
55

66
import Foundation
77

8+
enum PluginArchitecture: String, Codable, Sendable {
9+
case arm64
10+
case x86_64
11+
12+
static var current: PluginArchitecture {
13+
#if arch(arm64)
14+
.arm64
15+
#else
16+
.x86_64
17+
#endif
18+
}
19+
}
20+
21+
struct RegistryBinary: Codable, Sendable {
22+
let architecture: PluginArchitecture
23+
let downloadURL: String
24+
let sha256: String
25+
}
26+
827
struct RegistryManifest: Codable, Sendable {
928
let schemaVersion: Int
1029
let plugins: [RegistryPlugin]
@@ -19,14 +38,27 @@ struct RegistryPlugin: Codable, Sendable, Identifiable {
1938
let homepage: String?
2039
let category: RegistryCategory
2140
let databaseTypeIds: [String]?
22-
let downloadURL: String
23-
let sha256: String
41+
let downloadURL: String?
42+
let sha256: String?
43+
let binaries: [RegistryBinary]?
2444
let minAppVersion: String?
2545
let minPluginKitVersion: Int?
2646
let iconName: String?
2747
let isVerified: Bool
2848
}
2949

50+
extension RegistryPlugin {
51+
func resolvedBinary(for arch: PluginArchitecture = .current) throws -> (url: String, sha256: String) {
52+
if let binaries, let match = binaries.first(where: { $0.architecture == arch }) {
53+
return (match.downloadURL, match.sha256)
54+
}
55+
if let url = downloadURL, let hash = sha256 {
56+
return (url, hash)
57+
}
58+
throw PluginError.noCompatibleBinary
59+
}
60+
}
61+
3062
struct RegistryAuthor: Codable, Sendable {
3163
let name: String
3264
let url: String?

docs/development/plugin-registry.mdx

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,16 @@ Each entry matches the `RegistryPlugin` model:
3232
| `homepage` | `string` | No | Project URL |
3333
| `category` | `string` | Yes | One of: `database-driver`, `export-format`, `import-format`, `theme`, `other` |
3434
| `databaseTypeIds` | `[string]` | No | Maps to `DatabaseType.pluginTypeId` values. Used for auto-install prompts. |
35-
| `downloadURL` | `string` | Yes | Direct URL to the `.zip` file |
36-
| `sha256` | `string` | Yes | SHA-256 hex digest of the ZIP file |
35+
| `downloadURL` | `string` | No* | Direct URL to the `.zip` file |
36+
| `sha256` | `string` | No* | SHA-256 hex digest of the ZIP file |
37+
| `binaries` | `[object]` | No | Per-architecture binaries: `[{ "architecture": "arm64"\|"x86_64", "downloadURL": "...", "sha256": "..." }]` |
3738
| `minAppVersion` | `string` | No | Minimum TablePro version (e.g., `"0.17.0"`) |
3839
| `minPluginKitVersion` | `int` | No | Minimum PluginKit version (currently `1`) |
3940
| `iconName` | `string` | No | SF Symbol name for display |
4041
| `isVerified` | `bool` | Yes | Whether the plugin is verified by the TablePro team |
4142

43+
\* Either `downloadURL`/`sha256` (flat fields) or `binaries` array is required. If `binaries` is present, the app picks the matching architecture. Flat fields serve as fallback for older app versions.
44+
4245
## Oracle and ClickHouse Entries
4346

4447
These two database drivers ship as downloadable plugins instead of being bundled with the app. Their registry entries:
@@ -58,6 +61,10 @@ These two database drivers ship as downloadable plugins instead of being bundled
5861
"databaseTypeIds": ["Oracle"],
5962
"downloadURL": "https://github.com/datlechin/TablePro/releases/download/plugin-oracle-v1.0.0/OracleDriver-arm64.zip",
6063
"sha256": "<sha256-of-zip>",
64+
"binaries": [
65+
{ "architecture": "arm64", "downloadURL": "https://github.com/datlechin/TablePro/releases/download/plugin-oracle-v1.0.0/OracleDriver-arm64.zip", "sha256": "<sha256-of-zip>" },
66+
{ "architecture": "x86_64", "downloadURL": "https://github.com/datlechin/TablePro/releases/download/plugin-oracle-v1.0.0/OracleDriver-x86_64.zip", "sha256": "<sha256-of-zip>" }
67+
],
6168
"minAppVersion": "0.17.0",
6269
"minPluginKitVersion": 1,
6370
"iconName": "server.rack",
@@ -80,14 +87,18 @@ These two database drivers ship as downloadable plugins instead of being bundled
8087
"databaseTypeIds": ["ClickHouse"],
8188
"downloadURL": "https://github.com/datlechin/TablePro/releases/download/plugin-clickhouse-v1.0.0/ClickHouseDriver-arm64.zip",
8289
"sha256": "<sha256-of-zip>",
90+
"binaries": [
91+
{ "architecture": "arm64", "downloadURL": "https://github.com/datlechin/TablePro/releases/download/plugin-clickhouse-v1.0.0/ClickHouseDriver-arm64.zip", "sha256": "<sha256-of-zip>" },
92+
{ "architecture": "x86_64", "downloadURL": "https://github.com/datlechin/TablePro/releases/download/plugin-clickhouse-v1.0.0/ClickHouseDriver-x86_64.zip", "sha256": "<sha256-of-zip>" }
93+
],
8394
"minAppVersion": "0.17.0",
8495
"minPluginKitVersion": 1,
8596
"iconName": "chart.bar.xaxis",
8697
"isVerified": true
8798
}
8899
```
89100

90-
Replace `<sha256-of-zip>` with the actual SHA-256 output from `build-plugin.sh`. You need one entry per architecture, or a universal ZIP.
101+
Replace `<sha256-of-zip>` with the actual SHA-256 output from `build-plugin.sh`. The `binaries` array provides per-architecture downloads; `downloadURL`/`sha256` flat fields point to arm64 as fallback for older app versions.
91102

92103
## databaseTypeIds Mapping
93104

@@ -107,10 +118,8 @@ The `databaseTypeIds` field maps registry entries to `DatabaseType.pluginTypeId`
107118
## Publishing a Plugin Release
108119

109120
1. Tag the commit: `git tag plugin-oracle-v1.0.0 && git push --tags`
110-
2. The `build-plugin.yml` CI workflow builds, signs, and creates a GitHub release with ZIP artifacts
111-
3. Copy the SHA-256 from the build output
112-
4. Add or update the entry in `plugins.json` on the [TableProApp/plugins](https://github.com/TableProApp/plugins) repo
113-
5. The app fetches the updated manifest on next **Browse** visit (with ETag caching)
121+
2. The `build-plugin.yml` CI workflow builds both architectures (arm64 + x86_64), signs, creates a GitHub release with ZIP artifacts, and automatically updates the plugin registry with a `binaries` array
122+
3. The app fetches the updated manifest on next **Browse** visit (with ETag caching)
114123

115124
## Auto-Install Flow
116125

docs/vi/development/plugin-registry.mdx

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,16 @@ Mỗi mục tương ứng với model `RegistryPlugin`:
3232
| `homepage` | `string` | Không | URL dự án |
3333
| `category` | `string` || Một trong: `database-driver`, `export-format`, `import-format`, `theme`, `other` |
3434
| `databaseTypeIds` | `[string]` | Không | Ánh xạ đến giá trị `DatabaseType.pluginTypeId`. Dùng cho tự động cài đặt. |
35-
| `downloadURL` | `string` || URL trực tiếp đến file `.zip` |
36-
| `sha256` | `string` || SHA-256 hex digest của file ZIP |
35+
| `downloadURL` | `string` | Không* | URL trực tiếp đến file `.zip` |
36+
| `sha256` | `string` | Không* | SHA-256 hex digest của file ZIP |
37+
| `binaries` | `[object]` | Không | Binary theo kiến trúc: `[{ "architecture": "arm64"\|"x86_64", "downloadURL": "...", "sha256": "..." }]` |
3738
| `minAppVersion` | `string` | Không | Phiên bản TablePro tối thiểu (vd: `"0.17.0"`) |
3839
| `minPluginKitVersion` | `int` | Không | Phiên bản PluginKit tối thiểu (hiện tại là `1`) |
3940
| `iconName` | `string` | Không | Tên SF Symbol để hiển thị |
4041
| `isVerified` | `bool` || Plugin đã được đội ngũ TablePro xác minh hay chưa |
4142

43+
\* Cần có `downloadURL`/`sha256` (trường phẳng) hoặc mảng `binaries`. Nếu có `binaries`, ứng dụng chọn kiến trúc phù hợp. Trường phẳng dùng làm fallback cho phiên bản ứng dụng cũ.
44+
4245
## Mục Oracle và ClickHouse
4346

4447
Hai database driver này được phân phối dưới dạng plugin tải về thay vì đóng gói trong ứng dụng:
@@ -58,6 +61,10 @@ Hai database driver này được phân phối dưới dạng plugin tải về
5861
"databaseTypeIds": ["Oracle"],
5962
"downloadURL": "https://github.com/datlechin/TablePro/releases/download/plugin-oracle-v1.0.0/OracleDriver-arm64.zip",
6063
"sha256": "<sha256-cua-zip>",
64+
"binaries": [
65+
{ "architecture": "arm64", "downloadURL": "https://github.com/datlechin/TablePro/releases/download/plugin-oracle-v1.0.0/OracleDriver-arm64.zip", "sha256": "<sha256-cua-zip>" },
66+
{ "architecture": "x86_64", "downloadURL": "https://github.com/datlechin/TablePro/releases/download/plugin-oracle-v1.0.0/OracleDriver-x86_64.zip", "sha256": "<sha256-cua-zip>" }
67+
],
6168
"minAppVersion": "0.17.0",
6269
"minPluginKitVersion": 1,
6370
"iconName": "server.rack",
@@ -80,14 +87,18 @@ Hai database driver này được phân phối dưới dạng plugin tải về
8087
"databaseTypeIds": ["ClickHouse"],
8188
"downloadURL": "https://github.com/datlechin/TablePro/releases/download/plugin-clickhouse-v1.0.0/ClickHouseDriver-arm64.zip",
8289
"sha256": "<sha256-cua-zip>",
90+
"binaries": [
91+
{ "architecture": "arm64", "downloadURL": "https://github.com/datlechin/TablePro/releases/download/plugin-clickhouse-v1.0.0/ClickHouseDriver-arm64.zip", "sha256": "<sha256-cua-zip>" },
92+
{ "architecture": "x86_64", "downloadURL": "https://github.com/datlechin/TablePro/releases/download/plugin-clickhouse-v1.0.0/ClickHouseDriver-x86_64.zip", "sha256": "<sha256-cua-zip>" }
93+
],
8394
"minAppVersion": "0.17.0",
8495
"minPluginKitVersion": 1,
8596
"iconName": "chart.bar.xaxis",
8697
"isVerified": true
8798
}
8899
```
89100

90-
Thay `<sha256-cua-zip>` bằng SHA-256 thực tế từ output của `build-plugin.sh`.
101+
Thay `<sha256-cua-zip>` bằng SHA-256 thực tế từ output của `build-plugin.sh`. Mảng `binaries` cung cấp tải về theo kiến trúc; trường phẳng `downloadURL`/`sha256` trỏ đến arm64 làm fallback cho phiên bản ứng dụng cũ.
91102

92103
## Ánh xạ databaseTypeIds
93104

@@ -107,10 +118,8 @@ Trường `databaseTypeIds` ánh xạ mục registry đến giá trị `Database
107118
## Xuất bản Plugin
108119

109120
1. Tạo tag: `git tag plugin-oracle-v1.0.0 && git push --tags`
110-
2. CI workflow `build-plugin.yml` sẽ build, ký và tạo GitHub release với file ZIP
111-
3. Sao chép SHA-256 từ output build
112-
4. Thêm hoặc cập nhật mục trong `plugins.json` trên repo [TableProApp/plugins](https://github.com/TableProApp/plugins)
113-
5. Ứng dụng sẽ tải manifest mới khi mở **Duyệt** (có cache ETag)
121+
2. CI workflow `build-plugin.yml` sẽ build cả hai kiến trúc (arm64 + x86_64), ký, tạo GitHub release với file ZIP, và tự động cập nhật registry plugin với mảng `binaries`
122+
3. Ứng dụng sẽ tải manifest mới khi mở **Duyệt** (có cache ETag)
114123

115124
## Luồng tự động cài đặt
116125

0 commit comments

Comments
 (0)