104 Commits
Author SHA1 Message Date
Mark Gregory face625f23 row editing 2026-10-21 11:43:43 +01:00
Mark Gregory 1d8e8702a8 row editing 2026-10-21 10:39:23 +01:00
Mark Gregory 2aaeff3774 row editing 2026-10-21 10:30:18 +01:00
Mark Gregory 3f9f572434 row editing 2026-10-21 10:21:27 +01:00
Mark Gregory 2229238e52 row editing 2026-10-21 10:15:06 +01:00
Mark Gregory 60cbf790db row editing 2026-10-21 09:47:53 +01:00
Mark Gregory 0a4cca2b41 row editing 2026-10-20 09:36:41 +01:00
Mark Gregory 233ff2bf64 row editing 2026-10-20 09:22:16 +01:00
Mark Gregory 86d7ae49fb row editing 2026-10-20 09:19:57 +01:00
Mark Gregory 6768ef5fff row editing 2026-10-20 09:14:25 +01:00
Mark Gregory 499dd49ec2 connection updates 2026-10-20 09:04:25 +01:00
Mark Gregory 445f674367 connection updates 2026-10-20 08:55:17 +01:00
Mark Gregory b69be9f419 connection updates 2026-10-19 12:56:17 +01:00
Mark Gregory a27680d893 connection updates 2026-10-19 12:47:16 +01:00
Mark Gregory 682132e7ac connection updates 2026-10-19 12:46:18 +01:00
Mark Gregory b06a605f38 connection updates 2026-10-19 12:23:01 +01:00
Mark Gregory 69c8d2c4db connection updates 2026-10-19 12:17:32 +01:00
Mark Gregory 7cf013a783 connection updates 2026-10-19 12:11:39 +01:00
Mark Gregory 1df9549a0e connection updates 2026-10-19 12:09:39 +01:00
Mark Gregory 7b2176b31b connection updates 2026-10-19 11:55:36 +01:00
Mark Gregory 07c7e7d328 connection updates 2026-10-19 11:41:44 +01:00
Mark Gregory a34faa3b29 connection updates 2026-10-19 11:37:17 +01:00
Mark Gregory c6504b3d1b connection updates 2026-10-19 11:31:55 +01:00
Mark Gregory 5aba461451 connection updates 2026-10-19 11:23:26 +01:00
Mark Gregory bbc35ab4e3 connection updates 2026-10-19 11:10:42 +01:00
Mark Gregory 3c685eece7 connection updates 2026-10-19 10:59:43 +01:00
Mark Gregory 1c30dc070d new tests 2026-10-19 10:56:20 +01:00
Mark Gregory 82a54e7905 new tests 2026-10-19 10:53:12 +01:00
Mark Gregory 8d33346993 new tests 2026-10-19 10:46:22 +01:00
Mark Gregory 3307bd90e4 new tests 2026-10-19 10:40:04 +01:00
Mark Gregory f46f598baf new tests 2026-10-19 10:32:15 +01:00
Mark Gregory 6251f2490b new tests 2026-10-19 10:22:14 +01:00
Mark Gregory ab8783a1f7 new tests 2026-10-19 10:14:12 +01:00
Mark Gregory 271f4a44bc new tests 2026-10-19 10:12:13 +01:00
Mark Gregory 11a11d664f new tests 2026-10-19 10:08:40 +01:00
Mark Gregory c9ebbb6406 new tests 2026-10-19 10:06:58 +01:00
Mark Gregory c6af133e76 new tests 2026-10-19 10:05:48 +01:00
Mark Gregory 16be4e8cfc new tests 2026-10-19 09:56:56 +01:00
Mark Gregory ef84b1e563 new tests 2026-10-19 09:54:57 +01:00
Mark Gregory a2d0cf11a9 new tests broken 2026-10-19 09:53:26 +01:00
Mark Gregory 0d67b5ea0f new tests broken 2026-10-19 09:51:52 +01:00
Mark Gregory a8b66070c3 new tests 2026-10-19 09:50:07 +01:00
Mark Gregory ff3658352a new tests 2026-10-19 09:47:26 +01:00
Mark Gregory f63d40ff48 added typeaheead and introspetion 2026-10-19 09:38:08 +01:00
Mark Gregory 66e1845fc6 added typeaheead and introspetion 2026-10-19 09:32:16 +01:00
Mark Gregory 23513d3f2e added typeaheead and introspetion 2026-10-19 09:28:25 +01:00
Mark Gregory 4cbb5624af added typeaheead and introspetion 2026-10-19 09:24:12 +01:00
Mark Gregory c102bef2a6 added typeaheead and introspetion 2026-10-18 17:28:38 +01:00
Mark Gregory 543b639ffc added typeaheead and introspetion 2026-10-18 17:27:44 +01:00
Mark Gregory c0ac9d57df added typeaheead and introspetion 2026-10-18 17:26:31 +01:00
Mark Gregory d1cd3c2004 added typeaheead and introspetion 2026-10-18 17:24:28 +01:00
Mark Gregory 8e5515afc0 added typeaheead and introspetion 2026-10-18 17:18:14 +01:00
Mark Gregory 7f6b63d05b added typeaheead and introspetion 2026-10-18 17:13:45 +01:00
Mark Gregory 9609cb31c1 added typeaheead and introspetion 2026-10-18 17:11:33 +01:00
Mark Gregory bcd33b3fa5 added typeaheead and introspetion 2026-10-18 16:43:08 +01:00
Mark Gregory b6123a0043 added typeaheead and introspetion 2026-10-18 16:39:55 +01:00
Mark Gregory 1ca16eb69a added typeaheead and introspetion 2026-10-18 16:23:03 +01:00
Mark Gregory ecd6561831 new release script 2026-10-18 16:09:06 +01:00
Mark Gregory abd8c59e60 new release script 2026-10-18 16:06:16 +01:00
Mark Gregory 73e9a3efe2 new release script 2026-10-18 15:41:16 +01:00
Mark Gregory 55c4254b25 new release script 2026-10-18 15:33:34 +01:00
Mark Gregory b3366e05db new release script 2026-10-18 15:28:44 +01:00
Mark Gregory b796dd933b new release script 2026-10-18 14:59:04 +01:00
Mark Gregory d6d39937ed new release script 2026-10-18 14:57:05 +01:00
Mark Gregory 4306211981 new release script 2026-10-18 14:49:36 +01:00
Mark Gregory ebd0bd1e29 Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-18 14:43:13 +01:00
Mark Gregory 0891230b39 Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-18 14:34:02 +01:00
Mark Gregory 312efaa771 Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-18 14:27:16 +01:00
Mark Gregory 33ec9f7f14 Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-18 14:19:52 +01:00
Mark Gregory c389d91da2 Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-18 12:02:42 +01:00
Mark Gregory d0d7ff257e Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-18 11:53:06 +01:00
Mark Gregory c1bfb36de5 Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-18 11:46:19 +01:00
Mark Gregory d96fc225cf Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-18 11:36:34 +01:00
Mark Gregory 08a73d5171 Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-18 11:25:03 +01:00
Mark Gregory 40386134a8 Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-18 11:23:58 +01:00
Mark Gregory b3cc8b7463 Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-18 11:20:21 +01:00
Mark Gregory 3635999f47 Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-17 16:31:56 +01:00
Mark Gregory 2d5d48949a Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-17 16:25:02 +01:00
Mark Gregory 283fa76162 Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-17 15:26:46 +01:00
Mark Gregory d441aea02c Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-17 11:54:29 +01:00
Mark Gregory e1a9b4037e Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-17 11:31:54 +01:00
Mark Gregory 5cf34634d2 Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-17 11:15:38 +01:00
Mark Gregory f5433c4e48 Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-17 11:04:11 +01:00
Mark Gregory e6c8943530 Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-17 11:01:56 +01:00
Mark Gregory ca0b6fba23 Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-17 11:01:04 +01:00
Mark Gregory c3fb3e6fad Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-17 10:59:05 +01:00
Mark Gregory c511256f32 Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-17 10:54:02 +01:00
Mark Gregory 9cd4a3c443 Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-17 10:49:47 +01:00
Mark Gregory 939522d98c Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-16 22:51:54 +01:00
Mark Gregory 4e24e2daa4 Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-16 22:49:36 +01:00
Mark Gregory 8db8b30ed8 Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-16 22:46:01 +01:00
Mark Gregory 5db95d7212 Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-16 22:42:20 +01:00
Mark Gregory 3f3c7cb0ba Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-16 22:40:35 +01:00
Mark Gregory 9562afddf0 Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-16 22:39:15 +01:00
Mark Gregory 6e9e7b780d Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-16 22:38:51 +01:00
Mark Gregory a189d21af9 Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-16 22:37:26 +01:00
Mark Gregory 788c3373d8 Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-16 22:28:08 +01:00
Mark Gregory 37043f316e Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-16 22:26:09 +01:00
Mark Gregory ff40ddd1a9 Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-16 22:24:53 +01:00
Mark Gregory fb1430ad1a Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-16 22:20:48 +01:00
Mark Gregory d479d3c07b Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-16 22:17:33 +01:00
Mark Gregory 9c5c0b5a91 Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-16 22:14:53 +01:00
Mark Gregory e177b1d1ea Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-16 22:11:25 +01:00
Mark Gregory b1bce0c166 Improved progress display
Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
2026-10-16 22:11:14 +01:00
2794 changed files with 12954 additions and 7530 deletions
+2
View File
@@ -254,3 +254,5 @@ releases/
# README.md
# LICENSE
/build/
/test-results/
+31 -1
View File
@@ -1,7 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/build" />
<excludeFolder url="file://$MODULE_DIR$/build/Build/Intermediates.noindex/EagerLinkingTBDs" />
<excludeFolder url="file://$MODULE_DIR$/build/Build/Intermediates.noindex/PrecompiledHeaders" />
<excludeFolder url="file://$MODULE_DIR$/build/Build/Intermediates.noindex/XCBuildData/120e9a8ae12faa2f3ba3b63c6df49ba3.xcbuilddata" />
<excludeFolder url="file://$MODULE_DIR$/build/Build/Intermediates.noindex/XCBuildData/7f733ee9c557a22c77228b85d3bb3f5e.xcbuilddata" />
<excludeFolder url="file://$MODULE_DIR$/build/Build/Intermediates.noindex/XCBuildData/a06f417aacd1b83f24bd3cc2cf22ad87.xcbuilddata" />
<excludeFolder url="file://$MODULE_DIR$/build/Build/Intermediates.noindex/XCBuildData/db88895332a02f341932219e1765f45d.xcbuilddata" />
<excludeFolder url="file://$MODULE_DIR$/build/Index.noindex/DataStore/v5/records/0H" />
<excludeFolder url="file://$MODULE_DIR$/build/Index.noindex/DataStore/v5/records/2G" />
<excludeFolder url="file://$MODULE_DIR$/build/Index.noindex/DataStore/v5/records/8A" />
<excludeFolder url="file://$MODULE_DIR$/build/Index.noindex/DataStore/v5/records/8U" />
<excludeFolder url="file://$MODULE_DIR$/build/Index.noindex/DataStore/v5/records/AI" />
<excludeFolder url="file://$MODULE_DIR$/build/Index.noindex/DataStore/v5/records/BZ" />
<excludeFolder url="file://$MODULE_DIR$/build/Index.noindex/DataStore/v5/records/D2" />
<excludeFolder url="file://$MODULE_DIR$/build/Index.noindex/DataStore/v5/records/L6" />
<excludeFolder url="file://$MODULE_DIR$/build/Index.noindex/DataStore/v5/records/QF" />
<excludeFolder url="file://$MODULE_DIR$/build/Index.noindex/DataStore/v5/records/SA" />
<excludeFolder url="file://$MODULE_DIR$/build/Index.noindex/DataStore/v5/records/XW" />
<excludeFolder url="file://$MODULE_DIR$/build/Logs/Launch" />
<excludeFolder url="file://$MODULE_DIR$/build/Logs/Localization" />
<excludeFolder url="file://$MODULE_DIR$/build/Logs/Package" />
<excludeFolder url="file://$MODULE_DIR$/build/Logs/Test" />
<excludeFolder url="file://$MODULE_DIR$/releases" />
<excludeFolder url="file://$MODULE_DIR$/test-results" />
<excludeFolder url="file://$MODULE_DIR$/test-results/DBSyn-20261019-094628.xcresult" />
<excludeFolder url="file://$MODULE_DIR$/test-results/DBSyn-20261019-094908.xcresult" />
<excludeFolder url="file://$MODULE_DIR$/test-results/DBSyn-20261019-095109.xcresult" />
<excludeFolder url="file://$MODULE_DIR$/test-results/DBSyn-20261019-095434-45691.xcresult" />
<excludeFolder url="file://$MODULE_DIR$/test-results/DBSyn-20261019-095659-45920.xcresult" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
+288
View File
@@ -0,0 +1,288 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 56;
objects = {
/* Begin PBXContainerItemProxy section */
F0737000000000000000000D /* DBSyn target proxy */ = { isa = PBXContainerItemProxy; containerPortal = 000000000000000000000001; proxyType = 1; remoteGlobalIDString = 000000000000000000000005; remoteInfo = DBSyn; };
/* End PBXContainerItemProxy section */
/* Begin PBXTargetDependency section */
F0737000000000000000000E /* DBSyn dependency */ = { isa = PBXTargetDependency; target = 000000000000000000000005; targetProxy = F0737000000000000000000D; };
/* End PBXTargetDependency section */
/* Begin PBXBuildFile section */
B74000000000000000000001 /* LaravelMigrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B74000000000000000000002 /* LaravelMigrationView.swift */; };
B73200000000000000000001 /* SchemaDiagramPrintView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B73200000000000000000002 /* SchemaDiagramPrintView.swift */; };
B73000000000000000000001 /* SchemaDiagramView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B73000000000000000000002 /* SchemaDiagramView.swift */; };
000000000000000000000013 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 000000000000000000000010 /* App.swift */; };
000000000000000000000014 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 000000000000000000000011 /* MainView.swift */; };
000000000000000000000015 /* DatabaseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 000000000000000000000012 /* DatabaseModel.swift */; };
000000000000000000000017 /* MySQLProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 000000000000000000000016 /* MySQLProtocol.swift */; };
A10000000000000000000003 /* DBSyn.icns in Resources */ = {isa = PBXBuildFile; fileRef = A10000000000000000000001 /* DBSyn.icns */; };
A10000000000000000000004 /* DBSynBrand.png in Resources */ = {isa = PBXBuildFile; fileRef = A10000000000000000000002 /* DBSynBrand.png */; };
F07370000000000000000002 /* DiagramLogic.swift in Sources */ = {isa = PBXBuildFile; fileRef = F07370000000000000000001 /* DiagramLogic.swift */; };
F07370000000000000000006 /* DBSynTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F07370000000000000000005 /* DBSynTests.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
B74000000000000000000002 /* LaravelMigrationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaravelMigrationView.swift; sourceTree = "<group>"; };
B73200000000000000000002 /* SchemaDiagramPrintView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SchemaDiagramPrintView.swift; sourceTree = "<group>"; };
B73000000000000000000002 /* SchemaDiagramView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SchemaDiagramView.swift; sourceTree = "<group>"; };
00000000000000000000000F /* DBSyn.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DBSyn.app; sourceTree = BUILT_PRODUCTS_DIR; };
000000000000000000000010 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
000000000000000000000011 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; };
000000000000000000000012 /* DatabaseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseModel.swift; sourceTree = "<group>"; };
000000000000000000000016 /* MySQLProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MySQLProtocol.swift; sourceTree = "<group>"; };
A10000000000000000000001 /* DBSyn.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = DBSyn.icns; sourceTree = "<group>"; };
A10000000000000000000002 /* DBSynBrand.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = DBSynBrand.png; sourceTree = "<group>"; };
F07370000000000000000001 /* DiagramLogic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiagramLogic.swift; sourceTree = "<group>"; };
F07370000000000000000005 /* DBSynTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBSynTests.swift; sourceTree = "<group>"; };
F07370000000000000000007 /* DBSynTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DBSynTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
000000000000000000000008 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
F07370000000000000000009 /* Tests Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = (); runOnlyForDeploymentPostprocessing = 0; };
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
000000000000000000000002 = {
isa = PBXGroup;
children = (
000000000000000000000003 /* DBSyn */,
F07370000000000000000003 /* DBSynTests */,
000000000000000000000004 /* Products */,
);
sourceTree = "<group>";
};
000000000000000000000003 /* DBSyn */ = {
isa = PBXGroup;
children = (
000000000000000000000010 /* App.swift */,
000000000000000000000011 /* MainView.swift */,
000000000000000000000012 /* DatabaseModel.swift */,
F07370000000000000000001 /* DiagramLogic.swift */,
B73000000000000000000002 /* SchemaDiagramView.swift */,
B74000000000000000000002 /* LaravelMigrationView.swift */,
B73200000000000000000002 /* SchemaDiagramPrintView.swift */,
000000000000000000000016 /* MySQLProtocol.swift */,
A10000000000000000000005 /* Resources */,);
path = DBSyn;
sourceTree = "<group>";
};
000000000000000000000004 /* Products */ = {
isa = PBXGroup;
children = (
00000000000000000000000F /* DBSyn.app */,
F07370000000000000000007 /* DBSynTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
A10000000000000000000005 /* Resources */ = {
isa = PBXGroup;
children = (
A10000000000000000000001 /* DBSyn.icns */,
A10000000000000000000002 /* DBSynBrand.png */,
);
path = Resources;
sourceTree = "<group>";
};
F07370000000000000000003 /* DBSynTests */ = { isa = PBXGroup; children = (F07370000000000000000005 /* DBSynTests.swift */,); path = DBSynTests; sourceTree = "<group>"; };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
000000000000000000000005 /* DBSyn */ = {
isa = PBXNativeTarget;
buildConfigurationList = 00000000000000000000000A /* Build configuration list for PBXNativeTarget "DBSyn" */;
buildPhases = (
000000000000000000000006 /* Sources */,
000000000000000000000008 /* Frameworks */,
000000000000000000000007 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = DBSyn;
productName = DBSyn;
productReference = 00000000000000000000000F /* DBSyn.app */;
productType = "com.apple.product-type.application";
};
F07370000000000000000004 /* DBSynTests */ = { isa = PBXNativeTarget; buildConfigurationList = F0737000000000000000000A /* Test Configurations */; buildPhases = (F07370000000000000000008 /* Test Sources */, F07370000000000000000009 /* Tests Frameworks */,); buildRules = (); dependencies = (F0737000000000000000000E /* DBSyn dependency */,); name = DBSynTests; productName = DBSynTests; productReference = F07370000000000000000007 /* DBSynTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; };
/* End PBXNativeTarget section */
/* Begin PBXProject section */
000000000000000000000001 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1600;
TargetAttributes = {
000000000000000000000005 = {
CreatedOnToolsVersion = 16.0;
};
F07370000000000000000004 = { CreatedOnToolsVersion = 16.0; TestTargetID = 000000000000000000000005; };
};
};
buildConfigurationList = 000000000000000000000009 /* Build configuration list for PBXProject "DBSyn" */;
compatibilityVersion = "Xcode 14.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 000000000000000000000002;
productRefGroup = 000000000000000000000004 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
000000000000000000000005 /* DBSyn */,
F07370000000000000000004 /* DBSynTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
000000000000000000000007 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A10000000000000000000003 /* DBSyn.icns in Resources */,
A10000000000000000000004 /* DBSynBrand.png in Resources */,);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
000000000000000000000006 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
000000000000000000000013 /* App.swift in Sources */,
000000000000000000000014 /* MainView.swift in Sources */,
000000000000000000000015 /* DatabaseModel.swift in Sources */,
F07370000000000000000002 /* DiagramLogic.swift in Sources */,
B73000000000000000000001 /* SchemaDiagramView.swift in Sources */,
B74000000000000000000001 /* LaravelMigrationView.swift in Sources */,
B73200000000000000000001 /* SchemaDiagramPrintView.swift in Sources */,
000000000000000000000017 /* MySQLProtocol.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
F07370000000000000000008 /* Test Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = (F07370000000000000000006 /* DBSynTests.swift in Sources */,); runOnlyForDeploymentPostprocessing = 0; };
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
00000000000000000000000B /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
MACOSX_DEPLOYMENT_TARGET = 15.0;
SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
ONLY_ACTIVE_ARCH = YES;
SWIFT_VERSION = 5.0;
};
name = Debug;
};
00000000000000000000000C /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
MACOSX_DEPLOYMENT_TARGET = 15.0;
SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
};
name = Release;
};
00000000000000000000000D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 39R6U3437D;
ENABLE_APP_SANDBOX = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "DBSyn";
MARKETING_VERSION = 1.1.1;
CURRENT_PROJECT_VERSION = 83;
ENABLE_TESTABILITY = YES;
ONLY_ACTIVE_ARCH = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2026 ABSDEV. All rights reserved.";
INFOPLIST_KEY_CFBundleIconFile = "DBSyn.icns";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
PRODUCT_BUNDLE_IDENTIFIER = net.absdev.DBSyn;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
};
name = Debug;
};
00000000000000000000000E /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 39R6U3437D;
ENABLE_APP_SANDBOX = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "DBSyn";
MARKETING_VERSION = 1.1.1;
CURRENT_PROJECT_VERSION = 83;
ENABLE_TESTABILITY = YES;
ONLY_ACTIVE_ARCH = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2026 ABSDEV. All rights reserved.";
INFOPLIST_KEY_CFBundleIconFile = "DBSyn.icns";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
PRODUCT_BUNDLE_IDENTIFIER = net.absdev.DBSyn;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
};
name = Release;
};
F0737000000000000000000B /* Tests Debug */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGNING_ALLOWED = NO; ONLY_ACTIVE_ARCH = YES; SWIFT_ENABLE_EXPLICIT_MODULES = NO; GENERATE_INFOPLIST_FILE = YES; MACOSX_DEPLOYMENT_TARGET = 15.0; PRODUCT_BUNDLE_IDENTIFIER = net.absdev.DBSynTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DBSyn.app/Contents/MacOS/DBSyn"; }; name = Debug; };
F0737000000000000000000C /* Tests Release */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGNING_ALLOWED = NO; ONLY_ACTIVE_ARCH = YES; SWIFT_ENABLE_EXPLICIT_MODULES = NO; GENERATE_INFOPLIST_FILE = YES; MACOSX_DEPLOYMENT_TARGET = 15.0; PRODUCT_BUNDLE_IDENTIFIER = net.absdev.DBSynTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DBSyn.app/Contents/MacOS/DBSyn"; }; name = Release; };
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
000000000000000000000009 /* Build configuration list for PBXProject "DBSyn" */ = {
isa = XCConfigurationList;
buildConfigurations = (
00000000000000000000000B /* Debug */,
00000000000000000000000C /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
00000000000000000000000A /* Build configuration list for PBXNativeTarget "DBSyn" */ = {
isa = XCConfigurationList;
buildConfigurations = (
00000000000000000000000D /* Debug */,
00000000000000000000000E /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
F0737000000000000000000A /* Test Configurations */ = { isa = XCConfigurationList; buildConfigurations = (F0737000000000000000000B /* Tests Debug */, F0737000000000000000000C /* Tests Release */,); defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; };
/* End XCConfigurationList section */
};
rootObject = 000000000000000000000001 /* Project object */;
}
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme LastUpgradeVersion="1600" version="1.7">
<BuildAction parallelizeBuildables="YES" buildImplicitDependencies="YES"><BuildActionEntries><BuildActionEntry buildForTesting="YES" buildForRunning="YES" buildForProfiling="YES" buildForArchiving="YES" buildForAnalyzing="YES"><BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="000000000000000000000005" BuildableName="DBSyn.app" BlueprintName="DBSyn" ReferencedContainer="container:DBSyn.xcodeproj"/></BuildActionEntry></BuildActionEntries></BuildAction>
<TestAction buildConfiguration="Debug" selectedDebuggerIdentifier="Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier="Xcode.IDEFoundation.Launcher.Posix" shouldUseLaunchSchemeArgsEnv="YES"><Testables><TestableReference skipped="NO"><BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="F07370000000000000000004" BuildableName="DBSynTests.xctest" BlueprintName="DBSynTests" ReferencedContainer="container:DBSyn.xcodeproj"/></TestableReference></Testables></TestAction>
<LaunchAction buildConfiguration="Debug" selectedDebuggerIdentifier="Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier="Xcode.IDEFoundation.Launcher.Posix" launchStyle="0" useCustomWorkingDirectory="NO" ignoresPersistentStateOnLaunch="NO" debugDocumentVersioning="YES" debugServiceExtension="internal" allowLocationSimulation="YES"><BuildableProductRunnable runnableDebuggingMode="0"><BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="000000000000000000000005" BuildableName="DBSyn.app" BlueprintName="DBSyn" ReferencedContainer="container:DBSyn.xcodeproj"/></BuildableProductRunnable></LaunchAction>
<ProfileAction buildConfiguration="Release" shouldUseLaunchSchemeArgsEnv="YES" savedToolIdentifier="" useCustomWorkingDirectory="NO" debugDocumentVersioning="YES"><BuildableProductRunnable runnableDebuggingMode="0"><BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="000000000000000000000005" BuildableName="DBSyn.app" BlueprintName="DBSyn" ReferencedContainer="container:DBSyn.xcodeproj"/></BuildableProductRunnable></ProfileAction>
<AnalyzeAction buildConfiguration="Debug"/><ArchiveAction buildConfiguration="Release" revealArchiveInOrganizer="YES"/>
</Scheme>
+664
View File
@@ -0,0 +1,664 @@
import SwiftUI
import AppKit
extension Notification.Name {
static let dbsynOpenConnectionManager = Notification.Name("DBSyn.OpenConnectionManager")
static let workbenchRevealSQLEditor = Notification.Name("DBSyn.RevealSQLEditor")
}
@main
struct DBSynApp: App {
@StateObject private var model = DatabaseModel()
@AppStorage("preferences.appearance") private var appearance = "system"
@State private var showingMigrationGenerator = false
var body: some Scene {
WindowGroup("DBSyn") {
WorkbenchLaunchView()
.preferredColorScheme(appearance == "dark" ? .dark : appearance == "light" ? .light : nil)
.onAppear {
if let iconURL = Bundle.main.url(forResource: "DBSyn", withExtension: "icns"),
let icon = NSImage(contentsOf: iconURL) {
NSApplication.shared.applicationIconImage = icon
}
}
.environmentObject(model)
.sheet(isPresented: $showingMigrationGenerator) { LaravelMigrationView(model: model, onClose: { showingMigrationGenerator = false }) }
.frame(minWidth: 800, minHeight: 520)
}
.defaultSize(width: 1200, height: 800)
.windowResizability(.contentSize)
.commands {
CommandGroup(replacing: .appInfo) {
Button("About DBSyn") {
let icon = NSImage(contentsOfFile: Bundle.main.path(forResource: "DBSynBrand", ofType: "png") ?? "")
NSApplication.shared.orderFrontStandardAboutPanel(options: [
.applicationName: "DBSyn",
.applicationIcon: icon ?? NSApplication.shared.applicationIconImage ?? NSImage(),
.version: Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "1.0",
.credits: NSAttributedString(string: "Native MySQL client for macOS")
])
}
}
WorkbenchFileCommands(model: model)
CommandMenu("Tools") {
Button("Generate Laravel Migration…") { showingMigrationGenerator = true }
}
}
Settings {
WorkbenchSettingsView()
}
}
}
// MARK: - Application Preferences
@MainActor
final class WorkbenchPreferences: ObservableObject {
static let shared = WorkbenchPreferences()
@AppStorage("preferences.editorFontSize")
var editorFontSize = 13.0
@AppStorage("preferences.uppercaseKeywords")
var uppercaseKeywords = true
@AppStorage("preferences.showLineNumbers")
var showLineNumbers = true
@AppStorage("preferences.tabWidth")
var tabWidth = 4
@AppStorage("preferences.resultRowHeight")
var resultRowHeight = 24.0
@AppStorage("preferences.resultColumnWidth")
var resultColumnWidth = 170.0
@AppStorage("preferences.alternatingRows")
var alternatingRows = true
@AppStorage("preferences.appearance")
var appearance = "system"
@AppStorage("preferences.autoExpandTables")
var autoExpandTables = true
}
// MARK: - Workbench Settings
struct WorkbenchSettingsView: View {
@AppStorage("preferences.editorFontSize")
private var editorFontSize = 13.0
@AppStorage("preferences.uppercaseKeywords")
private var uppercaseKeywords = true
@AppStorage("preferences.showLineNumbers")
private var showLineNumbers = true
@AppStorage("preferences.tabWidth")
private var tabWidth = 4
@AppStorage("preferences.defaultRowLimit")
private var defaultRowLimit = 500
@AppStorage("preferences.resultRowHeight")
private var resultRowHeight = 24.0
@AppStorage("preferences.resultColumnWidth")
private var resultColumnWidth = 170.0
@AppStorage("preferences.alternatingRows")
private var alternatingRows = true
@AppStorage("preferences.autoExpandTables")
private var autoExpandTables = true
@AppStorage("preferences.appearance")
private var appearance = "system"
@AppStorage("preferences.migrationFilenamePrefix")
private var migrationFilenamePrefix = "create"
@State private var selectedSection: SettingsSection? = .general
private enum SettingsSection: String, CaseIterable, Identifiable {
case general = "General"
case connections = "Connections"
case security = "Security"
case editor = "SQL Editor"
case execution = "Query Execution"
case results = "Result Grid"
case database = "Database"
case diagrams = "Schema Designer"
case laravel = "Laravel"
case administration = "Administration"
case exports = "Export & Backup"
case advanced = "Advanced"
var id: String { rawValue }
var icon: String {
switch self {
case .general: return "gearshape"
case .connections: return "server.rack"
case .security: return "lock.shield"
case .editor: return "chevron.left.forwardslash.chevron.right"
case .execution: return "bolt"
case .results: return "tablecells"
case .database: return "externaldrive"
case .diagrams: return "point.3.connected.trianglepath.dotted"
case .laravel: return "curlybraces"
case .administration: return "gauge.with.dots.needle.67percent"
case .exports: return "square.and.arrow.up"
case .advanced: return "wrench.and.screwdriver"
}
}
}
@AppStorage("preferences.diagramPalette") private var diagramPalette = "Ocean"
@AppStorage("preferences.diagramShowAllColumns") private var diagramShowAllColumns = false
@AppStorage("preferences.migrationOutputDirectory") private var migrationOutputDirectory = ""
@AppStorage("preferences.migrationUseTimestamps") private var migrationUseTimestamps = true
@AppStorage("preferences.confirmDestructiveSQL") private var confirmDestructiveSQL = true
var body: some View {
HStack(spacing: 0) {
VStack(alignment: .leading, spacing: 4) {
Text("DBSyn Settings")
.font(.headline)
.padding(.horizontal, 14)
.padding(.top, 18)
.padding(.bottom, 12)
ScrollView {
VStack(alignment: .leading, spacing: 3) {
ForEach(SettingsSection.allCases) { section in
Button {
selectedSection = section
} label: {
Label(section.rawValue, systemImage: section.icon)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 12)
.padding(.vertical, 9)
.background(
(selectedSection ?? .general) == section
? Color.accentColor.opacity(0.22)
: Color.clear,
in: RoundedRectangle(cornerRadius: 7)
)
}
.buttonStyle(.plain)
.accessibilityIdentifier("settings.section.\(section.id)")
}
}
.padding(.horizontal, 10)
}
}
.frame(width: 230)
.frame(maxHeight: .infinity, alignment: .top)
.background(Color(nsColor: .controlBackgroundColor))
Divider()
ScrollView {
Group {
switch selectedSection ?? .general {
case .general: generalSettings
case .connections: connectionsSettings
case .security: securitySettings
case .editor: editorSettings
case .execution: executionSettings
case .results: resultSettings
case .database: databaseSettings
case .diagrams: diagramSettings
case .laravel: migrationSettings
case .administration: administrationSettings
case .exports: exportSettings
case .advanced: advancedSettings
}
}
.frame(maxWidth: .infinity, alignment: .leading)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.frame(minWidth: 850, idealWidth: 1050, minHeight: 570, idealHeight: 710)
.preferredColorScheme(appearance == "dark" ? .dark : appearance == "light" ? .light : nil)
}
private func information(_ message: String) -> some View {
Text(message).font(.callout).foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
.fixedSize(horizontal: false, vertical: true)
}
private var connectionsSettings: some View {
settingsPage("Connections") {
information("Manage and switch saved connections from the main window's Connections menu. Select a profile to connect, or edit its MySQL host, port and default schema.")
information("Connection profiles and credentials are stored separately. Passwords are held in macOS Keychain.")
Button("Open Connection Manager") {
NotificationCenter.default.post(name: .dbsynOpenConnectionManager, object: nil)
// Leave window management to the Settings scene.
}
.buttonStyle(.borderedProminent)
}
}
private var securitySettings: some View {
settingsPage("Security & Transport") {
information("Each connection has independent TLS and SSH transport options. Edit a connection to configure certificate-verified TLS, or an SSH tunnel using macOS OpenSSH.")
information("Do not disable certificate verification to work around connection failures. Native TLS handshake compatibility must be validated against your server before relying on it.")
information("Passwords use the DBSyn.MySQL Keychain service. Legacy WorkbenchReplacement.MySQL credentials are read for compatibility.")
}
}
private var databaseSettings: some View {
settingsPage("Database Navigator") {
settingsRow("Expand tables") {
Toggle("Automatically expand table folders", isOn: $autoExpandTables)
}
information("Select the active database in the schema navigator. Refresh schema metadata from the main window after switching connections.")
}
}
private var diagramSettings: some View {
settingsPage("Schema Designer") {
settingsRow("Default palette") {
Picker("Palette", selection: $diagramPalette) {
ForEach(["Ocean", "Slate", "Forest", "Plum"], id: \.self) { Text($0) }
}.labelsHidden().frame(width: 200)
}
settingsRow("Columns") {
Toggle("Show all columns by default", isOn: $diagramShowAllColumns)
}
information("Diagram-specific layouts and table selections are saved from the Schema Designer. You can override the palette and column visibility there.")
}
}
private var administrationSettings: some View {
settingsPage("Administration") {
information("Server status, connections, users, variables, performance and logs are available in the Administration navigator.")
information("Server start/stop and configuration-file editing require server-level operating-system privileges and are not available through ordinary MySQL credentials.")
}
}
private var exportSettings: some View {
settingsPage("Export & Backup") {
information("SQL and CSV export, backup and restore actions are available from the main window. Choose a destination when exporting.")
information("Backups and restores can be destructive. Review the target server and schema before starting a restore.")
}
}
private var advancedSettings: some View {
settingsPage("Advanced") {
settingsRow("Safety") {
Toggle("Confirm destructive SQL operations", isOn: $confirmDestructiveSQL)
}
information("The safety preference is reserved for a future SQL confirmation integration; it does not yet intercept SQL execution.")
information("For troubleshooting, use the macOS Console and the DBSyn test runner. TLS negotiation must be tested on a macOS host.")
}
}
private var migrationSettings: some View {
settingsPage("Laravel & Developer Tools") {
Text("Generate Laravel migrations from a connected MySQL table or an imported CREATE TABLE SQL file.")
.foregroundStyle(.secondary)
settingsRow("Filename prefix") {
TextField("create", text: $migrationFilenamePrefix)
.textFieldStyle(.roundedBorder)
}
settingsRow("Output folder") {
TextField("Choose folder when saving", text: $migrationOutputDirectory)
.textFieldStyle(.roundedBorder)
}
settingsRow("Filename") {
Toggle("Use timestamp prefix", isOn: $migrationUseTimestamps)
}
Text("Open Tools → Generate Laravel Migration to preview, edit and save the generated PHP file.")
.font(.callout).foregroundStyle(.secondary)
Text("Generated migrations must be reviewed before execution. DBSyn never executes generated PHP.")
.font(.caption).foregroundStyle(.secondary)
}
}
private var editorSettings: some View {
settingsPage("SQL Editor") {
settingsRow("Font Size") {
Stepper(
"\(Int(editorFontSize)) pt",
value: $editorFontSize,
in: 10...24,
step: 1
)
.frame(width: 130)
}
settingsRow("Tab Width") {
Picker("", selection: $tabWidth) {
Text("2 spaces").tag(2)
Text("4 spaces").tag(4)
Text("8 spaces").tag(8)
}
.labelsHidden()
.frame(width: 150)
}
settingsRow("SQL Formatting") {
Toggle(
"Automatically uppercase SQL keywords",
isOn: $uppercaseKeywords
)
}
settingsRow("Editor Display") {
Toggle(
"Show line numbers",
isOn: $showLineNumbers
)
}
}
}
private var executionSettings: some View {
settingsPage("Query Execution") {
settingsRow("Default Row Limit") {
HStack(spacing: 12) {
TextField(
"",
value: $defaultRowLimit,
format: .number
)
.textFieldStyle(.roundedBorder)
.frame(width: 110)
Stepper(
"",
value: $defaultRowLimit,
in: 50...100000,
step: 50
)
.labelsHidden()
}
}
Text(
"Controls the number of rows retrieved when opening a table from the schema navigator."
)
.font(.system(size: 12))
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.leading, 186)
Divider()
.padding(.vertical, 12)
Text("Examples")
.font(.headline)
.frame(maxWidth: .infinity, alignment: .leading)
Text(
"500 rows: Faster browsing and lower memory usage.\n" +
"5,000 rows: Useful for larger datasets.\n" +
"50,000 rows: May take longer to retrieve."
)
.font(.system(size: 12))
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
private var resultSettings: some View {
settingsPage("Result Grid") {
settingsRow("Row Height") {
Stepper(
"\(Int(resultRowHeight)) px",
value: $resultRowHeight,
in: 18...40,
step: 1
)
.frame(width: 130)
}
settingsRow("Column Width") {
Stepper(
"\(Int(resultColumnWidth)) px",
value: $resultColumnWidth,
in: 80...400,
step: 10
)
.frame(width: 140)
}
settingsRow("Appearance") {
Toggle(
"Alternating row colours",
isOn: $alternatingRows
)
}
}
}
private var generalSettings: some View {
settingsPage("General") {
settingsRow("Appearance") {
Picker("Appearance", selection: $appearance) {
Text("System").tag("system")
Text("Light").tag("light")
Text("Dark").tag("dark")
}
.pickerStyle(.segmented)
.labelsHidden()
.accessibilityLabel("Application appearance")
.frame(width: 260, alignment: .leading)
}
Text(appearance == "system"
? "DBSyn follows your Mac’s appearance setting."
: appearance == "light"
? "DBSyn always uses Light appearance."
: "DBSyn always uses Dark appearance.")
.font(.caption)
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.leading, 186)
settingsRow("Navigator") {
Toggle(
"Automatically expand table folders",
isOn: $autoExpandTables
)
}
}
}
private func settingsPage<Content: View>(
_ title: String,
@ViewBuilder content: () -> Content
) -> some View {
VStack(alignment: .leading, spacing: 18) {
Text(title)
.font(.system(size: 20, weight: .semibold))
Divider()
content()
Spacer(minLength: 0)
}
.padding(26)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color(nsColor: .windowBackgroundColor))
}
private func settingsRow<Content: View>(
_ title: String,
@ViewBuilder content: () -> Content
) -> some View {
HStack(alignment: .center, spacing: 16) {
Text(title)
.font(.system(size: 13))
.frame(width: 170, alignment: .leading)
content()
Spacer(minLength: 0)
}
.frame(height: 30)
}
}
// MARK: - Workbench Splash Screen
struct WorkbenchLaunchView: View {
@State private var showingSplash = true
var body: some View {
ZStack {
MainView()
if showingSplash {
WorkbenchSplashView()
.transition(.opacity)
.zIndex(10)
}
}
.task {
try? await Task.sleep(for: .seconds(3))
withAnimation(.easeOut(duration: 0.4)) {
showingSplash = false
}
}
}
}
struct WorkbenchSplashView: View {
var body: some View {
VStack(spacing: 20) {
Spacer()
if let path = Bundle.main.path(
forResource: "DBSynBrand",
ofType: "png"
), let image = NSImage(contentsOfFile: path) {
Image(nsImage: image)
.resizable()
.scaledToFit()
.frame(width: 160, height: 160)
}
Text("DBSyn")
.font(.system(size: 30, weight: .bold))
Text("Modern MySQL Client for macOS")
.font(.system(size: 15))
.foregroundStyle(.secondary)
HStack(spacing: 30) {
feature("cylinder.split.1x2", "Schemas")
feature("chevron.left.forwardslash.chevron.right", "SQL Editor")
feature("tablecells", "Result Grid")
feature("server.rack", "Connections")
}
.padding(.top, 20)
Spacer()
ProgressView()
.frame(width: 260)
Text("Starting DBSyn…")
.font(.caption)
.foregroundStyle(.secondary)
.padding(.bottom, 30)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(
LinearGradient(
colors: [
Color(nsColor: .windowBackgroundColor),
Color(nsColor: .controlBackgroundColor)
],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
}
private func feature(_ symbol: String, _ title: String) -> some View {
VStack(spacing: 8) {
Image(systemName: symbol)
.font(.system(size: 24))
.foregroundStyle(.blue)
Text(title)
.font(.system(size: 12))
}
.frame(width: 90)
}
}
// MARK: - Workbench File Menu
struct WorkbenchFileCommands: Commands {
@ObservedObject var model: DatabaseModel
var body: some Commands {
CommandGroup(replacing: .newItem) {
Button("New Query Tab") {
model.newQueryTab()
NotificationCenter.default.post(name: .workbenchRevealSQLEditor, object: nil)
}
.keyboardShortcut("t", modifiers: .command)
}
CommandGroup(after: .newItem) {
Button("Open SQL Script…") {
model.openSQLScript()
}
.keyboardShortcut("o", modifiers: .command)
Menu("Open Recent SQL Script") {
if model.recentSQLScripts.isEmpty {
Text("No Recent Scripts")
} else {
ForEach(model.recentSQLScripts, id: \.self) { path in
Button(URL(fileURLWithPath: path).lastPathComponent) {
model.openRecentSQLScript(at: path)
}
.help(path)
}
Divider()
Button("Clear Recent Scripts") { model.clearRecentSQLScripts() }
}
}
Divider()
Button("Save SQL Script") {
model.saveSQLScript()
}
.keyboardShortcut("s", modifiers: .command)
Button("Save SQL Script As…") {
model.saveSQLScript(asNewFile: true)
}
.keyboardShortcut("s", modifiers: [.command, .shift])
}
}
}
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
import Foundation
import CoreGraphics
/// Pure schema-diagram operations shared by the UI and XCTest suite.
enum DiagramLogic {
static func selectedObjects(_ requested: Set<String>, available: [String]) -> Set<String> {
requested.intersection(Set(available))
}
static func arrangedPositions(_ names: [String]) -> [String: CGPoint] {
let countPerRow = max(1, Int(ceil(sqrt(Double(max(1, names.count))))))
var positions: [String: CGPoint] = [:]
for (index, name) in names.enumerated() {
positions[name] = CGPoint(x: CGFloat(index % countPerRow) * 290 + 30,
y: CGFloat(index / countPerRow) * 300 + 30)
}
return positions
}
static func translatedPosition(_ origin: CGPoint, translation: CGSize, zoom: CGFloat) -> CGPoint {
let scale = max(zoom, 0.1)
return CGPoint(x: origin.x + translation.width / scale,
y: origin.y + translation.height / scale)
}
}
+291
View File
@@ -0,0 +1,291 @@
import SwiftUI
import AppKit
import UniformTypeIdentifiers
// Pure conversion engine; can be exercised independently of the UI.
enum LaravelMigrationGenerator {
static func generate(_ ddl: String) throws -> String {
guard let match = ddl.range(of: #"(?is)CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?`?([\w]+)`?\s*\("#, options: .regularExpression) else {
throw NSError(domain: "DBSyn.Migration", code: 1, userInfo: [NSLocalizedDescriptionKey: "No CREATE TABLE definition found. Supply MySQL CREATE TABLE SQL."])
}
let prefix = String(ddl[match]); let name = prefix.replacingOccurrences(of: #"(?is)^.*?CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?"#, with: "", options: .regularExpression).replacingOccurrences(of: "(", with: "").replacingOccurrences(of: "`", with: "").trimmingCharacters(in: .whitespacesAndNewlines)
let start = match.upperBound
var depth = 1; var end = start; var quote: Character? = nil
for index in ddl[start...].indices {
let c = ddl[index]
if let q = quote { if c == q { quote = nil }; continue }
if c == "'" || c == "\"" || c == "`" { quote = c; continue }
if c == "(" { depth += 1 }; if c == ")" { depth -= 1; if depth == 0 { end = index; break } }
}
guard depth == 0 else { throw NSError(domain: "DBSyn.Migration", code: 2, userInfo: [NSLocalizedDescriptionKey: "Unbalanced CREATE TABLE parentheses."]) }
let body = String(ddl[start..<end]); var lines: [String] = []
let definitions = splitColumns(body)
let primaryKeys = definitions.filter { $0.trimmingCharacters(in: .whitespacesAndNewlines).uppercased().hasPrefix("PRIMARY KEY") }
.flatMap { backtickNames($0) }
let autoPrimary = definitions.contains { $0.uppercased().contains("AUTO_INCREMENT") } && primaryKeys.count == 1
for definition in definitions {
let item = definition.trimmingCharacters(in: .whitespacesAndNewlines)
let upper = item.uppercased()
if upper.hasPrefix("PRIMARY KEY") {
if autoPrimary { continue }
let columns = backtickNames(item)
if columns.count == 1 {
lines.append(" $table->primary('\(columns[0])');")
} else if !columns.isEmpty {
let quotedColumns = columns.map { "'\($0)'" }.joined(separator: ", ")
lines.append(" $table->primary([\(quotedColumns)]);")
}
continue
}
if upper.hasPrefix("UNIQUE KEY") || upper.hasPrefix("UNIQUE INDEX") || upper.hasPrefix("KEY ") || upper.hasPrefix("INDEX ") {
let columns = backtickNames(item)
if columns.count > 1 {
let columnNames = Array(columns.dropFirst())
let quotedColumns = columnNames.map { "'\($0)'" }.joined(separator: ", ")
let indexMethod = upper.hasPrefix("UNIQUE") ? "unique" : "index"
lines.append(" $table->\(indexMethod)([\(quotedColumns)]);")
}
continue
}
if upper.hasPrefix("CONSTRAINT") || upper.hasPrefix("FOREIGN KEY") || upper.hasPrefix("CHECK") { lines.append(" // Review manually: " + item.replacingOccurrences(of: "\n", with: " ")); continue }
guard let column = item.range(of: #"^`?([A-Za-z_][\w]*)`?\s+"#, options: .regularExpression) else { continue }
let col = String(item[column]).trimmingCharacters(in: .whitespaces).replacingOccurrences(of: "`", with: "")
let spec = String(item[column.upperBound...]); let type = spec.lowercased()
let method: String
if type.hasPrefix("bigint") { method = "bigInteger" }
else if type.hasPrefix("tinyint(1)") || type.hasPrefix("boolean") { method = "boolean" }
else if type.hasPrefix("tinyint") { method = "tinyInteger" }
else if type.hasPrefix("smallint") { method = "smallInteger" }
else if type.hasPrefix("mediumint") { method = "mediumInteger" }
else if type.hasPrefix("int") || type.hasPrefix("integer") { method = "integer" }
else if type.hasPrefix("varchar") { method = "string" }
else if type.hasPrefix("char") { method = "char" }
else if type.hasPrefix("longtext") { method = "longText" }
else if type.hasPrefix("mediumtext") { method = "mediumText" }
else if type.hasPrefix("text") { method = "text" }
else if type.hasPrefix("datetime") { method = "dateTime" }
else if type.hasPrefix("timestamp") { method = "timestamp" }
else if type.hasPrefix("date") { method = "date" }
else if type.hasPrefix("time") { method = "time" }
else if type.hasPrefix("json") { method = "json" }
else if type.hasPrefix("double") { method = "double" }
else if type.hasPrefix("float") { method = "float" }
else if type.hasPrefix("decimal") { method = "decimal" }
else if type.hasPrefix("blob") { method = "binary" }
else { method = "string" }
var args = "'\(col)'"
if method == "string" || method == "char", let range = type.range(of: #"\(\s*\d+\s*\)"#, options: .regularExpression) { args += ", " + type[range].filter(\.isNumber) }
if method == "decimal", let range = type.range(of: #"\(\s*\d+\s*,\s*\d+\s*\)"#, options: .regularExpression) { args += ", " + type[range].dropFirst().dropLast() }
if upper.contains("AUTO_INCREMENT") && primaryKeys == [col] && method == "bigInteger" {
lines.append(" $table->id('\(col)');")
continue
}
var expr = " $table->\(method)(\(args))"
if upper.contains("UNSIGNED") { expr += "->unsigned()" }
if !upper.contains("NOT NULL") { expr += "->nullable()" }
if upper.contains("AUTO_INCREMENT") { expr += "->autoIncrement()" }
lines.append(expr + ";")
}
let safeName = name.replacingOccurrences(of: "'", with: "\\'")
return """
<?php
use Illuminate\\Database\\Migrations\\Migration;
use Illuminate\\Database\\Schema\\Blueprint;
use Illuminate\\Support\\Facades\\Schema;
return new class extends Migration {
public function up(): void
{
Schema::create('\(safeName)', function (Blueprint $table) {
\(lines.joined(separator: "\n"))
});
}
public function down(): void
{
Schema::dropIfExists('\(safeName)');
}
};
"""
}
private static func backtickNames(_ text: String) -> [String] {
guard let regex = try? NSRegularExpression(pattern: "`([^`]+)`") else { return [] }
let ns = text as NSString
return regex.matches(in: text, range: NSRange(location: 0, length: ns.length)).map { ns.substring(with: $0.range(at: 1)) }
}
private static func splitColumns(_ text: String) -> [String] {
var parts: [String] = []; var current = ""; var depth = 0; var quote: Character? = nil
for c in text {
if let q = quote { current.append(c); if c == q { quote = nil }; continue }
if c == "'" || c == "\"" || c == "`" { quote = c; current.append(c); continue }
if c == "(" { depth += 1 }; if c == ")" { depth -= 1 }
if c == "," && depth == 0 { parts.append(current); current = "" } else { current.append(c) }
}
if !current.isEmpty { parts.append(current) }; return parts
}
}
struct LaravelMigrationView: View {
@ObservedObject var model: DatabaseModel
@AppStorage("preferences.migrationFilenamePrefix") private var prefix = "create"
@State private var sourceSQL = ""
@State private var generated = ""
@State private var errorText = ""
@State private var tableName = ""
@State private var chosenDatabase = ""
@State private var isLoading = false
@State private var tableSearch = ""
@State private var statusText = ""
var onClose: () -> Void = {}
var body: some View {
VStack(alignment: .leading, spacing: 14) {
HStack {
Image(systemName: "curlybraces.square").font(.title2).foregroundStyle(.tint)
VStack(alignment: .leading) {
Text("Laravel Migration Generator").font(.title2.bold())
Text("Convert a MySQL table or CREATE TABLE SQL into a Laravel migration.").foregroundStyle(.secondary)
}
Spacer()
Button(action: onClose) { Image(systemName: "xmark.circle.fill").font(.title2).foregroundStyle(.secondary) }
.buttonStyle(.plain).help("Close Migration Generator")
}
VStack(alignment: .leading, spacing: 10) {
HStack(spacing: 12) {
Text("Database").frame(width: 80, alignment: .leading)
Picker("Database", selection: $chosenDatabase) {
Text("Select database…").tag("")
ForEach(databaseOptions, id: \.self) { name in
Text(name).tag(name)
}
}
.labelsHidden()
.frame(maxWidth: 330)
Button("Use Current Database") {
chosenDatabase = model.completionDatabase
}
.disabled(model.completionDatabase.isEmpty)
Spacer()
}
HStack(spacing: 12) {
Text("Table").frame(width: 80, alignment: .leading)
TextField("Enter a table name", text: $tableName)
.frame(maxWidth: 330)
if !availableTables.isEmpty {
Menu {
TextField("Search tables", text: $tableSearch)
Divider()
ForEach(filteredTables.prefix(100), id: \.self) { name in
Button(name) { tableName = name }
}
if filteredTables.isEmpty { Text("No matching tables") }
} label: {
Label("Browse Tables (\(availableTables.count))", systemImage: "magnifyingglass")
}
.frame(maxWidth: 230)
}
Button(isLoading ? "Loading…" : "Load Table") { loadTable() }
.disabled(tableName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || chosenDatabase.isEmpty || model.selected == nil || isLoading)
Button("Open SQL File…") { openFile() }
Spacer()
}
if chosenDatabase.isEmpty {
Text("Choose a database before loading a table. SQL files can be opened without a connection.")
.font(.caption).foregroundStyle(.secondary)
}
}
HSplitView {
VStack(alignment: .leading) {
Text("MySQL CREATE TABLE").font(.headline)
TextEditor(text: $sourceSQL).font(.system(.body, design: .monospaced)).frame(minWidth: 330)
}
VStack(alignment: .leading) {
Text("Laravel Migration · PHP").font(.headline)
TextEditor(text: $generated).font(.system(.body, design: .monospaced)).frame(minWidth: 330)
}
}
if !errorText.isEmpty { Text(errorText).foregroundStyle(.red).font(.callout) }
if !statusText.isEmpty { Text(statusText).foregroundStyle(.green).font(.callout) }
HStack {
Text("Review indexes, foreign keys, defaults and unsupported MySQL types before running a migration.").font(.caption).foregroundStyle(.secondary)
Spacer()
Button("Generate / Refresh") { generate() }.buttonStyle(.borderedProminent)
.disabled(sourceSQL.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
Button("Copy PHP") { NSPasteboard.general.clearContents(); NSPasteboard.general.setString(generated, forType: .string) }.disabled(generated.isEmpty)
Button("Save Migration…") { save() }.disabled(generated.isEmpty)
}
}
.padding(22)
.frame(minWidth: 980, minHeight: 650)
.onAppear {
if chosenDatabase.isEmpty {
chosenDatabase = model.completionDatabase
}
}
.onChange(of: chosenDatabase) { _, _ in tableName = ""; tableSearch = "" }
.onChange(of: sourceSQL) { _, _ in statusText = "Source changed — select Generate / Refresh to update PHP." }
}
private var databaseOptions: [String] {
Array(Set(model.databases + model.schemaTables.keys + [model.completionDatabase].filter { !$0.isEmpty })).sorted()
}
private var availableTables: [String] {
model.schemaTables[chosenDatabase] ?? []
}
private var filteredTables: [String] {
let query = tableSearch.trimmingCharacters(in: .whitespacesAndNewlines)
return query.isEmpty ? availableTables : availableTables.filter { $0.localizedCaseInsensitiveContains(query) }
}
private func generate() {
do {
generated = try LaravelMigrationGenerator.generate(sourceSQL)
errorText = ""
statusText = "Migration generated — review the PHP, then Copy or Save."
} catch {
errorText = error.localizedDescription
statusText = ""
}
}
private func openFile() {
let panel = NSOpenPanel(); panel.allowedContentTypes = [UTType(filenameExtension: "sql") ?? .plainText, .plainText]; panel.allowsMultipleSelection = false
if panel.runModal() == .OK, let url = panel.url {
do { sourceSQL = try String(contentsOf: url, encoding: .utf8); generate() }
catch { errorText = error.localizedDescription }
}
}
private func loadTable() {
guard let profile = model.selected else { return }
isLoading = true
let db = chosenDatabase.trimmingCharacters(in: .whitespacesAndNewlines)
guard !db.isEmpty else {
errorText = "Select a database before loading a table."
isLoading = false
return
}
let escaped = tableName.trimmingCharacters(in: .whitespacesAndNewlines).replacingOccurrences(of: "`", with: "``")
let escapedDB = db.replacingOccurrences(of: "`", with: "``")
errorText = ""
Task {
do {
let result = try await NativeMySQLRunner.run(profile: profile, database: db, password: PasswordStore.read(profile.id), sql: "SHOW CREATE TABLE `\(escapedDB)`.`\(escaped)`")
guard let ddl = result.rows.first?.last, !ddl.isEmpty else { throw NSError(domain: "DBSyn.Migration", code: 3, userInfo: [NSLocalizedDescriptionKey: "No table definition returned."]) }
sourceSQL = ddl; generate()
} catch { errorText = "Could not load \(db).\(tableName): \(error.localizedDescription)" }
isLoading = false
}
}
private func save() {
let panel = NSSavePanel(); panel.allowedContentTypes = [UTType(filenameExtension: "php") ?? .plainText]
let name = tableName.isEmpty ? "table" : tableName.lowercased()
let timestamp = DateFormatter()
timestamp.dateFormat = "yyyy_MM_dd_HHmmss"
let useTimestamps = UserDefaults.standard.object(forKey: "preferences.migrationUseTimestamps") as? Bool ?? true
let datePrefix = useTimestamps ? timestamp.string(from: Date()) + "_" : ""
panel.nameFieldStringValue = "\(datePrefix)\(prefix)_\(name)_table.php"
let folder = UserDefaults.standard.string(forKey: "preferences.migrationOutputDirectory") ?? ""
if !folder.isEmpty { panel.directoryURL = URL(fileURLWithPath: (folder as NSString).expandingTildeInPath, isDirectory: true) }
if panel.runModal() == .OK, let url = panel.url {
do { try generated.write(to: url, atomically: true, encoding: .utf8); errorText = ""; statusText = "Saved: \(url.lastPathComponent)" }
catch { errorText = error.localizedDescription }
}
}
}
+8196
View File
File diff suppressed because it is too large Load Diff
+746
View File
@@ -0,0 +1,746 @@
import Foundation
import CommonCrypto
import CoreFoundation
import CFNetwork
import Security
import Darwin
// Native MySQL wire protocol client. No external mysql executable is used.
// Initial scope: TCP, mysql_native_password and caching_sha2_password fast authentication.
struct MySQLWireError: LocalizedError {
let message: String
var errorDescription: String? { message }
}
// SSH transport uses the system OpenSSH client and key-based authentication.
// No remote MySQL password is passed to ssh. SSH host-key verification remains enabled.
private final class DBSynSSHTunnels {
static let shared = DBSynSSHTunnels()
private let lock = NSLock()
private var running: [UUID: (Process, ConnectionProfile, Int)] = [:]
func endpoint(for profile: ConnectionProfile) throws -> (String, Int) {
guard profile.useSSHTunnel else { return (profile.host, profile.port) }
lock.lock(); defer { lock.unlock() }
if let current = running[profile.id] {
if current.0.isRunning && current.1 == profile { return ("127.0.0.1", current.2) }
if current.0.isRunning { current.0.terminate() }
running.removeValue(forKey: profile.id)
}
guard !profile.sshHost.isEmpty, !profile.sshUser.isEmpty else {
throw MySQLWireError(message: "Configure SSH hostname and SSH username in Connection Settings")
}
// A stable local port per connection; ExitOnForwardFailure rejects conflicts.
let bytes = Array(profile.id.uuidString.utf8)
let checksum = bytes.reduce(0) { ($0 &* 31 &+ Int($1)) % 10000 }
let localPort = 45000 + checksum
let process = Process()
process.executableURL = URL(fileURLWithPath: "/usr/bin/ssh")
var args = ["-N", "-T", "-o", "BatchMode=yes", "-o", "ExitOnForwardFailure=yes", "-o", "ConnectTimeout=8", "-o", "StrictHostKeyChecking=yes", "-p", String(profile.sshPort)]
if !profile.sshIdentityFile.isEmpty { args += ["-i", (profile.sshIdentityFile as NSString).expandingTildeInPath] }
args += ["-L", "127.0.0.1:\(localPort):\(profile.host):\(profile.port)", "\(profile.sshUser)@\(profile.sshHost)"]
process.arguments = args
process.standardInput = FileHandle.nullDevice
process.standardOutput = FileHandle.nullDevice
process.standardError = FileHandle.nullDevice
do { try process.run() } catch { throw MySQLWireError(message: "Cannot launch SSH: \(error.localizedDescription)") }
// SSH may take several seconds to authenticate. A running process does
// not mean its local listener is ready. Probe the forwarded endpoint
// before handing it to the MySQL handshake.
let deadline = Date().addingTimeInterval(10)
var ready = false
while Date() < deadline && process.isRunning {
let probe = Process()
probe.executableURL = URL(fileURLWithPath: "/usr/bin/nc")
probe.arguments = ["-z", "-G", "1", "127.0.0.1", String(localPort)]
probe.standardInput = FileHandle.nullDevice
probe.standardOutput = FileHandle.nullDevice
probe.standardError = FileHandle.nullDevice
if (try? probe.run()) != nil {
probe.waitUntilExit()
if probe.terminationStatus == 0 { ready = true; break }
}
Thread.sleep(forTimeInterval: 0.15)
}
guard ready else {
if process.isRunning { process.terminate() }
throw MySQLWireError(message: "SSH forwarding did not become ready within 10 seconds (local port \(localPort)). Check SSH credentials, host key and target address.")
}
running[profile.id] = (process, profile, localPort)
return ("127.0.0.1", localPort)
}
func stop(_ id: UUID) {
lock.lock(); defer { lock.unlock() }
if let existing = running.removeValue(forKey: id), existing.0.isRunning { existing.0.terminate() }
}
}
private struct ByteCursor {
let bytes: [UInt8]
var offset = 0
mutating func take(_ n: Int) throws -> [UInt8] {
guard n >= 0, offset + n <= bytes.count else { throw MySQLWireError(message: "Malformed MySQL packet") }
defer { offset += n }
return Array(bytes[offset..<(offset+n)])
}
mutating func u8() throws -> UInt8 { try take(1)[0] }
mutating func u16() throws -> Int { let b = try take(2); return Int(b[0]) | (Int(b[1]) << 8) }
mutating func u32() throws -> Int { let b = try take(4); return Int(b[0]) | (Int(b[1]) << 8) | (Int(b[2]) << 16) | (Int(b[3]) << 24) }
mutating func nulString() throws -> String {
let start = offset
while offset < bytes.count && bytes[offset] != 0 { offset += 1 }
guard offset < bytes.count else { throw MySQLWireError(message: "Unterminated server string") }
let s = String(decoding: bytes[start..<offset], as: UTF8.self)
offset += 1
return s
}
mutating func lenInt() throws -> Int? {
let first = try u8()
switch first {
case 0xfb: return nil
case 0xfc: return try u16()
case 0xfd:
let b = try take(3); return Int(b[0]) | Int(b[1]) << 8 | Int(b[2]) << 16
case 0xfe:
let b = try take(8)
var v: UInt64 = 0
for (i, byte) in b.enumerated() { v |= UInt64(byte) << (8 * i) }
guard v <= UInt64(Int.max) else { throw MySQLWireError(message: "Field too large") }
return Int(v)
default: return Int(first)
}
}
mutating func lenString() throws -> String? {
guard let n = try lenInt() else { return nil }
return String(decoding: try take(n), as: UTF8.self)
}
}
private func le32(_ v: Int) -> [UInt8] { (0..<4).map { UInt8(truncatingIfNeeded: v >> ($0 * 8)) } }
private func sha1(_ data: [UInt8]) -> [UInt8] {
var result = [UInt8](repeating: 0, count: Int(CC_SHA1_DIGEST_LENGTH))
data.withUnsafeBytes { ptr in _ = CC_SHA1(ptr.baseAddress, CC_LONG(data.count), &result) }
return result
}
private func sha256(_ data: [UInt8]) -> [UInt8] {
var result = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
data.withUnsafeBytes { ptr in _ = CC_SHA256(ptr.baseAddress, CC_LONG(data.count), &result) }
return result
}
private func authResponse(password: String, salt: [UInt8], plugin: String) throws -> [UInt8] {
if password.isEmpty { return [] }
let pwd = Array(password.utf8)
if plugin == "mysql_native_password" {
let first = sha1(pwd), second = sha1(sha1(pwd))
let last = sha1(salt + second)
return zip(first, last).map { $0 ^ $1 }
}
if plugin == "caching_sha2_password" {
// MySQL caching_sha2_password authentication:
// XOR(SHA256(password),
// SHA256(SHA256(SHA256(password)) + challenge))
let first = sha256(pwd)
let second = sha256(first)
let third = sha256(second)
let last = sha256(third + salt)
return zip(first, last).map { $0 ^ $1 }
}
throw MySQLWireError(message: "Authentication plugin \(plugin) is not yet supported")
}
// Parse an RSA SubjectPublicKeyInfo PEM supplied by MySQL. Security.framework
// expects the inner PKCS#1 RSAPublicKey DER for SecKeyCreateWithData.
private func mysqlRSAPublicKey(_ packet: [UInt8]) throws -> SecKey {
guard packet.first == 0x01 else {
throw MySQLWireError(message: "Expected caching_sha2_password public-key response")
}
let pem = String(decoding: packet.dropFirst(), as: UTF8.self)
let base64 = pem.components(separatedBy: .newlines)
.filter { !$0.hasPrefix("-----") }.joined()
.trimmingCharacters(in: .whitespacesAndNewlines)
guard let spki = Data(base64Encoded: base64) else {
throw MySQLWireError(message: "Invalid MySQL RSA public key")
}
let der = [UInt8](spki)
func length(_ index: inout Int) throws -> Int {
guard index < der.count else { throw MySQLWireError(message: "Invalid RSA DER length") }
let first = Int(der[index]); index += 1
if first < 128 { return first }
let bytes = first & 0x7f
guard bytes > 0, bytes <= 4, index + bytes <= der.count else {
throw MySQLWireError(message: "Invalid RSA DER length")
}
var value = 0
for _ in 0..<bytes { value = (value << 8) | Int(der[index]); index += 1 }
return value
}
func element(_ index: inout Int, tag: UInt8) throws -> Range<Int> {
guard index < der.count, der[index] == tag else {
throw MySQLWireError(message: "Invalid RSA DER element")
}
index += 1
let count = try length(&index)
guard count <= der.count - index else { throw MySQLWireError(message: "Truncated RSA DER") }
let range = index..<(index + count)
index += count
return range
}
var i = 0
let outer = try element(&i, tag: 0x30)
i = outer.lowerBound
_ = try element(&i, tag: 0x30) // AlgorithmIdentifier
let bits = try element(&i, tag: 0x03)
guard bits.count > 1, der[bits.lowerBound] == 0 else {
throw MySQLWireError(message: "Invalid RSA public key bit string")
}
let pkcs1 = Data(der[(bits.lowerBound + 1)..<bits.upperBound])
var error: Unmanaged<CFError>?
guard let key = SecKeyCreateWithData(pkcs1 as CFData, [
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeyClass as String: kSecAttrKeyClassPublic
] as CFDictionary, &error) else {
throw MySQLWireError(message: "Cannot import MySQL RSA public key: \(error?.takeRetainedValue().localizedDescription ?? "unknown error")")
}
return key
}
// SecureTransport operates on the same socket before and after MySQL SSLRequest.
// CFStream SSL properties cannot reliably retrofit TLS onto already-open streams.
private final class MySQLSocketTransport {
let fd: Int32
private var ssl: SSLContext?
init(host: String, port: Int) throws {
var hints = addrinfo()
hints.ai_family = AF_UNSPEC
hints.ai_socktype = Int32(SOCK_STREAM)
hints.ai_protocol = Int32(IPPROTO_TCP)
var addresses: UnsafeMutablePointer<addrinfo>?
let result = getaddrinfo(host, String(port), &hints, &addresses)
guard result == 0 else { throw MySQLWireError(message: "Cannot resolve MySQL host: \(String(cString: gai_strerror(result)))") }
defer { freeaddrinfo(addresses) }
var candidate = addresses
var connected: Int32 = -1
while let item = candidate {
let info = item.pointee
let sock = Darwin.socket(info.ai_family, info.ai_socktype, info.ai_protocol)
if sock >= 0 {
if Darwin.connect(sock, info.ai_addr, info.ai_addrlen) == 0 {
connected = sock
break
}
_ = Darwin.close(sock)
}
candidate = info.ai_next
}
guard connected >= 0 else { throw MySQLWireError(message: "Cannot connect to MySQL at \(host):\(port): \(String(cString: strerror(errno)))") }
fd = connected
}
deinit {
if let ssl { _ = SSLClose(ssl) }
_ = Darwin.close(fd)
}
func read(_ buffer: UnsafeMutablePointer<UInt8>, count: Int) throws -> Int {
if let ssl {
var processed = 0
let status = SSLRead(ssl, buffer, count, &processed)
if status == errSecSuccess || (status == errSSLWouldBlock && processed > 0) { return processed }
throw MySQLWireError(message: "MySQL TLS read failed (OSStatus \(status))")
}
let n = Darwin.recv(fd, buffer, count, 0)
guard n > 0 else { throw MySQLWireError(message: n == 0 ? "MySQL connection closed" : "MySQL read failed: \(String(cString: strerror(errno)))") }
return n
}
func write(_ buffer: UnsafePointer<UInt8>, count: Int) throws -> Int {
if let ssl {
var processed = 0
let status = SSLWrite(ssl, buffer, count, &processed)
if status == errSecSuccess || (status == errSSLWouldBlock && processed > 0) { return processed }
throw MySQLWireError(message: "MySQL TLS write failed (OSStatus \(status))")
}
let n = Darwin.send(fd, buffer, count, 0)
guard n > 0 else { throw MySQLWireError(message: "MySQL write failed: \(String(cString: strerror(errno)))") }
return n
}
func startTLS(host: String, verify: Bool) throws {
NSLog("DBSyn TLS: host=%@ verify=%@", host, verify ? "ON" : "OFF")
guard let context = SSLCreateContext(nil, .clientSide, .streamType) else {
throw MySQLWireError(message: "Unable to create MySQL TLS context")
}
// SecureTransport callback connections refer to this retained transport.
let connection = Unmanaged.passUnretained(self).toOpaque()
var status = SSLSetIOFuncs(context, { connection, data, length in
let transport = Unmanaged<MySQLSocketTransport>.fromOpaque(connection).takeUnretainedValue()
let requested = length.pointee
let n = Darwin.recv(transport.fd, data, requested, 0)
if n > 0 { length.pointee = n; return errSecSuccess }
length.pointee = 0
return n == 0 ? errSSLClosedGraceful : errSecIO
}, { connection, data, length in
let transport = Unmanaged<MySQLSocketTransport>.fromOpaque(connection).takeUnretainedValue()
let n = Darwin.send(transport.fd, data, length.pointee, 0)
if n > 0 { length.pointee = n; return errSecSuccess }
length.pointee = 0
return errSecIO
})
guard status == errSecSuccess else { throw MySQLWireError(message: "TLS I/O setup failed (\(status))") }
status = SSLSetConnection(context, connection)
guard status == errSecSuccess else { throw MySQLWireError(message: "TLS socket setup failed (\(status))") }
status = host.withCString { SSLSetPeerDomainName(context, $0, host.utf8.count) }
guard status == errSecSuccess else { throw MySQLWireError(message: "TLS peer setup failed (\(status))") }
// REQUIRED encrypts but does not authenticate the server. Keep verification
// enabled for the verified mode; never silently fall back to REQUIRED.
// Certificate verification is handled after the TLS handshake.
status = SSLSetSessionOption(context, .breakOnServerAuth, true)
guard status == errSecSuccess else {
throw MySQLWireError(message: "TLS authentication setup failed (\(status))")
}
repeat {
status = SSLHandshake(context)
} while status == errSSLWouldBlock
if status == OSStatus(-9841) {
if verify {
var trust: SecTrust?
let trustStatus = SSLCopyPeerTrust(context, &trust)
guard trustStatus == errSecSuccess, let trust else {
throw MySQLWireError(message: "Unable to obtain MySQL server trust")
}
var trustError: CFError?
guard SecTrustEvaluateWithError(trust, &trustError) else {
throw MySQLWireError(
message: "MySQL server verification failed (verifyTLS=\(verify)): \(trustError?.localizedDescription ?? "untrusted server")"
)
}
}
repeat {
status = SSLHandshake(context)
} while status == errSSLWouldBlock
}
guard status == errSecSuccess else {
throw MySQLWireError(message: "MySQL TLS handshake failed (OSStatus \(status))")
}
ssl = context
}
}
final class MySQLWireConnection {
private let transport: MySQLSocketTransport
private var sequence: UInt8 = 0
private let maxPacket = 16_777_215
private let serverHost: String
private let requireTLS: Bool
private let verifyTLS: Bool
private var tlsActive = false
init(host: String, port: Int, requireTLS: Bool = false, verifyTLS: Bool = true) throws {
self.serverHost = host
self.requireTLS = requireTLS
self.verifyTLS = verifyTLS
guard (1...65535).contains(port) else { throw MySQLWireError(message: "Invalid port") }
transport = try MySQLSocketTransport(host: host, port: port)
}
private func readExact(_ count: Int) throws -> [UInt8] {
var buffer = [UInt8](repeating: 0, count: count)
var done = 0
while done < count {
let n = try buffer.withUnsafeMutableBufferPointer { ptr -> Int in
try transport.read(ptr.baseAddress!.advanced(by: done), count: count - done)
}
done += n
}
return buffer
}
private func readPacket() throws -> [UInt8] {
let header = try readExact(4)
let count = Int(header[0]) | Int(header[1]) << 8 | Int(header[2]) << 16
sequence = header[3] &+ 1
guard count <= maxPacket else { throw MySQLWireError(message: "MySQL packet exceeds protocol limit") }
return try readExact(count)
}
private func send(_ payload: [UInt8]) throws {
guard payload.count < maxPacket else { throw MySQLWireError(message: "Query exceeds initial client packet limit") }
let count = payload.count
let packet = [UInt8(count & 255), UInt8((count >> 8) & 255), UInt8((count >> 16) & 255), sequence] + payload
sequence = sequence &+ 1
var written = 0
while written < packet.count {
let n = try packet.withUnsafeBufferPointer { ptr -> Int in
try transport.write(ptr.baseAddress!.advanced(by: written), count: packet.count - written)
}
written += n
}
}
private func upgradeToTLS() throws {
try transport.startTLS(host: serverHost, verify: verifyTLS)
tlsActive = true
}
private func checkError(_ p: [UInt8]) throws {
guard p.first == 0xff else { return }
let details = String(decoding: p.dropFirst(3), as: UTF8.self)
throw MySQLWireError(message: "MySQL error: \(details)")
}
func authenticate(user: String, password: String, database: String) throws {
let hello = try readPacket()
var c = ByteCursor(bytes: hello)
let version = try c.u8()
guard version == 10 else { throw MySQLWireError(message: "Unsupported MySQL handshake") }
_ = try c.nulString()
_ = try c.u32()
let salt1 = try c.take(8)
_ = try c.u8()
let lower = try c.u16()
_ = try c.u8() // character set
_ = try c.u16() // status
let upper = try c.u16()
let capabilities = lower | (upper << 16)
let authLen = Int(try c.u8())
_ = try c.take(10)
let salt2 = try c.take(max(13, authLen - 8))
let salt = salt1 + salt2.prefix(max(0, authLen - 9))
// MySQL authentication uses the 20-byte server challenge.
guard salt.count == 20 else {
throw MySQLWireError(
message: "Invalid MySQL authentication challenge length: \(salt.count)"
)
}
let plugin = (try? c.nulString()) ?? "mysql_native_password"
NSLog(
"DBSyn MySQL authentication: plugin=%@ saltLength=%d tlsRequired=%@",
plugin,
salt.count,
requireTLS ? "YES" : "NO"
)
if requireTLS {
let clientSSL = 0x00000800
guard capabilities & clientSSL != 0 else {
throw MySQLWireError(message: "MySQL server does not advertise TLS; refusing to send credentials")
}
let sslFlags = 0x00000001 | 0x00000004 | 0x00000200 | 0x00000800 | 0x00002000 | 0x00008000 | 0x00080000
let request = le32(sslFlags & capabilities) + le32(16_777_216) + [UInt8(45)] + [UInt8](repeating: 0, count: 23)
try send(request)
try upgradeToTLS()
} else if !hostIsLoopback {
throw MySQLWireError(message: "Remote connections require verified TLS or an SSH tunnel")
}
// CLIENT_LONG_PASSWORD, LONG_FLAG, PROTOCOL_41, TRANSACTIONS,
// SECURE_CONNECTION and PLUGIN_AUTH. Do not set DEPRECATE_EOF.
let clientFlags = 0x00000001 | 0x00000004 | 0x00000200 | (requireTLS ? 0x00000800 : 0) | 0x00002000 | 0x00008000 | 0x00080000
let response = try authResponse(password: password, salt: Array(salt), plugin: plugin)
var body = le32(clientFlags & capabilities) + le32(16_777_216) + [UInt8(45)] + [UInt8](repeating: 0, count: 23)
body += Array(user.utf8) + [0, UInt8(response.count)] + response
body += Array(plugin.utf8) + [0]
try send(body)
var reply = try readPacket()
NSLog(
"DBSyn MySQL initial authentication response: type=0x%02X length=%d",
Int(reply.first ?? 0),
reply.count
)
try checkError(reply)
var currentPlugin = plugin
var currentSalt = Array(salt)
if reply.first == 0xfe {
var switchCursor = ByteCursor(bytes: Array(reply.dropFirst()))
let newPlugin = try switchCursor.nulString()
let rawSalt = Array(reply.dropFirst(1 + newPlugin.utf8.count + 1))
let newSalt = rawSalt.last == 0 ? Array(rawSalt.dropLast()) : rawSalt
currentPlugin = newPlugin
currentSalt = newSalt
try send(authResponse(password: password, salt: newSalt, plugin: newPlugin))
reply = try readPacket()
try checkError(reply)
}
if reply.first == 0x01 {
guard currentPlugin == "caching_sha2_password", reply.count >= 2 else {
throw MySQLWireError(message: "Unsupported authentication continuation")
}
NSLog(
"DBSyn MySQL authentication continuation: code=0x%02X TLS=%@",
Int(reply[1]),
tlsActive ? "YES" : "NO"
)
switch reply[1] {
case 0x03: // Fast authentication accepted; wait for OK.
reply = try readPacket()
case 0x04: // Full authentication required.
guard hostIsLoopback || tlsActive else {
throw MySQLWireError(message: "Full authentication requires encrypted transport")
}
if tlsActive {
try send(Array(password.utf8) + [0])
} else if password.isEmpty {
try send([0])
} else {
// RSA fallback is restricted to trusted loopback transport.
try send([0x02])
let keyPacket = try readPacket()
try checkError(keyPacket)
let key = try mysqlRSAPublicKey(keyPacket)
guard !currentSalt.isEmpty else {
throw MySQLWireError(message: "Missing MySQL authentication nonce")
}
var scrambled = Array(password.utf8) + [0]
for i in scrambled.indices { scrambled[i] ^= currentSalt[i % currentSalt.count] }
var rsaError: Unmanaged<CFError>?
guard let encrypted = SecKeyCreateEncryptedData(
key, .rsaEncryptionOAEPSHA1, Data(scrambled) as CFData, &rsaError
) as Data? else {
throw MySQLWireError(message: "MySQL RSA encryption failed: \(rsaError?.takeRetainedValue().localizedDescription ?? "unknown error")")
}
try send(Array(encrypted))
}
reply = try readPacket()
default:
throw MySQLWireError(message: "Unsupported caching_sha2_password response: \(reply[1])")
}
}
NSLog(
"DBSyn MySQL final authentication response: type=0x%02X length=%d",
Int(reply.first ?? 0),
reply.count
)
try checkError(reply)
guard reply.first == 0x00 else { throw MySQLWireError(message: "Authentication did not complete") }
if !database.isEmpty { _ = try query("USE `\(database.replacingOccurrences(of: "`", with: "``"))`") }
}
private var hostIsLoopback = false
func markHost(_ host: String) { hostIsLoopback = ["localhost", "127.0.0.1", "::1"].contains(host.lowercased()) }
// Streams result rows without the query editor's 100,000-row limit.
// The callback is invoked on the session's serial background queue.
func streamFirstColumn(_ sql: String, onRow: (String) throws -> Void) throws -> Int64 {
sequence = 0
try send([0x03] + Array(sql.utf8))
let first = try readPacket()
try checkError(first)
guard first.first != 0x00 else { return 0 }
var cursor = ByteCursor(bytes: first)
guard let columnCount = try cursor.lenInt(), columnCount == 1 else {
throw MySQLWireError(message: "Export query must return exactly one column")
}
_ = try readPacket() // Column metadata
_ = try readPacket() // Column EOF
var count: Int64 = 0
while true {
let packet = try readPacket()
try checkError(packet)
if packet.first == 0xfe && packet.count < 9 { break }
var row = ByteCursor(bytes: packet)
guard let value = try row.lenString() else {
throw MySQLWireError(message: "Unexpected NULL export row")
}
try onRow(value)
count += 1
}
return count
}
func query(_ sql: String) throws -> QueryResult {
sequence = 0
try send([0x03] + Array(sql.utf8))
let first = try readPacket()
try checkError(first)
if first.first == 0x00 { return QueryResult(message: "Statement executed successfully") }
var c = ByteCursor(bytes: first)
guard let columnCount = try c.lenInt(), columnCount <= 4096 else { throw MySQLWireError(message: "Invalid column count") }
var names: [String] = []
for _ in 0..<columnCount {
var field = ByteCursor(bytes: try readPacket())
for _ in 0..<4 { _ = try field.lenString() }
names.append(try field.lenString() ?? "")
}
_ = try readPacket() // column EOF (initial protocol requires EOF)
var rows: [[String]] = []
while true {
let packet = try readPacket()
try checkError(packet)
if packet.first == 0xfe && packet.count < 9 { break }
var row = ByteCursor(bytes: packet)
var values: [String] = []
for _ in 0..<columnCount { values.append(try row.lenString() ?? "NULL") }
rows.append(values)
if rows.count > 100_000 { throw MySQLWireError(message: "Result exceeds 100,000 row safety limit") }
}
return QueryResult(columns: names, rows: rows, message: "\(rows.count) row(s)")
}
}
// A session owns one authenticated TCP connection. Access is serialized by the
// queue below because MySQLWireConnection is not safe for concurrent queries.
private final class PersistentMySQLSessions {
static let shared = PersistentMySQLSessions()
private struct Entry {
let profile: ConnectionProfile
let password: String
let connection: MySQLWireConnection
}
// Each profile has its own serial executor. An unreachable server cannot
// block requests to any other saved connection.
private final class SessionSlot {
let queue: DispatchQueue
var entry: Entry?
// Only accessed on queue except generation, which is lock protected.
private let generationLock = NSLock()
private var currentGeneration: UInt64 = 0
init(id: UUID) {
queue = DispatchQueue(label: "DBSyn.MySQLSession.\(id.uuidString)")
}
func generation() -> UInt64 {
generationLock.lock(); defer { generationLock.unlock() }
return currentGeneration
}
func invalidate() {
generationLock.lock(); currentGeneration &+= 1; generationLock.unlock()
}
}
private let slotsLock = NSLock()
private var slots: [UUID: SessionSlot] = [:]
private func slot(for id: UUID) -> SessionSlot {
slotsLock.lock(); defer { slotsLock.unlock() }
if let existing = slots[id] { return existing }
let created = SessionSlot(id: id)
slots[id] = created
return created
}
func execute(profile: ConnectionProfile, database: String,
password: String, sql: String) throws -> QueryResult {
let session = slot(for: profile.id)
let expectedGeneration = session.generation()
return try session.queue.sync {
guard session.generation() == expectedGeneration else {
throw MySQLWireError(message: "Connection request superseded")
}
if let previous = session.entry,
(previous.profile != profile || previous.password != password) {
session.entry = nil
DBSynSSHTunnels.shared.stop(profile.id)
}
if session.entry == nil {
let endpoint = try DBSynSSHTunnels.shared.endpoint(for: profile)
let connection = try MySQLWireConnection(
host: endpoint.0, port: endpoint.1,
requireTLS: !profile.useSSHTunnel &&
!["127.0.0.1", "localhost", "::1"].contains(endpoint.0.lowercased()),
verifyTLS: profile.verifyTLS
)
connection.markHost(endpoint.0)
try connection.authenticate(user: profile.username,
password: password, database: database)
guard session.generation() == expectedGeneration else {
throw MySQLWireError(message: "Connection request superseded")
}
session.entry = Entry(profile: profile, password: password,
connection: connection)
}
guard let entry = session.entry else {
throw MySQLWireError(message: "MySQL session was not created")
}
do {
let result = try entry.connection.query(sql)
guard session.generation() == expectedGeneration else {
throw MySQLWireError(message: "Connection request superseded")
}
return result
} catch {
session.entry = nil
throw error
}
}
}
func stream(profile: ConnectionProfile, database: String,
password: String, sql: String,
onRow: (String) throws -> Void) throws -> Int64 {
let session = slot(for: profile.id)
let expectedGeneration = session.generation()
return try session.queue.sync {
guard session.generation() == expectedGeneration,
let entry = session.entry,
entry.profile == profile, entry.password == password else {
throw MySQLWireError(message: "Export session is no longer connected")
}
do {
return try entry.connection.streamFirstColumn(sql, onRow: { row in
guard session.generation() == expectedGeneration else {
throw MySQLWireError(message: "Export cancelled: connection switched")
}
try onRow(row)
})
} catch {
session.entry = nil
throw error
}
}
}
func disconnect(profileID: UUID) {
let session = slot(for: profileID)
// Invalidate immediately without waiting for a blocked network read.
session.invalidate()
session.queue.async {
session.entry = nil
DBSynSSHTunnels.shared.stop(profileID)
}
}
}
enum NativeMySQLRunner {
static func run(profile: ConnectionProfile, database: String,
password: String, sql: String) async throws -> QueryResult {
try await Task.detached(priority: .userInitiated) {
try PersistentMySQLSessions.shared.execute(
profile: profile, database: database, password: password, sql: sql
)
}.value
}
static func stream(profile: ConnectionProfile, database: String,
password: String, sql: String,
onRow: @escaping (String) throws -> Void) async throws -> Int64 {
try await Task.detached(priority: .userInitiated) {
try PersistentMySQLSessions.shared.stream(
profile: profile, database: database, password: password,
sql: sql, onRow: { row in
try Task<Never, Never>.checkCancellation()
try onRow(row)
}
)
}.value
}
// Connection shutdown may wait behind a slow network operation on the
// session's serial queue. Never make the SwiftUI main thread wait for it.
static func disconnect(profileID: UUID) {
PersistentMySQLSessions.shared.disconnect(profileID: profileID)
}
}
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 460 KiB

+149
View File
@@ -0,0 +1,149 @@
import AppKit
/// Resolution-independent diagram renderer shared by PDF, PNG, SVG and macOS printing.
/// The exported palette is intentionally independent of the user's macOS appearance.
final class SchemaDiagramPrintView: NSView {
struct Column { let name: String; let type: String; let primary: Bool }
struct Entry { let name: String; let isView: Bool; let columns: [Column]; let origin: CGPoint }
struct Link { let from: String; let to: String; let column: String; let target: String }
let database: String
let diagram: String
let entries: [Entry]
let links: [Link]
let theme: String
private let cardWidth: CGFloat = 260
private let headerHeight: CGFloat = 34
private let rowHeight: CGFloat = 19
private let margin: CGFloat = 55
private let titleHeight: CGFloat = 76
private let navy = NSColor(calibratedRed: 0.12, green: 0.20, blue: 0.34, alpha: 1)
private var blue: NSColor {
switch theme {
case "Slate": return NSColor(calibratedRed: 0.30, green: 0.38, blue: 0.49, alpha: 1)
case "Forest": return NSColor(calibratedRed: 0.16, green: 0.43, blue: 0.36, alpha: 1)
case "Plum": return NSColor(calibratedRed: 0.42, green: 0.32, blue: 0.55, alpha: 1)
default: return NSColor(calibratedRed: 0.12, green: 0.38, blue: 0.66, alpha: 1)
}
}
private var violet: NSColor { blue.blended(withFraction: 0.25, of: .systemPurple) ?? blue }
private let ink = NSColor(calibratedRed: 0.16, green: 0.23, blue: 0.32, alpha: 1)
private let muted = NSColor(calibratedRed: 0.43, green: 0.49, blue: 0.57, alpha: 1)
private let border = NSColor(calibratedRed: 0.79, green: 0.83, blue: 0.88, alpha: 1)
init(database: String, diagram: String, entries: [Entry], links: [Link], theme: String = "Ocean") {
self.database = database
self.diagram = diagram
self.entries = entries
self.links = links
self.theme = theme
let width = max(900, (entries.map { $0.origin.x + 260 }.max() ?? 750) + 110)
let height = max(650, (entries.map { $0.origin.y + CGFloat($0.columns.count) * 19 + 46 }.max() ?? 500) + 130)
super.init(frame: CGRect(x: 0, y: 0, width: width, height: height))
}
required init?(coder: NSCoder) { nil }
override var isFlipped: Bool { true }
private func cardHeight(_ entry: Entry) -> CGFloat { headerHeight + CGFloat(entry.columns.count) * rowHeight + 12 }
private func cardRect(_ entry: Entry) -> CGRect {
CGRect(x: entry.origin.x + margin, y: entry.origin.y + titleHeight,
width: cardWidth, height: cardHeight(entry))
}
private func text(_ string: String, at point: CGPoint, font: NSFont, color: NSColor) {
(string as NSString).draw(at: point, withAttributes: [.font: font, .foregroundColor: color])
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
NSColor.white.setFill()
NSBezierPath(rect: bounds).fill()
navy.setFill()
NSBezierPath(rect: CGRect(x: 0, y: 0, width: bounds.width, height: 8)).fill()
text(database, at: CGPoint(x: margin, y: 25), font: .systemFont(ofSize: 22, weight: .bold), color: navy)
text("SCHEMA DIAGRAM / \(diagram)", at: CGPoint(x: margin, y: 54), font: .systemFont(ofSize: 10, weight: .medium), color: muted)
text("\(entries.count) objects • \(links.count) relationships", at: CGPoint(x: margin, y: bounds.height - 27), font: .systemFont(ofSize: 10), color: muted)
let byName = Dictionary(uniqueKeysWithValues: entries.map { ($0.name, $0) })
for link in links {
guard let source = byName[link.from], let target = byName[link.to] else { continue }
let a = cardRect(source), b = cardRect(target)
let toRight = a.midX <= b.midX
let start = CGPoint(x: toRight ? a.maxX : a.minX, y: a.midY)
let end = CGPoint(x: toRight ? b.minX : b.maxX, y: b.midY)
let curve = NSBezierPath()
curve.move(to: start)
curve.curve(to: end,
controlPoint1: CGPoint(x: start.x + (toRight ? 55 : -55), y: start.y),
controlPoint2: CGPoint(x: end.x + (toRight ? -55 : 55), y: end.y))
NSColor(calibratedRed: 0.39, green: 0.53, blue: 0.69, alpha: 1).setStroke()
curve.lineWidth = 1.7
curve.stroke()
}
for entry in entries {
let rect = cardRect(entry)
NSColor.white.setFill()
NSBezierPath(roundedRect: rect, xRadius: 7, yRadius: 7).fill()
border.setStroke()
NSBezierPath(roundedRect: rect, xRadius: 7, yRadius: 7).stroke()
let header = CGRect(x: rect.minX, y: rect.minY, width: rect.width, height: headerHeight)
(entry.isView ? violet : blue).setFill()
NSBezierPath(rect: header).fill()
text(entry.name, at: CGPoint(x: rect.minX + 12, y: rect.minY + 9),
font: .systemFont(ofSize: 12, weight: .semibold), color: .white)
for (i, column) in entry.columns.enumerated() {
let y = rect.minY + headerHeight + CGFloat(i) * rowHeight
if i.isMultiple(of: 2) {
NSColor(calibratedRed: 0.96, green: 0.97, blue: 0.99, alpha: 1).setFill()
NSBezierPath(rect: CGRect(x: rect.minX + 1, y: y, width: rect.width - 2, height: rowHeight)).fill()
}
let label = (column.primary ? "◆ " : " ") + column.name
text(String(label.prefix(29)), at: CGPoint(x: rect.minX + 9, y: y + 3),
font: .systemFont(ofSize: 10, weight: column.primary ? .semibold : .regular), color: ink)
let type = String(column.type.prefix(22)) as NSString
let attrs: [NSAttributedString.Key: Any] = [.font: NSFont.systemFont(ofSize: 9), .foregroundColor: muted]
let typeWidth = type.size(withAttributes: attrs).width
type.draw(at: CGPoint(x: rect.maxX - typeWidth - 10, y: y + 4), withAttributes: attrs)
}
}
}
private func escape(_ text: String) -> String {
text.replacingOccurrences(of: "&", with: "&amp;")
.replacingOccurrences(of: "<", with: "&lt;")
.replacingOccurrences(of: ">", with: "&gt;")
.replacingOccurrences(of: "\"", with: "&quot;")
}
func svgDocument() -> String {
var svg = """
<svg xmlns="http://www.w3.org/2000/svg" width="\(Int(bounds.width))" height="\(Int(bounds.height))" viewBox="0 0 \(Int(bounds.width)) \(Int(bounds.height))">
<rect width="100%" height="100%" fill="#ffffff"/>
<rect width="100%" height="8" fill="#1f3457"/>
<text x="55" y="44" font-family="-apple-system,Arial,sans-serif" font-size="22" font-weight="700" fill="#1f3457">\(escape(database))</text>
<text x="55" y="66" font-family="Arial,sans-serif" font-size="10" fill="#6e7d91">SCHEMA DIAGRAM / \(escape(diagram))</text>
"""
let byName = Dictionary(uniqueKeysWithValues: entries.map { ($0.name, $0) })
for link in links {
guard let source = byName[link.from], let target = byName[link.to] else { continue }
let a = cardRect(source), b = cardRect(target), right = a.midX <= b.midX
let sx = right ? a.maxX : a.minX, ex = right ? b.minX : b.maxX
let delta: CGFloat = right ? 55 : -55
svg += "<path d=\"M \(sx) \(a.midY) C \(sx + delta) \(a.midY), \(ex - delta) \(b.midY), \(ex) \(b.midY)\" fill=\"none\" stroke=\"#6488af\" stroke-width=\"1.7\"/>\n"
}
let svgBlue = blue.usingColorSpace(.deviceRGB) ?? blue
let svgViolet = violet.usingColorSpace(.deviceRGB) ?? violet
func hex(_ color: NSColor) -> String {
String(format: "#%02X%02X%02X", Int(color.redComponent * 255), Int(color.greenComponent * 255), Int(color.blueComponent * 255))
}
for entry in entries {
let r = cardRect(entry)
svg += "<rect x=\"\(r.minX)\" y=\"\(r.minY)\" width=\"\(r.width)\" height=\"\(r.height)\" rx=\"7\" fill=\"white\" stroke=\"#cbd4e0\"/>\n"
svg += "<rect x=\"\(r.minX)\" y=\"\(r.minY)\" width=\"\(r.width)\" height=\"34\" fill=\"\(entry.isView ? hex(svgViolet) : hex(svgBlue))\"/>\n"
svg += "<text x=\"\(r.minX + 12)\" y=\"\(r.minY + 23)\" font-family=\"Arial,sans-serif\" font-size=\"12\" font-weight=\"600\" fill=\"white\">\(escape(entry.name))</text>\n"
for (index, column) in entry.columns.enumerated() {
let y = r.minY + 34 + CGFloat(index) * 19
if index.isMultiple(of: 2) { svg += "<rect x=\"\(r.minX + 1)\" y=\"\(y)\" width=\"\(r.width - 2)\" height=\"19\" fill=\"#f5f8fc\"/>\n" }
svg += "<text x=\"\(r.minX + 9)\" y=\"\(y + 13)\" font-family=\"Arial,sans-serif\" font-size=\"10\" fill=\"#293b52\">\(escape((column.primary ? "◆ " : " ") + column.name))</text>\n"
svg += "<text x=\"\(r.maxX - 9)\" y=\"\(y + 13)\" text-anchor=\"end\" font-family=\"Arial,sans-serif\" font-size=\"9\" fill=\"#6e7d91\">\(escape(column.type))</text>\n"
}
}
svg += "<text x=\"55\" y=\"\(bounds.height - 20)\" font-family=\"Arial,sans-serif\" font-size=\"10\" fill=\"#6e7d91\">\(entries.count) objects / \(links.count) relationships</text>\n</svg>"
return svg
}
}
+499
View File
@@ -0,0 +1,499 @@
import SwiftUI
import AppKit
private struct DiagramRelation: Identifiable {
var id: String { "\(from).\(column).\(to).\(targetColumn)" }
let from: String
let column: String
let to: String
let targetColumn: String
}
private struct DiagramPoint: Codable {
var x: Double
var y: Double
}
// Keep the drag translation local to the node. Updating the entire diagram's
// positions dictionary on every mouse event caused layout churn and jerky drags.
private struct DiagramDraggableNode<Content: View>: View {
let zoom: CGFloat
let onDrop: (CGSize) -> Void
@ViewBuilder let content: () -> Content
@GestureState private var translation: CGSize = .zero
var body: some View {
content()
.offset(x: translation.width / max(zoom, 0.1),
y: translation.height / max(zoom, 0.1))
.highPriorityGesture(
DragGesture(minimumDistance: 3, coordinateSpace: .global)
.updating($translation) { value, state, _ in
state = value.translation
}
.onEnded { value in
onDrop(value.translation)
}
)
}
}
@MainActor
struct DBSynSchemaDiagramView: View {
@ObservedObject var model: DatabaseModel
let database: String
var onClose: () -> Void = {}
@State private var positions: [String: CGPoint] = [:]
@State private var relations: [DiagramRelation] = []
@State private var zoom: CGFloat = 1
@State private var search = ""
@State private var selectedObject: String?
@State private var loadingRelationships = false
@State private var errorMessage: String?
@State private var showViews = true
@State private var showingObjects = true
@State private var includedObjects: Set<String> = []
@State private var selectionRevision = 0
@State private var theme = "Ocean"
@State private var showAllColumns = false
@State private var relatedOnly = false
@State private var diagramName = "Main"
@State private var diagrams: [String] = ["Main"]
@State private var newDiagramName = ""
@State private var showingNewDiagram = false
@State private var showingDeleteConfirmation = false
@State private var exportError: String?
@State private var showingExportError = false
private let nodeWidth: CGFloat = 245
private let headerHeight: CGFloat = 36
private let rowHeight: CGFloat = 21
private var maxColumns: Int { showAllColumns ? 200 : 10 }
private var snapshot: DatabaseModel.SchemaIntrospection? { model.introspection(for: database) }
private var objects: [String] {
guard let snapshot else { return [] }
let all = snapshot.tables + (showViews ? snapshot.views : [])
return all.filter { name in
includedObjects.contains(name) && (search.isEmpty || name.localizedCaseInsensitiveContains(search)) &&
(!relatedOnly || selectedObject == nil || name == selectedObject || relations.contains { ($0.from == selectedObject && $0.to == name) || ($0.to == selectedObject && $0.from == name) })
}
}
private var diagramNamespace: String {
"DBSyn.diagram.v2.\(model.selected?.id.uuidString ?? "unknown").\(database)"
}
private var storageKey: String { "\(diagramNamespace).layout.\(diagramName)" }
private var catalogKey: String { "\(diagramNamespace).catalog" }
private var selectionKey: String { "\(diagramNamespace).objects.\(diagramName)" }
private var themeKey: String { "\(diagramNamespace).theme.\(diagramName)" }
private var allObjects: [String] { (snapshot?.tables ?? []) + (snapshot?.views ?? []) }
private var nodeColor: Color {
switch theme {
case "Slate": return Color(red: 0.30, green: 0.38, blue: 0.49)
case "Forest": return Color(red: 0.16, green: 0.43, blue: 0.36)
case "Plum": return Color(red: 0.42, green: 0.32, blue: 0.55)
default: return Color(red: 0.12, green: 0.38, blue: 0.66)
}
}
private func restoreOptions() {
if let stored = UserDefaults.standard.stringArray(forKey: selectionKey) {
includedObjects = Set(stored)
} else { includedObjects = Set(allObjects) }
theme = UserDefaults.standard.string(forKey: themeKey) ?? UserDefaults.standard.string(forKey: "preferences.diagramPalette") ?? "Ocean"
showAllColumns = UserDefaults.standard.bool(forKey: "preferences.diagramShowAllColumns")
}
private func saveOptions() {
UserDefaults.standard.set(Array(includedObjects).sorted(), forKey: selectionKey)
UserDefaults.standard.set(theme, forKey: themeKey)
}
private func columns(_ name: String) -> [DatabaseModel.IntrospectedColumn] {
snapshot?.columnsByTable[name] ?? []
}
private func height(_ name: String) -> CGFloat {
headerHeight + CGFloat(min(columns(name).count, maxColumns)) * rowHeight + 12
}
private func position(_ name: String) -> CGPoint {
positions[name] ?? CGPoint(x: 30, y: 30)
}
private func center(_ name: String, right: Bool) -> CGPoint {
let p = position(name)
return CGPoint(x: p.x + (right ? nodeWidth : 0), y: p.y + min(height(name), 260) / 2)
}
var body: some View {
VStack(spacing: 0) {
// Two compact toolbar rows avoid the truncated controls shown in the original sheet.
HStack(spacing: 10) {
Label(database, systemImage: "point.3.connected.trianglepath.dotted")
.font(.headline).lineLimit(1)
Text("SCHEMA DESIGNER").font(.caption2.weight(.semibold)).foregroundStyle(.secondary)
Spacer(minLength: 12)
Button("Close", systemImage: "xmark") { savePositions(); saveOptions(); onClose() }
.labelStyle(.iconOnly).help("Close diagram")
.keyboardShortcut(.cancelAction)
}
.padding(.horizontal, 16).padding(.top, 12).padding(.bottom, 8)
HStack(spacing: 10) {
Picker("Diagram", selection: $diagramName) {
ForEach(diagrams, id: \.self) { Text($0).tag($0) }
}
.frame(width: 175)
.onChange(of: diagramName) { oldValue, _ in
savePositions(named: oldValue)
positions = [:]
restorePositions()
restoreOptions()
if positions.isEmpty { arrange() }
}
Button { showingNewDiagram = true } label: { Image(systemName: "plus") }
.help("New diagram")
Button { showingDeleteConfirmation = true } label: { Image(systemName: "trash") }
.disabled(diagrams.count <= 1).help("Delete diagram")
Divider().frame(height: 18)
Button { showingObjects.toggle() } label: {
Label("Tables", systemImage: "sidebar.left")
}.help("Show or hide the table selection sidebar")
TextField("Find table or view", text: $search)
.textFieldStyle(.roundedBorder).frame(minWidth: 130, idealWidth: 220, maxWidth: 260)
Toggle("Views", isOn: $showViews).toggleStyle(.checkbox)
Toggle("Related", isOn: $relatedOnly).toggleStyle(.checkbox)
Spacer(minLength: 8)
Menu {
ForEach(["Ocean", "Slate", "Forest", "Plum"], id: \.self) { option in
Button { theme = option; saveOptions() } label: {
if theme == option { Label(option, systemImage: "checkmark") }
else { Text(option) }
}
}
} label: { Label("Colours", systemImage: "paintpalette") }
Menu {
Toggle("Show all columns", isOn: $showAllColumns)
Button("Auto Layout") { arrange(); savePositions() }
Button("Refresh Metadata") { model.introspect(database: database); loadRelationships() }
} label: { Label("Layout", systemImage: "slider.horizontal.3") }
Button { zoom = max(0.4, zoom - 0.15) } label: { Image(systemName: "minus.magnifyingglass") }
Text("\(Int(zoom * 100))%").monospacedDigit().frame(width: 45)
Button { zoom = min(2, zoom + 0.15) } label: { Image(systemName: "plus.magnifyingglass") }
Menu {
Button("Export PDF…") { exportDiagram(.pdf) }
Button("Export PNG…") { exportDiagram(.png) }
Button("Export SVG…") { exportDiagram(.svg) }
Divider()
Button("Print…") { printDiagram() }
} label: { Label("Export / Print", systemImage: "square.and.arrow.up") }
}
.controlSize(.small)
.padding(.horizontal, 16).padding(.bottom, 12)
Divider()
HStack(spacing: 0) {
if showingObjects {
VStack(alignment: .leading, spacing: 10) {
HStack {
Text("OBJECTS").font(.caption.weight(.semibold)).foregroundStyle(.secondary)
Spacer()
Text("\(includedObjects.count)/\(allObjects.count)")
.font(.caption.monospacedDigit()).foregroundStyle(.secondary)
}
HStack {
Button("All") { updateIncludedObjects(Set(allObjects)) }
Button("None") { updateIncludedObjects([]) }
Spacer()
}.buttonStyle(.link)
ScrollView {
LazyVStack(alignment: .leading, spacing: 5) {
ForEach(allObjects.filter { search.isEmpty || $0.localizedCaseInsensitiveContains(search) }, id: \.self) { name in
Toggle(isOn: Binding(
get: { includedObjects.contains(name) },
set: { enabled in
var updated = includedObjects
if enabled { updated.insert(name) }
else { updated.remove(name) }
updateIncludedObjects(updated)
})) {
Label(name, systemImage: snapshot?.views.contains(name) == true ? "eye" : "tablecells")
.lineLimit(1).font(.system(size: 12))
}.toggleStyle(.checkbox)
}
}.frame(maxWidth: .infinity, alignment: .leading)
}
}
.padding(12).frame(width: 235)
.background(Color(nsColor: .controlBackgroundColor))
Divider()
}
VStack(spacing: 0) {
if snapshot == nil {
ContentUnavailableView("No Introspection Data", systemImage: "externaldrive.badge.questionmark", description: Text("Run Introspection on this database to load tables and views."))
Button("Introspect Now") { model.introspect(database: database); loadRelationships() }.padding()
} else {
ScrollView([.horizontal, .vertical]) {
ZStack(alignment: .topLeading) {
Canvas { context, _ in
for relation in relations where objects.contains(relation.from) && objects.contains(relation.to) {
let a = position(relation.from)
let b = position(relation.to)
let right = a.x <= b.x
let start = center(relation.from, right: right)
let end = center(relation.to, right: !right)
let bend: CGFloat = right ? 65 : -65
var path = Path()
path.move(to: start)
path.addCurve(to: end,
control1: CGPoint(x: start.x + bend, y: start.y),
control2: CGPoint(x: end.x - bend, y: end.y))
context.stroke(path, with: .color(.accentColor.opacity(0.7)), lineWidth: 1.7)
}
}
ForEach(objects, id: \.self) { name in
DiagramDraggableNode(zoom: zoom, onDrop: { translation in
let start = position(name)
positions[name] = CGPoint(
x: max(10, start.x + translation.width / max(zoom, 0.1)),
y: max(10, start.y + translation.height / max(zoom, 0.1)))
savePositions()
}) {
diagramNode(name)
}
.position(x: position(name).x + nodeWidth / 2,
y: position(name).y + height(name) / 2)
}
}
.frame(width: canvasWidth, height: canvasHeight, alignment: .topLeading)
.scaleEffect(zoom, anchor: .topLeading)
.frame(width: canvasWidth * zoom, height: canvasHeight * zoom, alignment: .topLeading)
}
.background(Color(nsColor: .textBackgroundColor))
.id(selectionRevision)
.onChange(of: snapshot?.loadedAt) { _, _ in
if UserDefaults.standard.stringArray(forKey: selectionKey) == nil {
includedObjects = Set(allObjects)
}
if positions.isEmpty { arrange() }
loadRelationships()
}
}
}
}
Divider()
HStack {
Text("\(snapshot?.tables.count ?? 0) tables · \(snapshot?.views.count ?? 0) views · \(relations.count) foreign-key relationships")
if loadingRelationships { ProgressView().controlSize(.small) }
if let errorMessage { Text(errorMessage).foregroundStyle(.secondary).lineLimit(1) }
Spacer()
Text("Drag tables to arrange · Select to highlight relationships")
}
.font(.caption)
.padding(9)
}
.frame(minWidth: 850, idealWidth: 1320, minHeight: 570, idealHeight: 850)
.background(DiagramResizableWindow())
.onAppear { loadCatalog(); restorePositions(); restoreOptions(); if positions.isEmpty { arrange() }; loadRelationships() }
.onDisappear { savePositions(); saveOptions() }
.alert("New Schema Diagram", isPresented: $showingNewDiagram) {
TextField("Diagram name", text: $newDiagramName)
Button("Create") { createDiagram() }
Button("Cancel", role: .cancel) { }
} message: { Text("Create a separate saved layout for this database.") }
.confirmationDialog("Delete \(diagramName)?", isPresented: $showingDeleteConfirmation) {
Button("Delete Diagram", role: .destructive) { deleteDiagram() }
} message: { Text("Only the saved diagram layout is removed. Database objects are not changed.") }
.alert("Diagram Export", isPresented: $showingExportError) {
Button("OK", role: .cancel) { }
} message: { Text(exportError ?? "Unable to export diagram") }
}
private var canvasWidth: CGFloat {
max(1200, objects.map { position($0).x + nodeWidth + 80 }.max() ?? 1200)
}
private var canvasHeight: CGFloat {
max(800, objects.map { position($0).y + height($0) + 80 }.max() ?? 800)
}
private func diagramNode(_ name: String) -> some View {
let isView = snapshot?.views.contains(name) == true
let related = selectedObject == nil || selectedObject == name || relations.contains { ($0.from == selectedObject && $0.to == name) || ($0.to == selectedObject && $0.from == name) }
return VStack(alignment: .leading, spacing: 0) {
HStack(spacing: 7) {
Image(systemName: isView ? "eye" : "tablecells")
Text(name).fontWeight(.semibold).lineLimit(1)
Spacer()
if isView { Text("VIEW").font(.system(size: 9, weight: .bold)) }
}
.font(.system(size: 12))
.padding(.horizontal, 10)
.frame(height: headerHeight)
.foregroundStyle(.white)
.background(isView ? nodeColor.opacity(0.76) : nodeColor)
ForEach(Array(columns(name).prefix(maxColumns)), id: \.id) { column in
HStack(spacing: 5) {
Image(systemName: column.key == "PRI" ? "key.fill" : "circle.fill")
.font(.system(size: column.key == "PRI" ? 10 : 4))
.foregroundStyle(column.key == "PRI" ? Color.orange : Color.secondary)
.frame(width: 13)
Text(column.name).lineLimit(1)
Spacer(minLength: 4)
Text(column.dataType).foregroundStyle(.secondary).lineLimit(1)
}
.font(.system(size: 10))
.padding(.horizontal, 9)
.frame(height: rowHeight)
}
if columns(name).count > maxColumns {
Text("+ \(columns(name).count - maxColumns) more columns")
.font(.caption2).foregroundStyle(.secondary).padding(.horizontal, 10)
}
Spacer(minLength: 5)
}
.frame(width: nodeWidth, height: height(name))
.background(Color(nsColor: .controlBackgroundColor))
.clipShape(RoundedRectangle(cornerRadius: 7))
.overlay(RoundedRectangle(cornerRadius: 7).stroke(selectedObject == name ? Color.accentColor : Color(nsColor: .separatorColor), lineWidth: selectedObject == name ? 2.5 : 1))
.shadow(color: .black.opacity(0.13), radius: 3, y: 2)
.opacity(related ? 1 : 0.4)
.onTapGesture { selectedObject = selectedObject == name ? nil : name }
}
// Rebuild the visible canvas whenever the selected objects change. The
// previous implementation only saved the checkboxes, leaving the diagram
// scrolled to stale positions after None / All / individual selections.
private func updateIncludedObjects(_ updated: Set<String>) {
let valid = DiagramLogic.selectedObjects(updated, available: allObjects)
guard valid != includedObjects else { return }
includedObjects = valid
if let selectedObject, !valid.contains(selectedObject) {
self.selectedObject = nil
}
// Keep the selected subset compact, rather than retaining the old
// coordinates of objects from a much larger diagram.
arrange()
savePositions()
saveOptions()
selectionRevision += 1
}
private func arrange() {
let names = allObjects.filter { includedObjects.contains($0) }
positions.merge(DiagramLogic.arrangedPositions(names)) { _, latest in latest }
}
private func loadCatalog() {
let stored = UserDefaults.standard.stringArray(forKey: catalogKey) ?? ["Main"]
diagrams = stored.isEmpty ? ["Main"] : stored
if !diagrams.contains(diagramName) { diagramName = diagrams[0] }
}
private func createDiagram() {
let name = newDiagramName.trimmingCharacters(in: .whitespacesAndNewlines)
guard !name.isEmpty, !diagrams.contains(name) else { return }
savePositions()
diagrams.append(name)
UserDefaults.standard.set(diagrams, forKey: catalogKey)
diagramName = name
newDiagramName = ""
}
private func deleteDiagram() {
guard diagrams.count > 1 else { return }
UserDefaults.standard.removeObject(forKey: storageKey)
diagrams.removeAll { $0 == diagramName }
UserDefaults.standard.set(diagrams, forKey: catalogKey)
diagramName = diagrams[0]
}
private func savePositions(named name: String) {
let points = positions.mapValues { DiagramPoint(x: Double($0.x), y: Double($0.y)) }
if let data = try? JSONEncoder().encode(points) {
UserDefaults.standard.set(data, forKey: "\(diagramNamespace).layout.\(name)")
}
}
private func savePositions() { savePositions(named: diagramName) }
private func restorePositions() {
guard let data = UserDefaults.standard.data(forKey: storageKey),
let saved = try? JSONDecoder().decode([String: DiagramPoint].self, from: data) else { return }
positions = saved.mapValues { CGPoint(x: $0.x, y: $0.y) }
}
private func loadRelationships() {
guard let profile = model.selected else { return }
loadingRelationships = true
errorMessage = nil
let password = PasswordStore.read(profile.id)
let escaped = database.replacingOccurrences(of: "'", with: "''")
Task {
do {
let result = try await NativeMySQLRunner.run(profile: profile, database: database, password: password,
sql: "SELECT TABLE_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = '\(escaped)' AND REFERENCED_TABLE_NAME IS NOT NULL ORDER BY TABLE_NAME, ORDINAL_POSITION")
guard model.selected?.id == profile.id else { loadingRelationships = false; return }
relations = result.rows.compactMap { row in
guard row.count >= 4 else { return nil }
return DiagramRelation(from: row[0], column: row[1], to: row[2], targetColumn: row[3])
}
} catch {
errorMessage = "Foreign keys unavailable: \(error.localizedDescription)"
}
loadingRelationships = false
}
}
private enum DiagramFormat: Equatable { case pdf, png, svg }
private func printLayout() -> SchemaDiagramPrintView {
let entries = objects.map { name in
SchemaDiagramPrintView.Entry(name: name,
isView: snapshot?.views.contains(name) == true,
columns: columns(name).map { .init(name: $0.name, type: $0.dataType, primary: $0.key == "PRI") },
origin: position(name))
}
let links = relations.filter { objects.contains($0.from) && objects.contains($0.to) }
.map { SchemaDiagramPrintView.Link(from: $0.from, to: $0.to, column: $0.column, target: $0.targetColumn) }
return SchemaDiagramPrintView(database: database, diagram: diagramName, entries: entries, links: links, theme: theme)
}
private func exportDiagram(_ format: DiagramFormat) {
let panel = NSSavePanel()
panel.title = "Export Schema Diagram"
panel.nameFieldStringValue = "\(database)-\(diagramName).\(format == .pdf ? "pdf" : format == .png ? "png" : "svg")"
panel.canCreateDirectories = true
guard panel.runModal() == .OK, let url = panel.url else { return }
do {
let view = printLayout()
let data: Data
switch format {
case .pdf: data = view.dataWithPDF(inside: view.bounds)
case .png:
guard let bitmap = view.bitmapImageRepForCachingDisplay(in: view.bounds) else {
throw NSError(domain: "DBSynDiagram", code: 1, userInfo: [NSLocalizedDescriptionKey: "Unable to create bitmap"])
}
view.cacheDisplay(in: view.bounds, to: bitmap)
guard let png = bitmap.representation(using: .png, properties: [:]) else {
throw NSError(domain: "DBSynDiagram", code: 2, userInfo: [NSLocalizedDescriptionKey: "Unable to encode PNG"])
}
data = png
case .svg: data = Data(view.svgDocument().utf8)
}
try data.write(to: url, options: .atomic)
} catch {
exportError = error.localizedDescription
showingExportError = true
}
}
private func printDiagram() {
let view = printLayout()
guard let info = NSPrintInfo.shared.copy() as? NSPrintInfo else { return }
info.horizontalPagination = .fit
info.verticalPagination = .fit
info.isHorizontallyCentered = true
info.isVerticallyCentered = true
info.orientation = view.bounds.width > view.bounds.height ? .landscape : .portrait
NSPrintOperation(view: view, printInfo: info).run()
}
}
/// Allows the schema designer sheet to be resized rather than locking its initial dimensions.
private struct DiagramResizableWindow: NSViewRepresentable {
func makeNSView(context: Context) -> NSView {
let view = NSView(frame: .zero)
DispatchQueue.main.async { [weak view] in
guard let window = view?.window else { return }
window.styleMask.insert(.resizable)
window.minSize = NSSize(width: 850, height: 570)
}
return view
}
func updateNSView(_ nsView: NSView, context: Context) { }
}
+98
View File
@@ -0,0 +1,98 @@
import XCTest
@testable import DBSyn
final class DBSynTests: XCTestCase {
func testSQLSplitsMultipleStatements() {
XCTAssertEqual(SQLStatementSplitter.split("USE AAMEDIUK; SELECT * FROM ADCUSERS;"),
["USE AAMEDIUK", "SELECT * FROM ADCUSERS"])
}
func testSQLSplitterPreservesQuotedSemicolons() {
XCTAssertEqual(SQLStatementSplitter.split("SELECT 'a;b'; SELECT `a;b` FROM t;"),
["SELECT 'a;b'", "SELECT `a;b` FROM t"])
}
func testSQLSplitterIgnoresComments() {
XCTAssertEqual(SQLStatementSplitter.split("# comment;\nUSE db; /* comment; */ SELECT 1;"),
["USE db", "SELECT 1"])
}
func testSQLSplitterIgnoresEmptyStatements() {
XCTAssertEqual(SQLStatementSplitter.split(";; SELECT 1;;;"), ["SELECT 1"])
}
func testUSEDatabaseUnquoted() {
XCTAssertEqual(SQLStatementSplitter.usedDatabase("USE AAMEDIUK"), "AAMEDIUK")
}
func testUSEDatabaseQuoted() {
XCTAssertEqual(SQLStatementSplitter.usedDatabase("USE `my``db`"), "my`db")
}
func testUSEDatabaseRejectsOtherStatements() {
XCTAssertNil(SQLStatementSplitter.usedDatabase("SELECT * FROM ADCUSERS"))
}
func testSQLFormatterUppercasesKeywords() {
XCTAssertEqual(SQLFormatter.uppercaseKeywords("select * from ADCUSERS where id = 1"),
"SELECT * FROM ADCUSERS WHERE id = 1")
}
func testSQLFormatterPreservesStringLiteralsAndComments() {
let input = "select 'select from' # select stays lower\nfrom users"
XCTAssertEqual(SQLFormatter.uppercaseKeywords(input),
"SELECT 'select from' # select stays lower\nFROM users")
}
func testConnectionProfileCodableRoundTrip() throws {
var profile = ConnectionProfile()
profile.name = "Development"
profile.database = "AAMEDIUK"
let decoded = try JSONDecoder().decode(ConnectionProfile.self, from: JSONEncoder().encode(profile))
XCTAssertEqual(decoded, profile)
}
func testIntrospectionCacheCodableRoundTrip() throws {
let connectionID = UUID()
let column = DatabaseModel.IntrospectedColumn(table: "ADCUSERS", name: "id", dataType: "bigint", nullable: false, key: "PRI", defaultValue: nil, extra: "auto_increment")
let snapshot = DatabaseModel.SchemaIntrospection(connectionID: connectionID, database: "AAMEDIUK", loadedAt: Date(timeIntervalSince1970: 1000), tables: ["ADCUSERS"], views: ["ACTIVE_USERS"], columnsByTable: ["ADCUSERS": [column]])
let decoded = try JSONDecoder().decode(DatabaseModel.SchemaIntrospection.self, from: JSONEncoder().encode(snapshot))
XCTAssertEqual(decoded.connectionID, connectionID)
XCTAssertEqual(decoded.database, "AAMEDIUK")
XCTAssertEqual(decoded.tables, ["ADCUSERS"])
XCTAssertEqual(decoded.views, ["ACTIVE_USERS"])
XCTAssertEqual(decoded.columnsByTable["ADCUSERS"]?.first?.name, "id")
}
func testDiagramSelectNone() {
XCTAssertEqual(DiagramLogic.selectedObjects([], available: ["A", "B"]), [])
}
func testDiagramSelectSubsetAndIgnoreRemovedObjects() {
XCTAssertEqual(DiagramLogic.selectedObjects(["A", "MISSING"], available: ["A", "B"]), ["A"])
}
func testDiagramSelectAll() {
let available = ["A", "B", "VIEW_C"]
XCTAssertEqual(DiagramLogic.selectedObjects(Set(available), available: available), Set(available))
}
func testDiagramLayoutContainsEverySelectedTable() {
let names = (0..<25).map { "table_\($0)" }
let positions = DiagramLogic.arrangedPositions(names)
XCTAssertEqual(positions.count, names.count)
XCTAssertEqual(positions["table_0"], CGPoint(x: 30, y: 30))
XCTAssertEqual(Set(positions.values.map { "\($0.x),\($0.y)" }).count, names.count)
}
func testDiagramDragRespectsZoom() {
let origin = CGPoint(x: 100, y: 200)
XCTAssertEqual(DiagramLogic.translatedPosition(origin, translation: CGSize(width: 60, height: -40), zoom: 2),
CGPoint(x: 130, y: 180))
}
func testDiagramDragHandlesZeroZoom() {
let point = DiagramLogic.translatedPosition(.zero, translation: CGSize(width: 10, height: 5), zoom: 0)
XCTAssertTrue(point.x.isFinite && point.y.isFinite)
}
}
@@ -1,239 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 56;
objects = {
/* Begin PBXBuildFile section */
000000000000000000000013 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 000000000000000000000010 /* App.swift */; };
000000000000000000000014 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 000000000000000000000011 /* MainView.swift */; };
000000000000000000000015 /* DatabaseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 000000000000000000000012 /* DatabaseModel.swift */; };
000000000000000000000017 /* MySQLProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 000000000000000000000016 /* MySQLProtocol.swift */; };
A10000000000000000000003 /* WorkbenchReplacement.icns in Resources */ = {isa = PBXBuildFile; fileRef = A10000000000000000000001 /* WorkbenchReplacement.icns */; };
A10000000000000000000004 /* WorkbenchBrand.png in Resources */ = {isa = PBXBuildFile; fileRef = A10000000000000000000002 /* WorkbenchBrand.png */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
00000000000000000000000F /* WorkbenchReplacement.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WorkbenchReplacement.app; sourceTree = BUILT_PRODUCTS_DIR; };
000000000000000000000010 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
000000000000000000000011 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; };
000000000000000000000012 /* DatabaseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseModel.swift; sourceTree = "<group>"; };
000000000000000000000016 /* MySQLProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MySQLProtocol.swift; sourceTree = "<group>"; };
A10000000000000000000001 /* WorkbenchReplacement.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = WorkbenchReplacement.icns; sourceTree = "<group>"; };
A10000000000000000000002 /* WorkbenchBrand.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = WorkbenchBrand.png; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
000000000000000000000008 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
000000000000000000000002 = {
isa = PBXGroup;
children = (
000000000000000000000003 /* WorkbenchReplacement */,
000000000000000000000004 /* Products */,
);
sourceTree = "<group>";
};
000000000000000000000003 /* WorkbenchReplacement */ = {
isa = PBXGroup;
children = (
000000000000000000000010 /* App.swift */,
000000000000000000000011 /* MainView.swift */,
000000000000000000000012 /* DatabaseModel.swift */,
000000000000000000000016 /* MySQLProtocol.swift */,
A10000000000000000000005 /* Resources */,);
path = WorkbenchReplacement;
sourceTree = "<group>";
};
000000000000000000000004 /* Products */ = {
isa = PBXGroup;
children = (
00000000000000000000000F /* WorkbenchReplacement.app */,
);
name = Products;
sourceTree = "<group>";
};
A10000000000000000000005 /* Resources */ = {
isa = PBXGroup;
children = (
A10000000000000000000001 /* WorkbenchReplacement.icns */,
A10000000000000000000002 /* WorkbenchBrand.png */,
);
path = Resources;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
000000000000000000000005 /* WorkbenchReplacement */ = {
isa = PBXNativeTarget;
buildConfigurationList = 00000000000000000000000A /* Build configuration list for PBXNativeTarget "WorkbenchReplacement" */;
buildPhases = (
000000000000000000000006 /* Sources */,
000000000000000000000008 /* Frameworks */,
000000000000000000000007 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = WorkbenchReplacement;
productName = WorkbenchReplacement;
productReference = 00000000000000000000000F /* WorkbenchReplacement.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
000000000000000000000001 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1600;
TargetAttributes = {
000000000000000000000005 = {
CreatedOnToolsVersion = 16.0;
};
};
};
buildConfigurationList = 000000000000000000000009 /* Build configuration list for PBXProject "WorkbenchReplacement" */;
compatibilityVersion = "Xcode 14.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 000000000000000000000002;
productRefGroup = 000000000000000000000004 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
000000000000000000000005 /* WorkbenchReplacement */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
000000000000000000000007 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A10000000000000000000003 /* WorkbenchReplacement.icns in Resources */,
A10000000000000000000004 /* WorkbenchBrand.png in Resources */,);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
000000000000000000000006 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
000000000000000000000013 /* App.swift in Sources */,
000000000000000000000014 /* MainView.swift in Sources */,
000000000000000000000015 /* DatabaseModel.swift in Sources */,
000000000000000000000017 /* MySQLProtocol.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
00000000000000000000000B /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
MACOSX_DEPLOYMENT_TARGET = 15.0;
SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
00000000000000000000000C /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
MACOSX_DEPLOYMENT_TARGET = 15.0;
SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
};
name = Release;
};
00000000000000000000000D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 39R6U3437D;
ENABLE_APP_SANDBOX = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "Workbench Replacement";
INFOPLIST_KEY_CFBundleIconFile = WorkbenchReplacement;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
PRODUCT_BUNDLE_IDENTIFIER = net.absdev.WorkbenchReplacement;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
};
name = Debug;
};
00000000000000000000000E /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 39R6U3437D;
ENABLE_APP_SANDBOX = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "Workbench Replacement";
INFOPLIST_KEY_CFBundleIconFile = WorkbenchReplacement;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
PRODUCT_BUNDLE_IDENTIFIER = net.absdev.WorkbenchReplacement;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
000000000000000000000009 /* Build configuration list for PBXProject "WorkbenchReplacement" */ = {
isa = XCConfigurationList;
buildConfigurations = (
00000000000000000000000B /* Debug */,
00000000000000000000000C /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
00000000000000000000000A /* Build configuration list for PBXNativeTarget "WorkbenchReplacement" */ = {
isa = XCConfigurationList;
buildConfigurations = (
00000000000000000000000D /* Debug */,
00000000000000000000000E /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 000000000000000000000001 /* Project object */;
}
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
-429
View File
@@ -1,429 +0,0 @@
import SwiftUI
@main
struct WorkbenchReplacementApp: App {
@StateObject private var model = DatabaseModel()
var body: some Scene {
WindowGroup("Workbench Replacement") {
WorkbenchLaunchView()
.environmentObject(model)
.frame(minWidth: 800, minHeight: 520)
}
.defaultSize(width: 1200, height: 800)
.windowResizability(.contentSize)
.commands {
WorkbenchFileCommands(model: model)
}
Settings {
WorkbenchSettingsView()
}
}
}
// MARK: - Application Preferences
@MainActor
final class WorkbenchPreferences: ObservableObject {
static let shared = WorkbenchPreferences()
@AppStorage("preferences.editorFontSize")
var editorFontSize = 13.0
@AppStorage("preferences.uppercaseKeywords")
var uppercaseKeywords = true
@AppStorage("preferences.showLineNumbers")
var showLineNumbers = true
@AppStorage("preferences.tabWidth")
var tabWidth = 4
@AppStorage("preferences.resultRowHeight")
var resultRowHeight = 24.0
@AppStorage("preferences.resultColumnWidth")
var resultColumnWidth = 170.0
@AppStorage("preferences.alternatingRows")
var alternatingRows = true
@AppStorage("preferences.appearance")
var appearance = "light"
@AppStorage("preferences.autoExpandTables")
var autoExpandTables = true
}
// MARK: - Workbench Settings
struct WorkbenchSettingsView: View {
@AppStorage("preferences.editorFontSize")
private var editorFontSize = 13.0
@AppStorage("preferences.uppercaseKeywords")
private var uppercaseKeywords = true
@AppStorage("preferences.showLineNumbers")
private var showLineNumbers = true
@AppStorage("preferences.tabWidth")
private var tabWidth = 4
@AppStorage("preferences.defaultRowLimit")
private var defaultRowLimit = 500
@AppStorage("preferences.resultRowHeight")
private var resultRowHeight = 24.0
@AppStorage("preferences.resultColumnWidth")
private var resultColumnWidth = 170.0
@AppStorage("preferences.alternatingRows")
private var alternatingRows = true
@AppStorage("preferences.autoExpandTables")
private var autoExpandTables = true
var body: some View {
TabView {
editorSettings
.tabItem {
Label(
"SQL Editor",
systemImage: "chevron.left.forwardslash.chevron.right"
)
}
executionSettings
.tabItem {
Label(
"Query Execution",
systemImage: "bolt"
)
}
resultSettings
.tabItem {
Label(
"Result Grid",
systemImage: "tablecells"
)
}
generalSettings
.tabItem {
Label(
"General",
systemImage: "gearshape"
)
}
}
.frame(width: 620, height: 390)
.preferredColorScheme(.light)
}
private var editorSettings: some View {
settingsPage("SQL Editor") {
settingsRow("Font Size") {
Stepper(
"\(Int(editorFontSize)) pt",
value: $editorFontSize,
in: 10...24,
step: 1
)
.frame(width: 130)
}
settingsRow("Tab Width") {
Picker("", selection: $tabWidth) {
Text("2 spaces").tag(2)
Text("4 spaces").tag(4)
Text("8 spaces").tag(8)
}
.labelsHidden()
.frame(width: 150)
}
settingsRow("SQL Formatting") {
Toggle(
"Automatically uppercase SQL keywords",
isOn: $uppercaseKeywords
)
}
settingsRow("Editor Display") {
Toggle(
"Show line numbers",
isOn: $showLineNumbers
)
}
}
}
private var executionSettings: some View {
settingsPage("Query Execution") {
settingsRow("Default Row Limit") {
HStack(spacing: 12) {
TextField(
"",
value: $defaultRowLimit,
format: .number
)
.textFieldStyle(.roundedBorder)
.frame(width: 110)
Stepper(
"",
value: $defaultRowLimit,
in: 50...100000,
step: 50
)
.labelsHidden()
}
}
Text(
"Controls the number of rows retrieved when opening a table from the schema navigator."
)
.font(.system(size: 12))
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.leading, 190)
Divider()
.padding(.vertical, 12)
Text("Examples")
.font(.headline)
Text(
"500 rows: Faster browsing and lower memory usage.\n" +
"5,000 rows: Useful for larger datasets.\n" +
"50,000 rows: May take longer to retrieve."
)
.font(.system(size: 12))
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
private var resultSettings: some View {
settingsPage("Result Grid") {
settingsRow("Row Height") {
Stepper(
"\(Int(resultRowHeight)) px",
value: $resultRowHeight,
in: 18...40,
step: 1
)
.frame(width: 130)
}
settingsRow("Column Width") {
Stepper(
"\(Int(resultColumnWidth)) px",
value: $resultColumnWidth,
in: 80...400,
step: 10
)
.frame(width: 140)
}
settingsRow("Appearance") {
Toggle(
"Alternating row colours",
isOn: $alternatingRows
)
}
}
}
private var generalSettings: some View {
settingsPage("General") {
settingsRow("Navigator") {
Toggle(
"Automatically expand table folders",
isOn: $autoExpandTables
)
}
}
}
private func settingsPage<Content: View>(
_ title: String,
@ViewBuilder content: () -> Content
) -> some View {
VStack(alignment: .leading, spacing: 18) {
Text(title)
.font(.system(size: 20, weight: .semibold))
Divider()
content()
Spacer(minLength: 0)
}
.padding(26)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color(nsColor: .windowBackgroundColor))
}
private func settingsRow<Content: View>(
_ title: String,
@ViewBuilder content: () -> Content
) -> some View {
HStack(alignment: .center, spacing: 16) {
Text(title)
.font(.system(size: 13))
.frame(width: 170, alignment: .trailing)
content()
Spacer(minLength: 0)
}
.frame(height: 30)
}
}
// MARK: - Workbench Splash Screen
struct WorkbenchLaunchView: View {
@State private var showingSplash = true
var body: some View {
ZStack {
MainView()
if showingSplash {
WorkbenchSplashView()
.transition(.opacity)
.zIndex(10)
}
}
.task {
try? await Task.sleep(for: .seconds(3))
withAnimation(.easeOut(duration: 0.4)) {
showingSplash = false
}
}
}
}
struct WorkbenchSplashView: View {
var body: some View {
VStack(spacing: 20) {
Spacer()
if let path = Bundle.main.path(
forResource: "WorkbenchBrand",
ofType: "png"
), let image = NSImage(contentsOfFile: path) {
Image(nsImage: image)
.resizable()
.scaledToFit()
.frame(width: 160, height: 160)
}
Text("Workbench Replacement")
.font(.system(size: 30, weight: .bold))
Text("Modern MySQL Client for macOS")
.font(.system(size: 15))
.foregroundStyle(.secondary)
HStack(spacing: 30) {
feature("cylinder.split.1x2", "Schemas")
feature("chevron.left.forwardslash.chevron.right", "SQL Editor")
feature("tablecells", "Result Grid")
feature("server.rack", "Connections")
}
.padding(.top, 20)
Spacer()
ProgressView()
.frame(width: 260)
Text("Starting Workbench Replacement…")
.font(.caption)
.foregroundStyle(.secondary)
.padding(.bottom, 30)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(
LinearGradient(
colors: [
Color(red: 0.96, green: 0.98, blue: 1),
Color(red: 0.88, green: 0.93, blue: 1)
],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
.preferredColorScheme(.light)
}
private func feature(_ symbol: String, _ title: String) -> some View {
VStack(spacing: 8) {
Image(systemName: symbol)
.font(.system(size: 24))
.foregroundStyle(.blue)
Text(title)
.font(.system(size: 12))
}
.frame(width: 90)
}
}
// MARK: - Workbench File Menu
struct WorkbenchFileCommands: Commands {
@ObservedObject var model: DatabaseModel
var body: some Commands {
CommandGroup(replacing: .newItem) {
Button("New Query Tab") {
model.newQueryTab()
}
.keyboardShortcut("t", modifiers: .command)
}
CommandGroup(after: .newItem) {
Button("Open SQL Script…") {
model.openSQLScript()
}
.keyboardShortcut("o", modifiers: .command)
Divider()
Button("Save SQL Script") {
model.saveSQLScript()
}
.keyboardShortcut("s", modifiers: .command)
Button("Save SQL Script As…") {
model.saveSQLScript(asNewFile: true)
}
.keyboardShortcut("s", modifiers: [.command, .shift])
}
}
}
-485
View File
@@ -1,485 +0,0 @@
import Foundation
import AppKit
import Security
struct ConnectionProfile: Identifiable, Codable, Hashable {
var id = UUID()
var name = "Local MySQL"
var host = "127.0.0.1"
var port = 3306
var username = "root"
var database = ""
}
struct QueryResult {
var columns: [String] = []
var rows: [[String]] = []
var message = ""
}
enum PasswordStore {
static func read(_ id: UUID) -> String {
let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "WorkbenchReplacement.MySQL",
kSecAttrAccount as String: id.uuidString,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne]
var value: CFTypeRef?
guard SecItemCopyMatching(query as CFDictionary, &value) == errSecSuccess,
let data = value as? Data else { return "" }
return String(data: data, encoding: .utf8) ?? ""
}
static func save(_ password: String, id: UUID) {
let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "WorkbenchReplacement.MySQL",
kSecAttrAccount as String: id.uuidString]
SecItemDelete(query as CFDictionary)
guard !password.isEmpty else { return }
var attributes = query
attributes[kSecValueData as String] = Data(password.utf8)
SecItemAdd(attributes as CFDictionary, nil)
}
}
struct WorkbenchQueryTab: Identifiable {
let id: UUID
var title: String
var sql: String
var result: QueryResult
var fileURL: URL?
init(
id: UUID = UUID(),
title: String,
sql: String = "",
result: QueryResult = QueryResult(),
fileURL: URL? = nil
) {
self.id = id
self.title = title
self.sql = sql
self.result = result
self.fileURL = fileURL
}
}
@MainActor
final class DatabaseModel: ObservableObject {
@Published var profiles: [ConnectionProfile] = []
@Published var selectedID: UUID?
@Published var queryTabs: [WorkbenchQueryTab] = [
WorkbenchQueryTab(
title: "Query 1",
sql: "SELECT VERSION() AS server_version;"
)
]
@Published var activeQueryID: UUID?
@Published var sql = "SELECT VERSION() AS server_version;"
@Published var result = QueryResult()
@Published var databases: [String] = []
@Published var schemaTables: [String: [String]] = [:]
@Published var schemaViews: [String: [String]] = [:]
@Published var selectedDatabase = ""
@Published var isRunning = false
@Published var status = "Not connected"
private let storageKey = "WorkbenchReplacement.connectionProfiles.native.v1"
init() {
if let data = UserDefaults.standard.data(forKey: storageKey),
let decoded = try? JSONDecoder().decode([ConnectionProfile].self, from: data) { profiles = decoded }
if profiles.isEmpty { profiles = [ConnectionProfile()] }
selectedID = profiles.first?.id
activeQueryID = queryTabs.first?.id
}
func newQueryTab() {
saveActiveQuery()
let tab = WorkbenchQueryTab(
title: "Query \\(queryTabs.count + 1)"
)
queryTabs.append(tab)
activeQueryID = tab.id
sql = tab.sql
result = tab.result
}
func saveActiveQuery() {
guard let id = activeQueryID,
let index = queryTabs.firstIndex(where: { $0.id == id })
else { return }
queryTabs[index].sql = sql
queryTabs[index].result = result
}
func activateQuery(_ id: UUID) {
saveActiveQuery()
guard let tab = queryTabs.first(where: { $0.id == id })
else { return }
activeQueryID = tab.id
sql = tab.sql
result = tab.result
}
func closeQuery(_ id: UUID) {
saveActiveQuery()
queryTabs.removeAll { $0.id == id }
if queryTabs.isEmpty {
let tab = WorkbenchQueryTab(title: "Query 1")
queryTabs.append(tab)
}
if activeQueryID == id {
activeQueryID = nil
activateQuery(queryTabs[0].id)
}
}
func openSQLScript() {
let panel = NSOpenPanel()
panel.allowedContentTypes = [.plainText]
panel.allowsOtherFileTypes = true
panel.canChooseDirectories = false
guard panel.runModal() == .OK,
let url = panel.url,
let contents = try? String(contentsOf: url, encoding: .utf8)
else { return }
saveActiveQuery()
let tab = WorkbenchQueryTab(
title: url.lastPathComponent,
sql: contents,
fileURL: url
)
queryTabs.append(tab)
activateQuery(tab.id)
status = "Opened \\(url.lastPathComponent)"
}
func saveSQLScript(asNewFile: Bool = false) {
saveActiveQuery()
guard let id = activeQueryID,
let index = queryTabs.firstIndex(where: { $0.id == id })
else { return }
var destination = asNewFile ? nil : queryTabs[index].fileURL
if destination == nil {
let panel = NSSavePanel()
panel.nameFieldStringValue = "query.sql"
panel.canCreateDirectories = true
guard panel.runModal() == .OK else { return }
destination = panel.url
}
guard let url = destination else { return }
do {
try sql.write(to: url, atomically: true, encoding: .utf8)
queryTabs[index].fileURL = url
queryTabs[index].title = url.lastPathComponent
status = "Saved \\(url.lastPathComponent)"
} catch {
status = "Save failed: \\(error.localizedDescription)"
}
}
var selected: ConnectionProfile? { profiles.first { $0.id == selectedID } }
func save(_ profile: ConnectionProfile, password: String) {
if let i = profiles.firstIndex(where: { $0.id == profile.id }) { profiles[i] = profile }
else { profiles.append(profile) }
PasswordStore.save(password, id: profile.id)
selectedID = profile.id
persist()
}
private func persist() {
if let data = try? JSONEncoder().encode(profiles) { UserDefaults.standard.set(data, forKey: storageKey) }
}
func formatSQL() {
sql = SQLFormatter.uppercaseKeywords(sql)
}
func loadTables(database: String) {
guard let profile = selected else {
status = "Select a MySQL connection"
return
}
let password = PasswordStore.read(profile.id)
let escaped = database.replacingOccurrences(
of: "`",
with: "``"
)
status = "Loading tables from \(database)…"
Task {
do {
let parsed = try await NativeMySQLRunner.run(
profile: profile,
database: database,
password: password,
sql: "SHOW FULL TABLES FROM `\(escaped)`"
)
var tables: [String] = []
var views: [String] = []
for row in parsed.rows {
guard let name = row.first else {
continue
}
if row.count > 1 && row[1] == "VIEW" {
views.append(name)
} else {
tables.append(name)
}
}
schemaTables[database] = tables
schemaViews[database] = views
status = "Loaded \(tables.count) tables and \(views.count) views from \(database)"
} catch {
status = "Unable to load tables: \(error.localizedDescription)"
result = QueryResult(message: error.localizedDescription)
}
}
}
func selectTable(database: String, table: String) {
let db = database.replacingOccurrences(
of: "`",
with: "``"
)
let tbl = table.replacingOccurrences(
of: "`",
with: "``"
)
selectedDatabase = database
let configuredLimit = UserDefaults.standard.integer(
forKey: "preferences.defaultRowLimit"
)
let rowLimit = configuredLimit > 0
? min(configuredLimit, 100000)
: 500
sql = "SELECT * FROM `\(db)`.`\(tbl)` LIMIT \(rowLimit);"
runSQL()
}
func loadDatabases() { execute("SHOW DATABASES;", listingDatabases: true) }
func runSQL() {
if UserDefaults.standard.object(
forKey: "preferences.uppercaseKeywords"
) as? Bool ?? true {
formatSQL()
}
execute(sql, listingDatabases: false)
}
private func execute(_ statement: String, listingDatabases: Bool) {
guard let profile = selected else { status = "Select a connection"; return }
guard !statement.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return }
isRunning = true
status = "Connecting…"
let activeDB = listingDatabases ? "" : (selectedDatabase.isEmpty ? profile.database : selectedDatabase)
let password = PasswordStore.read(profile.id)
Task {
do {
let parsed = try await NativeMySQLRunner.run(profile: profile, database: activeDB, password: password, sql: statement)
if listingDatabases {
databases = parsed.rows.compactMap { $0.first }
status = "Connected to \(profile.name)"
} else {
result = parsed
if let id = activeQueryID,
let index = queryTabs.firstIndex(where: { $0.id == id }) {
queryTabs[index].sql = sql
queryTabs[index].result = parsed
}
status = "Query completed (\(parsed.rows.count) rows)"
}
} catch {
status = "Error"
result = QueryResult(message: error.localizedDescription)
}
isRunning = false
}
}
}
// MARK: - SQL Keyword Formatter
enum SQLFormatter {
private static let keywords: Set<String> = [
"SELECT", "FROM", "WHERE", "INSERT", "INTO", "VALUES",
"UPDATE", "DELETE", "CREATE", "ALTER", "DROP", "TABLE",
"DATABASE", "SCHEMA", "USE", "SHOW", "DESCRIBE", "EXPLAIN",
"JOIN", "INNER", "LEFT", "RIGHT", "FULL", "OUTER",
"ON", "AS", "AND", "OR", "NOT", "NULL", "IS", "IN",
"BETWEEN", "LIKE", "ORDER", "BY", "GROUP", "HAVING",
"LIMIT", "OFFSET", "ASC", "DESC", "DISTINCT", "ALL",
"PRIMARY", "KEY", "FOREIGN", "REFERENCES", "INDEX",
"UNIQUE", "DEFAULT", "AUTO_INCREMENT", "IF", "EXISTS",
"CASCADE", "RESTRICT", "SET", "BEGIN", "COMMIT",
"ROLLBACK", "TRANSACTION", "UNION", "CASE", "WHEN",
"THEN", "ELSE", "END", "TRUE", "FALSE", "COUNT",
"SUM", "AVG", "MIN", "MAX", "HAVING", "WITH",
"RECURSIVE", "TRUNCATE", "REPLACE", "VIEW",
"PROCEDURE", "FUNCTION", "TRIGGER", "GRANT",
"REVOKE", "DISTINCT", "CURRENT_TIMESTAMP"
]
static func uppercaseKeywords(_ sql: String) -> String {
let chars = Array(sql)
var output = ""
var index = 0
enum State {
case normal
case singleQuote
case doubleQuote
case backtick
case lineComment
case blockComment
}
var state = State.normal
while index < chars.count {
let ch = chars[index]
let next: Character? = index + 1 < chars.count
? chars[index + 1] : nil
switch state {
case .normal:
if ch == "'" {
state = .singleQuote
output.append(ch)
index += 1
} else if ch == "\"" {
state = .doubleQuote
output.append(ch)
index += 1
} else if ch == "`" {
state = .backtick
output.append(ch)
index += 1
} else if ch == "-" && next == "-" &&
(index + 2 == chars.count ||
chars[index + 2].isWhitespace) {
state = .lineComment
output += "--"
index += 2
} else if ch == "#" {
state = .lineComment
output.append(ch)
index += 1
} else if ch == "/" && next == "*" {
state = .blockComment
output += "/*"
index += 2
} else if ch.isLetter || ch == "_" {
let start = index
while index < chars.count &&
(chars[index].isLetter ||
chars[index].isNumber ||
chars[index] == "_" ||
chars[index] == "$") {
index += 1
}
let word = String(chars[start..<index])
let upper = word.uppercased()
output += keywords.contains(upper) ? upper : word
} else {
output.append(ch)
index += 1
}
case .singleQuote:
output.append(ch)
index += 1
if ch == "\\" && index < chars.count {
output.append(chars[index])
index += 1
} else if ch == "'" {
if index < chars.count && chars[index] == "'" {
output.append(chars[index])
index += 1
} else {
state = .normal
}
}
case .doubleQuote:
output.append(ch)
index += 1
if ch == "\\" && index < chars.count {
output.append(chars[index])
index += 1
} else if ch == "\"" {
if index < chars.count && chars[index] == "\"" {
output.append(chars[index])
index += 1
} else {
state = .normal
}
}
case .backtick:
output.append(ch)
index += 1
if ch == "`" {
if index < chars.count && chars[index] == "`" {
output.append(chars[index])
index += 1
} else {
state = .normal
}
}
case .lineComment:
output.append(ch)
index += 1
if ch == "\n" {
state = .normal
}
case .blockComment:
output.append(ch)
index += 1
if ch == "*" && index < chars.count &&
chars[index] == "/" {
output.append("/")
index += 1
state = .normal
}
}
}
return output
}
}
File diff suppressed because it is too large Load Diff
-448
View File
@@ -1,448 +0,0 @@
import Foundation
import CommonCrypto
import CoreFoundation
import Security
// Native MySQL wire protocol client. No external mysql executable is used.
// Initial scope: TCP, mysql_native_password and caching_sha2_password fast authentication.
struct MySQLWireError: LocalizedError {
let message: String
var errorDescription: String? { message }
}
private struct ByteCursor {
let bytes: [UInt8]
var offset = 0
mutating func take(_ n: Int) throws -> [UInt8] {
guard n >= 0, offset + n <= bytes.count else { throw MySQLWireError(message: "Malformed MySQL packet") }
defer { offset += n }
return Array(bytes[offset..<(offset+n)])
}
mutating func u8() throws -> UInt8 { try take(1)[0] }
mutating func u16() throws -> Int { let b = try take(2); return Int(b[0]) | (Int(b[1]) << 8) }
mutating func u32() throws -> Int { let b = try take(4); return Int(b[0]) | (Int(b[1]) << 8) | (Int(b[2]) << 16) | (Int(b[3]) << 24) }
mutating func nulString() throws -> String {
let start = offset
while offset < bytes.count && bytes[offset] != 0 { offset += 1 }
guard offset < bytes.count else { throw MySQLWireError(message: "Unterminated server string") }
let s = String(decoding: bytes[start..<offset], as: UTF8.self)
offset += 1
return s
}
mutating func lenInt() throws -> Int? {
let first = try u8()
switch first {
case 0xfb: return nil
case 0xfc: return try u16()
case 0xfd:
let b = try take(3); return Int(b[0]) | Int(b[1]) << 8 | Int(b[2]) << 16
case 0xfe:
let b = try take(8)
var v: UInt64 = 0
for (i, byte) in b.enumerated() { v |= UInt64(byte) << (8 * i) }
guard v <= UInt64(Int.max) else { throw MySQLWireError(message: "Field too large") }
return Int(v)
default: return Int(first)
}
}
mutating func lenString() throws -> String? {
guard let n = try lenInt() else { return nil }
return String(decoding: try take(n), as: UTF8.self)
}
}
private func le32(_ v: Int) -> [UInt8] { (0..<4).map { UInt8(truncatingIfNeeded: v >> ($0 * 8)) } }
private func sha1(_ data: [UInt8]) -> [UInt8] {
var result = [UInt8](repeating: 0, count: Int(CC_SHA1_DIGEST_LENGTH))
data.withUnsafeBytes { ptr in _ = CC_SHA1(ptr.baseAddress, CC_LONG(data.count), &result) }
return result
}
private func sha256(_ data: [UInt8]) -> [UInt8] {
var result = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
data.withUnsafeBytes { ptr in _ = CC_SHA256(ptr.baseAddress, CC_LONG(data.count), &result) }
return result
}
private func authResponse(password: String, salt: [UInt8], plugin: String) throws -> [UInt8] {
if password.isEmpty { return [] }
let pwd = Array(password.utf8)
if plugin == "mysql_native_password" {
let first = sha1(pwd), second = sha1(sha1(pwd))
let last = sha1(salt + second)
return zip(first, last).map { $0 ^ $1 }
}
if plugin == "caching_sha2_password" {
let first = sha256(pwd), second = sha256(sha256(pwd))
let last = sha256(second + salt)
return zip(first, last).map { $0 ^ $1 }
}
throw MySQLWireError(message: "Authentication plugin \(plugin) is not yet supported")
}
// Parse an RSA SubjectPublicKeyInfo PEM supplied by MySQL. Security.framework
// expects the inner PKCS#1 RSAPublicKey DER for SecKeyCreateWithData.
private func mysqlRSAPublicKey(_ packet: [UInt8]) throws -> SecKey {
guard packet.first == 0x01 else {
throw MySQLWireError(message: "Expected caching_sha2_password public-key response")
}
let pem = String(decoding: packet.dropFirst(), as: UTF8.self)
let base64 = pem.components(separatedBy: .newlines)
.filter { !$0.hasPrefix("-----") }.joined()
.trimmingCharacters(in: .whitespacesAndNewlines)
guard let spki = Data(base64Encoded: base64) else {
throw MySQLWireError(message: "Invalid MySQL RSA public key")
}
let der = [UInt8](spki)
func length(_ index: inout Int) throws -> Int {
guard index < der.count else { throw MySQLWireError(message: "Invalid RSA DER length") }
let first = Int(der[index]); index += 1
if first < 128 { return first }
let bytes = first & 0x7f
guard bytes > 0, bytes <= 4, index + bytes <= der.count else {
throw MySQLWireError(message: "Invalid RSA DER length")
}
var value = 0
for _ in 0..<bytes { value = (value << 8) | Int(der[index]); index += 1 }
return value
}
func element(_ index: inout Int, tag: UInt8) throws -> Range<Int> {
guard index < der.count, der[index] == tag else {
throw MySQLWireError(message: "Invalid RSA DER element")
}
index += 1
let count = try length(&index)
guard count <= der.count - index else { throw MySQLWireError(message: "Truncated RSA DER") }
let range = index..<(index + count)
index += count
return range
}
var i = 0
let outer = try element(&i, tag: 0x30)
i = outer.lowerBound
_ = try element(&i, tag: 0x30) // AlgorithmIdentifier
let bits = try element(&i, tag: 0x03)
guard bits.count > 1, der[bits.lowerBound] == 0 else {
throw MySQLWireError(message: "Invalid RSA public key bit string")
}
let pkcs1 = Data(der[(bits.lowerBound + 1)..<bits.upperBound])
var error: Unmanaged<CFError>?
guard let key = SecKeyCreateWithData(pkcs1 as CFData, [
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeyClass as String: kSecAttrKeyClassPublic
] as CFDictionary, &error) else {
throw MySQLWireError(message: "Cannot import MySQL RSA public key: \(error?.takeRetainedValue().localizedDescription ?? "unknown error")")
}
return key
}
final class MySQLWireConnection {
private let input: InputStream
private let output: OutputStream
private var sequence: UInt8 = 0
private let maxPacket = 16_777_215
init(host: String, port: Int) throws {
guard (1...65535).contains(port) else { throw MySQLWireError(message: "Invalid port") }
var read: Unmanaged<CFReadStream>?
var write: Unmanaged<CFWriteStream>?
CFStreamCreatePairWithSocketToHost(nil, host as CFString, UInt32(port), &read, &write)
guard let r = read?.takeRetainedValue(), let w = write?.takeRetainedValue() else {
throw MySQLWireError(message: "Cannot create connection")
}
input = unsafeBitCast(r, to: InputStream.self)
output = unsafeBitCast(w, to: OutputStream.self)
input.open(); output.open()
// A CFStream may still be .opening immediately after open(). The
// first blocking read below waits for the server greeting.
if input.streamStatus == .error || output.streamStatus == .error {
throw MySQLWireError(message: input.streamError?.localizedDescription ?? output.streamError?.localizedDescription ?? "Could not open MySQL connection")
}
}
deinit { input.close(); output.close() }
private func readExact(_ count: Int) throws -> [UInt8] {
var buffer = [UInt8](repeating: 0, count: count)
var done = 0
while done < count {
let n = buffer.withUnsafeMutableBufferPointer { ptr -> Int in
guard let base = ptr.baseAddress else { return -1 }
return input.read(base.advanced(by: done), maxLength: count - done)
}
guard n > 0 else { throw MySQLWireError(message: input.streamError?.localizedDescription ?? "MySQL connection closed") }
done += n
}
return buffer
}
private func readPacket() throws -> [UInt8] {
let header = try readExact(4)
let count = Int(header[0]) | Int(header[1]) << 8 | Int(header[2]) << 16
sequence = header[3] &+ 1
return try readExact(count)
}
private func send(_ payload: [UInt8]) throws {
guard payload.count < maxPacket else { throw MySQLWireError(message: "Query exceeds initial client packet limit") }
let count = payload.count
let packet = [UInt8(count & 255), UInt8((count >> 8) & 255), UInt8((count >> 16) & 255), sequence] + payload
sequence = sequence &+ 1
var written = 0
while written < packet.count {
let n = packet.withUnsafeBufferPointer { ptr -> Int in
guard let base = ptr.baseAddress else { return -1 }
return output.write(base.advanced(by: written), maxLength: packet.count - written)
}
guard n > 0 else { throw MySQLWireError(message: output.streamError?.localizedDescription ?? "MySQL write failed") }
written += n
}
}
private func checkError(_ p: [UInt8]) throws {
guard p.first == 0xff else { return }
let details = String(decoding: p.dropFirst(3), as: UTF8.self)
throw MySQLWireError(message: "MySQL error: \(details)")
}
func authenticate(user: String, password: String, database: String) throws {
let hello = try readPacket()
var c = ByteCursor(bytes: hello)
let version = try c.u8()
guard version == 10 else { throw MySQLWireError(message: "Unsupported MySQL handshake") }
_ = try c.nulString()
_ = try c.u32()
let salt1 = try c.take(8)
_ = try c.u8()
let lower = try c.u16()
_ = try c.u8() // character set
_ = try c.u16() // status
let upper = try c.u16()
let capabilities = lower | (upper << 16)
let authLen = Int(try c.u8())
_ = try c.take(10)
let salt2 = try c.take(max(13, authLen - 8))
let salt = salt1 + salt2.prefix(max(0, authLen - 9))
let plugin = (try? c.nulString()) ?? "mysql_native_password"
// This initial implementation deliberately refuses cleartext remote credentials.
guard hostIsLoopback else { throw MySQLWireError(message: "Direct remote connections require TLS, which is not yet implemented. Use a local SSH tunnel.") }
// CLIENT_LONG_PASSWORD, LONG_FLAG, PROTOCOL_41, TRANSACTIONS,
// SECURE_CONNECTION and PLUGIN_AUTH. Do not set DEPRECATE_EOF.
let clientFlags = 0x00000001 | 0x00000004 | 0x00000200 | 0x00002000 | 0x00008000 | 0x00080000
let response = try authResponse(password: password, salt: Array(salt), plugin: plugin)
var body = le32(clientFlags & capabilities) + le32(16_777_216) + [UInt8(45)] + [UInt8](repeating: 0, count: 23)
body += Array(user.utf8) + [0, UInt8(response.count)] + response
body += Array(plugin.utf8) + [0]
try send(body)
var reply = try readPacket()
try checkError(reply)
var currentPlugin = plugin
var currentSalt = Array(salt)
if reply.first == 0xfe {
var switchCursor = ByteCursor(bytes: Array(reply.dropFirst()))
let newPlugin = try switchCursor.nulString()
let rawSalt = Array(reply.dropFirst(1 + newPlugin.utf8.count + 1))
let newSalt = rawSalt.last == 0 ? Array(rawSalt.dropLast()) : rawSalt
currentPlugin = newPlugin
currentSalt = newSalt
try send(authResponse(password: password, salt: newSalt, plugin: newPlugin))
reply = try readPacket()
try checkError(reply)
}
if reply.first == 0x01 {
guard currentPlugin == "caching_sha2_password", reply.count >= 2 else {
throw MySQLWireError(message: "Unsupported authentication continuation")
}
switch reply[1] {
case 0x03: // Fast authentication accepted; wait for OK.
reply = try readPacket()
case 0x04: // Full authentication required.
guard hostIsLoopback else {
throw MySQLWireError(message: "Remote full authentication requires TLS (not implemented). Use a trusted local SSH tunnel.")
}
if password.isEmpty {
try send([0])
} else {
// Request the server's RSA public key (0x02). This is
// intended ONLY for a trusted local loopback connection.
try send([0x02])
let keyPacket = try readPacket()
try checkError(keyPacket)
let key = try mysqlRSAPublicKey(keyPacket)
guard !currentSalt.isEmpty else {
throw MySQLWireError(message: "Missing MySQL authentication nonce")
}
var scrambled = Array(password.utf8) + [0]
for i in scrambled.indices { scrambled[i] ^= currentSalt[i % currentSalt.count] }
var rsaError: Unmanaged<CFError>?
guard let encrypted = SecKeyCreateEncryptedData(
key, .rsaEncryptionOAEPSHA1, Data(scrambled) as CFData, &rsaError
) as Data? else {
throw MySQLWireError(message: "MySQL RSA encryption failed: \(rsaError?.takeRetainedValue().localizedDescription ?? "unknown error")")
}
try send(Array(encrypted))
}
reply = try readPacket()
default:
throw MySQLWireError(message: "Unsupported caching_sha2_password response: \(reply[1])")
}
}
try checkError(reply)
guard reply.first == 0x00 else { throw MySQLWireError(message: "Authentication did not complete") }
if !database.isEmpty { _ = try query("USE `\(database.replacingOccurrences(of: "`", with: "``"))`") }
}
private var hostIsLoopback = false
func markHost(_ host: String) { hostIsLoopback = ["localhost", "127.0.0.1", "::1"].contains(host.lowercased()) }
// Streams result rows without the query editor's 100,000-row limit.
// The callback is invoked on the session's serial background queue.
func streamFirstColumn(_ sql: String, onRow: (String) throws -> Void) throws -> Int64 {
sequence = 0
try send([0x03] + Array(sql.utf8))
let first = try readPacket()
try checkError(first)
guard first.first != 0x00 else { return 0 }
var cursor = ByteCursor(bytes: first)
guard let columnCount = try cursor.lenInt(), columnCount == 1 else {
throw MySQLWireError(message: "Export query must return exactly one column")
}
_ = try readPacket() // Column metadata
_ = try readPacket() // Column EOF
var count: Int64 = 0
while true {
let packet = try readPacket()
try checkError(packet)
if packet.first == 0xfe && packet.count < 9 { break }
var row = ByteCursor(bytes: packet)
guard let value = try row.lenString() else {
throw MySQLWireError(message: "Unexpected NULL export row")
}
try onRow(value)
count += 1
}
return count
}
func query(_ sql: String) throws -> QueryResult {
sequence = 0
try send([0x03] + Array(sql.utf8))
let first = try readPacket()
try checkError(first)
if first.first == 0x00 { return QueryResult(message: "Statement executed successfully") }
var c = ByteCursor(bytes: first)
guard let columnCount = try c.lenInt(), columnCount <= 4096 else { throw MySQLWireError(message: "Invalid column count") }
var names: [String] = []
for _ in 0..<columnCount {
var field = ByteCursor(bytes: try readPacket())
for _ in 0..<4 { _ = try field.lenString() }
names.append(try field.lenString() ?? "")
}
_ = try readPacket() // column EOF (initial protocol requires EOF)
var rows: [[String]] = []
while true {
let packet = try readPacket()
try checkError(packet)
if packet.first == 0xfe && packet.count < 9 { break }
var row = ByteCursor(bytes: packet)
var values: [String] = []
for _ in 0..<columnCount { values.append(try row.lenString() ?? "NULL") }
rows.append(values)
if rows.count > 100_000 { throw MySQLWireError(message: "Result exceeds 100,000 row safety limit") }
}
return QueryResult(columns: names, rows: rows, message: "\(rows.count) row(s)")
}
}
// A session owns one authenticated TCP connection. Access is serialized by the
// queue below because MySQLWireConnection is not safe for concurrent queries.
private final class PersistentMySQLSessions {
static let shared = PersistentMySQLSessions()
private struct Entry {
let profile: ConnectionProfile
let password: String
let connection: MySQLWireConnection
}
private let queue = DispatchQueue(label: "WorkbenchReplacement.MySQLSessions")
private var connections: [UUID: Entry] = [:]
func execute(profile: ConnectionProfile, database: String,
password: String, sql: String) throws -> QueryResult {
try queue.sync {
// A changed connection profile or password requires a new session.
if let previous = connections[profile.id],
(previous.profile != profile || previous.password != password) {
connections.removeValue(forKey: profile.id)
}
if connections[profile.id] == nil {
let connection = try MySQLWireConnection(host: profile.host, port: profile.port)
connection.markHost(profile.host)
try connection.authenticate(user: profile.username,
password: password, database: database)
connections[profile.id] = Entry(profile: profile,
password: password,
connection: connection)
}
guard let entry = connections[profile.id] else {
throw MySQLWireError(message: "MySQL session was not created")
}
do {
// Crucially, no new authentication takes place here. USE,
// session variables and transactions survive between queries.
return try entry.connection.query(sql)
} catch {
// The wire protocol can be out of sync after a failed query.
// Discard it rather than silently issuing commands on a bad socket.
connections.removeValue(forKey: profile.id)
throw error
}
}
}
func stream(profile: ConnectionProfile, database: String,
password: String, sql: String,
onRow: (String) throws -> Void) throws -> Int64 {
try queue.sync {
guard let entry = connections[profile.id],
entry.profile == profile, entry.password == password else {
throw MySQLWireError(message: "Export session is no longer connected")
}
do {
return try entry.connection.streamFirstColumn(sql, onRow: onRow)
} catch {
connections.removeValue(forKey: profile.id)
throw error
}
}
}
func disconnect(profileID: UUID) {
queue.sync { _ = connections.removeValue(forKey: profileID) }
}
}
enum NativeMySQLRunner {
static func run(profile: ConnectionProfile, database: String,
password: String, sql: String) async throws -> QueryResult {
try await Task.detached(priority: .userInitiated) {
try PersistentMySQLSessions.shared.execute(
profile: profile, database: database, password: password, sql: sql
)
}.value
}
static func stream(profile: ConnectionProfile, database: String,
password: String, sql: String,
onRow: @escaping (String) throws -> Void) async throws -> Int64 {
try await Task.detached(priority: .userInitiated) {
try PersistentMySQLSessions.shared.stream(
profile: profile, database: database, password: password,
sql: sql, onRow: { row in
try Task<Never, Never>.checkCancellation()
try onRow(row)
}
)
}.value
}
static func disconnect(profileID: UUID) {
PersistentMySQLSessions.shared.disconnect(profileID: profileID)
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 530 KiB

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.get-task-allow</key>
<true/>
</dict>
</plist>
@@ -1,383 +0,0 @@
#if 0
#elif defined(__arm64__) && __arm64__
// Generated by Apple Swift version 6.4 effective-5.10 (swiftlang-6.4.0.34.1 clang-2100.3.34.1)
#ifndef WORKBENCHREPLACEMENT_SWIFT_H
#define WORKBENCHREPLACEMENT_SWIFT_H
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgcc-compat"
#if !defined(__has_include)
# define __has_include(x) 0
#endif
#if !defined(__has_attribute)
# define __has_attribute(x) 0
#endif
#if !defined(__has_feature)
# define __has_feature(x) 0
#endif
#if !defined(__has_warning)
# define __has_warning(x) 0
#endif
#if __has_include(<swift/objc-prologue.h>)
# include <swift/objc-prologue.h>
#endif
#pragma clang diagnostic ignored "-Wauto-import"
#if defined(__OBJC__)
#include <Foundation/Foundation.h>
#endif // defined(__OBJC__)
#if defined(__cplusplus)
#include <cstdint>
#include <cstddef>
#include <cstring>
#include <stdlib.h>
#include <new>
#include <type_traits>
#else
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#endif
#if defined(__cplusplus)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnon-modular-include-in-framework-module"
#if defined(__arm64e__) && __has_include(<ptrauth.h>)
# include <ptrauth.h>
#else
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreserved-macro-identifier"
# ifndef __ptrauth_swift_value_witness_function_pointer
# define __ptrauth_swift_value_witness_function_pointer(x)
# endif
# ifndef __ptrauth_swift_class_method_pointer
# define __ptrauth_swift_class_method_pointer(x)
# endif
#pragma clang diagnostic pop
#endif
#pragma clang diagnostic pop
#endif
#if !defined(SWIFT_TYPEDEFS)
# define SWIFT_TYPEDEFS 1
# if __has_include(<uchar.h>)
# include <uchar.h>
# elif !defined(__cplusplus)
typedef unsigned char char8_t;
typedef uint_least16_t char16_t;
typedef uint_least32_t char32_t;
# endif
typedef float swift_float2 __attribute__((__ext_vector_type__(2)));
typedef float swift_float3 __attribute__((__ext_vector_type__(3)));
typedef float swift_float4 __attribute__((__ext_vector_type__(4)));
typedef double swift_double2 __attribute__((__ext_vector_type__(2)));
typedef double swift_double3 __attribute__((__ext_vector_type__(3)));
typedef double swift_double4 __attribute__((__ext_vector_type__(4)));
typedef int swift_int2 __attribute__((__ext_vector_type__(2)));
typedef int swift_int3 __attribute__((__ext_vector_type__(3)));
typedef int swift_int4 __attribute__((__ext_vector_type__(4)));
typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2)));
typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3)));
typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4)));
#endif
#if !defined(SWIFT_PASTE)
# define SWIFT_PASTE_HELPER(x, y) x##y
# define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y)
#endif
#if !defined(SWIFT_METATYPE)
# define SWIFT_METATYPE(X) Class
#endif
#if !defined(SWIFT_CLASS_PROPERTY)
# if __has_feature(objc_class_property)
# define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__
# else
# define SWIFT_CLASS_PROPERTY(...)
# endif
#endif
#if !defined(SWIFT_RUNTIME_NAME)
# if __has_attribute(objc_runtime_name)
# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X)))
# else
# define SWIFT_RUNTIME_NAME(X)
# endif
#endif
#if !defined(SWIFT_COMPILE_NAME)
# if __has_attribute(swift_name)
# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X)))
# else
# define SWIFT_COMPILE_NAME(X)
# endif
#endif
#if !defined(SWIFT_METHOD_FAMILY)
# if __has_attribute(objc_method_family)
# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X)))
# else
# define SWIFT_METHOD_FAMILY(X)
# endif
#endif
#if !defined(SWIFT_NOESCAPE)
# if __has_attribute(noescape)
# define SWIFT_NOESCAPE __attribute__((noescape))
# else
# define SWIFT_NOESCAPE
# endif
#endif
#if !defined(SWIFT_RELEASES_ARGUMENT)
# if __has_attribute(ns_consumed)
# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed))
# else
# define SWIFT_RELEASES_ARGUMENT
# endif
#endif
#if !defined(SWIFT_WARN_UNUSED_RESULT)
# if __has_attribute(warn_unused_result)
# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
# else
# define SWIFT_WARN_UNUSED_RESULT
# endif
#endif
#if !defined(SWIFT_NORETURN)
# if __has_attribute(noreturn)
# define SWIFT_NORETURN __attribute__((noreturn))
# else
# define SWIFT_NORETURN
# endif
#endif
#if !defined(SWIFT_CLASS_EXTRA)
# define SWIFT_CLASS_EXTRA
#endif
#if !defined(SWIFT_PROTOCOL_EXTRA)
# define SWIFT_PROTOCOL_EXTRA
#endif
#if !defined(SWIFT_ENUM_EXTRA)
# define SWIFT_ENUM_EXTRA
#endif
#if !defined(SWIFT_CLASS)
# if __has_attribute(objc_subclassing_restricted)
# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA
# define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA
# else
# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA
# define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA
# endif
#endif
#if !defined(SWIFT_RESILIENT_CLASS)
# if __has_attribute(objc_class_stub)
# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) __attribute__((objc_class_stub))
# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_class_stub)) SWIFT_CLASS_NAMED(SWIFT_NAME)
# else
# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME)
# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) SWIFT_CLASS_NAMED(SWIFT_NAME)
# endif
#endif
#if !defined(SWIFT_PROTOCOL)
# define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA
# define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA
#endif
#if !defined(SWIFT_EXTENSION)
# define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__)
#endif
#if !defined(OBJC_DESIGNATED_INITIALIZER)
# if __has_attribute(objc_designated_initializer)
# define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))
# else
# define OBJC_DESIGNATED_INITIALIZER
# endif
#endif
#if !defined(SWIFT_ENUM_ATTR)
# if __has_attribute(enum_extensibility)
# define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility)))
# else
# define SWIFT_ENUM_ATTR(_extensibility)
# endif
#endif
#if !defined(SWIFT_ENUM)
# if (defined(__cplusplus) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L) || __has_feature(objc_fixed_enum)
# define SWIFT_ENUM(_type, _name, _extensibility) enum _name : _type _name; enum SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type
# if __has_feature(generalized_swift_name)
# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type
# else
# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility)
# endif
# else
# define SWIFT_ENUM(_type, _name, _extensibility) _type _name; enum
# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility)
# endif
#endif
#if !defined(SWIFT_ENUM_TAG)
# if (defined(__cplusplus) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L) || __has_feature(objc_fixed_enum)
# define SWIFT_ENUM_TAG enum
# else
# define SWIFT_ENUM_TAG
# endif
#endif
#if !defined(SWIFT_ENUM_FWD_DECL)
# if (defined(__cplusplus) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L) || __has_feature(objc_fixed_enum)
# define SWIFT_ENUM_FWD_DECL(_type, _name) enum _name : _type;
# else
# define SWIFT_ENUM_FWD_DECL(_type, _name) typedef _type _name;
# endif
#endif
#if !defined(SWIFT_UNAVAILABLE)
# define SWIFT_UNAVAILABLE __attribute__((unavailable))
#endif
#if !defined(SWIFT_UNAVAILABLE_MSG)
# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg)))
#endif
#if !defined(SWIFT_AVAILABILITY)
# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__)))
#endif
#if !defined(SWIFT_AVAILABILITY_DOMAIN)
# define SWIFT_AVAILABILITY_DOMAIN(dom, ...) __attribute__((availability(domain: dom, __VA_ARGS__)))
#endif
#if !defined(SWIFT_WEAK_IMPORT)
# define SWIFT_WEAK_IMPORT __attribute__((weak_import))
#endif
#if !defined(SWIFT_DEPRECATED)
# define SWIFT_DEPRECATED __attribute__((deprecated))
#endif
#if !defined(SWIFT_DEPRECATED_MSG)
# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__)))
#endif
#if !defined(SWIFT_DEPRECATED_OBJC)
# if __has_feature(attribute_diagnose_if_objc)
# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning")))
# else
# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg)
# endif
#endif
#if defined(__OBJC__)
#if !defined(IBSegueAction)
# define IBSegueAction
#endif
#endif
#if !defined(SWIFT_EXTERN)
# if defined(__cplusplus)
# define SWIFT_EXTERN extern "C"
# else
# define SWIFT_EXTERN extern
# endif
#endif
#if !defined(SWIFT_CALL)
# define SWIFT_CALL __attribute__((swiftcall))
#endif
#if !defined(SWIFT_INDIRECT_RESULT)
# define SWIFT_INDIRECT_RESULT __attribute__((swift_indirect_result))
#endif
#if !defined(SWIFT_CONTEXT)
# define SWIFT_CONTEXT __attribute__((swift_context))
#endif
#if !defined(SWIFT_ERROR_RESULT)
# define SWIFT_ERROR_RESULT __attribute__((swift_error_result))
#endif
#if defined(__cplusplus)
# define SWIFT_NOEXCEPT noexcept
#else
# define SWIFT_NOEXCEPT
#endif
#if !defined(SWIFT_C_INLINE_THUNK)
# if __has_attribute(always_inline)
# if __has_attribute(nodebug)
# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) __attribute__((nodebug))
# else
# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline))
# endif
# else
# define SWIFT_C_INLINE_THUNK inline
# endif
#endif
#if defined(_WIN32)
#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL)
# define SWIFT_IMPORT_STDLIB_SYMBOL __declspec(dllimport)
#endif
#else
#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL)
# define SWIFT_IMPORT_STDLIB_SYMBOL
#endif
#endif
#if !__has_feature(nullability)
# define _Nonnull
# define _Nullable
# define _Null_unspecified
#elif !defined(__OBJC__)
# pragma clang diagnostic ignored "-Wnullability-extension"
#endif
#if !__has_feature(nullability_nullable_result)
# define _Nullable_result _Nullable
#endif
#if __has_feature(objc_modules)
#if __has_warning("-Watimport-in-framework-header")
#pragma clang diagnostic ignored "-Watimport-in-framework-header"
#endif
#endif
#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch"
#pragma clang diagnostic ignored "-Wduplicate-method-arg"
#if __has_warning("-Wpragma-clang-attribute")
# pragma clang diagnostic ignored "-Wpragma-clang-attribute"
#endif
#pragma clang diagnostic ignored "-Wunknown-pragmas"
#pragma clang diagnostic ignored "-Wnullability"
#pragma clang diagnostic ignored "-Warc-bridge-casts-disallowed-in-nonarc"
#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension"
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
#if __has_attribute(external_source_symbol)
# pragma push_macro("any")
# undef any
# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="WorkbenchReplacement",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol))
# pragma pop_macro("any")
#endif
#if defined(__cplusplus)
extern "C" {
#endif
#if defined(__cplusplus)
} // extern "C"
#endif
#if __has_attribute(external_source_symbol)
# pragma clang attribute pop
#endif
#if defined(__OBJC__)
#if __has_feature(objc_modules)
#if __has_warning("-Watimport-in-framework-header")
#pragma clang diagnostic ignored "-Watimport-in-framework-header"
#endif
#endif
#endif // defined(__OBJC__)
#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch"
#pragma clang diagnostic ignored "-Wduplicate-method-arg"
#if __has_warning("-Wpragma-clang-attribute")
# pragma clang diagnostic ignored "-Wpragma-clang-attribute"
#endif
#pragma clang diagnostic ignored "-Wunknown-pragmas"
#pragma clang diagnostic ignored "-Wnullability"
#pragma clang diagnostic ignored "-Warc-bridge-casts-disallowed-in-nonarc"
#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension"
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
#if __has_attribute(external_source_symbol)
# pragma push_macro("any")
# undef any
# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="WorkbenchReplacement",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol))
# pragma pop_macro("any")
#endif
#if defined(__OBJC__)
#endif // defined(__OBJC__)
#if __has_attribute(external_source_symbol)
# pragma clang attribute pop
#endif
#if defined(__cplusplus)
#endif
#pragma clang diagnostic pop
#endif
#else
#error unsupported Swift architecture
#endif
@@ -1,224 +0,0 @@
{
"source": "/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement/App.swift",
"tables": {
"Localizable": [
{
"comment": "",
"key": "Workbench Replacement",
"location": {
"startingColumn": 21,
"startingLine": 8
}
},
{
"comment": "",
"key": "SQL Editor",
"location": {
"startingColumn": 25,
"startingLine": 99
}
},
{
"comment": "",
"key": "Query Execution",
"location": {
"startingColumn": 25,
"startingLine": 107
}
},
{
"comment": "",
"key": "Result Grid",
"location": {
"startingColumn": 25,
"startingLine": 115
}
},
{
"comment": "",
"key": "General",
"location": {
"startingColumn": 25,
"startingLine": 123
}
},
{
"comment": "",
"key": "%lld pt",
"location": {
"startingColumn": 21,
"startingLine": 137
}
},
{
"comment": "",
"key": "",
"location": {
"startingColumn": 24,
"startingLine": 146
}
},
{
"comment": "",
"key": "2 spaces",
"location": {
"startingColumn": 26,
"startingLine": 147
}
},
{
"comment": "",
"key": "4 spaces",
"location": {
"startingColumn": 26,
"startingLine": 148
}
},
{
"comment": "",
"key": "8 spaces",
"location": {
"startingColumn": 26,
"startingLine": 149
}
},
{
"comment": "",
"key": "Automatically uppercase SQL keywords",
"location": {
"startingColumn": 21,
"startingLine": 157
}
},
{
"comment": "",
"key": "Show line numbers",
"location": {
"startingColumn": 21,
"startingLine": 164
}
},
{
"comment": "",
"key": "Controls the number of rows retrieved when opening a table from the schema navigator.",
"location": {
"startingColumn": 17,
"startingLine": 195
}
},
{
"comment": "",
"key": "Examples",
"location": {
"startingColumn": 18,
"startingLine": 205
}
},
{
"comment": "",
"key": "",
"location": {
"startingColumn": 25,
"startingLine": 177
}
},
{
"comment": "",
"key": "",
"location": {
"startingColumn": 25,
"startingLine": 185
}
},
{
"comment": "",
"key": "%lld px",
"location": {
"startingColumn": 21,
"startingLine": 224
}
},
{
"comment": "",
"key": "%lld px",
"location": {
"startingColumn": 21,
"startingLine": 234
}
},
{
"comment": "",
"key": "Alternating row colours",
"location": {
"startingColumn": 21,
"startingLine": 244
}
},
{
"comment": "",
"key": "Automatically expand table folders",
"location": {
"startingColumn": 21,
"startingLine": 256
}
},
{
"comment": "",
"key": "Workbench Replacement",
"location": {
"startingColumn": 18,
"startingLine": 344
}
},
{
"comment": "",
"key": "Modern MySQL Client for macOS",
"location": {
"startingColumn": 18,
"startingLine": 347
}
},
{
"comment": "",
"key": "Starting Workbench Replacement…",
"location": {
"startingColumn": 18,
"startingLine": 364
}
},
{
"comment": "",
"key": "New Query Tab",
"location": {
"startingColumn": 20,
"startingLine": 404
}
},
{
"comment": "",
"key": "Open SQL Script…",
"location": {
"startingColumn": 20,
"startingLine": 411
}
},
{
"comment": "",
"key": "Save SQL Script",
"location": {
"startingColumn": 20,
"startingLine": 418
}
},
{
"comment": "",
"key": "Save SQL Script As…",
"location": {
"startingColumn": 20,
"startingLine": 423
}
}
]
},
"version": 1
}
@@ -1,5 +0,0 @@
{
"source": "/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement/DatabaseModel.swift",
"tables": {},
"version": 1
}
@@ -1,7 +0,0 @@
{
"source" : "ExtractedAppShortcutsMetadata",
"tables" : {
},
"version" : 2
}
@@ -1,656 +0,0 @@
{
"source": "/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement/MainView.swift",
"tables": {
"Localizable": [
{
"comment": "",
"key": "MySQL Workbench",
"location": {
"startingColumn": 18,
"startingLine": 99
}
},
{
"comment": "",
"key": "Navigator",
"location": {
"startingColumn": 20,
"startingLine": 188
}
},
{
"comment": "",
"key": "Administration",
"location": {
"startingColumn": 22,
"startingLine": 189
}
},
{
"comment": "",
"key": "Schemas",
"location": {
"startingColumn": 22,
"startingLine": 190
}
},
{
"comment": "",
"key": "SCHEMAS",
"location": {
"startingColumn": 22,
"startingLine": 210
}
},
{
"comment": "",
"key": "Edit Connection",
"location": {
"startingColumn": 36,
"startingLine": 264
}
},
{
"comment": "",
"key": "Set as Default Schema",
"location": {
"startingColumn": 24,
"startingLine": 381
}
},
{
"comment": "",
"key": "Refresh Tables",
"location": {
"startingColumn": 24,
"startingLine": 385
}
},
{
"comment": "",
"key": "Load Tables",
"location": {
"startingColumn": 28,
"startingLine": 455
}
},
{
"comment": "",
"key": "Tables",
"location": {
"startingColumn": 22,
"startingLine": 428
}
},
{
"comment": "",
"key": "Select First 100 Rows",
"location": {
"startingColumn": 20,
"startingLine": 511
}
},
{
"comment": "",
"key": "New Query Tab",
"location": {
"startingColumn": 19,
"startingLine": 632
}
},
{
"comment": "",
"key": "Default Schema:",
"location": {
"startingColumn": 18,
"startingLine": 657
}
},
{
"comment": "",
"key": "SQL Editor",
"location": {
"startingColumn": 18,
"startingLine": 667
}
},
{
"comment": "",
"key": "Execute SQL",
"location": {
"startingColumn": 19,
"startingLine": 645
}
},
{
"comment": "",
"key": "Format SQL",
"location": {
"startingColumn": 19,
"startingLine": 652
}
},
{
"comment": "",
"key": "%lld",
"location": {
"startingColumn": 26,
"startingLine": 707
}
},
{
"comment": "",
"key": "Result Grid",
"location": {
"startingColumn": 22,
"startingLine": 739
}
},
{
"comment": "",
"key": "No query results",
"location": {
"startingColumn": 26,
"startingLine": 757
}
},
{
"comment": "",
"key": "Action Output",
"location": {
"startingColumn": 22,
"startingLine": 779
}
},
{
"comment": "",
"key": "UTF-8",
"location": {
"startingColumn": 18,
"startingLine": 854
}
},
{
"comment": "",
"key": "Connection Settings",
"location": {
"startingColumn": 18,
"startingLine": 1029
}
},
{
"comment": "",
"key": "Connection Name",
"location": {
"startingColumn": 21,
"startingLine": 1034
}
},
{
"comment": "",
"key": "Hostname",
"location": {
"startingColumn": 21,
"startingLine": 1039
}
},
{
"comment": "",
"key": "Port",
"location": {
"startingColumn": 21,
"startingLine": 1044
}
},
{
"comment": "",
"key": "Username",
"location": {
"startingColumn": 21,
"startingLine": 1050
}
},
{
"comment": "",
"key": "Password (macOS Keychain)",
"location": {
"startingColumn": 21,
"startingLine": 1055
}
},
{
"comment": "",
"key": "Default Schema",
"location": {
"startingColumn": 21,
"startingLine": 1060
}
},
{
"comment": "",
"key": "Cancel",
"location": {
"startingColumn": 24,
"startingLine": 1068
}
},
{
"comment": "",
"key": "Save",
"location": {
"startingColumn": 24,
"startingLine": 1072
}
},
{
"comment": "",
"key": "The server did not return any columns.",
"location": {
"startingColumn": 25,
"startingLine": 1394
}
},
{
"comment": "",
"key": "Loading %@…",
"location": {
"startingColumn": 30,
"startingLine": 1372
}
},
{
"comment": "",
"key": "No Data",
"location": {
"startingColumn": 21,
"startingLine": 1391
}
},
{
"comment": "",
"key": "Unable to Load %@",
"location": {
"startingColumn": 25,
"startingLine": 1378
}
},
{
"comment": "",
"key": "Retry",
"location": {
"startingColumn": 28,
"startingLine": 1384
}
},
{
"comment": "",
"key": "Refresh",
"location": {
"startingColumn": 23,
"startingLine": 1433
}
},
{
"comment": "",
"key": "SQL Editor",
"location": {
"startingColumn": 23,
"startingLine": 1440
}
},
{
"comment": "",
"key": "Not loaded",
"location": {
"startingColumn": 22,
"startingLine": 1518
}
},
{
"comment": "",
"key": "Updated %@",
"location": {
"startingColumn": 21,
"startingLine": 1515
}
},
{
"comment": "",
"key": "%lld rows",
"location": {
"startingColumn": 18,
"startingLine": 1523
}
},
{
"comment": "",
"key": "Import SQL into %@?",
"location": {
"startingColumn": 13,
"startingLine": 1780
}
},
{
"comment": "",
"key": "Operation",
"location": {
"startingColumn": 20,
"startingLine": 1600
}
},
{
"comment": "",
"key": "Data Export",
"location": {
"startingColumn": 22,
"startingLine": 1601
}
},
{
"comment": "",
"key": "Data Import / Restore",
"location": {
"startingColumn": 22,
"startingLine": 1602
}
},
{
"comment": "",
"key": "MySQL Connection",
"location": {
"startingColumn": 26,
"startingLine": 1609
}
},
{
"comment": "",
"key": "Export Options",
"location": {
"startingColumn": 30,
"startingLine": 1633
}
},
{
"comment": "",
"key": "Import Options",
"location": {
"startingColumn": 30,
"startingLine": 1689
}
},
{
"comment": "",
"key": "Progress",
"location": {
"startingColumn": 26,
"startingLine": 1701
}
},
{
"comment": "",
"key": "Connection",
"location": {
"startingColumn": 36,
"startingLine": 1611
}
},
{
"comment": "",
"key": "Database",
"location": {
"startingColumn": 28,
"startingLine": 1615
}
},
{
"comment": "",
"key": "Select Database",
"location": {
"startingColumn": 30,
"startingLine": 1616
}
},
{
"comment": "",
"key": "Refresh Databases",
"location": {
"startingColumn": 32,
"startingLine": 1624
}
},
{
"comment": "",
"key": "No tables found. Select a database or refresh the list.",
"location": {
"startingColumn": 34,
"startingLine": 1655
}
},
{
"comment": "",
"key": "InnoDB data is exported using a consistent snapshot and a single streamed query per table. Tables without unique indexes are supported.",
"location": {
"startingColumn": 30,
"startingLine": 1684
}
},
{
"comment": "",
"key": "Table structure",
"location": {
"startingColumn": 32,
"startingLine": 1635
}
},
{
"comment": "",
"key": "Table data",
"location": {
"startingColumn": 32,
"startingLine": 1636
}
},
{
"comment": "",
"key": "Add DROP TABLE IF EXISTS",
"location": {
"startingColumn": 32,
"startingLine": 1637
}
},
{
"comment": "",
"key": "Include views (definitions)",
"location": {
"startingColumn": 32,
"startingLine": 1639
}
},
{
"comment": "",
"key": "Include triggers",
"location": {
"startingColumn": 32,
"startingLine": 1641
}
},
{
"comment": "",
"key": "Include stored procedures and functions",
"location": {
"startingColumn": 32,
"startingLine": 1643
}
},
{
"comment": "",
"key": "Loading tables…",
"location": {
"startingColumn": 57,
"startingLine": 1653
}
},
{
"comment": "",
"key": "Tables (%lld selected)",
"location": {
"startingColumn": 34,
"startingLine": 1646
}
},
{
"comment": "",
"key": "All",
"location": {
"startingColumn": 36,
"startingLine": 1648
}
},
{
"comment": "",
"key": "None",
"location": {
"startingColumn": 36,
"startingLine": 1649
}
},
{
"comment": "",
"key": "Refresh",
"location": {
"startingColumn": 36,
"startingLine": 1650
}
},
{
"comment": "",
"key": "Views (%lld selected)",
"location": {
"startingColumn": 38,
"startingLine": 1669
}
},
{
"comment": "",
"key": "All",
"location": {
"startingColumn": 40,
"startingLine": 1671
}
},
{
"comment": "",
"key": "None",
"location": {
"startingColumn": 40,
"startingLine": 1672
}
},
{
"comment": "",
"key": "Import SQL statements into the selected database.",
"location": {
"startingColumn": 30,
"startingLine": 1691
}
},
{
"comment": "",
"key": "Supports MySQL DELIMITER directives, triggers and stored routines. Import is not transactional; use a verified backup before restoring.",
"location": {
"startingColumn": 30,
"startingLine": 1694
}
},
{
"comment": "",
"key": "Close",
"location": {
"startingColumn": 24,
"startingLine": 1720
}
},
{
"comment": "",
"key": "Verify Backup…",
"location": {
"startingColumn": 28,
"startingLine": 1728
}
},
{
"comment": "",
"key": "Verify an SQL file against its SHA-256 checksum sidecar",
"location": {
"startingColumn": 27,
"startingLine": 1731
}
},
{
"comment": "",
"key": "Cancel",
"location": {
"startingColumn": 28,
"startingLine": 1735
}
},
{
"comment": "",
"key": "Export SQL…",
"location": {
"startingColumn": 31,
"startingLine": 1742
}
},
{
"comment": "",
"key": "Import SQL…",
"location": {
"startingColumn": 31,
"startingLine": 1743
}
},
{
"comment": "",
"key": "Import SQL",
"location": {
"startingColumn": 20,
"startingLine": 1783
}
},
{
"comment": "",
"key": "Cancel",
"location": {
"startingColumn": 20,
"startingLine": 1791
}
},
{
"comment": "",
"key": "SQL files can modify or delete existing database objects and data. Ensure you have a backup before continuing.",
"location": {
"startingColumn": 17,
"startingLine": 1796
}
},
{
"comment": "",
"key": "Database Import / Export",
"location": {
"startingColumn": 22,
"startingLine": 1808
}
},
{
"comment": "",
"key": "Built-in native MySQL database tools",
"location": {
"startingColumn": 22,
"startingLine": 1811
}
}
]
},
"version": 1
}
@@ -1,5 +0,0 @@
{
"source": "/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement/MySQLProtocol.swift",
"tables": {},
"version": 1
}
@@ -1,49 +0,0 @@
{
"" : {
"diagnostics" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement-primary.dia",
"emit-module-dependencies" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement-primary-emit-module.d",
"emit-module-diagnostics" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement-primary-emit-module.dia",
"pch" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement-primary-Bridging-header.pch",
"swift-dependencies" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement-primary.swiftdeps"
},
"/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement/App.swift" : {
"const-values" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.swiftconstvalues",
"dependencies" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.d",
"diagnostics" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.dia",
"index-unit-output-path" : "/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.o",
"llvm-bc" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.bc",
"object" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.o",
"swift-dependencies" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.swiftdeps",
"swiftmodule" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App~partial.swiftmodule"
},
"/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement/DatabaseModel.swift" : {
"const-values" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/DatabaseModel.swiftconstvalues",
"dependencies" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/DatabaseModel.d",
"diagnostics" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/DatabaseModel.dia",
"index-unit-output-path" : "/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/DatabaseModel.o",
"llvm-bc" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/DatabaseModel.bc",
"object" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/DatabaseModel.o",
"swift-dependencies" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/DatabaseModel.swiftdeps",
"swiftmodule" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/DatabaseModel~partial.swiftmodule"
},
"/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement/MainView.swift" : {
"const-values" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.swiftconstvalues",
"dependencies" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.d",
"diagnostics" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.dia",
"index-unit-output-path" : "/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.o",
"llvm-bc" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.bc",
"object" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.o",
"swift-dependencies" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.swiftdeps",
"swiftmodule" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView~partial.swiftmodule"
},
"/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement/MySQLProtocol.swift" : {
"const-values" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MySQLProtocol.swiftconstvalues",
"dependencies" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MySQLProtocol.d",
"diagnostics" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MySQLProtocol.dia",
"index-unit-output-path" : "/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MySQLProtocol.o",
"llvm-bc" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MySQLProtocol.bc",
"object" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MySQLProtocol.o",
"swift-dependencies" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MySQLProtocol.swiftdeps",
"swiftmodule" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MySQLProtocol~partial.swiftmodule"
}
}
@@ -1,377 +0,0 @@
// Generated by Apple Swift version 6.4 effective-5.10 (swiftlang-6.4.0.34.1 clang-2100.3.34.1)
#ifndef WORKBENCHREPLACEMENT_SWIFT_H
#define WORKBENCHREPLACEMENT_SWIFT_H
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgcc-compat"
#if !defined(__has_include)
# define __has_include(x) 0
#endif
#if !defined(__has_attribute)
# define __has_attribute(x) 0
#endif
#if !defined(__has_feature)
# define __has_feature(x) 0
#endif
#if !defined(__has_warning)
# define __has_warning(x) 0
#endif
#if __has_include(<swift/objc-prologue.h>)
# include <swift/objc-prologue.h>
#endif
#pragma clang diagnostic ignored "-Wauto-import"
#if defined(__OBJC__)
#include <Foundation/Foundation.h>
#endif // defined(__OBJC__)
#if defined(__cplusplus)
#include <cstdint>
#include <cstddef>
#include <cstring>
#include <stdlib.h>
#include <new>
#include <type_traits>
#else
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#endif
#if defined(__cplusplus)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnon-modular-include-in-framework-module"
#if defined(__arm64e__) && __has_include(<ptrauth.h>)
# include <ptrauth.h>
#else
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreserved-macro-identifier"
# ifndef __ptrauth_swift_value_witness_function_pointer
# define __ptrauth_swift_value_witness_function_pointer(x)
# endif
# ifndef __ptrauth_swift_class_method_pointer
# define __ptrauth_swift_class_method_pointer(x)
# endif
#pragma clang diagnostic pop
#endif
#pragma clang diagnostic pop
#endif
#if !defined(SWIFT_TYPEDEFS)
# define SWIFT_TYPEDEFS 1
# if __has_include(<uchar.h>)
# include <uchar.h>
# elif !defined(__cplusplus)
typedef unsigned char char8_t;
typedef uint_least16_t char16_t;
typedef uint_least32_t char32_t;
# endif
typedef float swift_float2 __attribute__((__ext_vector_type__(2)));
typedef float swift_float3 __attribute__((__ext_vector_type__(3)));
typedef float swift_float4 __attribute__((__ext_vector_type__(4)));
typedef double swift_double2 __attribute__((__ext_vector_type__(2)));
typedef double swift_double3 __attribute__((__ext_vector_type__(3)));
typedef double swift_double4 __attribute__((__ext_vector_type__(4)));
typedef int swift_int2 __attribute__((__ext_vector_type__(2)));
typedef int swift_int3 __attribute__((__ext_vector_type__(3)));
typedef int swift_int4 __attribute__((__ext_vector_type__(4)));
typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2)));
typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3)));
typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4)));
#endif
#if !defined(SWIFT_PASTE)
# define SWIFT_PASTE_HELPER(x, y) x##y
# define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y)
#endif
#if !defined(SWIFT_METATYPE)
# define SWIFT_METATYPE(X) Class
#endif
#if !defined(SWIFT_CLASS_PROPERTY)
# if __has_feature(objc_class_property)
# define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__
# else
# define SWIFT_CLASS_PROPERTY(...)
# endif
#endif
#if !defined(SWIFT_RUNTIME_NAME)
# if __has_attribute(objc_runtime_name)
# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X)))
# else
# define SWIFT_RUNTIME_NAME(X)
# endif
#endif
#if !defined(SWIFT_COMPILE_NAME)
# if __has_attribute(swift_name)
# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X)))
# else
# define SWIFT_COMPILE_NAME(X)
# endif
#endif
#if !defined(SWIFT_METHOD_FAMILY)
# if __has_attribute(objc_method_family)
# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X)))
# else
# define SWIFT_METHOD_FAMILY(X)
# endif
#endif
#if !defined(SWIFT_NOESCAPE)
# if __has_attribute(noescape)
# define SWIFT_NOESCAPE __attribute__((noescape))
# else
# define SWIFT_NOESCAPE
# endif
#endif
#if !defined(SWIFT_RELEASES_ARGUMENT)
# if __has_attribute(ns_consumed)
# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed))
# else
# define SWIFT_RELEASES_ARGUMENT
# endif
#endif
#if !defined(SWIFT_WARN_UNUSED_RESULT)
# if __has_attribute(warn_unused_result)
# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
# else
# define SWIFT_WARN_UNUSED_RESULT
# endif
#endif
#if !defined(SWIFT_NORETURN)
# if __has_attribute(noreturn)
# define SWIFT_NORETURN __attribute__((noreturn))
# else
# define SWIFT_NORETURN
# endif
#endif
#if !defined(SWIFT_CLASS_EXTRA)
# define SWIFT_CLASS_EXTRA
#endif
#if !defined(SWIFT_PROTOCOL_EXTRA)
# define SWIFT_PROTOCOL_EXTRA
#endif
#if !defined(SWIFT_ENUM_EXTRA)
# define SWIFT_ENUM_EXTRA
#endif
#if !defined(SWIFT_CLASS)
# if __has_attribute(objc_subclassing_restricted)
# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA
# define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA
# else
# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA
# define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA
# endif
#endif
#if !defined(SWIFT_RESILIENT_CLASS)
# if __has_attribute(objc_class_stub)
# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) __attribute__((objc_class_stub))
# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_class_stub)) SWIFT_CLASS_NAMED(SWIFT_NAME)
# else
# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME)
# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) SWIFT_CLASS_NAMED(SWIFT_NAME)
# endif
#endif
#if !defined(SWIFT_PROTOCOL)
# define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA
# define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA
#endif
#if !defined(SWIFT_EXTENSION)
# define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__)
#endif
#if !defined(OBJC_DESIGNATED_INITIALIZER)
# if __has_attribute(objc_designated_initializer)
# define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))
# else
# define OBJC_DESIGNATED_INITIALIZER
# endif
#endif
#if !defined(SWIFT_ENUM_ATTR)
# if __has_attribute(enum_extensibility)
# define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility)))
# else
# define SWIFT_ENUM_ATTR(_extensibility)
# endif
#endif
#if !defined(SWIFT_ENUM)
# if (defined(__cplusplus) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L) || __has_feature(objc_fixed_enum)
# define SWIFT_ENUM(_type, _name, _extensibility) enum _name : _type _name; enum SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type
# if __has_feature(generalized_swift_name)
# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type
# else
# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility)
# endif
# else
# define SWIFT_ENUM(_type, _name, _extensibility) _type _name; enum
# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility)
# endif
#endif
#if !defined(SWIFT_ENUM_TAG)
# if (defined(__cplusplus) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L) || __has_feature(objc_fixed_enum)
# define SWIFT_ENUM_TAG enum
# else
# define SWIFT_ENUM_TAG
# endif
#endif
#if !defined(SWIFT_ENUM_FWD_DECL)
# if (defined(__cplusplus) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L) || __has_feature(objc_fixed_enum)
# define SWIFT_ENUM_FWD_DECL(_type, _name) enum _name : _type;
# else
# define SWIFT_ENUM_FWD_DECL(_type, _name) typedef _type _name;
# endif
#endif
#if !defined(SWIFT_UNAVAILABLE)
# define SWIFT_UNAVAILABLE __attribute__((unavailable))
#endif
#if !defined(SWIFT_UNAVAILABLE_MSG)
# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg)))
#endif
#if !defined(SWIFT_AVAILABILITY)
# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__)))
#endif
#if !defined(SWIFT_AVAILABILITY_DOMAIN)
# define SWIFT_AVAILABILITY_DOMAIN(dom, ...) __attribute__((availability(domain: dom, __VA_ARGS__)))
#endif
#if !defined(SWIFT_WEAK_IMPORT)
# define SWIFT_WEAK_IMPORT __attribute__((weak_import))
#endif
#if !defined(SWIFT_DEPRECATED)
# define SWIFT_DEPRECATED __attribute__((deprecated))
#endif
#if !defined(SWIFT_DEPRECATED_MSG)
# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__)))
#endif
#if !defined(SWIFT_DEPRECATED_OBJC)
# if __has_feature(attribute_diagnose_if_objc)
# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning")))
# else
# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg)
# endif
#endif
#if defined(__OBJC__)
#if !defined(IBSegueAction)
# define IBSegueAction
#endif
#endif
#if !defined(SWIFT_EXTERN)
# if defined(__cplusplus)
# define SWIFT_EXTERN extern "C"
# else
# define SWIFT_EXTERN extern
# endif
#endif
#if !defined(SWIFT_CALL)
# define SWIFT_CALL __attribute__((swiftcall))
#endif
#if !defined(SWIFT_INDIRECT_RESULT)
# define SWIFT_INDIRECT_RESULT __attribute__((swift_indirect_result))
#endif
#if !defined(SWIFT_CONTEXT)
# define SWIFT_CONTEXT __attribute__((swift_context))
#endif
#if !defined(SWIFT_ERROR_RESULT)
# define SWIFT_ERROR_RESULT __attribute__((swift_error_result))
#endif
#if defined(__cplusplus)
# define SWIFT_NOEXCEPT noexcept
#else
# define SWIFT_NOEXCEPT
#endif
#if !defined(SWIFT_C_INLINE_THUNK)
# if __has_attribute(always_inline)
# if __has_attribute(nodebug)
# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) __attribute__((nodebug))
# else
# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline))
# endif
# else
# define SWIFT_C_INLINE_THUNK inline
# endif
#endif
#if defined(_WIN32)
#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL)
# define SWIFT_IMPORT_STDLIB_SYMBOL __declspec(dllimport)
#endif
#else
#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL)
# define SWIFT_IMPORT_STDLIB_SYMBOL
#endif
#endif
#if !__has_feature(nullability)
# define _Nonnull
# define _Nullable
# define _Null_unspecified
#elif !defined(__OBJC__)
# pragma clang diagnostic ignored "-Wnullability-extension"
#endif
#if !__has_feature(nullability_nullable_result)
# define _Nullable_result _Nullable
#endif
#if __has_feature(objc_modules)
#if __has_warning("-Watimport-in-framework-header")
#pragma clang diagnostic ignored "-Watimport-in-framework-header"
#endif
#endif
#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch"
#pragma clang diagnostic ignored "-Wduplicate-method-arg"
#if __has_warning("-Wpragma-clang-attribute")
# pragma clang diagnostic ignored "-Wpragma-clang-attribute"
#endif
#pragma clang diagnostic ignored "-Wunknown-pragmas"
#pragma clang diagnostic ignored "-Wnullability"
#pragma clang diagnostic ignored "-Warc-bridge-casts-disallowed-in-nonarc"
#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension"
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
#if __has_attribute(external_source_symbol)
# pragma push_macro("any")
# undef any
# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="WorkbenchReplacement",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol))
# pragma pop_macro("any")
#endif
#if defined(__cplusplus)
extern "C" {
#endif
#if defined(__cplusplus)
} // extern "C"
#endif
#if __has_attribute(external_source_symbol)
# pragma clang attribute pop
#endif
#if defined(__OBJC__)
#if __has_feature(objc_modules)
#if __has_warning("-Watimport-in-framework-header")
#pragma clang diagnostic ignored "-Watimport-in-framework-header"
#endif
#endif
#endif // defined(__OBJC__)
#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch"
#pragma clang diagnostic ignored "-Wduplicate-method-arg"
#if __has_warning("-Wpragma-clang-attribute")
# pragma clang diagnostic ignored "-Wpragma-clang-attribute"
#endif
#pragma clang diagnostic ignored "-Wunknown-pragmas"
#pragma clang diagnostic ignored "-Wnullability"
#pragma clang diagnostic ignored "-Warc-bridge-casts-disallowed-in-nonarc"
#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension"
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
#if __has_attribute(external_source_symbol)
# pragma push_macro("any")
# undef any
# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="WorkbenchReplacement",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol))
# pragma pop_macro("any")
#endif
#if defined(__OBJC__)
#endif // defined(__OBJC__)
#if __has_attribute(external_source_symbol)
# pragma clang attribute pop
#endif
#if defined(__cplusplus)
#endif
#pragma clang diagnostic pop
#endif
@@ -1,703 +0,0 @@
[
{
"isFramework" : true,
"moduleName" : "Accessibility",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/Accessibility.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : true,
"moduleName" : "AppKit",
"modulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/AppKit-H8IDH8MPDKFF.swiftmodule"
},
{
"isFramework" : true,
"moduleName" : "ColorSync",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/ColorSync.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : true,
"moduleName" : "Combine",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/Combine.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : false,
"moduleName" : "CoreAudio",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/CoreAudio.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : true,
"moduleName" : "CoreData",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/CoreData.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : false,
"moduleName" : "CoreFoundation",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/CoreFoundation.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : true,
"moduleName" : "CoreGraphics",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/CoreGraphics.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : false,
"moduleName" : "CoreImage",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/CoreImage.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : true,
"moduleName" : "CoreMedia",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/CoreMedia.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : true,
"moduleName" : "CoreText",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/CoreText.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : true,
"moduleName" : "CoreTransferable",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/CoreTransferable.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : true,
"moduleName" : "CoreVideo",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/CoreVideo.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : true,
"moduleName" : "CryptoKit",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/CryptoKit.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : false,
"moduleName" : "Darwin",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/Darwin.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : true,
"moduleName" : "DataDetection",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/DataDetection.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : true,
"moduleName" : "DeveloperToolsSupport",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/DeveloperToolsSupport.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : false,
"moduleName" : "Dispatch",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/Dispatch.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : true,
"moduleName" : "Foundation",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/Foundation.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : false,
"moduleName" : "IOKit",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/IOKit.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : true,
"moduleName" : "ImageIO",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/ImageIO.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : true,
"moduleName" : "LocalAuthentication",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/LocalAuthentication.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : false,
"moduleName" : "Metal",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/Metal.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : false,
"moduleName" : "OSLog",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/OSLog.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : false,
"moduleName" : "ObjectiveC",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/ObjectiveC.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : false,
"moduleName" : "Observation",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/Observation.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : false,
"moduleName" : "QuartzCore",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/QuartzCore.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : false,
"moduleName" : "Spatial",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/Spatial.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : false,
"moduleName" : "Swift",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/Swift.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : false,
"moduleName" : "SwiftOnoneSupport",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/SwiftOnoneSupport.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : true,
"moduleName" : "SwiftUI",
"modulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/SwiftUI-3PKZHEDTAMMUE.swiftmodule"
},
{
"isFramework" : true,
"moduleName" : "SwiftUICore",
"modulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/SwiftUICore-2XTMXI6ZSN5EQ.swiftmodule"
},
{
"isFramework" : true,
"moduleName" : "Symbols",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/Symbols.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : false,
"moduleName" : "Synchronization",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/Synchronization.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : false,
"moduleName" : "System",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/System.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : false,
"moduleName" : "UniformTypeIdentifiers",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/UniformTypeIdentifiers.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : false,
"moduleName" : "XPC",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/XPC.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : false,
"moduleName" : "_Builtin_float",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/_Builtin_float.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : false,
"moduleName" : "_Concurrency",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/_Concurrency.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : false,
"moduleName" : "_DarwinFoundation1",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/_DarwinFoundation1.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : false,
"moduleName" : "_DarwinFoundation2",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/_DarwinFoundation2.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : false,
"moduleName" : "_DarwinFoundation3",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/_DarwinFoundation3.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : false,
"moduleName" : "_StringProcessing",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/_StringProcessing.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : false,
"moduleName" : "os",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/os.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"isFramework" : false,
"moduleName" : "simd",
"modulePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/prebuilt-modules\/27.0\/simd.swiftmodule\/arm64e-apple-macos.swiftmodule"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/Accessibility.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/Accessibility-DPGX2NRWMX0O48TTI8LGUX618.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "Accessibility"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/AppKit.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/AppKit-CL9YTLJPOU9I6ZH8JT9DWO0D4.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "AppKit"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/ApplicationServices.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/ApplicationServices-3BSKXHGPFFJGNJDUX4MJHAROJ.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "ApplicationServices"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/CFNetwork.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/CFNetwork-PPAEVAYES3T1V01FG7N3BQIG.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "CFNetwork"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/usr\/include\/cups.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/CUPS-57WLPWZ0ACOTR94MV37RVAV5N.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "CUPS"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/ColorSync.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/ColorSync-5IMHY8TOW2LEQC89ZGQZ3SE2.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "ColorSync"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/usr\/include\/CommonCrypto\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/CommonCrypto-8EIK622R16HUB1HTESZKO8X91.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "CommonCrypto"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/CoreAudio.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/CoreAudio-2T82IY3GJUAOBV3GCM185L413.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "CoreAudio"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/CoreAudioTypes.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/CoreAudioTypes-414FRUDUSHE0NKQXWMBAKZ0LB.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "CoreAudioTypes"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/CoreData.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/CoreData-365S85DEAB5MO16ZWK6H24GK0.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "CoreData"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/CoreFoundation.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/CoreFoundation-5FDPRYALLSNT3V14CJHZFSENJ.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "CoreFoundation"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/CoreGraphics.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/CoreGraphics-8M433BZBPSU56NLVMEW2VRNO3.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "CoreGraphics"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/CoreImage.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/CoreImage-FGQVU0R8OIEEQIIUMDUUILMU.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "CoreImage"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/CoreMedia.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/CoreMedia-2FJXBVBJME8JU66536CKRAKPM.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "CoreMedia"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/CoreServices.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/CoreServices-CFUHL0JHQ5BZ6JO9EU6U9ZFT2.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "CoreServices"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/CoreText.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/CoreText-E1R0W0Y9CSZ897CAZN8WPG95C.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "CoreText"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/CoreTransferable.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/CoreTransferable-3HTTC4B6RSGI7AMY7J9UNT2US.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "CoreTransferable"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/CoreVideo.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/CoreVideo-EJL32H99KVX6Y1J5TJJGEMK5S.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "CoreVideo"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/usr\/include\/Darwin.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/Darwin-6KN4I1VCULLK6EFH4J9ITQ32R.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "Darwin"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/DataDetection.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/DataDetection-4TKH0FTJUHLMOGOBICYVY82SJ.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "DataDetection"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/DeveloperToolsSupport.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/DeveloperToolsSupport-2G0OY5Y6O2AVW1VZ8C54EDQ9O.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "DeveloperToolsSupport"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/DiskArbitration.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/DiskArbitration-UH5H15Q4SG0MDJZNRGQDQ6UF.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "DiskArbitration"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/usr\/include\/dispatch.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/Dispatch-E168X8VQFIKYVAVVFOVYO15WH.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "Dispatch"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/Foundation.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/Foundation-7EMR5SDFA90I2YC1MIW01MOVL.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "Foundation"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/IOKit.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/IOKit-DKQW29SB1ZTDE6ZAO5AADMA1S.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "IOKit"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/IOSurface.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/IOSurface-8ZVH3UFJ5E2RSXE7HEG17X5FN.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "IOSurface"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/ImageIO.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/ImageIO-5J9HQ8F6U459VGN562S803HL5.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "ImageIO"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/LocalAuthentication.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/LocalAuthentication-678PKH1D7S8W88AP15VFRNL98.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "LocalAuthentication"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/usr\/include\/DarwinBasic.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/MachO-9GGXQQ5TIOCIBWIKR2CCZVSB1.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "MachO"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/Metal.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/Metal-CT6H1MCTUNZDQSSNO61HFZLZE.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "Metal"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/OSLog.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/OSLog-1693B6K17WI8Y5EYR3IJ2T13T.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "OSLog"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/usr\/include\/ObjectiveC.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/ObjectiveC-AYKBDSM6QHPMDWDQLVOZLM6LD.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "ObjectiveC"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/OpenGL.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/OpenGL-1OAFZ5HF502S7XTONQ752M7MU.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "OpenGL"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/QuartzCore.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/QuartzCore-D5VLT03F69HLIRAW7N9WCLS9Z.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "QuartzCore"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/Security.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/Security-DRBVTNEPIZ0WOLQMXCDMTFKBD.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "Security"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/usr\/include\/Spatial\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/Spatial-AORHAWQKQRCNZZE216QZ3LCWU.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "Spatial"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/usr\/lib\/swift\/shims\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/SwiftShims-7GQFCGGUXRYXHOVDWLS4DMRNU.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "SwiftShims"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/SwiftUI.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/SwiftUI-XIL8POI980N33K0HETUJTYDG.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "SwiftUI"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/SwiftUICore.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/SwiftUICore-2Y04G6WWMZE8UDANXA2Y6OQEU.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "SwiftUICore"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/Symbols.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/Symbols-9PHANA662J50AKDDU8SRE4Y2D.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "Symbols"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/UniformTypeIdentifiers.framework\/Modules\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/UniformTypeIdentifiers-5ZR71CNS0PF9KPP6CSDEK2SX4.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "UniformTypeIdentifiers"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/usr\/include\/xpc.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/XPC-609XYKDGD6VXBC8ARWLTD1K49.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "XPC"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/usr\/include\/DarwinFoundation1.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/_AvailabilityInternal-DKCAO3K87LF31T67852G4DCJU.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "_AvailabilityInternal"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/clang\/21\/include\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/_Builtin_float-EMCOHEFNTJPICZJEFV6RJZ3O3.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "_Builtin_float"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/clang\/21\/include\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/_Builtin_intrinsics-30FCRZ78EOU6JJIEWGYNNBAIA.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "_Builtin_intrinsics"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/clang\/21\/include\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/_Builtin_inttypes-BHRGBMFO3Y6Q68VJPDIUTRYH6.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "_Builtin_inttypes"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/clang\/21\/include\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/_Builtin_limits-6TTNDEB7UHOY33LFSFD6F36XJ.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "_Builtin_limits"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/clang\/21\/include\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/_Builtin_stdarg-4EY8XNKQUAC345WEPR6YBEEA4.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "_Builtin_stdarg"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/clang\/21\/include\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/_Builtin_stdatomic-1W7MP7XGF46FPBZMA5Z6NQTUB.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "_Builtin_stdatomic"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/clang\/21\/include\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/_Builtin_stdbool-F6HY6XDJX8IQ935YV5ERYU9E.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "_Builtin_stdbool"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/clang\/21\/include\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/_Builtin_stddef-7F2EHY6ITPAN3FM4F9L17B9SR.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "_Builtin_stddef"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/clang\/21\/include\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/_Builtin_stdint-BBZI8293S6ROM23BKB4RS2NSA.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "_Builtin_stdint"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/clang\/21\/include\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/_Builtin_tgmath-BAAFSB9DEIVSNDPQ8KGQEA8A0.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "_Builtin_tgmath"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/usr\/include\/DarwinFoundation1.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/_DarwinFoundation1-5MMHZM1WLEH8G5195181RF2VF.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "_DarwinFoundation1"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/usr\/include\/DarwinFoundation2.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/_DarwinFoundation2-19ZVNUJ7MLO2RCUPM8UA9VF3D.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "_DarwinFoundation2"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/usr\/include\/DarwinFoundation3.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/_DarwinFoundation3-8I77NUEUT13L61MIP7TBMS25A.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "_DarwinFoundation3"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/usr\/lib\/swift\/shims\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/_SwiftConcurrencyShims-7I99W7D71NF1LKBY35KVIA9NI.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "_SwiftConcurrencyShims"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/usr\/include\/launch.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/launch-EU5LE79LSVQWWZOXH11UNBYFC.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "launch"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/usr\/include\/libDER\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/libDER-62KN659C29YNFUT2A79HM56GL.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "libDER"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/usr\/include\/libkern.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/libkern-7N5WSDNOM8I5Q6ZE8VEO29KQF.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "libkern"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/usr\/include\/os.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/os-2NYCDUKGDWWQRNK44ZTXFZ9NI.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "os"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/usr\/include\/os.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/os_object-5BQFHOYI9T655N8MK6JDC0RKP.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "os_object"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/usr\/include\/os.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/os_workgroup-1K6BHLEB7QAP14LQLYM4LWLB4.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "os_workgroup"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/clang\/21\/include\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/ptrauth-5IQJJRW7TMKUKMSELKTCUEWHP.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "ptrauth"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/clang\/21\/include\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/ptrcheck-5X00TZ58XUX1IWW1ENLNN66D0.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "ptrcheck"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/usr\/include\/simd\/module.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/simd-7S4CBA41E3B1FEXRLQHTT3K03.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "simd"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/usr\/include\/c_standard_library.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/string_h-5S5KP08DIY39WNXXUI87DWVY3.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "string_h"
},
{
"clangModuleMapPath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/usr\/include\/DarwinFoundation2.modulemap",
"clangModulePath" : "\/Volumes\/Developer\/Applications\/WorkbenchReplacement\/build\/SDKExplicitPrecompiledModules\/sys_types-9HGMUOK1SMV8T2HBR01FGBU22.pcm",
"isBridgingHeaderDependency" : false,
"isFramework" : false,
"moduleName" : "sys_types"
}
]
@@ -1,4 +0,0 @@
/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.o
/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.o
/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/DatabaseModel.o
/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MySQLProtocol.o
@@ -1,4 +0,0 @@
/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.swiftconstvalues
/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.swiftconstvalues
/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/DatabaseModel.swiftconstvalues
/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MySQLProtocol.swiftconstvalues
@@ -1,4 +0,0 @@
/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement/App.swift
/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement/MainView.swift
/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement/DatabaseModel.swift
/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement/MySQLProtocol.swift
@@ -1,9 +0,0 @@
{
"ABIRoot": {
"kind": "Root",
"name": "NO_MODULE",
"printedName": "NO_MODULE",
"json_format_version": 8
},
"ConstValues": []
}
@@ -1 +0,0 @@
["AnyResolverProviding","AppEntity","AppEnum","AppExtension","AppIntent","AppIntentsPackage","AppShortcutProviding","AppShortcutsProvider","AppUnionValue","AppUnionValueCasesProviding","DynamicOptionsProvider","EntityQuery","ExtensionPointDefining","IntentValueQuery","Resolver","TransientEntity","_AssistantIntentsProvider","_GenerativeFunctionExtractable","_IntentValueRepresentable"]
@@ -1 +0,0 @@
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/lib/libPreviewsJITStubExecutor.a
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.get-task-allow</key>
<true/>
</dict>
</plist>
@@ -1 +0,0 @@
p-°(0& !com.apple.security.get-task-allowÿ
@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>
@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>
@@ -1,49 +0,0 @@
{
"" : {
"diagnostics" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement-primary.dia",
"emit-module-dependencies" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement-primary-emit-module.d",
"emit-module-diagnostics" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement-primary-emit-module.dia",
"pch" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement-primary-Bridging-header.pch",
"swift-dependencies" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement-primary.swiftdeps"
},
"/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement/App.swift" : {
"const-values" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.swiftconstvalues",
"dependencies" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.d",
"diagnostics" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.dia",
"index-unit-output-path" : "/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.o",
"llvm-bc" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.bc",
"object" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.o",
"swift-dependencies" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.swiftdeps",
"swiftmodule" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App~partial.swiftmodule"
},
"/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement/DatabaseModel.swift" : {
"const-values" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/DatabaseModel.swiftconstvalues",
"dependencies" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/DatabaseModel.d",
"diagnostics" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/DatabaseModel.dia",
"index-unit-output-path" : "/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/DatabaseModel.o",
"llvm-bc" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/DatabaseModel.bc",
"object" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/DatabaseModel.o",
"swift-dependencies" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/DatabaseModel.swiftdeps",
"swiftmodule" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/DatabaseModel~partial.swiftmodule"
},
"/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement/MainView.swift" : {
"const-values" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.swiftconstvalues",
"dependencies" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.d",
"diagnostics" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.dia",
"index-unit-output-path" : "/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.o",
"llvm-bc" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.bc",
"object" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.o",
"swift-dependencies" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.swiftdeps",
"swiftmodule" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView~partial.swiftmodule"
},
"/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement/MySQLProtocol.swift" : {
"const-values" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MySQLProtocol.swiftconstvalues",
"dependencies" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MySQLProtocol.d",
"diagnostics" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MySQLProtocol.dia",
"index-unit-output-path" : "/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MySQLProtocol.o",
"llvm-bc" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MySQLProtocol.bc",
"object" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MySQLProtocol.o",
"swift-dependencies" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MySQLProtocol.swiftdeps",
"swiftmodule" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MySQLProtocol~partial.swiftmodule"
}
}
@@ -1 +0,0 @@
{"case-sensitive":"false","roots":[],"version":0}
@@ -1 +0,0 @@
["AnyResolverProviding","AppEntity","AppEnum","AppExtension","AppIntent","AppIntentsPackage","AppShortcutProviding","AppShortcutsProvider","AppUnionValue","AppUnionValueCasesProviding","DynamicOptionsProvider","EntityQuery","ExtensionPointDefining","IntentValueQuery","Resolver","TransientEntity","_AssistantIntentsProvider","_GenerativeFunctionExtractable","_IntentValueRepresentable"]
@@ -1,4 +0,0 @@
/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.o
/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.o
/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/DatabaseModel.o
/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MySQLProtocol.o
@@ -1,4 +0,0 @@
/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/App.swiftconstvalues
/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.swiftconstvalues
/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/DatabaseModel.swiftconstvalues
/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MySQLProtocol.swiftconstvalues
@@ -1,4 +0,0 @@
/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement/App.swift
/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement/MainView.swift
/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement/DatabaseModel.swift
/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement/MySQLProtocol.swift
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.get-task-allow</key>
<true/>
</dict>
</plist>
@@ -1,66 +0,0 @@
{
"buildCommand" : {
"command" : "build",
"skipDependencies" : false,
"style" : "buildOnly"
},
"configuredTargets" : [
{
"guid" : "34f2007b1d0798da0712fb82baec90fb2878cdfbaa5fcb5fa30c8485a83dfb14"
}
],
"containerPath" : "/Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement.xcodeproj",
"continueBuildingAfterErrors" : false,
"dependencyScope" : "workspace",
"enableIndexBuildArena" : false,
"hideShellScriptEnvironment" : false,
"parameters" : {
"action" : "build",
"activeArchitecture" : "arm64",
"activeRunDestination" : {
"disableOnlyActiveArch" : false,
"hostTargetedPlatform" : null,
"platform" : "macosx",
"sdk" : "macosx27.0",
"sdkVariant" : "macos",
"supportedArchitectures" : [
"arm64e",
"arm64",
"x86_64"
],
"targetArchitecture" : "arm64"
},
"arenaInfo" : {
"buildIntermediatesPath" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex",
"buildProductsPath" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Products",
"derivedDataPath" : "/Volumes/Developer/Applications/WorkbenchReplacement/build",
"indexDataStoreFolderPath" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Index.noindex/DataStore",
"indexEnableDataStore" : true,
"indexPCHPath" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Index.noindex/PrecompiledHeaders",
"pchPath" : "/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Intermediates.noindex/PrecompiledHeaders"
},
"configurationName" : "Debug",
"overrides" : {
"commandLine" : {
"table" : {
}
},
"synthesized" : {
"table" : {
"ACTION" : "build",
"ENABLE_PREVIEWS" : "NO",
"ENABLE_XOJIT_PREVIEWS" : "YES",
"ONLY_ACTIVE_ARCH" : "YES",
"TOOLCHAINS" : "com.apple.dt.toolchain.Metal.32023.921.5 $(inherited)"
}
}
}
},
"schemeCommand" : "launch",
"showNonLoggedProgress" : true,
"useDryRun" : false,
"useImplicitDependencies" : true,
"useLegacyBuildLocations" : false,
"useParallelTargets" : true
}
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More