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
31 changes: 20 additions & 11 deletions .github/workflows/build-plugin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ jobs:
run: ./scripts/build-plugin.sh "${{ steps.plugin-info.outputs.target }}" x86_64

- name: Capture SHA-256 hashes
# Hashes are captured before notarize intentionally: notarytool does not
# modify the ZIP on disk, and stapling applies to app bundles, not ZIPs.
id: sha256
run: |
BUNDLE_NAME="${{ steps.plugin-info.outputs.bundle_name }}"
Expand Down Expand Up @@ -125,9 +127,8 @@ jobs:
run: |
TAG="${GITHUB_REF#refs/tags/}"

# Registry entry uses arm64 ZIP (Apple Silicon, vast majority of users).
# x86_64 ZIP is available on the release page for manual install.
DOWNLOAD_URL="https://github.com/${{ github.repository }}/releases/download/${TAG}/${{ steps.plugin-info.outputs.bundle_name }}-arm64.zip"
ARM64_URL="https://github.com/${{ github.repository }}/releases/download/${TAG}/${{ steps.plugin-info.outputs.bundle_name }}-arm64.zip"
X86_64_URL="https://github.com/${{ github.repository }}/releases/download/${TAG}/${{ steps.plugin-info.outputs.bundle_name }}-x86_64.zip"

