Skip to content

Commit f919ab4

Browse files
committed
feat: bundle ClickHouse, MSSQL, Redis, XLSX, MQL, SQLImport plugins as built-in
1 parent f63e9cc commit f919ab4

3 files changed

Lines changed: 117 additions & 4 deletions

File tree

CLAUDE.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
77
TablePro is a native macOS database client (SwiftUI + AppKit) — a fast, lightweight alternative to TablePlus. macOS 14.0+, Swift 5.9, Universal Binary (arm64 + x86_64).
88

99
- **Source**: `TablePro/``Core/` (business logic, services), `Views/` (UI), `Models/` (data structures), `ViewModels/`, `Extensions/`, `Theme/`
10-
- **Plugins**: `Plugins/``.tableplugin` bundles + `TableProPluginKit` shared framework. Built-in (bundled in app): MySQL, PostgreSQL, SQLite, CSV, JSON, SQL export. Separately distributed via plugin registry: ClickHouse, MSSQL, MongoDB, Redis, Oracle, DuckDB, XLSX, MQL, SQLImport
10+
- **Plugins**: `Plugins/``.tableplugin` bundles + `TableProPluginKit` shared framework. Built-in (bundled in app): MySQL, PostgreSQL, SQLite, ClickHouse, MSSQL, Redis, CSV, JSON, SQL export, XLSX, MQL, SQLImport, DynamoDB. Separately distributed via plugin registry: MongoDB, Oracle, DuckDB, Cassandra, Etcd, CloudflareD1
1111
- **C bridges**: Each plugin contains its own C bridge module (e.g., `Plugins/MySQLDriverPlugin/CMariaDB/`, `Plugins/PostgreSQLDriverPlugin/CLibPQ/`)
1212
- **Static libs**: `Libs/` — pre-built `libmariadb*.a`, `libpq*.a`, etc. Downloaded from GitHub Releases via `scripts/download-libs.sh` (not in git)
1313
- **SPM deps**: CodeEditSourceEditor (`main` branch, tree-sitter editor), Sparkle (2.8.1, auto-update), OracleNIO. Managed via Xcode, no `Package.swift`.
@@ -79,10 +79,10 @@ Plugin bundles under `Plugins/`:
7979
| MySQLDriverPlugin | MySQL, MariaDB | CMariaDB | Built-in |
8080
| PostgreSQLDriverPlugin | PostgreSQL, Redshift | CLibPQ | Built-in |
8181
| SQLiteDriverPlugin | SQLite | (Foundation sqlite3) | Built-in |
82-
| ClickHouseDriverPlugin | ClickHouse | (URLSession HTTP) | Registry |
83-
| MSSQLDriverPlugin | SQL Server | CFreeTDS | Registry |
82+
| ClickHouseDriverPlugin | ClickHouse | (URLSession HTTP) | Built-in |
83+
| MSSQLDriverPlugin | SQL Server | CFreeTDS | Built-in |
84+
| RedisDriverPlugin | Redis | CRedis | Built-in |
8485
| MongoDBDriverPlugin | MongoDB | CLibMongoc | Registry |
85-
| RedisDriverPlugin | Redis | CRedis | Registry |
8686
| DuckDBDriverPlugin | DuckDB | CDuckDB | Registry |
8787
| OracleDriverPlugin | Oracle | OracleNIO (SPM) | Registry |
8888

