Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
44 changes: 44 additions & 0 deletions Docs/BackendReferencePlugin.md
Original file line number Diff line number Diff line change
@@ -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 `<dynamic>`.
24 changes: 20 additions & 4 deletions Docs/BackendReferencePlugin.zh-Hans.md
Original file line number Diff line number Diff line change
@@ -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 配置步骤

Expand All @@ -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 \
Expand Down
1 change: 1 addition & 0 deletions Docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions Docs/README.zh-Hans.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 工程生成可浏览的后端、端点、参数与配置参考。 |

## 推荐接入顺序

Expand Down
19 changes: 19 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ let package = Package(
name: "BackendReferencePlugin",
targets: ["BackendReferencePlugin"]
),
.plugin(
name: "BackendReferenceCommandPlugin",
targets: ["BackendReferenceCommandPlugin"]
),
],
targets: [
.target(
Expand All @@ -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"],
Expand Down
Original file line number Diff line number Diff line change
@@ -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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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")
]
```

Expand Down Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions README.zh-Hans.md
Original file line number Diff line number Diff line change
Expand Up @@ -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")
]
```

Expand Down Expand Up @@ -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

Expand Down
8 changes: 5 additions & 3 deletions Sources/BackendReferenceGenerator/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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? {
Expand Down
Loading