# Get current app version as minAppVersion
MIN_APP_VERSION=$(sed -n 's/.*MARKETING_VERSION = \(.*\);/\1/p' \
Expand All @@ -147,8 +148,10 @@ jobs:
"${{ steps.plugin-info.outputs.version }}" \
"${{ steps.plugin-info.outputs.summary }}" \
'${{ steps.plugin-info.outputs.db_type_ids }}' \
"$DOWNLOAD_URL" \
"$ARM64_URL" \
"${{ steps.sha256.outputs.arm64 }}" \
"$X86_64_URL" \
"${{ steps.sha256.outputs.x86_64 }}" \
"$MIN_APP_VERSION" \
"${{ steps.plugin-info.outputs.icon }}" \
"${{ steps.plugin-info.outputs.homepage }}" \
Expand All @@ -160,11 +163,13 @@ jobs:
version = sys.argv[3]
summary = sys.argv[4]
db_type_ids = json.loads(sys.argv[5])
download_url = sys.argv[6]
sha256 = sys.argv[7]
min_app_version = sys.argv[8]
icon = sys.argv[9]
homepage = sys.argv[10]
arm64_url = sys.argv[6]
arm64_sha = sys.argv[7]
x86_64_url = sys.argv[8]
x86_64_sha = sys.argv[9]
min_app_version = sys.argv[10]
icon = sys.argv[11]
homepage = sys.argv[12]

with open("plugins.json", "r") as f:
manifest = json.load(f)
Expand All @@ -178,8 +183,12 @@ jobs:
"homepage": homepage,
"category": "database-driver",
"databaseTypeIds": db_type_ids,
"downloadURL": download_url,
"sha256": sha256,
"downloadURL": arm64_url,
"sha256": arm64_sha,
"binaries": [
{"architecture": "arm64", "downloadURL": arm64_url, "sha256": arm64_sha},
{"architecture": "x86_64", "downloadURL": x86_64_url, "sha256": x86_64_sha}
],
"minAppVersion": min_app_version,
"minPluginKitVersion": 1,
"iconName": icon,
Expand Down
1 change: 1 addition & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ identifier_name:
- protocol_tcp
- where_
- TableProTabSmart
- x86_64

nesting:
type_level:
Expand Down
6 changes: 4 additions & 2 deletions TablePro/Core/Plugins/Registry/PluginManager+Registry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ extension PluginManager {
throw PluginError.pluginConflict(existingName: registryPlugin.name)
}

guard let downloadURL = URL(string: registryPlugin.downloadURL) else {
let resolved = try registryPlugin.resolvedBinary()

guard let downloadURL = URL(string: resolved.url) else {
throw PluginError.downloadFailed("Invalid download URL")
}

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

if hexChecksum != registryPlugin.sha256.lowercased() {
if hexChecksum != resolved.sha256.lowercased() {
throw PluginError.checksumMismatch
}

Expand Down
36 changes: 34 additions & 2 deletions TablePro/Core/Plugins/Registry/RegistryModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,25 @@

import Foundation

enum PluginArchitecture: String, Codable, Sendable {
case arm64
case x86_64

static var current: PluginArchitecture {
#if arch(arm64)
.arm64
#else
.x86_64
#endif
}
}

struct RegistryBinary: Codable, Sendable {
let architecture: PluginArchitecture
let downloadURL: String
let sha256: String
}

struct RegistryManifest: Codable, Sendable {
let schemaVersion: Int
let plugins: [RegistryPlugin]
Expand All @@ -19,14 +38,27 @@ struct RegistryPlugin: Codable, Sendable, Identifiable {
let homepage: String?
let category: RegistryCategory
let databaseTypeIds: [String]?
let downloadURL: String
let sha256: String
let downloadURL: String?
let sha256: String?
let binaries: [RegistryBinary]?
let minAppVersion: String?
let minPluginKitVersion: Int?
let iconName: String?
let isVerified: Bool
}

extension RegistryPlugin {
func resolvedBinary(for arch: PluginArchitecture = .current) throws -> (url: String, sha256: String) {
if let binaries, let match = binaries.first(where: { $0.architecture == arch }) {
return (match.downloadURL, match.sha256)
}
if let url = downloadURL, let hash = sha256 {
return (url, hash)
}
throw PluginError.noCompatibleBinary
}
}

struct RegistryAuthor: Codable, Sendable {
let name: String
let url: String?
Expand Down
23 changes: 16 additions & 7 deletions docs/development/plugin-registry.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,16 @@ Each entry matches the `RegistryPlugin` model:
| `homepage` | `string` | No | Project URL |
| `category` | `string` | Yes | One of: `database-driver`, `export-format`, `import-format`, `theme`, `other` |
| `databaseTypeIds` | `[string]` | No | Maps to `DatabaseType.pluginTypeId` values. Used for auto-install prompts. |
| `downloadURL` | `string` | Yes | Direct URL to the `.zip` file |
| `sha256` | `string` | Yes | SHA-256 hex digest of the ZIP file |
| `downloadURL` | `string` | No* | Direct URL to the `.zip` file |
| `sha256` | `string` | No* | SHA-256 hex digest of the ZIP file |
| `binaries` | `[object]` | No | Per-architecture binaries: `[{ "architecture": "arm64"\|"x86_64", "downloadURL": "...", "sha256": "..." }]` |
| `minAppVersion` | `string` | No | Minimum TablePro version (e.g., `"0.17.0"`) |
| `minPluginKitVersion` | `int` | No | Minimum PluginKit version (currently `1`) |
| `iconName` | `string` | No | SF Symbol name for display |
| `isVerified` | `bool` | Yes | Whether the plugin is verified by the TablePro team |

\* 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.

## Oracle and ClickHouse Entries

These two database drivers ship as downloadable plugins instead of being bundled with the app. Their registry entries:
Expand All @@ -58,6 +61,10 @@ These two database drivers ship as downloadable plugins instead of being bundled
"databaseTypeIds": ["Oracle"],
"downloadURL": "https://github.com/datlechin/TablePro/releases/download/plugin-oracle-v1.0.0/OracleDriver-arm64.zip",
"sha256": "<sha256-of-zip>",
"binaries": [
{ "architecture": "arm64", "downloadURL": "https://github.com/datlechin/TablePro/releases/download/plugin-oracle-v1.0.0/OracleDriver-arm64.zip", "sha256": "<sha256-of-zip>" },
{ "architecture": "x86_64", "downloadURL": "https://github.com/datlechin/TablePro/releases/download/plugin-oracle-v1.0.0/OracleDriver-x86_64.zip", "sha256": "<sha256-of-zip>" }
],
"minAppVersion": "0.17.0",
"minPluginKitVersion": 1,
"iconName": "server.rack",
Expand All @@ -80,14 +87,18 @@ These two database drivers ship as downloadable plugins instead of being bundled
"databaseTypeIds": ["ClickHouse"],
"downloadURL": "https://github.com/datlechin/TablePro/releases/download/plugin-clickhouse-v1.0.0/ClickHouseDriver-arm64.zip",
"sha256": "<sha256-of-zip>",
"binaries": [
{ "architecture": "arm64", "downloadURL": "https://github.com/datlechin/TablePro/releases/download/plugin-clickhouse-v1.0.0/ClickHouseDriver-arm64.zip", "sha256": "<sha256-of-zip>" },
{ "architecture": "x86_64", "downloadURL": "https://github.com/datlechin/TablePro/releases/download/plugin-clickhouse-v1.0.0/ClickHouseDriver-x86_64.zip", "sha256": "<sha256-of-zip>" }
],
"minAppVersion": "0.17.0",
"minPluginKitVersion": 1,
"iconName": "chart.bar.xaxis",
"isVerified": true
}
```

Replace `<sha256-of-zip>` with the actual SHA-256 output from `build-plugin.sh`. You need one entry per architecture, or a universal ZIP.
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.

## databaseTypeIds Mapping

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

1. Tag the commit: `git tag plugin-oracle-v1.0.0 && git push --tags`
2. The `build-plugin.yml` CI workflow builds, signs, and creates a GitHub release with ZIP artifacts
3. Copy the SHA-256 from the build output
4. Add or update the entry in `plugins.json` on the [TableProApp/plugins](https://github.com/TableProApp/plugins) repo
5. The app fetches the updated manifest on next **Browse** visit (with ETag caching)
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
3. The app fetches the updated manifest on next **Browse** visit (with ETag caching)

## Auto-Install Flow

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

\* 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ũ.

## Mục Oracle và ClickHouse

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:
Expand All @@ -58,6 +61,10 @@ Hai database driver này được phân phối dưới dạng plugin tải về
"databaseTypeIds": ["Oracle"],
"downloadURL": "https://github.com/datlechin/TablePro/releases/download/plugin-oracle-v1.0.0/OracleDriver-arm64.zip",
"sha256": "<sha256-cua-zip>",
"binaries": [
{ "architecture": "arm64", "downloadURL": "https://github.com/datlechin/TablePro/releases/download/plugin-oracle-v1.0.0/OracleDriver-arm64.zip", "sha256": "<sha256-cua-zip>" },
{ "architecture": "x86_64", "downloadURL": "https://github.com/datlechin/TablePro/releases/download/plugin-oracle-v1.0.0/OracleDriver-x86_64.zip", "sha256": "<sha256-cua-zip>" }
],
"minAppVersion": "0.17.0",
"minPluginKitVersion": 1,
"iconName": "server.rack",
Expand All @@ -80,14 +87,18 @@ Hai database driver này được phân phối dưới dạng plugin tải về
"databaseTypeIds": ["ClickHouse"],
"downloadURL": "https://github.com/datlechin/TablePro/releases/download/plugin-clickhouse-v1.0.0/ClickHouseDriver-arm64.zip",
"sha256": "<sha256-cua-zip>",
"binaries": [
{ "architecture": "arm64", "downloadURL": "https://github.com/datlechin/TablePro/releases/download/plugin-clickhouse-v1.0.0/ClickHouseDriver-arm64.zip", "sha256": "<sha256-cua-zip>" },
{ "architecture": "x86_64", "downloadURL": "https://github.com/datlechin/TablePro/releases/download/plugin-clickhouse-v1.0.0/ClickHouseDriver-x86_64.zip", "sha256": "<sha256-cua-zip>" }
],
"minAppVersion": "0.17.0",
"minPluginKitVersion": 1,
"iconName": "chart.bar.xaxis",
"isVerified": true
}
```

Thay `<sha256-cua-zip>` bằng SHA-256 thực tế từ output của `build-plugin.sh`.
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ũ.

## Ánh xạ databaseTypeIds

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

1. Tạo tag: `git tag plugin-oracle-v1.0.0 && git push --tags`
2. CI workflow `build-plugin.yml` sẽ build, ký và tạo GitHub release với file ZIP
3. Sao chép SHA-256 từ output build
4. Thêm hoặc cập nhật mục trong `plugins.json` trên repo [TableProApp/plugins](https://github.com/TableProApp/plugins)
5. Ứng dụng sẽ tải manifest mới khi mở **Duyệt** (có cache ETag)
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`
3. Ứng dụng sẽ tải manifest mới khi mở **Duyệt** (có cache ETag)

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

Expand Down