TablePro.xcodeproj/project.pbxproj

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@
2626
5A86B000D00000000 /* JSONExport.tableplugin in Copy Plug-Ins */ = {isa = PBXBuildFile; fileRef = 5A86B000100000000 /* JSONExport.tableplugin */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
2727
5A86C000A00000000 /* TableProPluginKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A860000100000000 /* TableProPluginKit.framework */; };
2828
5A86C000D00000000 /* SQLExport.tableplugin in Copy Plug-Ins */ = {isa = PBXBuildFile; fileRef = 5A86C000100000000 /* SQLExport.tableplugin */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
29+
5A863000D00000000 /* ClickHouseDriver.tableplugin in Copy Plug-Ins */ = {isa = PBXBuildFile; fileRef = 5A863000100000000 /* ClickHouseDriver.tableplugin */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
30+
5A864000D00000000 /* MSSQLDriver.tableplugin in Copy Plug-Ins */ = {isa = PBXBuildFile; fileRef = 5A864000100000000 /* MSSQLDriver.tableplugin */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
31+
5A867000D00000000 /* RedisDriver.tableplugin in Copy Plug-Ins */ = {isa = PBXBuildFile; fileRef = 5A867000100000000 /* RedisDriver.tableplugin */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
32+
5A86D000D00000000 /* XLSXExport.tableplugin in Copy Plug-Ins */ = {isa = PBXBuildFile; fileRef = 5A86D000100000000 /* XLSXExport.tableplugin */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
33+
5A86E000D00000000 /* MQLExport.tableplugin in Copy Plug-Ins */ = {isa = PBXBuildFile; fileRef = 5A86E000100000000 /* MQLExport.tableplugin */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
34+
5A86F000D00000000 /* SQLImport.tableplugin in Copy Plug-Ins */ = {isa = PBXBuildFile; fileRef = 5A86F000100000000 /* SQLImport.tableplugin */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
2935
5A86D000A00000000 /* TableProPluginKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A860000100000000 /* TableProPluginKit.framework */; };
3036
5A86E000A00000000 /* TableProPluginKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A860000100000000 /* TableProPluginKit.framework */; };
3137
5A86F000A00000000 /* TableProPluginKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A860000100000000 /* TableProPluginKit.framework */; };
@@ -126,6 +132,41 @@
126132
remoteGlobalIDString = 5A86C000000000000;
127133
remoteInfo = SQLExport;
128134
};
135+
5A864000B00000000 /* PBXContainerItemProxy */ = {
136+
isa = PBXContainerItemProxy;
137+
containerPortal = 5A1091BF2EF17EDC0055EA7C /* Project object */;
138+
proxyType = 1;
139+
remoteGlobalIDString = 5A864000000000000;
140+
remoteInfo = MSSQLDriver;
141+
};
142+
5A867000B00000000 /* PBXContainerItemProxy */ = {
143+
isa = PBXContainerItemProxy;
144+
containerPortal = 5A1091BF2EF17EDC0055EA7C /* Project object */;
145+
proxyType = 1;
146+
remoteGlobalIDString = 5A867000000000000;
147+
remoteInfo = RedisDriver;
148+
};
149+
5A86D000B00000000 /* PBXContainerItemProxy */ = {
150+
isa = PBXContainerItemProxy;
151+
containerPortal = 5A1091BF2EF17EDC0055EA7C /* Project object */;
152+
proxyType = 1;
153+
remoteGlobalIDString = 5A86D000000000000;
154+
remoteInfo = XLSXExport;
155+
};
156+
5A86E000B00000000 /* PBXContainerItemProxy */ = {
157+
isa = PBXContainerItemProxy;
158+
containerPortal = 5A1091BF2EF17EDC0055EA7C /* Project object */;
159+
proxyType = 1;
160+
remoteGlobalIDString = 5A86E000000000000;
161+
remoteInfo = MQLExport;
162+
};
163+
5A86F000B00000000 /* PBXContainerItemProxy */ = {
164+
isa = PBXContainerItemProxy;
165+
containerPortal = 5A1091BF2EF17EDC0055EA7C /* Project object */;
166+
proxyType = 1;
167+
remoteGlobalIDString = 5A86F000000000000;
168+
remoteInfo = SQLImport;
169+
};
129170
5ABCC5AB2F43856700EAF3FC /* PBXContainerItemProxy */ = {
130171
isa = PBXContainerItemProxy;
131172
containerPortal = 5A1091BF2EF17EDC0055EA7C /* Project object */;
@@ -152,9 +193,15 @@
152193
5A865000D00000000 /* MySQLDriver.tableplugin in Copy Plug-Ins */,
153194
5A868000D00000000 /* PostgreSQLDriver.tableplugin in Copy Plug-Ins */,
154195
5A862000D00000000 /* SQLiteDriver.tableplugin in Copy Plug-Ins */,
196+
5A863000D00000000 /* ClickHouseDriver.tableplugin in Copy Plug-Ins */,
197+
5A864000D00000000 /* MSSQLDriver.tableplugin in Copy Plug-Ins */,
198+
5A867000D00000000 /* RedisDriver.tableplugin in Copy Plug-Ins */,
155199
5A86A000D00000000 /* CSVExport.tableplugin in Copy Plug-Ins */,
156200
5A86B000D00000000 /* JSONExport.tableplugin in Copy Plug-Ins */,
157201
5A86C000D00000000 /* SQLExport.tableplugin in Copy Plug-Ins */,
202+
5A86D000D00000000 /* XLSXExport.tableplugin in Copy Plug-Ins */,
203+
5A86E000D00000000 /* MQLExport.tableplugin in Copy Plug-Ins */,
204+
5A86F000D00000000 /* SQLImport.tableplugin in Copy Plug-Ins */,
158205
5ADDB00000000000000000D0 /* DynamoDBDriverPlugin.tableplugin in Copy Plug-Ins */,
159206
);
160207
name = "Copy Plug-Ins";
@@ -823,12 +870,17 @@
823870
5A861000C00000000 /* PBXTargetDependency */,
824871
5A862000C00000000 /* PBXTargetDependency */,
825872
5A863000C00000000 /* PBXTargetDependency */,
873+
5A864000C00000000 /* PBXTargetDependency */,
826874
5A865000C00000000 /* PBXTargetDependency */,
875+
5A867000C00000000 /* PBXTargetDependency */,
827876
5A868000C00000000 /* PBXTargetDependency */,
828877
5A869000C00000000 /* PBXTargetDependency */,
829878
5A86A000C00000000 /* PBXTargetDependency */,
830879
5A86B000C00000000 /* PBXTargetDependency */,
831880
5A86C000C00000000 /* PBXTargetDependency */,
881+
5A86D000C00000000 /* PBXTargetDependency */,
882+
5A86E000C00000000 /* PBXTargetDependency */,
883+
5A86F000C00000000 /* PBXTargetDependency */,
832884
5ADDB00000000000000000C1 /* PBXTargetDependency */,
833885
);
834886
fileSystemSynchronizedGroups = (
@@ -1771,6 +1823,31 @@
17711823
target = 5A86C000000000000 /* SQLExport */;
17721824
targetProxy = 5A86C000B00000000 /* PBXContainerItemProxy */;
17731825
};
1826+
5A864000C00000000 /* PBXTargetDependency */ = {
1827+
isa = PBXTargetDependency;
1828+
target = 5A864000000000000 /* MSSQLDriver */;
1829+
targetProxy = 5A864000B00000000 /* PBXContainerItemProxy */;
1830+
};
1831+
5A867000C00000000 /* PBXTargetDependency */ = {
1832+
isa = PBXTargetDependency;
1833+
target = 5A867000000000000 /* RedisDriver */;
1834+
targetProxy = 5A867000B00000000 /* PBXContainerItemProxy */;
1835+
};
1836+
5A86D000C00000000 /* PBXTargetDependency */ = {
1837+
isa = PBXTargetDependency;
1838+
target = 5A86D000000000000 /* XLSXExport */;
1839+
targetProxy = 5A86D000B00000000 /* PBXContainerItemProxy */;
1840+
};
1841+
5A86E000C00000000 /* PBXTargetDependency */ = {
1842+
isa = PBXTargetDependency;
1843+
target = 5A86E000000000000 /* MQLExport */;
1844+
targetProxy = 5A86E000B00000000 /* PBXContainerItemProxy */;
1845+
};
1846+
5A86F000C00000000 /* PBXTargetDependency */ = {
1847+
isa = PBXTargetDependency;
1848+
target = 5A86F000000000000 /* SQLImport */;
1849+
targetProxy = 5A86F000B00000000 /* PBXContainerItemProxy */;
1850+
};
17741851
5ABCC5AC2F43856700EAF3FC /* PBXTargetDependency */ = {
17751852
isa = PBXTargetDependency;
17761853
target = 5A1091C62EF17EDC0055EA7C /* TablePro */;

TablePro/Core/Plugins/PluginManager.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ final class PluginManager {
269269

270270
if let builtInDir = builtInPluginsDir {
271271
discoverPlugins(from: builtInDir, source: .builtIn)
272+
removeUserInstalledDuplicates(builtInDir: builtInDir)
272273
}
273274

274275
discoverPlugins(from: userPluginsDir, source: .userInstalled)
@@ -320,6 +321,41 @@ final class PluginManager {
320321
}
321322
}
322323

324+
/// Remove user-installed plugins that now ship as built-in to avoid dead weight.
325+
private func removeUserInstalledDuplicates(builtInDir: URL) {
326+
let fm = FileManager.default
327+
guard let builtInBundles = try? fm.contentsOfDirectory(
328+
at: builtInDir,
329+
includingPropertiesForKeys: nil,
330+
options: [.skipsHiddenFiles]
331+
) else { return }
332+
333+
var builtInBundleIds = Set<String>()
334+
for url in builtInBundles where url.pathExtension == "tableplugin" {
335+
if let bundle = Bundle(url: url), let id = bundle.bundleIdentifier {
336+
builtInBundleIds.insert(id)
337+
}
338+
}
339+
340+
guard let userPlugins = try? fm.contentsOfDirectory(
341+
at: userPluginsDir,
342+
includingPropertiesForKeys: nil,
343+
options: [.skipsHiddenFiles]
344+
) else { return }
345+
346+
for url in userPlugins where url.pathExtension == "tableplugin" {
347+
guard let bundle = Bundle(url: url), let id = bundle.bundleIdentifier else { continue }
348+
if builtInBundleIds.contains(id) {
349+
do {
350+
try fm.removeItem(at: url)
351+
Self.logger.info("Removed user-installed '\(id)' — now ships as built-in")
352+
} catch {
353+
Self.logger.warning("Failed to remove duplicate plugin '\(id)': \(error.localizedDescription)")
354+
}
355+
}
356+
}
357+
}
358+
323359
private func discoverPlugin(at url: URL, source: PluginSource) throws {
324360
guard let bundle = Bundle(url: url) else {
325361
throw PluginError.invalidBundle("Cannot create bundle from \(url.lastPathComponent)")

0 commit comments

Comments
 (0)