diff --git a/CHANGELOG.md b/CHANGELOG.md index 90faff9..aa080a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to NetworkingKit are documented in this file. +## 2.4.3 - 2026-07-23 + +### Added + +- Add `BackendReferenceCommandPlugin` for generating the backend API HTML reference in an Xcode project's `Docs/BackendAPIReference` directory. + ## 2.4.2 - 2026-07-23 ### Fixed diff --git a/Docs/BackendReferencePlugin.md b/Docs/BackendReferencePlugin.md new file mode 100644 index 0000000..72e619f --- /dev/null +++ b/Docs/BackendReferencePlugin.md @@ -0,0 +1,44 @@ +# Backend API HTML reference + +NetworkingKit can scan an Xcode app's Swift source and create a browsable HTML reference of backend servers, configuration values, feature-grouped endpoints, parameters, requests, and source files. + +## Recommended: generate files in `Docs` + +Use `BackendReferenceCommandPlugin` when the reference should live in the app project and be easy to open, commit, or attach to an artifact. + +The plugin writes its entry page to: + +```text +$SRCROOT/Docs/BackendAPIReference/index.html +``` + +### Xcode steps + +1. Add `NetworkingKit` through **File → Add Package Dependencies…**. +2. Select **File → Packages → Generate Backend API Reference**. +3. On first use, inspect the plugin and choose **Allow Command to Change Files**. This grants permission to write inside the project `Docs/` directory. +4. Open `Docs/BackendAPIReference/index.html` in Finder or a browser. +5. Run the same menu command whenever the app's networking definitions change. + +No Run Script, checkout path, or environment variable is required. The Xcode command plugin receives the current project directory directly. + +## Build-time preview + +`BackendReferencePlugin` is a Build Tool Plugin for automatically generating the same reference during every build: + +1. Select the App target and open **Build Phases**. +2. In **Run Build Tool Plug-ins**, click `+` and choose `BackendReferencePlugin`. +3. Build the app. +4. Open the plugin output in the Xcode Report navigator; its entry page is `BackendAPIReference/index.html` in Derived Data. + +Build Tool Plugins are sandboxed and cannot write into the project root. Use the Command Plugin when the HTML must be saved in `Docs/`. + +## Discovery rules + +- A `NetworkClient` or `SharedNetworkClient` `baseURL` identifies a backend server. +- An app request protocol constrained to a concrete client associates requests with that server. +- `RestfulRequest` and `GraphQLRequest` declarations are endpoints. +- The nearest preceding `// MARK: - Feature name` groups endpoints into features. +- Stored request properties are parameters; configuration values are displayed in a configuration table. + +The scanner does not execute Swift code. Dynamic expressions remain source expressions or are shown as ``. diff --git a/Docs/BackendReferencePlugin.zh-Hans.md b/Docs/BackendReferencePlugin.zh-Hans.md index 06775c6..c96c546 100644 --- a/Docs/BackendReferencePlugin.zh-Hans.md +++ b/Docs/BackendReferencePlugin.zh-Hans.md @@ -1,8 +1,24 @@ # 后端 API HTML 索引 -`BackendReferencePlugin` 会在每次编译 App Target 时扫描 Swift 源码,生成后端服务器、Feature、端点、请求参数与网络配置的 HTML 索引。默认推荐使用 Build Tool Plugin:无需维护 `--package-path` 或额外脚本,且始终与本次构建使用相同的源码。 +NetworkingKit 同时提供 Build Tool Plugin 和 Command Plugin,用于扫描 App 的 Swift 源码并生成后端服务器、Feature、端点、请求参数与网络配置的 HTML 索引。 -## 推荐:Build Tool Plugin +## 推荐:Command Plugin 写入 Docs + +需要在 App 根目录保留可浏览的 HTML 时,使用 `BackendReferenceCommandPlugin`。它会生成: + +```text +$SRCROOT/Docs/BackendAPIReference/index.html +``` + +在 Xcode 中执行: + +1. 选择 **File → Packages → Generate Backend API Reference**。 +2. 第一次执行时,审阅插件并选择 **Allow Command to Change Files**,授权它写入项目的 `Docs/` 目录。 +3. 在 Finder 或 Xcode 中打开 `Docs/BackendAPIReference/index.html`。 + +Command Plugin 使用 XcodeProjectPlugin API 定位当前 App 工程,不需要配置 `--package-path`、Run Script 或环境变量。适合在需要时刷新并提交、归档或分享这份文档。 + +## Build Tool Plugin:构建产物预览 ### Xcode 配置步骤 @@ -12,11 +28,11 @@ 4. 直接 Build App。构建日志会显示 **Generate backend API reference**。 5. 在 Xcode Report navigator 中展开该构建步骤;HTML 位于该 Target 对应的 Derived Data 插件输出目录,入口文件为 `BackendAPIReference/index.html`。在 Finder 中打开该目录即可浏览文档。 -Build Tool Plugin 受 SwiftPM 沙盒保护,只能写入 Derived Data 的插件工作目录,不能写回 App 的源码根目录。这是它无需路径配置、可安全在本地与 CI 自动运行的原因。 +Build Tool Plugin 受 SwiftPM 沙盒保护,只能写入 Derived Data 的插件工作目录,不能写回 App 的源码根目录。它适合每次构建时自动生成预览;若需要固定的 Docs 文件,请使用上面的 Command Plugin。 ## 备用:导出到 App 根目录 -只有需要将 HTML 固定保存为 `$SRCROOT/BackendAPIReference/`、提交到制品库或交付给非 Xcode 用户时,才在 App Target 最后添加一个 **Run Script Build Phase**。该备用方式需要为 Package checkout 提供路径: +只有无法从 Xcode 运行 Command Plugin、但仍需在自动化环境导出 HTML 时,才使用 Run Script Build Phase。该备用方式需要为 Package checkout 提供路径: ```sh swift run --package-path "$NETWORKING_KIT_CHECKOUT" BackendReferenceGenerator \ diff --git a/Docs/README.md b/Docs/README.md index a3fe419..237d4c5 100644 --- a/Docs/README.md +++ b/Docs/README.md @@ -14,6 +14,7 @@ These guides are the reference for production use. Read them in the order below | [Observability](Observability.md) | Add request IDs, OSLog, OpenTelemetry, or aggregate network metrics. | | [Errors](Errors.md) | Handle stable errors and show App-localized user messages. | | [Security](Security.md) | Apply certificate or public-key pinning with a safe certificate-rotation plan. | +| [Backend API reference](BackendReferencePlugin.md) | Generate a browsable backend, endpoint, parameter, and configuration reference from an Xcode project. | ## Recommended adoption path diff --git a/Docs/README.zh-Hans.md b/Docs/README.zh-Hans.md index 1720f00..50e26e3 100644 --- a/Docs/README.zh-Hans.md +++ b/Docs/README.zh-Hans.md @@ -14,6 +14,7 @@ | [可观测性](Observability.zh-Hans.md) | 添加请求 ID、OSLog、OpenTelemetry 或聚合网络指标。 | | [错误与本地化](Errors.zh-Hans.md) | 处理稳定错误并显示 App 本地化的用户文案。 | | [传输安全](Security.zh-Hans.md) | 在有完善证书轮换方案时使用证书或公钥 pinning。 | +| [后端 API HTML 索引](BackendReferencePlugin.zh-Hans.md) | 从 Xcode 工程生成可浏览的后端、端点、参数与配置参考。 | ## 推荐接入顺序 diff --git a/Package.swift b/Package.swift index aee27af..93a6af9 100644 --- a/Package.swift +++ b/Package.swift @@ -26,6 +26,10 @@ let package = Package( name: "BackendReferencePlugin", targets: ["BackendReferencePlugin"] ), + .plugin( + name: "BackendReferenceCommandPlugin", + targets: ["BackendReferenceCommandPlugin"] + ), ], targets: [ .target( @@ -42,6 +46,21 @@ let package = Package( dependencies: ["BackendReferenceGenerator"], path: "Plugins/BackendReferencePlugin" ), + .plugin( + name: "BackendReferenceCommandPlugin", + capability: .command( + intent: .custom( + verb: "generate-backend-reference", + description: "Generate the backend API HTML reference." + ), + permissions: [ + .writeToPackageDirectory( + reason: "Generates the backend API HTML reference in Docs/BackendAPIReference." + ) + ] + ), + path: "Plugins/BackendReferenceCommandPlugin" + ), .testTarget( name: "NetworkingKitTests", dependencies: ["NetworkingKit"], diff --git a/Plugins/BackendReferenceCommandPlugin/BackendReferenceCommandPlugin.swift b/Plugins/BackendReferenceCommandPlugin/BackendReferenceCommandPlugin.swift new file mode 100644 index 0000000..f595cd0 --- /dev/null +++ b/Plugins/BackendReferenceCommandPlugin/BackendReferenceCommandPlugin.swift @@ -0,0 +1,49 @@ +import Foundation +import PackagePlugin + +@main +struct BackendReferenceCommandPlugin: CommandPlugin { + private var pluginPackageRoot: URL { + URL(fileURLWithPath: #filePath) + .deletingLastPathComponent() + .deletingLastPathComponent() + .deletingLastPathComponent() + } + + func performCommand(context: PluginContext, arguments: [String]) throws { + try generate(appRoot: context.package.directoryURL, scriptsRoot: context.package.directoryURL) + } + + fileprivate func generate(appRoot: URL, scriptsRoot: URL) throws { + let generator = scriptsRoot.appending(path: "Sources/BackendReferenceGenerator/main.swift") + let outputDirectory = appRoot.appending(path: "Docs/BackendAPIReference", directoryHint: .isDirectory) + try runSwift(script: generator, arguments: [ + "--source-directory", appRoot.path, + "--output-directory", outputDirectory.path + ]) + } + + private func runSwift(script: URL, arguments: [String]) throws { + let process = Process() + process.executableURL = URL(fileURLWithPath: "/usr/bin/env") + process.arguments = ["swift", script.path] + arguments + var environment = ProcessInfo.processInfo.environment + environment.removeValue(forKey: "SDKROOT") + process.environment = environment + try process.run() + process.waitUntilExit() + guard process.terminationStatus == 0 else { + throw NSError(domain: "BackendReferenceCommandPlugin", code: Int(process.terminationStatus)) + } + } +} + +#if canImport(XcodeProjectPlugin) +import XcodeProjectPlugin + +extension BackendReferenceCommandPlugin: XcodeCommandPlugin { + func performCommand(context: XcodePluginContext, arguments: [String]) throws { + try generate(appRoot: context.xcodeProject.directoryURL, scriptsRoot: pluginPackageRoot) + } +} +#endif diff --git a/README.md b/README.md index 5f19bde..7b9d460 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Or add the package manifest dependency: ```swift dependencies: [ - .package(url: "https://github.com/relaxfinger/NetworkingKit.git", from: "2.4.2") + .package(url: "https://github.com/relaxfinger/NetworkingKit.git", from: "2.4.3") ] ``` @@ -139,6 +139,7 @@ The README is intentionally the shortest path to a first request. Use the detail | Logs, tracing, and metrics | [Observability](Docs/Observability.md) | [可观测性](Docs/Observability.zh-Hans.md) | | Stable errors and localized UI messages | [Errors](Docs/Errors.md) | [错误与本地化](Docs/Errors.zh-Hans.md) | | Certificate and public-key pinning | [Security](Docs/Security.md) | [传输安全](Docs/Security.zh-Hans.md) | +| Backend API HTML reference and Xcode plugins | [Backend API reference](Docs/BackendReferencePlugin.md) | [后端 API HTML 索引](Docs/BackendReferencePlugin.zh-Hans.md) | ## Demo diff --git a/README.zh-Hans.md b/README.zh-Hans.md index 6787143..0b5c9a0 100644 --- a/README.zh-Hans.md +++ b/README.zh-Hans.md @@ -33,7 +33,7 @@ https://github.com/relaxfinger/NetworkingKit.git ```swift dependencies: [ - .package(url: "https://github.com/relaxfinger/NetworkingKit.git", from: "2.4.2") + .package(url: "https://github.com/relaxfinger/NetworkingKit.git", from: "2.4.3") ] ``` @@ -139,7 +139,7 @@ README 只保留从安装到首个请求的最短路径。构建生产网络层 | 日志、追踪与指标 | [Observability](Docs/Observability.md) | [可观测性](Docs/Observability.zh-Hans.md) | | 稳定错误与本地化 UI 文案 | [Errors](Docs/Errors.md) | [错误与本地化](Docs/Errors.zh-Hans.md) | | 证书和公钥 pinning | [Security](Docs/Security.md) | [传输安全](Docs/Security.zh-Hans.md) | -| 后端服务器、端点与参数 HTML 索引 | — | [后端 API HTML 索引](Docs/BackendReferencePlugin.zh-Hans.md) | +| 后端服务器、端点与参数 HTML 索引 | [Backend API reference](Docs/BackendReferencePlugin.md) | [后端 API HTML 索引](Docs/BackendReferencePlugin.zh-Hans.md) | ## Demo diff --git a/Sources/BackendReferenceGenerator/main.swift b/Sources/BackendReferenceGenerator/main.swift index 02d6dc1..97c14a9 100644 --- a/Sources/BackendReferenceGenerator/main.swift +++ b/Sources/BackendReferenceGenerator/main.swift @@ -20,10 +20,10 @@ struct Endpoint { enum BackendReferenceGenerator { static func run() throws { let arguments = Array(CommandLine.arguments.dropFirst()) - guard let sourceDirectory = value(after: "--source-directory", in: arguments), - let stamp = value(after: "--stamp", in: arguments) else { + guard let sourceDirectory = value(after: "--source-directory", in: arguments) else { throw GeneratorError.invalidArguments } + let stamp = value(after: "--stamp", in: arguments) let sourceURL = URL(fileURLWithPath: sourceDirectory) let projectRoot = findProjectRoot(startingAt: sourceURL) @@ -41,7 +41,9 @@ enum BackendReferenceGenerator { for server in servers { try writeServerPage(server, endpoints: endpoints.filter { $0.server == server.name }, to: outputDirectory) } - try "Generated by BackendReferencePlugin\n".write(to: URL(fileURLWithPath: stamp), atomically: true, encoding: .utf8) + if let stamp { + try "Generated by BackendReferencePlugin\n".write(to: URL(fileURLWithPath: stamp), atomically: true, encoding: .utf8) + } } static func value(after option: String, in arguments: [String]) -> String? {