From fb428f12bc6f8b22be96ae96862e2b68040d1405 Mon Sep 17 00:00:00 2001 From: Qiu Yuzhou Date: Mon, 5 Oct 2026 19:56:18 +0800 Subject: [PATCH] =?UTF-8?q?refactor(app):=20=E6=8C=89=E5=88=86=E5=B1=82?= =?UTF-8?q?=E9=87=8D=E7=BB=84=20App/=20=E6=BA=90=E7=A0=81=E7=9B=AE?= =?UTF-8?q?=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit App/ 根目录原有 73 个散落源文件,已无法从目录读出任何分组信息。 按「目录即分层」重组为 Composition / Application / Presentation / UI / PlatformEffects / Resources 六个目录,UI 层再按子视图分目录(Workspace、 Home、Servers(含 Plugin)、Subscriptions、Rules、Settings、Diagnostics、 StatusMenu)。纯移动:类型名、模块名与 import 均未改动,App/ 根层不再有 散落文件。 配套改动: - project.yml:Core 测试镜像的 excludes 由裸文件名改为 `**/` 递归形式。 XcodeGen 的 exclude 不跨 `/`,裸名只匹配 App 根层;MainApp.swift 下移后 旧写法会静默失效,把 @main 组合根编进测试镜像。info.path 与 entitlements.path 指向 App/Resources/;并排除 `**/*.md`——App/ 下任何 非源码文件都会被 XcodeGen 收进 Resources 阶段并封进签名后的 bundle。 - SkeletonTests 的架构守卫由「默认扫描全部 App/ + 7 个豁免文件 + 1 个豁免 前缀 + 4 个豁免目录」改为正面圈定 App/UI/,豁免登记全部删除;新增运行时 适配器放进 Application/ 即自动落在扫描范围外,无需再登记。 - README 工程结构说明同步;根 .gitignore 的生成物路径随 Resources/ 归位。 验证:clean build 0 error;task test 1022 用例全通过(含两个架构守卫); task format 无改动(证明纯移动);task lint 与移动前同为 21 条内容型 warning;packaging-gate.sh ALL CHECKS PASSED,确认 plist/entitlements 归位 后签名与 entitlements 语义不变。 注意:迁移时须先删旧的生成物再 xcodegen generate,否则遗留的 App/Info.plist 会被当作普通资源扫进 Resources 阶段。 --- .gitignore | 4 +- .../CatalogCommitCoordinator.swift | 0 .../CatalogWorkflow+Diagnostics.swift | 0 .../CatalogWorkflow+GroupExport.swift | 0 .../CatalogWorkflow+Import.swift | 0 .../CatalogWorkflow+Legacy.swift | 0 .../CatalogWorkflow+Policy.swift | 0 .../CatalogWorkflow+Subscriptions.swift | 0 .../CatalogWorkflow/CatalogWorkflow+URI.swift | 0 .../CatalogWorkflow/CatalogWorkflow.swift | 0 .../CatalogWorkflowDependencies.swift | 0 .../CatalogWorkflowImportModels.swift | 0 .../CatalogWorkflowModels.swift | 0 .../PluginManagementModel.swift | 0 .../CatalogWorkflow}/PluginOptionsDraft.swift | 0 .../DiagnosticsWorkflow}/AgentLogTail.swift | 0 .../DiagnosticsWorkflow.swift | 0 .../LocalInterfaceFacts.swift | 0 .../ProxyControlAdapters.swift | 0 .../ProxyControlWorkflow.swift | 0 .../SystemInterfaceFactsProvider.swift | 0 .../TerminalCommandAddress.swift | 0 .../ProxyRuntime}/CoalescedFactSignal.swift | 0 .../ProxyRuntimeController+Commands.swift | 0 .../ProxyRuntimeController+Convergence.swift | 0 .../ProxyRuntimeController+CustomRules.swift | 0 .../ProxyRuntimeController+Facts.swift | 0 .../ProxyRuntimeController+Health.swift | 0 .../ProxyRuntimeController+Mode.swift | 0 .../ProxyRuntimeController+Preparation.swift | 0 .../ProxyRuntimeController+SettingsSync.swift | 0 ...oxyRuntimeController+SystemProxyGate.swift | 0 .../ProxyRuntimeController.swift | 0 .../ProxyRuntime}/ProxyRuntimeFacts.swift | 0 .../ProxyRuntime}/SystemEndpointProbe.swift | 0 .../SystemProxyNetworkChangeMonitor.swift | 0 .../ProxyRuntime}/SystemProxyObserver.swift | 0 .../RulesWorkflow}/RulesWorkflow.swift | 0 .../Services}/FirewallStatusChecker.swift | 0 .../Services}/LaunchAgentService.swift | 0 .../Services}/LoginAtLoginService.swift | 0 .../Services}/SystemProxyHelperService.swift | 0 .../ListenerModeOccupancyGate.swift | 0 .../SettingsPortAdapter.swift | 0 .../SettingsWorkflow/SettingsWorkflow.swift | 0 .../SettingsWorkflowModels.swift | 0 .../App/{ => Composition}/MainApp.swift | 0 .../SilentLaunchController.swift | 0 .../WindowActivationPolicy.swift | 0 .../{ => PlatformEffects}/QrCodeCodec.swift | 0 .../ActivationFeedbackState.swift | 0 .../ErrorPresentation+Runtime.swift | 0 .../ErrorPresentation.swift | 0 .../{ => Presentation}/StatusMenuModel.swift | 0 .../App/{ => Presentation}/UIErrorAlert.swift | 0 .../AppIcon.appiconset/Contents.json | 0 .../AppIcon.appiconset/Icon_128x128.png | Bin .../AppIcon.appiconset/Icon_128x128@2x.png | Bin .../AppIcon.appiconset/Icon_16x16.png | Bin .../AppIcon.appiconset/Icon_16x16@2x.png | Bin .../AppIcon.appiconset/Icon_256x256.png | Bin .../AppIcon.appiconset/Icon_256x256@2x.png | Bin .../AppIcon.appiconset/Icon_32x32.png | Bin .../AppIcon.appiconset/Icon_32x32@2x.png | Bin .../AppIcon.appiconset/Icon_512x512.png | Bin .../AppIcon.appiconset/Icon_512x512@2x.png | Bin .../Assets.xcassets/Contents.json | 0 .../App/{ => Resources}/Localizable.xcstrings | 0 .../App/{ => Resources}/Rules.xcstrings | 0 .../Diagnostics}/DiagnosticsView.swift | 0 .../App/{ => UI/Home}/HomeComponents.swift | 0 .../{ => UI/Home}/HomeServerListState.swift | 0 .../{ => UI/Home}/HomeServerListView.swift | 0 .../App/{ => UI/Home}/HomeView+Cards.swift | 0 .../App/{ => UI/Home}/HomeView.swift | 0 .../{ => UI/Home}/RuntimeControlCard.swift | 0 .../Rules}/CustomRuleEditorSheet.swift | 0 .../{ => UI/Rules}/RulesAddressTestView.swift | 0 .../{ => UI/Rules}/RulesDeletionButton.swift | 0 .../Rules}/RulesOperationStatusView.swift | 0 .../Rules}/RulesSourceDetailsView.swift | 0 .../App/{ => UI/Rules}/RulesView.swift | 0 .../Servers}/CatalogExpansionState.swift | 0 .../{ => UI/Servers}/GroupDetailView.swift | 0 .../Servers}/ImportOutcomePresentation.swift | 0 .../{ => UI/Servers}/ImportServersSheet.swift | 0 .../App/{ => UI/Servers}/MoveNodeSheet.swift | 0 .../App/{ => UI/Servers}/NewGroupSheet.swift | 0 .../App/{ => UI/Servers}/NewServerSheet.swift | 0 .../Servers}/PasswordAutofillOptOut.swift | 0 .../Servers/Plugin}/PluginEditorSheet.swift | 0 .../Plugin}/PluginManagementCopy.swift | 0 .../Plugin}/PluginManagementSection.swift | 0 .../Servers/Plugin}/ServerPluginSection.swift | 0 .../{ => UI/Servers}/ServerDetailView.swift | 0 .../{ => UI/Servers}/ServerFormFields.swift | 0 .../ServerTreeBlankClickObserver.swift | 0 .../{ => UI/Servers}/ServersView+Share.swift | 0 .../App/{ => UI/Servers}/ServersView.swift | 0 .../Settings}/ListenerModeEditorSheet.swift | 0 .../App/{ => UI/Settings}/SettingsView.swift | 0 .../{ => UI/StatusMenu}/ProxyStatusMenu.swift | 0 .../SubscriptionInformationPresentation.swift | 0 .../Subscriptions}/SubscriptionsView.swift | 0 .../{ => UI/Workspace}/MainWindowView.swift | 0 .../App/{ => UI/Workspace}/SidebarRow.swift | 0 .../{ => UI/Workspace}/StatusCardModel.swift | 0 .../{ => UI/Workspace}/StatusCardView.swift | 0 .../{ => UI/Workspace}/WorkspaceRoute.swift | 0 ShadowsocksX-NG2/README.md | 12 +++- ShadowsocksX-NG2/Tests/SkeletonTests.swift | 62 +++++------------- ShadowsocksX-NG2/project.yml | 22 +++++-- 112 files changed, 44 insertions(+), 56 deletions(-) rename ShadowsocksX-NG2/App/{ => Application/CatalogWorkflow}/CatalogCommitCoordinator.swift (100%) rename ShadowsocksX-NG2/App/{ => Application}/CatalogWorkflow/CatalogWorkflow+Diagnostics.swift (100%) rename ShadowsocksX-NG2/App/{ => Application}/CatalogWorkflow/CatalogWorkflow+GroupExport.swift (100%) rename ShadowsocksX-NG2/App/{ => Application}/CatalogWorkflow/CatalogWorkflow+Import.swift (100%) rename ShadowsocksX-NG2/App/{ => Application}/CatalogWorkflow/CatalogWorkflow+Legacy.swift (100%) rename ShadowsocksX-NG2/App/{ => Application}/CatalogWorkflow/CatalogWorkflow+Policy.swift (100%) rename ShadowsocksX-NG2/App/{ => Application}/CatalogWorkflow/CatalogWorkflow+Subscriptions.swift (100%) rename ShadowsocksX-NG2/App/{ => Application}/CatalogWorkflow/CatalogWorkflow+URI.swift (100%) rename ShadowsocksX-NG2/App/{ => Application}/CatalogWorkflow/CatalogWorkflow.swift (100%) rename ShadowsocksX-NG2/App/{ => Application}/CatalogWorkflow/CatalogWorkflowDependencies.swift (100%) rename ShadowsocksX-NG2/App/{ => Application}/CatalogWorkflow/CatalogWorkflowImportModels.swift (100%) rename ShadowsocksX-NG2/App/{ => Application}/CatalogWorkflow/CatalogWorkflowModels.swift (100%) rename ShadowsocksX-NG2/App/{ => Application/CatalogWorkflow}/PluginManagementModel.swift (100%) rename ShadowsocksX-NG2/App/{ => Application/CatalogWorkflow}/PluginOptionsDraft.swift (100%) rename ShadowsocksX-NG2/App/{ => Application/DiagnosticsWorkflow}/AgentLogTail.swift (100%) rename ShadowsocksX-NG2/App/{ => Application}/DiagnosticsWorkflow/DiagnosticsWorkflow.swift (100%) rename ShadowsocksX-NG2/App/{ => Application}/ProxyControlWorkflow/LocalInterfaceFacts.swift (100%) rename ShadowsocksX-NG2/App/{ => Application}/ProxyControlWorkflow/ProxyControlAdapters.swift (100%) rename ShadowsocksX-NG2/App/{ => Application}/ProxyControlWorkflow/ProxyControlWorkflow.swift (100%) rename ShadowsocksX-NG2/App/{ => Application}/ProxyControlWorkflow/SystemInterfaceFactsProvider.swift (100%) rename ShadowsocksX-NG2/App/{ => Application}/ProxyControlWorkflow/TerminalCommandAddress.swift (100%) rename ShadowsocksX-NG2/App/{ => Application/ProxyRuntime}/CoalescedFactSignal.swift (100%) rename ShadowsocksX-NG2/App/{ => Application/ProxyRuntime}/ProxyRuntimeController+Commands.swift (100%) rename ShadowsocksX-NG2/App/{ => Application/ProxyRuntime}/ProxyRuntimeController+Convergence.swift (100%) rename ShadowsocksX-NG2/App/{ => Application/ProxyRuntime}/ProxyRuntimeController+CustomRules.swift (100%) rename ShadowsocksX-NG2/App/{ => Application/ProxyRuntime}/ProxyRuntimeController+Facts.swift (100%) rename ShadowsocksX-NG2/App/{ => Application/ProxyRuntime}/ProxyRuntimeController+Health.swift (100%) rename ShadowsocksX-NG2/App/{ => Application/ProxyRuntime}/ProxyRuntimeController+Mode.swift (100%) rename ShadowsocksX-NG2/App/{ => Application/ProxyRuntime}/ProxyRuntimeController+Preparation.swift (100%) rename ShadowsocksX-NG2/App/{ => Application/ProxyRuntime}/ProxyRuntimeController+SettingsSync.swift (100%) rename ShadowsocksX-NG2/App/{ => Application/ProxyRuntime}/ProxyRuntimeController+SystemProxyGate.swift (100%) rename ShadowsocksX-NG2/App/{ => Application/ProxyRuntime}/ProxyRuntimeController.swift (100%) rename ShadowsocksX-NG2/App/{ => Application/ProxyRuntime}/ProxyRuntimeFacts.swift (100%) rename ShadowsocksX-NG2/App/{ => Application/ProxyRuntime}/SystemEndpointProbe.swift (100%) rename ShadowsocksX-NG2/App/{ => Application/ProxyRuntime}/SystemProxyNetworkChangeMonitor.swift (100%) rename ShadowsocksX-NG2/App/{ => Application/ProxyRuntime}/SystemProxyObserver.swift (100%) rename ShadowsocksX-NG2/App/{ => Application/RulesWorkflow}/RulesWorkflow.swift (100%) rename ShadowsocksX-NG2/App/{ => Application/Services}/FirewallStatusChecker.swift (100%) rename ShadowsocksX-NG2/App/{ => Application/Services}/LaunchAgentService.swift (100%) rename ShadowsocksX-NG2/App/{ => Application/Services}/LoginAtLoginService.swift (100%) rename ShadowsocksX-NG2/App/{ => Application/Services}/SystemProxyHelperService.swift (100%) rename ShadowsocksX-NG2/App/{ => Application}/SettingsWorkflow/ListenerModeOccupancyGate.swift (100%) rename ShadowsocksX-NG2/App/{ => Application}/SettingsWorkflow/SettingsPortAdapter.swift (100%) rename ShadowsocksX-NG2/App/{ => Application}/SettingsWorkflow/SettingsWorkflow.swift (100%) rename ShadowsocksX-NG2/App/{ => Application}/SettingsWorkflow/SettingsWorkflowModels.swift (100%) rename ShadowsocksX-NG2/App/{ => Composition}/MainApp.swift (100%) rename ShadowsocksX-NG2/App/{ => Composition}/SilentLaunchController.swift (100%) rename ShadowsocksX-NG2/App/{ => Composition}/WindowActivationPolicy.swift (100%) rename ShadowsocksX-NG2/App/{ => PlatformEffects}/QrCodeCodec.swift (100%) rename ShadowsocksX-NG2/App/{ => Presentation}/ActivationFeedbackState.swift (100%) rename ShadowsocksX-NG2/App/{ => Presentation}/ErrorPresentation+Runtime.swift (100%) rename ShadowsocksX-NG2/App/{ => Presentation}/ErrorPresentation.swift (100%) rename ShadowsocksX-NG2/App/{ => Presentation}/StatusMenuModel.swift (100%) rename ShadowsocksX-NG2/App/{ => Presentation}/UIErrorAlert.swift (100%) rename ShadowsocksX-NG2/App/{ => Resources}/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename ShadowsocksX-NG2/App/{ => Resources}/Assets.xcassets/AppIcon.appiconset/Icon_128x128.png (100%) rename ShadowsocksX-NG2/App/{ => Resources}/Assets.xcassets/AppIcon.appiconset/Icon_128x128@2x.png (100%) rename ShadowsocksX-NG2/App/{ => Resources}/Assets.xcassets/AppIcon.appiconset/Icon_16x16.png (100%) rename ShadowsocksX-NG2/App/{ => Resources}/Assets.xcassets/AppIcon.appiconset/Icon_16x16@2x.png (100%) rename ShadowsocksX-NG2/App/{ => Resources}/Assets.xcassets/AppIcon.appiconset/Icon_256x256.png (100%) rename ShadowsocksX-NG2/App/{ => Resources}/Assets.xcassets/AppIcon.appiconset/Icon_256x256@2x.png (100%) rename ShadowsocksX-NG2/App/{ => Resources}/Assets.xcassets/AppIcon.appiconset/Icon_32x32.png (100%) rename ShadowsocksX-NG2/App/{ => Resources}/Assets.xcassets/AppIcon.appiconset/Icon_32x32@2x.png (100%) rename ShadowsocksX-NG2/App/{ => Resources}/Assets.xcassets/AppIcon.appiconset/Icon_512x512.png (100%) rename ShadowsocksX-NG2/App/{ => Resources}/Assets.xcassets/AppIcon.appiconset/Icon_512x512@2x.png (100%) rename ShadowsocksX-NG2/App/{ => Resources}/Assets.xcassets/Contents.json (100%) rename ShadowsocksX-NG2/App/{ => Resources}/Localizable.xcstrings (100%) rename ShadowsocksX-NG2/App/{ => Resources}/Rules.xcstrings (100%) rename ShadowsocksX-NG2/App/{ => UI/Diagnostics}/DiagnosticsView.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Home}/HomeComponents.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Home}/HomeServerListState.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Home}/HomeServerListView.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Home}/HomeView+Cards.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Home}/HomeView.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Home}/RuntimeControlCard.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Rules}/CustomRuleEditorSheet.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Rules}/RulesAddressTestView.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Rules}/RulesDeletionButton.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Rules}/RulesOperationStatusView.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Rules}/RulesSourceDetailsView.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Rules}/RulesView.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Servers}/CatalogExpansionState.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Servers}/GroupDetailView.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Servers}/ImportOutcomePresentation.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Servers}/ImportServersSheet.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Servers}/MoveNodeSheet.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Servers}/NewGroupSheet.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Servers}/NewServerSheet.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Servers}/PasswordAutofillOptOut.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Servers/Plugin}/PluginEditorSheet.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Servers/Plugin}/PluginManagementCopy.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Servers/Plugin}/PluginManagementSection.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Servers/Plugin}/ServerPluginSection.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Servers}/ServerDetailView.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Servers}/ServerFormFields.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Servers}/ServerTreeBlankClickObserver.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Servers}/ServersView+Share.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Servers}/ServersView.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Settings}/ListenerModeEditorSheet.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Settings}/SettingsView.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/StatusMenu}/ProxyStatusMenu.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Subscriptions}/SubscriptionInformationPresentation.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Subscriptions}/SubscriptionsView.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Workspace}/MainWindowView.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Workspace}/SidebarRow.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Workspace}/StatusCardModel.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Workspace}/StatusCardView.swift (100%) rename ShadowsocksX-NG2/App/{ => UI/Workspace}/WorkspaceRoute.swift (100%) diff --git a/.gitignore b/.gitignore index 9786d53c..5bc9e1b1 100644 --- a/.gitignore +++ b/.gitignore @@ -96,8 +96,8 @@ ShadowsocksX-NG2/DerivedData/ #### # NG2 generated build files (project.yml is the source of truth; run `xcodegen generate`) ShadowsocksX-NG2/*.xcodeproj -ShadowsocksX-NG2/App/Info.plist -ShadowsocksX-NG2/App/ShadowsocksX-NG2.entitlements +ShadowsocksX-NG2/App/Resources/Info.plist +ShadowsocksX-NG2/App/Resources/ShadowsocksX-NG2.entitlements ShadowsocksX-NG2/Tests/Info.plist ShadowsocksX-NG2/Tests/Smoke/Info.plist diff --git a/ShadowsocksX-NG2/App/CatalogCommitCoordinator.swift b/ShadowsocksX-NG2/App/Application/CatalogWorkflow/CatalogCommitCoordinator.swift similarity index 100% rename from ShadowsocksX-NG2/App/CatalogCommitCoordinator.swift rename to ShadowsocksX-NG2/App/Application/CatalogWorkflow/CatalogCommitCoordinator.swift diff --git a/ShadowsocksX-NG2/App/CatalogWorkflow/CatalogWorkflow+Diagnostics.swift b/ShadowsocksX-NG2/App/Application/CatalogWorkflow/CatalogWorkflow+Diagnostics.swift similarity index 100% rename from ShadowsocksX-NG2/App/CatalogWorkflow/CatalogWorkflow+Diagnostics.swift rename to ShadowsocksX-NG2/App/Application/CatalogWorkflow/CatalogWorkflow+Diagnostics.swift diff --git a/ShadowsocksX-NG2/App/CatalogWorkflow/CatalogWorkflow+GroupExport.swift b/ShadowsocksX-NG2/App/Application/CatalogWorkflow/CatalogWorkflow+GroupExport.swift similarity index 100% rename from ShadowsocksX-NG2/App/CatalogWorkflow/CatalogWorkflow+GroupExport.swift rename to ShadowsocksX-NG2/App/Application/CatalogWorkflow/CatalogWorkflow+GroupExport.swift diff --git a/ShadowsocksX-NG2/App/CatalogWorkflow/CatalogWorkflow+Import.swift b/ShadowsocksX-NG2/App/Application/CatalogWorkflow/CatalogWorkflow+Import.swift similarity index 100% rename from ShadowsocksX-NG2/App/CatalogWorkflow/CatalogWorkflow+Import.swift rename to ShadowsocksX-NG2/App/Application/CatalogWorkflow/CatalogWorkflow+Import.swift diff --git a/ShadowsocksX-NG2/App/CatalogWorkflow/CatalogWorkflow+Legacy.swift b/ShadowsocksX-NG2/App/Application/CatalogWorkflow/CatalogWorkflow+Legacy.swift similarity index 100% rename from ShadowsocksX-NG2/App/CatalogWorkflow/CatalogWorkflow+Legacy.swift rename to ShadowsocksX-NG2/App/Application/CatalogWorkflow/CatalogWorkflow+Legacy.swift diff --git a/ShadowsocksX-NG2/App/CatalogWorkflow/CatalogWorkflow+Policy.swift b/ShadowsocksX-NG2/App/Application/CatalogWorkflow/CatalogWorkflow+Policy.swift similarity index 100% rename from ShadowsocksX-NG2/App/CatalogWorkflow/CatalogWorkflow+Policy.swift rename to ShadowsocksX-NG2/App/Application/CatalogWorkflow/CatalogWorkflow+Policy.swift diff --git a/ShadowsocksX-NG2/App/CatalogWorkflow/CatalogWorkflow+Subscriptions.swift b/ShadowsocksX-NG2/App/Application/CatalogWorkflow/CatalogWorkflow+Subscriptions.swift similarity index 100% rename from ShadowsocksX-NG2/App/CatalogWorkflow/CatalogWorkflow+Subscriptions.swift rename to ShadowsocksX-NG2/App/Application/CatalogWorkflow/CatalogWorkflow+Subscriptions.swift diff --git a/ShadowsocksX-NG2/App/CatalogWorkflow/CatalogWorkflow+URI.swift b/ShadowsocksX-NG2/App/Application/CatalogWorkflow/CatalogWorkflow+URI.swift similarity index 100% rename from ShadowsocksX-NG2/App/CatalogWorkflow/CatalogWorkflow+URI.swift rename to ShadowsocksX-NG2/App/Application/CatalogWorkflow/CatalogWorkflow+URI.swift diff --git a/ShadowsocksX-NG2/App/CatalogWorkflow/CatalogWorkflow.swift b/ShadowsocksX-NG2/App/Application/CatalogWorkflow/CatalogWorkflow.swift similarity index 100% rename from ShadowsocksX-NG2/App/CatalogWorkflow/CatalogWorkflow.swift rename to ShadowsocksX-NG2/App/Application/CatalogWorkflow/CatalogWorkflow.swift diff --git a/ShadowsocksX-NG2/App/CatalogWorkflow/CatalogWorkflowDependencies.swift b/ShadowsocksX-NG2/App/Application/CatalogWorkflow/CatalogWorkflowDependencies.swift similarity index 100% rename from ShadowsocksX-NG2/App/CatalogWorkflow/CatalogWorkflowDependencies.swift rename to ShadowsocksX-NG2/App/Application/CatalogWorkflow/CatalogWorkflowDependencies.swift diff --git a/ShadowsocksX-NG2/App/CatalogWorkflow/CatalogWorkflowImportModels.swift b/ShadowsocksX-NG2/App/Application/CatalogWorkflow/CatalogWorkflowImportModels.swift similarity index 100% rename from ShadowsocksX-NG2/App/CatalogWorkflow/CatalogWorkflowImportModels.swift rename to ShadowsocksX-NG2/App/Application/CatalogWorkflow/CatalogWorkflowImportModels.swift diff --git a/ShadowsocksX-NG2/App/CatalogWorkflow/CatalogWorkflowModels.swift b/ShadowsocksX-NG2/App/Application/CatalogWorkflow/CatalogWorkflowModels.swift similarity index 100% rename from ShadowsocksX-NG2/App/CatalogWorkflow/CatalogWorkflowModels.swift rename to ShadowsocksX-NG2/App/Application/CatalogWorkflow/CatalogWorkflowModels.swift diff --git a/ShadowsocksX-NG2/App/PluginManagementModel.swift b/ShadowsocksX-NG2/App/Application/CatalogWorkflow/PluginManagementModel.swift similarity index 100% rename from ShadowsocksX-NG2/App/PluginManagementModel.swift rename to ShadowsocksX-NG2/App/Application/CatalogWorkflow/PluginManagementModel.swift diff --git a/ShadowsocksX-NG2/App/PluginOptionsDraft.swift b/ShadowsocksX-NG2/App/Application/CatalogWorkflow/PluginOptionsDraft.swift similarity index 100% rename from ShadowsocksX-NG2/App/PluginOptionsDraft.swift rename to ShadowsocksX-NG2/App/Application/CatalogWorkflow/PluginOptionsDraft.swift diff --git a/ShadowsocksX-NG2/App/AgentLogTail.swift b/ShadowsocksX-NG2/App/Application/DiagnosticsWorkflow/AgentLogTail.swift similarity index 100% rename from ShadowsocksX-NG2/App/AgentLogTail.swift rename to ShadowsocksX-NG2/App/Application/DiagnosticsWorkflow/AgentLogTail.swift diff --git a/ShadowsocksX-NG2/App/DiagnosticsWorkflow/DiagnosticsWorkflow.swift b/ShadowsocksX-NG2/App/Application/DiagnosticsWorkflow/DiagnosticsWorkflow.swift similarity index 100% rename from ShadowsocksX-NG2/App/DiagnosticsWorkflow/DiagnosticsWorkflow.swift rename to ShadowsocksX-NG2/App/Application/DiagnosticsWorkflow/DiagnosticsWorkflow.swift diff --git a/ShadowsocksX-NG2/App/ProxyControlWorkflow/LocalInterfaceFacts.swift b/ShadowsocksX-NG2/App/Application/ProxyControlWorkflow/LocalInterfaceFacts.swift similarity index 100% rename from ShadowsocksX-NG2/App/ProxyControlWorkflow/LocalInterfaceFacts.swift rename to ShadowsocksX-NG2/App/Application/ProxyControlWorkflow/LocalInterfaceFacts.swift diff --git a/ShadowsocksX-NG2/App/ProxyControlWorkflow/ProxyControlAdapters.swift b/ShadowsocksX-NG2/App/Application/ProxyControlWorkflow/ProxyControlAdapters.swift similarity index 100% rename from ShadowsocksX-NG2/App/ProxyControlWorkflow/ProxyControlAdapters.swift rename to ShadowsocksX-NG2/App/Application/ProxyControlWorkflow/ProxyControlAdapters.swift diff --git a/ShadowsocksX-NG2/App/ProxyControlWorkflow/ProxyControlWorkflow.swift b/ShadowsocksX-NG2/App/Application/ProxyControlWorkflow/ProxyControlWorkflow.swift similarity index 100% rename from ShadowsocksX-NG2/App/ProxyControlWorkflow/ProxyControlWorkflow.swift rename to ShadowsocksX-NG2/App/Application/ProxyControlWorkflow/ProxyControlWorkflow.swift diff --git a/ShadowsocksX-NG2/App/ProxyControlWorkflow/SystemInterfaceFactsProvider.swift b/ShadowsocksX-NG2/App/Application/ProxyControlWorkflow/SystemInterfaceFactsProvider.swift similarity index 100% rename from ShadowsocksX-NG2/App/ProxyControlWorkflow/SystemInterfaceFactsProvider.swift rename to ShadowsocksX-NG2/App/Application/ProxyControlWorkflow/SystemInterfaceFactsProvider.swift diff --git a/ShadowsocksX-NG2/App/ProxyControlWorkflow/TerminalCommandAddress.swift b/ShadowsocksX-NG2/App/Application/ProxyControlWorkflow/TerminalCommandAddress.swift similarity index 100% rename from ShadowsocksX-NG2/App/ProxyControlWorkflow/TerminalCommandAddress.swift rename to ShadowsocksX-NG2/App/Application/ProxyControlWorkflow/TerminalCommandAddress.swift diff --git a/ShadowsocksX-NG2/App/CoalescedFactSignal.swift b/ShadowsocksX-NG2/App/Application/ProxyRuntime/CoalescedFactSignal.swift similarity index 100% rename from ShadowsocksX-NG2/App/CoalescedFactSignal.swift rename to ShadowsocksX-NG2/App/Application/ProxyRuntime/CoalescedFactSignal.swift diff --git a/ShadowsocksX-NG2/App/ProxyRuntimeController+Commands.swift b/ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeController+Commands.swift similarity index 100% rename from ShadowsocksX-NG2/App/ProxyRuntimeController+Commands.swift rename to ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeController+Commands.swift diff --git a/ShadowsocksX-NG2/App/ProxyRuntimeController+Convergence.swift b/ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeController+Convergence.swift similarity index 100% rename from ShadowsocksX-NG2/App/ProxyRuntimeController+Convergence.swift rename to ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeController+Convergence.swift diff --git a/ShadowsocksX-NG2/App/ProxyRuntimeController+CustomRules.swift b/ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeController+CustomRules.swift similarity index 100% rename from ShadowsocksX-NG2/App/ProxyRuntimeController+CustomRules.swift rename to ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeController+CustomRules.swift diff --git a/ShadowsocksX-NG2/App/ProxyRuntimeController+Facts.swift b/ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeController+Facts.swift similarity index 100% rename from ShadowsocksX-NG2/App/ProxyRuntimeController+Facts.swift rename to ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeController+Facts.swift diff --git a/ShadowsocksX-NG2/App/ProxyRuntimeController+Health.swift b/ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeController+Health.swift similarity index 100% rename from ShadowsocksX-NG2/App/ProxyRuntimeController+Health.swift rename to ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeController+Health.swift diff --git a/ShadowsocksX-NG2/App/ProxyRuntimeController+Mode.swift b/ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeController+Mode.swift similarity index 100% rename from ShadowsocksX-NG2/App/ProxyRuntimeController+Mode.swift rename to ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeController+Mode.swift diff --git a/ShadowsocksX-NG2/App/ProxyRuntimeController+Preparation.swift b/ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeController+Preparation.swift similarity index 100% rename from ShadowsocksX-NG2/App/ProxyRuntimeController+Preparation.swift rename to ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeController+Preparation.swift diff --git a/ShadowsocksX-NG2/App/ProxyRuntimeController+SettingsSync.swift b/ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeController+SettingsSync.swift similarity index 100% rename from ShadowsocksX-NG2/App/ProxyRuntimeController+SettingsSync.swift rename to ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeController+SettingsSync.swift diff --git a/ShadowsocksX-NG2/App/ProxyRuntimeController+SystemProxyGate.swift b/ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeController+SystemProxyGate.swift similarity index 100% rename from ShadowsocksX-NG2/App/ProxyRuntimeController+SystemProxyGate.swift rename to ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeController+SystemProxyGate.swift diff --git a/ShadowsocksX-NG2/App/ProxyRuntimeController.swift b/ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeController.swift similarity index 100% rename from ShadowsocksX-NG2/App/ProxyRuntimeController.swift rename to ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeController.swift diff --git a/ShadowsocksX-NG2/App/ProxyRuntimeFacts.swift b/ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeFacts.swift similarity index 100% rename from ShadowsocksX-NG2/App/ProxyRuntimeFacts.swift rename to ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeFacts.swift diff --git a/ShadowsocksX-NG2/App/SystemEndpointProbe.swift b/ShadowsocksX-NG2/App/Application/ProxyRuntime/SystemEndpointProbe.swift similarity index 100% rename from ShadowsocksX-NG2/App/SystemEndpointProbe.swift rename to ShadowsocksX-NG2/App/Application/ProxyRuntime/SystemEndpointProbe.swift diff --git a/ShadowsocksX-NG2/App/SystemProxyNetworkChangeMonitor.swift b/ShadowsocksX-NG2/App/Application/ProxyRuntime/SystemProxyNetworkChangeMonitor.swift similarity index 100% rename from ShadowsocksX-NG2/App/SystemProxyNetworkChangeMonitor.swift rename to ShadowsocksX-NG2/App/Application/ProxyRuntime/SystemProxyNetworkChangeMonitor.swift diff --git a/ShadowsocksX-NG2/App/SystemProxyObserver.swift b/ShadowsocksX-NG2/App/Application/ProxyRuntime/SystemProxyObserver.swift similarity index 100% rename from ShadowsocksX-NG2/App/SystemProxyObserver.swift rename to ShadowsocksX-NG2/App/Application/ProxyRuntime/SystemProxyObserver.swift diff --git a/ShadowsocksX-NG2/App/RulesWorkflow.swift b/ShadowsocksX-NG2/App/Application/RulesWorkflow/RulesWorkflow.swift similarity index 100% rename from ShadowsocksX-NG2/App/RulesWorkflow.swift rename to ShadowsocksX-NG2/App/Application/RulesWorkflow/RulesWorkflow.swift diff --git a/ShadowsocksX-NG2/App/FirewallStatusChecker.swift b/ShadowsocksX-NG2/App/Application/Services/FirewallStatusChecker.swift similarity index 100% rename from ShadowsocksX-NG2/App/FirewallStatusChecker.swift rename to ShadowsocksX-NG2/App/Application/Services/FirewallStatusChecker.swift diff --git a/ShadowsocksX-NG2/App/LaunchAgentService.swift b/ShadowsocksX-NG2/App/Application/Services/LaunchAgentService.swift similarity index 100% rename from ShadowsocksX-NG2/App/LaunchAgentService.swift rename to ShadowsocksX-NG2/App/Application/Services/LaunchAgentService.swift diff --git a/ShadowsocksX-NG2/App/LoginAtLoginService.swift b/ShadowsocksX-NG2/App/Application/Services/LoginAtLoginService.swift similarity index 100% rename from ShadowsocksX-NG2/App/LoginAtLoginService.swift rename to ShadowsocksX-NG2/App/Application/Services/LoginAtLoginService.swift diff --git a/ShadowsocksX-NG2/App/SystemProxyHelperService.swift b/ShadowsocksX-NG2/App/Application/Services/SystemProxyHelperService.swift similarity index 100% rename from ShadowsocksX-NG2/App/SystemProxyHelperService.swift rename to ShadowsocksX-NG2/App/Application/Services/SystemProxyHelperService.swift diff --git a/ShadowsocksX-NG2/App/SettingsWorkflow/ListenerModeOccupancyGate.swift b/ShadowsocksX-NG2/App/Application/SettingsWorkflow/ListenerModeOccupancyGate.swift similarity index 100% rename from ShadowsocksX-NG2/App/SettingsWorkflow/ListenerModeOccupancyGate.swift rename to ShadowsocksX-NG2/App/Application/SettingsWorkflow/ListenerModeOccupancyGate.swift diff --git a/ShadowsocksX-NG2/App/SettingsWorkflow/SettingsPortAdapter.swift b/ShadowsocksX-NG2/App/Application/SettingsWorkflow/SettingsPortAdapter.swift similarity index 100% rename from ShadowsocksX-NG2/App/SettingsWorkflow/SettingsPortAdapter.swift rename to ShadowsocksX-NG2/App/Application/SettingsWorkflow/SettingsPortAdapter.swift diff --git a/ShadowsocksX-NG2/App/SettingsWorkflow/SettingsWorkflow.swift b/ShadowsocksX-NG2/App/Application/SettingsWorkflow/SettingsWorkflow.swift similarity index 100% rename from ShadowsocksX-NG2/App/SettingsWorkflow/SettingsWorkflow.swift rename to ShadowsocksX-NG2/App/Application/SettingsWorkflow/SettingsWorkflow.swift diff --git a/ShadowsocksX-NG2/App/SettingsWorkflow/SettingsWorkflowModels.swift b/ShadowsocksX-NG2/App/Application/SettingsWorkflow/SettingsWorkflowModels.swift similarity index 100% rename from ShadowsocksX-NG2/App/SettingsWorkflow/SettingsWorkflowModels.swift rename to ShadowsocksX-NG2/App/Application/SettingsWorkflow/SettingsWorkflowModels.swift diff --git a/ShadowsocksX-NG2/App/MainApp.swift b/ShadowsocksX-NG2/App/Composition/MainApp.swift similarity index 100% rename from ShadowsocksX-NG2/App/MainApp.swift rename to ShadowsocksX-NG2/App/Composition/MainApp.swift diff --git a/ShadowsocksX-NG2/App/SilentLaunchController.swift b/ShadowsocksX-NG2/App/Composition/SilentLaunchController.swift similarity index 100% rename from ShadowsocksX-NG2/App/SilentLaunchController.swift rename to ShadowsocksX-NG2/App/Composition/SilentLaunchController.swift diff --git a/ShadowsocksX-NG2/App/WindowActivationPolicy.swift b/ShadowsocksX-NG2/App/Composition/WindowActivationPolicy.swift similarity index 100% rename from ShadowsocksX-NG2/App/WindowActivationPolicy.swift rename to ShadowsocksX-NG2/App/Composition/WindowActivationPolicy.swift diff --git a/ShadowsocksX-NG2/App/QrCodeCodec.swift b/ShadowsocksX-NG2/App/PlatformEffects/QrCodeCodec.swift similarity index 100% rename from ShadowsocksX-NG2/App/QrCodeCodec.swift rename to ShadowsocksX-NG2/App/PlatformEffects/QrCodeCodec.swift diff --git a/ShadowsocksX-NG2/App/ActivationFeedbackState.swift b/ShadowsocksX-NG2/App/Presentation/ActivationFeedbackState.swift similarity index 100% rename from ShadowsocksX-NG2/App/ActivationFeedbackState.swift rename to ShadowsocksX-NG2/App/Presentation/ActivationFeedbackState.swift diff --git a/ShadowsocksX-NG2/App/ErrorPresentation+Runtime.swift b/ShadowsocksX-NG2/App/Presentation/ErrorPresentation+Runtime.swift similarity index 100% rename from ShadowsocksX-NG2/App/ErrorPresentation+Runtime.swift rename to ShadowsocksX-NG2/App/Presentation/ErrorPresentation+Runtime.swift diff --git a/ShadowsocksX-NG2/App/ErrorPresentation.swift b/ShadowsocksX-NG2/App/Presentation/ErrorPresentation.swift similarity index 100% rename from ShadowsocksX-NG2/App/ErrorPresentation.swift rename to ShadowsocksX-NG2/App/Presentation/ErrorPresentation.swift diff --git a/ShadowsocksX-NG2/App/StatusMenuModel.swift b/ShadowsocksX-NG2/App/Presentation/StatusMenuModel.swift similarity index 100% rename from ShadowsocksX-NG2/App/StatusMenuModel.swift rename to ShadowsocksX-NG2/App/Presentation/StatusMenuModel.swift diff --git a/ShadowsocksX-NG2/App/UIErrorAlert.swift b/ShadowsocksX-NG2/App/Presentation/UIErrorAlert.swift similarity index 100% rename from ShadowsocksX-NG2/App/UIErrorAlert.swift rename to ShadowsocksX-NG2/App/Presentation/UIErrorAlert.swift diff --git a/ShadowsocksX-NG2/App/Assets.xcassets/AppIcon.appiconset/Contents.json b/ShadowsocksX-NG2/App/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from ShadowsocksX-NG2/App/Assets.xcassets/AppIcon.appiconset/Contents.json rename to ShadowsocksX-NG2/App/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/ShadowsocksX-NG2/App/Assets.xcassets/AppIcon.appiconset/Icon_128x128.png b/ShadowsocksX-NG2/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon_128x128.png similarity index 100% rename from ShadowsocksX-NG2/App/Assets.xcassets/AppIcon.appiconset/Icon_128x128.png rename to ShadowsocksX-NG2/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon_128x128.png diff --git a/ShadowsocksX-NG2/App/Assets.xcassets/AppIcon.appiconset/Icon_128x128@2x.png b/ShadowsocksX-NG2/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon_128x128@2x.png similarity index 100% rename from ShadowsocksX-NG2/App/Assets.xcassets/AppIcon.appiconset/Icon_128x128@2x.png rename to ShadowsocksX-NG2/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon_128x128@2x.png diff --git a/ShadowsocksX-NG2/App/Assets.xcassets/AppIcon.appiconset/Icon_16x16.png b/ShadowsocksX-NG2/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon_16x16.png similarity index 100% rename from ShadowsocksX-NG2/App/Assets.xcassets/AppIcon.appiconset/Icon_16x16.png rename to ShadowsocksX-NG2/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon_16x16.png diff --git a/ShadowsocksX-NG2/App/Assets.xcassets/AppIcon.appiconset/Icon_16x16@2x.png b/ShadowsocksX-NG2/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon_16x16@2x.png similarity index 100% rename from ShadowsocksX-NG2/App/Assets.xcassets/AppIcon.appiconset/Icon_16x16@2x.png rename to ShadowsocksX-NG2/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon_16x16@2x.png diff --git a/ShadowsocksX-NG2/App/Assets.xcassets/AppIcon.appiconset/Icon_256x256.png b/ShadowsocksX-NG2/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon_256x256.png similarity index 100% rename from ShadowsocksX-NG2/App/Assets.xcassets/AppIcon.appiconset/Icon_256x256.png rename to ShadowsocksX-NG2/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon_256x256.png diff --git a/ShadowsocksX-NG2/App/Assets.xcassets/AppIcon.appiconset/Icon_256x256@2x.png b/ShadowsocksX-NG2/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon_256x256@2x.png similarity index 100% rename from ShadowsocksX-NG2/App/Assets.xcassets/AppIcon.appiconset/Icon_256x256@2x.png rename to ShadowsocksX-NG2/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon_256x256@2x.png diff --git a/ShadowsocksX-NG2/App/Assets.xcassets/AppIcon.appiconset/Icon_32x32.png b/ShadowsocksX-NG2/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon_32x32.png similarity index 100% rename from ShadowsocksX-NG2/App/Assets.xcassets/AppIcon.appiconset/Icon_32x32.png rename to ShadowsocksX-NG2/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon_32x32.png diff --git a/ShadowsocksX-NG2/App/Assets.xcassets/AppIcon.appiconset/Icon_32x32@2x.png b/ShadowsocksX-NG2/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon_32x32@2x.png similarity index 100% rename from ShadowsocksX-NG2/App/Assets.xcassets/AppIcon.appiconset/Icon_32x32@2x.png rename to ShadowsocksX-NG2/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon_32x32@2x.png diff --git a/ShadowsocksX-NG2/App/Assets.xcassets/AppIcon.appiconset/Icon_512x512.png b/ShadowsocksX-NG2/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon_512x512.png similarity index 100% rename from ShadowsocksX-NG2/App/Assets.xcassets/AppIcon.appiconset/Icon_512x512.png rename to ShadowsocksX-NG2/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon_512x512.png diff --git a/ShadowsocksX-NG2/App/Assets.xcassets/AppIcon.appiconset/Icon_512x512@2x.png b/ShadowsocksX-NG2/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon_512x512@2x.png similarity index 100% rename from ShadowsocksX-NG2/App/Assets.xcassets/AppIcon.appiconset/Icon_512x512@2x.png rename to ShadowsocksX-NG2/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon_512x512@2x.png diff --git a/ShadowsocksX-NG2/App/Assets.xcassets/Contents.json b/ShadowsocksX-NG2/App/Resources/Assets.xcassets/Contents.json similarity index 100% rename from ShadowsocksX-NG2/App/Assets.xcassets/Contents.json rename to ShadowsocksX-NG2/App/Resources/Assets.xcassets/Contents.json diff --git a/ShadowsocksX-NG2/App/Localizable.xcstrings b/ShadowsocksX-NG2/App/Resources/Localizable.xcstrings similarity index 100% rename from ShadowsocksX-NG2/App/Localizable.xcstrings rename to ShadowsocksX-NG2/App/Resources/Localizable.xcstrings diff --git a/ShadowsocksX-NG2/App/Rules.xcstrings b/ShadowsocksX-NG2/App/Resources/Rules.xcstrings similarity index 100% rename from ShadowsocksX-NG2/App/Rules.xcstrings rename to ShadowsocksX-NG2/App/Resources/Rules.xcstrings diff --git a/ShadowsocksX-NG2/App/DiagnosticsView.swift b/ShadowsocksX-NG2/App/UI/Diagnostics/DiagnosticsView.swift similarity index 100% rename from ShadowsocksX-NG2/App/DiagnosticsView.swift rename to ShadowsocksX-NG2/App/UI/Diagnostics/DiagnosticsView.swift diff --git a/ShadowsocksX-NG2/App/HomeComponents.swift b/ShadowsocksX-NG2/App/UI/Home/HomeComponents.swift similarity index 100% rename from ShadowsocksX-NG2/App/HomeComponents.swift rename to ShadowsocksX-NG2/App/UI/Home/HomeComponents.swift diff --git a/ShadowsocksX-NG2/App/HomeServerListState.swift b/ShadowsocksX-NG2/App/UI/Home/HomeServerListState.swift similarity index 100% rename from ShadowsocksX-NG2/App/HomeServerListState.swift rename to ShadowsocksX-NG2/App/UI/Home/HomeServerListState.swift diff --git a/ShadowsocksX-NG2/App/HomeServerListView.swift b/ShadowsocksX-NG2/App/UI/Home/HomeServerListView.swift similarity index 100% rename from ShadowsocksX-NG2/App/HomeServerListView.swift rename to ShadowsocksX-NG2/App/UI/Home/HomeServerListView.swift diff --git a/ShadowsocksX-NG2/App/HomeView+Cards.swift b/ShadowsocksX-NG2/App/UI/Home/HomeView+Cards.swift similarity index 100% rename from ShadowsocksX-NG2/App/HomeView+Cards.swift rename to ShadowsocksX-NG2/App/UI/Home/HomeView+Cards.swift diff --git a/ShadowsocksX-NG2/App/HomeView.swift b/ShadowsocksX-NG2/App/UI/Home/HomeView.swift similarity index 100% rename from ShadowsocksX-NG2/App/HomeView.swift rename to ShadowsocksX-NG2/App/UI/Home/HomeView.swift diff --git a/ShadowsocksX-NG2/App/RuntimeControlCard.swift b/ShadowsocksX-NG2/App/UI/Home/RuntimeControlCard.swift similarity index 100% rename from ShadowsocksX-NG2/App/RuntimeControlCard.swift rename to ShadowsocksX-NG2/App/UI/Home/RuntimeControlCard.swift diff --git a/ShadowsocksX-NG2/App/CustomRuleEditorSheet.swift b/ShadowsocksX-NG2/App/UI/Rules/CustomRuleEditorSheet.swift similarity index 100% rename from ShadowsocksX-NG2/App/CustomRuleEditorSheet.swift rename to ShadowsocksX-NG2/App/UI/Rules/CustomRuleEditorSheet.swift diff --git a/ShadowsocksX-NG2/App/RulesAddressTestView.swift b/ShadowsocksX-NG2/App/UI/Rules/RulesAddressTestView.swift similarity index 100% rename from ShadowsocksX-NG2/App/RulesAddressTestView.swift rename to ShadowsocksX-NG2/App/UI/Rules/RulesAddressTestView.swift diff --git a/ShadowsocksX-NG2/App/RulesDeletionButton.swift b/ShadowsocksX-NG2/App/UI/Rules/RulesDeletionButton.swift similarity index 100% rename from ShadowsocksX-NG2/App/RulesDeletionButton.swift rename to ShadowsocksX-NG2/App/UI/Rules/RulesDeletionButton.swift diff --git a/ShadowsocksX-NG2/App/RulesOperationStatusView.swift b/ShadowsocksX-NG2/App/UI/Rules/RulesOperationStatusView.swift similarity index 100% rename from ShadowsocksX-NG2/App/RulesOperationStatusView.swift rename to ShadowsocksX-NG2/App/UI/Rules/RulesOperationStatusView.swift diff --git a/ShadowsocksX-NG2/App/RulesSourceDetailsView.swift b/ShadowsocksX-NG2/App/UI/Rules/RulesSourceDetailsView.swift similarity index 100% rename from ShadowsocksX-NG2/App/RulesSourceDetailsView.swift rename to ShadowsocksX-NG2/App/UI/Rules/RulesSourceDetailsView.swift diff --git a/ShadowsocksX-NG2/App/RulesView.swift b/ShadowsocksX-NG2/App/UI/Rules/RulesView.swift similarity index 100% rename from ShadowsocksX-NG2/App/RulesView.swift rename to ShadowsocksX-NG2/App/UI/Rules/RulesView.swift diff --git a/ShadowsocksX-NG2/App/CatalogExpansionState.swift b/ShadowsocksX-NG2/App/UI/Servers/CatalogExpansionState.swift similarity index 100% rename from ShadowsocksX-NG2/App/CatalogExpansionState.swift rename to ShadowsocksX-NG2/App/UI/Servers/CatalogExpansionState.swift diff --git a/ShadowsocksX-NG2/App/GroupDetailView.swift b/ShadowsocksX-NG2/App/UI/Servers/GroupDetailView.swift similarity index 100% rename from ShadowsocksX-NG2/App/GroupDetailView.swift rename to ShadowsocksX-NG2/App/UI/Servers/GroupDetailView.swift diff --git a/ShadowsocksX-NG2/App/ImportOutcomePresentation.swift b/ShadowsocksX-NG2/App/UI/Servers/ImportOutcomePresentation.swift similarity index 100% rename from ShadowsocksX-NG2/App/ImportOutcomePresentation.swift rename to ShadowsocksX-NG2/App/UI/Servers/ImportOutcomePresentation.swift diff --git a/ShadowsocksX-NG2/App/ImportServersSheet.swift b/ShadowsocksX-NG2/App/UI/Servers/ImportServersSheet.swift similarity index 100% rename from ShadowsocksX-NG2/App/ImportServersSheet.swift rename to ShadowsocksX-NG2/App/UI/Servers/ImportServersSheet.swift diff --git a/ShadowsocksX-NG2/App/MoveNodeSheet.swift b/ShadowsocksX-NG2/App/UI/Servers/MoveNodeSheet.swift similarity index 100% rename from ShadowsocksX-NG2/App/MoveNodeSheet.swift rename to ShadowsocksX-NG2/App/UI/Servers/MoveNodeSheet.swift diff --git a/ShadowsocksX-NG2/App/NewGroupSheet.swift b/ShadowsocksX-NG2/App/UI/Servers/NewGroupSheet.swift similarity index 100% rename from ShadowsocksX-NG2/App/NewGroupSheet.swift rename to ShadowsocksX-NG2/App/UI/Servers/NewGroupSheet.swift diff --git a/ShadowsocksX-NG2/App/NewServerSheet.swift b/ShadowsocksX-NG2/App/UI/Servers/NewServerSheet.swift similarity index 100% rename from ShadowsocksX-NG2/App/NewServerSheet.swift rename to ShadowsocksX-NG2/App/UI/Servers/NewServerSheet.swift diff --git a/ShadowsocksX-NG2/App/PasswordAutofillOptOut.swift b/ShadowsocksX-NG2/App/UI/Servers/PasswordAutofillOptOut.swift similarity index 100% rename from ShadowsocksX-NG2/App/PasswordAutofillOptOut.swift rename to ShadowsocksX-NG2/App/UI/Servers/PasswordAutofillOptOut.swift diff --git a/ShadowsocksX-NG2/App/PluginEditorSheet.swift b/ShadowsocksX-NG2/App/UI/Servers/Plugin/PluginEditorSheet.swift similarity index 100% rename from ShadowsocksX-NG2/App/PluginEditorSheet.swift rename to ShadowsocksX-NG2/App/UI/Servers/Plugin/PluginEditorSheet.swift diff --git a/ShadowsocksX-NG2/App/PluginManagementCopy.swift b/ShadowsocksX-NG2/App/UI/Servers/Plugin/PluginManagementCopy.swift similarity index 100% rename from ShadowsocksX-NG2/App/PluginManagementCopy.swift rename to ShadowsocksX-NG2/App/UI/Servers/Plugin/PluginManagementCopy.swift diff --git a/ShadowsocksX-NG2/App/PluginManagementSection.swift b/ShadowsocksX-NG2/App/UI/Servers/Plugin/PluginManagementSection.swift similarity index 100% rename from ShadowsocksX-NG2/App/PluginManagementSection.swift rename to ShadowsocksX-NG2/App/UI/Servers/Plugin/PluginManagementSection.swift diff --git a/ShadowsocksX-NG2/App/ServerPluginSection.swift b/ShadowsocksX-NG2/App/UI/Servers/Plugin/ServerPluginSection.swift similarity index 100% rename from ShadowsocksX-NG2/App/ServerPluginSection.swift rename to ShadowsocksX-NG2/App/UI/Servers/Plugin/ServerPluginSection.swift diff --git a/ShadowsocksX-NG2/App/ServerDetailView.swift b/ShadowsocksX-NG2/App/UI/Servers/ServerDetailView.swift similarity index 100% rename from ShadowsocksX-NG2/App/ServerDetailView.swift rename to ShadowsocksX-NG2/App/UI/Servers/ServerDetailView.swift diff --git a/ShadowsocksX-NG2/App/ServerFormFields.swift b/ShadowsocksX-NG2/App/UI/Servers/ServerFormFields.swift similarity index 100% rename from ShadowsocksX-NG2/App/ServerFormFields.swift rename to ShadowsocksX-NG2/App/UI/Servers/ServerFormFields.swift diff --git a/ShadowsocksX-NG2/App/ServerTreeBlankClickObserver.swift b/ShadowsocksX-NG2/App/UI/Servers/ServerTreeBlankClickObserver.swift similarity index 100% rename from ShadowsocksX-NG2/App/ServerTreeBlankClickObserver.swift rename to ShadowsocksX-NG2/App/UI/Servers/ServerTreeBlankClickObserver.swift diff --git a/ShadowsocksX-NG2/App/ServersView+Share.swift b/ShadowsocksX-NG2/App/UI/Servers/ServersView+Share.swift similarity index 100% rename from ShadowsocksX-NG2/App/ServersView+Share.swift rename to ShadowsocksX-NG2/App/UI/Servers/ServersView+Share.swift diff --git a/ShadowsocksX-NG2/App/ServersView.swift b/ShadowsocksX-NG2/App/UI/Servers/ServersView.swift similarity index 100% rename from ShadowsocksX-NG2/App/ServersView.swift rename to ShadowsocksX-NG2/App/UI/Servers/ServersView.swift diff --git a/ShadowsocksX-NG2/App/ListenerModeEditorSheet.swift b/ShadowsocksX-NG2/App/UI/Settings/ListenerModeEditorSheet.swift similarity index 100% rename from ShadowsocksX-NG2/App/ListenerModeEditorSheet.swift rename to ShadowsocksX-NG2/App/UI/Settings/ListenerModeEditorSheet.swift diff --git a/ShadowsocksX-NG2/App/SettingsView.swift b/ShadowsocksX-NG2/App/UI/Settings/SettingsView.swift similarity index 100% rename from ShadowsocksX-NG2/App/SettingsView.swift rename to ShadowsocksX-NG2/App/UI/Settings/SettingsView.swift diff --git a/ShadowsocksX-NG2/App/ProxyStatusMenu.swift b/ShadowsocksX-NG2/App/UI/StatusMenu/ProxyStatusMenu.swift similarity index 100% rename from ShadowsocksX-NG2/App/ProxyStatusMenu.swift rename to ShadowsocksX-NG2/App/UI/StatusMenu/ProxyStatusMenu.swift diff --git a/ShadowsocksX-NG2/App/SubscriptionInformationPresentation.swift b/ShadowsocksX-NG2/App/UI/Subscriptions/SubscriptionInformationPresentation.swift similarity index 100% rename from ShadowsocksX-NG2/App/SubscriptionInformationPresentation.swift rename to ShadowsocksX-NG2/App/UI/Subscriptions/SubscriptionInformationPresentation.swift diff --git a/ShadowsocksX-NG2/App/SubscriptionsView.swift b/ShadowsocksX-NG2/App/UI/Subscriptions/SubscriptionsView.swift similarity index 100% rename from ShadowsocksX-NG2/App/SubscriptionsView.swift rename to ShadowsocksX-NG2/App/UI/Subscriptions/SubscriptionsView.swift diff --git a/ShadowsocksX-NG2/App/MainWindowView.swift b/ShadowsocksX-NG2/App/UI/Workspace/MainWindowView.swift similarity index 100% rename from ShadowsocksX-NG2/App/MainWindowView.swift rename to ShadowsocksX-NG2/App/UI/Workspace/MainWindowView.swift diff --git a/ShadowsocksX-NG2/App/SidebarRow.swift b/ShadowsocksX-NG2/App/UI/Workspace/SidebarRow.swift similarity index 100% rename from ShadowsocksX-NG2/App/SidebarRow.swift rename to ShadowsocksX-NG2/App/UI/Workspace/SidebarRow.swift diff --git a/ShadowsocksX-NG2/App/StatusCardModel.swift b/ShadowsocksX-NG2/App/UI/Workspace/StatusCardModel.swift similarity index 100% rename from ShadowsocksX-NG2/App/StatusCardModel.swift rename to ShadowsocksX-NG2/App/UI/Workspace/StatusCardModel.swift diff --git a/ShadowsocksX-NG2/App/StatusCardView.swift b/ShadowsocksX-NG2/App/UI/Workspace/StatusCardView.swift similarity index 100% rename from ShadowsocksX-NG2/App/StatusCardView.swift rename to ShadowsocksX-NG2/App/UI/Workspace/StatusCardView.swift diff --git a/ShadowsocksX-NG2/App/WorkspaceRoute.swift b/ShadowsocksX-NG2/App/UI/Workspace/WorkspaceRoute.swift similarity index 100% rename from ShadowsocksX-NG2/App/WorkspaceRoute.swift rename to ShadowsocksX-NG2/App/UI/Workspace/WorkspaceRoute.swift diff --git a/ShadowsocksX-NG2/README.md b/ShadowsocksX-NG2/README.md index 9ca30268..f1bdbf6a 100644 --- a/ShadowsocksX-NG2/README.md +++ b/ShadowsocksX-NG2/README.md @@ -8,8 +8,16 @@ ShadowsocksX-NG 的现代化重写版本。新工程的全部源码与构建配 ## 工程结构 -- `project.yml` — XcodeGen 工程定义,唯一事实来源。`.xcodeproj` 与 `App/Info.plist`、`*.entitlements`、`Tests/Info.plist` 都是生成物(已 gitignore),改工程结构一律改 `project.yml` 后重新生成。 -- `App/` — app target 源码(SwiftUI 菜单栏 app,LSUIElement)。 +- `project.yml` — XcodeGen 工程定义,唯一事实来源。`.xcodeproj` 与 `App/Resources/Info.plist`、`*.entitlements`、`Tests/Info.plist` 都是生成物(已 gitignore),改工程结构一律改 `project.yml` 后重新生成。 +- `App/` — app target 源码(SwiftUI 菜单栏 app,LSUIElement)。目录即分层,层内按功能分组: + - `Composition/` — 组合根与进程生命周期:`@main`、依赖装配、应用形态与窗口激活策略。 + - `Application/` — 应用层(无 SwiftUI 视图):`*Workflow/` 工作流 module(UI-facing interface 与实现协作者)、`ProxyRuntime/` 运行时控制器家族与事实投影、`Services/` LaunchAgent/登录项/helper/防火墙。 + - `Presentation/` — 跨功能表现层:错误呈现、被多个界面面共用的状态模型。 + - `UI/` — 视图层,按子视图分目录(`Workspace/`、`Home/`、`Servers/`、`Subscriptions/`、`Rules/`、`Settings/`、`Diagnostics/`、`StatusMenu/`,`Servers/Plugin/` 为插件管理子视图)。 + - `PlatformEffects/` — 平台效应 seam(协议 + InMemory 替身 + AppKit adapter),系统框架访问集中于此。 + - `Resources/` — `Info.plist`、`entitlements`(生成物)与 `Assets.xcassets`、`*.xcstrings`(入库)。 + + 分层边界由 `Tests/SkeletonTests.swift` 的正向圈定守卫断言:只扫描 `App/UI/`,视图层不得引用运行时控制器、工作流实现协作者或原始目录/凭据存储类型;新增运行时适配器放进 `Application/` 即自动落在扫描范围外,无需登记豁免。注意 `App/` 下任何非源码文件都会被 XcodeGen 收进 Resources 阶段并封进签名后的 bundle,因此文档不放 `App/`(`project.yml` 已排除 `**/*.md`)。 - `Agent/` — 代理运行时 wrapper(独立可执行文件,装入 `Contents/MacOS/`,由 LaunchAgent 常驻):读 `sslocal-active.json` 契约,以绝对路径启动和监管 sslocal(SIGTERM 链式停止、SIGUSR1 热重载/结构重启、崩溃时非零退出交 KeepAlive 重放,spec #21 D2/D5/D7)。PAC HTTP endpoint 已随 issue #67 移除。 - `LaunchAgent/` — `SMAppService.agent(plistName:)` 的注册清单,装入 `Contents/Library/LaunchAgents/`;`ProgramArguments` 用 bundle 相对路径,由 launchd 按注册 app 的 bundle 位置解析。 - `Domain/` — 领域核心(配置目录树、凭据引用与持久化,spec #21 D3/D5),与 UI 无关;其中 `RuntimePaths`、`SslocalRuntimeDocument`、`RuntimeLog` 同时编入 wrapper 目标,保证跨进程契约单一来源。 diff --git a/ShadowsocksX-NG2/Tests/SkeletonTests.swift b/ShadowsocksX-NG2/Tests/SkeletonTests.swift index 37f9dedd..736a3a41 100644 --- a/ShadowsocksX-NG2/Tests/SkeletonTests.swift +++ b/ShadowsocksX-NG2/Tests/SkeletonTests.swift @@ -20,14 +20,18 @@ final class SkeletonTests: XCTestCase { } /// 架构 deletion check(issue #49):UI 源文件不得引用目录工作流的实现 -/// 协作者或原始目录/凭据存储类型。组合根(MainApp)与各 workflow module -/// 目录豁免;新增视图文件自动纳入扫描,新增运行时/系统适配器需显式登记 -/// 豁免名单。规则只点名实现缝,不锁定实现内部的文件划分或私有 helper。 +/// 协作者或原始目录/凭据存储类型。 +/// +/// 扫描范围由目录正面圈定:只扫 `App/UI/`(视图层)。分层因此是目录声明而非 +/// 豁免登记——把文件移出 `UI/` 是显式的架构动作;组合根放 `App/Composition/`、 +/// 运行时与系统适配器放 `App/Application/`、平台效应 seam 放 +/// `App/PlatformEffects/`,天然落在扫描范围之外,新增时无需登记豁免。 +/// 规则只点名实现缝,不锁定实现内部的文件划分或私有 helper。 final class CatalogWorkflowArchitectureTests: XCTestCase { func testUISourceFilesDoNotReferenceCatalogWorkflowImplementation() throws { - let appDirectory = Self.testTargetRoot.appendingPathComponent("App") - let uiFiles = try Self.uiSourceFiles(in: appDirectory) - XCTAssertFalse(uiFiles.isEmpty, "UI 源文件集合不应为空(App 目录缺失?)") + let uiDirectory = Self.testTargetRoot.appendingPathComponent("App/UI") + let uiFiles = try Self.swiftSourceFiles(in: uiDirectory) + XCTAssertFalse(uiFiles.isEmpty, "UI 源文件集合不应为空(App/UI 目录缺失?)") var violations: [String] = [] for file in uiFiles { @@ -56,30 +60,6 @@ final class CatalogWorkflowArchitectureTests: XCTestCase { private static let testTargetRoot = URL(fileURLWithPath: #filePath) .deletingLastPathComponent().deletingLastPathComponent() - /// 豁免文件:组合根(生产 adapter 唯一装配点)与运行时/系统适配器(合法 - /// 接触原始目录与凭据类型)。 - private static let exemptedFiles: Set = [ - "MainApp.swift", - "CatalogCommitCoordinator.swift", - // 运行时 facts 投影:适配器侧把控制器状态映射为 typed facts,非 UI 表面。 - "ProxyRuntimeFacts.swift", - "SystemProxyHelperService.swift", - "LaunchAgentService.swift", - "LoginAtLoginService.swift", - "FirewallStatusChecker.swift", - ] - - /// 豁免前缀:控制器家族(本体与按命令面/收敛脊柱/系统代理门禁等分的各文件) - /// 同属运行时适配器,合法接触代理控制器类型。 - private static let exemptedFilePrefixes: Set = [ - "ProxyRuntimeController" - ] - - /// 豁免目录:workflow module 实现(含被测的目录工作流自身)。 - private static let exemptedDirectories: Set = [ - "CatalogWorkflow", "ProxyControlWorkflow", "SettingsWorkflow", "DiagnosticsWorkflow", - ] - /// 禁令:实现协作者类型、原始目录/凭据存储类型与 workflow 内部成员缝。 private static let forbiddenPatterns = makeForbiddenPatterns() @@ -109,28 +89,18 @@ final class CatalogWorkflowArchitectureTests: XCTestCase { } } - /// UI 源文件 = App 下全部 Swift 文件,除豁免名单与 workflow module 目录。 - private static func uiSourceFiles(in directory: URL) throws -> [URL] { + /// 视图层源文件 = `App/UI/` 下全部 Swift 文件(递归含各子视图目录)。 + private static func swiftSourceFiles(in directory: URL) throws -> [URL] { guard let enumerator = FileManager.default.enumerator( at: directory, includingPropertiesForKeys: nil) else { - XCTFail("无法枚举 App 源码目录:\(directory.path)") + XCTFail("无法枚举 UI 源码目录:\(directory.path)") return [] } - var result: [URL] = [] - for case let url as URL in enumerator { - let name = url.lastPathComponent - guard - name.hasSuffix(".swift"), - !exemptedFiles.contains(name), - !exemptedFilePrefixes.contains(where: { name.hasPrefix($0) }) - else { continue } - let relativePath = url.path.replacingOccurrences(of: directory.path + "/", with: "") - guard !exemptedDirectories.contains(where: { relativePath.hasPrefix("\($0)/") }) - else { continue } - result.append(url) + return enumerator.compactMap { item in + guard let url = item as? URL, url.pathExtension == "swift" else { return nil } + return url } - return result } } diff --git a/ShadowsocksX-NG2/project.yml b/ShadowsocksX-NG2/project.yml index 5fe46e24..c2680d7d 100644 --- a/ShadowsocksX-NG2/project.yml +++ b/ShadowsocksX-NG2/project.yml @@ -24,7 +24,15 @@ targets: type: application platform: macOS sources: + # 目录即分层:Composition(组合根)/ Application(工作流与运行时适配)/ + # Presentation(跨功能表现层)/ UI(视图,按子视图分目录)/ + # PlatformEffects(平台效应 seam)/ Resources(plist、entitlements、资源)。 + # 分层边界由 Tests/SkeletonTests.swift 的 UI 圈定守卫断言。 - path: App + excludes: + # 文档不属于 target 源码:App/ 下任何非源码文件都会被 XcodeGen 收进 + # Resources 阶段并封进签名后的 app bundle(实测),因此显式排除。 + - "**/*.md" # 领域核心:配置目录树、凭据引用与持久化(spec #21 D3/D5) - path: Domain # LaunchAgent 注册清单:SMAppService.agent(plistName:) 从 bundle 内 @@ -99,7 +107,7 @@ targets: # 必须 > 旧版 1.x 已发布构建的 CFBundleVersion(始终为 1),保证 build 号严格递增。 CURRENT_PROJECT_VERSION: 2 info: - path: App/Info.plist + path: App/Resources/Info.plist properties: CFBundleShortVersionString: $(MARKETING_VERSION) CFBundleVersion: $(CURRENT_PROJECT_VERSION) @@ -108,7 +116,7 @@ targets: NSPrincipalClass: NSApplication LSApplicationCategoryType: public.app-category.utilities entitlements: - path: App/ShadowsocksX-NG2.entitlements + path: App/Resources/ShadowsocksX-NG2.entitlements properties: {} scheme: testTargets: @@ -142,10 +150,12 @@ targets: sources: - path: App excludes: - - "MainApp.swift" - - "Info.plist" - - "ShadowsocksX-NG2.entitlements" - - "Assets.xcassets" + # 递归 glob(`**/`)而非裸文件名:XcodeGen 的 exclude 不跨 `/`,裸名只 + # 匹配 App 根层。文件下移后裸名会静默失效,把 @main 组合根编进测试镜像。 + - "**/MainApp.swift" + - "**/Info.plist" + - "**/ShadowsocksX-NG2.entitlements" + - "**/Assets.xcassets" - path: Domain settings: base: