From a6ffbea4e01d95aceef7e2da62003704ef29ebf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BD=A9=E8=BE=95=E5=8D=81=E5=9B=9B?= Date: Mon, 20 Jul 2026 18:41:01 +0800 Subject: [PATCH 1/4] chore: ignore Swift package resolution file --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 1948635..7941d86 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .codegraph/ .swiftpm/ .DS_Store +Package.resolved From 8dc9bc423cb0666edab978077ed11c48db008cc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BD=A9=E8=BE=95=E5=8D=81=E5=9B=9B?= Date: Mon, 20 Jul 2026 18:46:05 +0800 Subject: [PATCH 2/4] docs: add Simplified Chinese README --- README.md | 2 + README.zh-CN.md | 121 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 README.zh-CN.md diff --git a/README.md b/README.md index cc3e539..5a91399 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Astrolabe Runtime for iOS +English | [简体中文](README.zh-CN.md) + Astrolabe Runtime for iOS exposes UIKit and Core Animation inspection data to the Astrolabe Host during development. Runtime activation is compiled out of Release builds. diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 0000000..6dad56f --- /dev/null +++ b/README.zh-CN.md @@ -0,0 +1,121 @@ +# Astrolabe Runtime for iOS + +[English](README.md) | 简体中文 + +Astrolabe Runtime for iOS 在开发期间向 Astrolabe Host 暴露 UIKit 和 Core Animation +检查数据。Release 构建不会编译 Runtime 激活逻辑。 + +当前 Package 版本:`1.0.0`。 + +## 环境要求 + +- iOS 15 或更高版本 +- 支持 Swift 5.9 或更高版本的 Xcode +- Astrolabe Host 工具 + +## 安装 + +在 Xcode 中添加以下 Package: + +```text +https://github.com/regulusleow/astrolabe-runtime-ios +``` + +将 `AstrolabeRuntime` Product 链接到 App Target。 + +## 接入 + +在当前 Scene 生命周期中启动和停止 Runtime。 + +Objective-C: + +```objc +@import AstrolabeRuntime; + +- (void)sceneDidBecomeActive:(UIScene *)scene { +#if DEBUG + [ASTRuntime start]; +#endif +} + +- (void)sceneDidEnterBackground:(UIScene *)scene { +#if DEBUG + [ASTRuntime stop]; +#endif +} +``` + +Swift: + +```swift +func sceneDidBecomeActive(_ scene: UIScene) { +#if DEBUG + ASTRuntime.start() +#endif +} + +func sceneDidEnterBackground(_ scene: UIScene) { +#if DEBUG + ASTRuntime.stop() +#endif +} +``` + +Objective-C 代码需要处理启动失败时,可以使用 `startWithCompletion:`。需要自定义端口、请求限制或 +检查授权时,可以使用 `UIKitRuntimeLifecycle`。 + +## 能力 + +- 发现模拟器和已配对 USB 设备上的 Runtime。 +- 采集 UIKit 和 Core Animation 层级。 +- 获取 Frame、可见性、无障碍、文本、字体排版、颜色、边框、阴影、控件状态、图片和 Auto Layout + 元数据。 +- 通过稳定的不透明标识符查询节点详情。 +- 对受支持的文本、字体、颜色、Alpha、边框、圆角、阴影和已标识的 Auto Layout 约束值应用白名单内 + 的内存临时表现补丁。 + +补丁不会修改源代码、App 二进制文件、业务模型或持久化存储。补丁在 Host 重新连接后仍然有效,并在 +Runtime 停止或 App 进程退出时失效。 + +## 架构 + +| Target | 职责 | +| --- | --- | +| `AstrolabeRuntimeCore` | iOS SDK 使用的协议服务、路由、Session、Transport、节点注册表和平台无关补丁协调。 | +| `AstrolabeRuntimeUIKit` | UIKit 和 Core Animation 采集、属性映射、Auto Layout、无障碍信息及白名单内的修改操作。 | +| `AstrolabeRuntime` | 公共 `ASTRuntime` 外观接口、SDK 元数据、生命周期和依赖组装。 | +| `AstrolabeRuntimeObjC` | 最小化的 Objective-C Runtime 元数据适配器。 | + +该 Package 实现 `astrolabe-protocol` 定义的平台无关 Wire Protocol。iOS 特有的数据采集、映射、 +生命周期和端口选择保留在本仓库中。 + +## 开发 + +运行兼容 macOS 的测试和 Release 构建: + +```bash +npm ci +npm test +swift test --parallel +swift build -c release --product AstrolabeRuntime +``` + +在 iOS 模拟器中运行 UIKit 和集成测试: + +```bash +xcodebuild \ + -scheme astrolabe-runtime-ios \ + -destination 'platform=iOS Simulator,name=iPhone 16 Pro,OS=latest' \ + test +``` + +## 安全性 + +Runtime 仅监听 Loopback TCP Endpoint,并且在 Release 构建中不可用。USB Transport 同样依赖 +usbmux 强制执行的主机与设备配对机制。请勿分发包含敏感运行时数据的 Debug 构建。 + +临时修改仅限 Runtime 自身维护的补丁能力清单中声明的属性。Runtime 不会暴露任意 Selector、方法调用或业务操作。 + +## 许可证 + +Astrolabe Runtime for iOS 使用 [Apache License 2.0](LICENSE) 许可。 From bccbfcd5789a68f8a846d5d1e537105de0836750 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BD=A9=E8=BE=95=E5=8D=81=E5=9B=9B?= Date: Mon, 27 Jul 2026 23:17:24 +0800 Subject: [PATCH 3/4] chore: prepare 2.0.0 release --- Package.swift | 2 +- README.md | 2 +- README.zh-CN.md | 2 +- .../AstrolabeRuntime/AstrolabeRuntime.swift | 2 +- docs/ios-runtime-strategy.md | 2 +- package-lock.json | 4 +-- package.json | 2 +- scripts/versioning.mjs | 32 +++++++++++++++++-- test/versioning.test.mjs | 11 +++++++ 9 files changed, 49 insertions(+), 10 deletions(-) diff --git a/Package.swift b/Package.swift index 2660375..c1df1a2 100644 --- a/Package.swift +++ b/Package.swift @@ -17,7 +17,7 @@ let package = Package( dependencies: [ .package( url: "https://github.com/regulusleow/astrolabe-protocol.git", - exact: "1.0.0" + exact: "2.0.0" ) ], targets: [ diff --git a/README.md b/README.md index 5a91399..e4d1984 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Astrolabe Runtime for iOS exposes UIKit and Core Animation inspection data to the Astrolabe Host during development. Runtime activation is compiled out of Release builds. -Current package release: `1.0.0`. +Current package release: `2.0.0`. ## Requirements diff --git a/README.zh-CN.md b/README.zh-CN.md index 6dad56f..72471b6 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -5,7 +5,7 @@ Astrolabe Runtime for iOS 在开发期间向 Astrolabe Host 暴露 UIKit 和 Core Animation 检查数据。Release 构建不会编译 Runtime 激活逻辑。 -当前 Package 版本:`1.0.0`。 +当前 Package 版本:`2.0.0`。 ## 环境要求 diff --git a/Sources/AstrolabeRuntime/AstrolabeRuntime.swift b/Sources/AstrolabeRuntime/AstrolabeRuntime.swift index d3644bb..dac695f 100644 --- a/Sources/AstrolabeRuntime/AstrolabeRuntime.swift +++ b/Sources/AstrolabeRuntime/AstrolabeRuntime.swift @@ -9,7 +9,7 @@ import AstrolabeProtocol public enum AstrolabeRuntimeSDK { /// Release version advertised by the embedded runtime. - public static let runtimeVersion = "1.0.0" + public static let runtimeVersion = "2.0.0" public static var protocolVersion: RuntimeProtocolVersion { .v2 diff --git a/docs/ios-runtime-strategy.md b/docs/ios-runtime-strategy.md index c236bd6..b82195f 100644 --- a/docs/ios-runtime-strategy.md +++ b/docs/ios-runtime-strategy.md @@ -284,7 +284,7 @@ Completed runtime migration steps: 1. The platform-neutral wire contract, JSON Schemas, and fixtures live in the independent `astrolabe-protocol` repository. -2. Runtime and Host both lock `AstrolabeProtocol` to package version `1.0.0`. +2. Runtime and Host both lock `AstrolabeProtocol` to package version `2.0.0`. 3. The Runtime package no longer publishes or maintains a duplicate protocol target. 4. Handshake, App information, hierarchy, node detail, simulator TCP, and USB diff --git a/package-lock.json b/package-lock.json index 32c8e5f..6bf2c0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "astrolabe-runtime-ios", - "version": "1.0.0", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "astrolabe-runtime-ios", - "version": "1.0.0" + "version": "2.0.0" } } } diff --git a/package.json b/package.json index 47dcc73..e21aca4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "astrolabe-runtime-ios", - "version": "1.0.0", + "version": "2.0.0", "private": true, "type": "module", "scripts": { diff --git a/scripts/versioning.mjs b/scripts/versioning.mjs index 8c0177c..10fa42d 100644 --- a/scripts/versioning.mjs +++ b/scripts/versioning.mjs @@ -6,6 +6,7 @@ const runtimeMetadataPath = "Sources/AstrolabeRuntime/AstrolabeRuntime.swift"; export const versionedPaths = Object.freeze([ runtimeMetadataPath, "README.md", + "README.zh-CN.md", "package-lock.json", "package.json" ]); @@ -13,6 +14,8 @@ export const versionedPaths = Object.freeze([ const releaseVersionPattern = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/; const swiftVersionPattern = /static let runtimeVersion = "(\d+\.\d+\.\d+)"/g; const documentationVersionPattern = /Current package release: `(\d+\.\d+\.\d+)`/g; +const localizedDocumentationVersionPattern = + /^(.*Package.*`)(\d+\.\d+\.\d+)(`.*)$/gm; export function assertReleaseVersion(version) { if (!releaseVersionPattern.test(version)) { @@ -72,6 +75,11 @@ export function synchronizeRepositoryVersion(projectRoot, version) { join(projectRoot, "README.md"), documentationVersionPattern, `Current package release: \`${version}\`` + ), + textVersionUpdate( + join(projectRoot, "README.zh-CN.md"), + localizedDocumentationVersionPattern, + `$1${version}$3` ) ]; updates.forEach(({ path, content }) => writeFileSync(path, content)); @@ -106,6 +114,14 @@ export function versionConsistencyIssues(projectRoot) { expectedVersion, issues ); + inspectTextVersion( + join(projectRoot, "README.zh-CN.md"), + "README.zh-CN.md", + localizedDocumentationVersionPattern, + expectedVersion, + issues, + 2 + ); return issues; } @@ -145,13 +161,25 @@ function inspectJSONVersion(path, displayPath, expectedVersion, issues) { ); } -function inspectTextVersion(path, displayPath, pattern, expectedVersion, issues) { +function inspectTextVersion( + path, + displayPath, + pattern, + expectedVersion, + issues, + captureIndex = 1 +) { const matches = [...readFileSync(path, "utf8").matchAll(pattern)]; if (matches.length !== 1) { issues.push(`${displayPath}: expected one version field, found ${matches.length}`); return; } - appendVersionIssue(displayPath, matches[0][1], expectedVersion, issues); + appendVersionIssue( + displayPath, + matches[0][captureIndex], + expectedVersion, + issues + ); } function appendVersionIssue(path, actualVersion, expectedVersion, issues) { diff --git a/test/versioning.test.mjs b/test/versioning.test.mjs index 21599e2..3d44d94 100644 --- a/test/versioning.test.mjs +++ b/test/versioning.test.mjs @@ -36,6 +36,10 @@ test("version synchronization updates runtime release metadata", () => { readFileSync(join(fixture.root, fixture.readmePath), "utf8"), /Current package release: `2\.0\.0`/ ); + assert.match( + readFileSync(join(fixture.root, fixture.chineseReadmePath), "utf8"), + /Package.*`2\.0\.0`/ + ); assert.deepEqual(versionConsistencyIssues(fixture.root), []); } finally { fixture.cleanup(); @@ -138,9 +142,11 @@ function makeVersionFixture() { const root = mkdtempSync(join(tmpdir(), "astrolabe-runtime-version-test-")); const swiftPath = "Sources/AstrolabeRuntime/AstrolabeRuntime.swift"; const readmePath = "README.md"; + const chineseReadmePath = "README.zh-CN.md"; const expectedVersionedPaths = [ swiftPath, readmePath, + chineseReadmePath, "package-lock.json", "package.json" ]; @@ -166,6 +172,10 @@ function makeVersionFixture() { join(root, readmePath), "Current package release: `0.1.3`.\n" ); + writeFileSync( + join(root, chineseReadmePath), + "Localized Package release: `0.1.3`.\n" + ); writeFileSync( join(root, "Package.swift"), '.package(url: "https://github.com/regulusleow/astrolabe-protocol.git", exact: "2.0.0")\n' @@ -175,6 +185,7 @@ function makeVersionFixture() { root, swiftPath, readmePath, + chineseReadmePath, expectedVersionedPaths, cleanup: () => rmSync(root, { recursive: true, force: true }) }; From cdc51966e070ea65cb7dbd80032c7b004ddb1bc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BD=A9=E8=BE=95=E5=8D=81=E5=9B=9B?= Date: Mon, 27 Jul 2026 23:22:10 +0800 Subject: [PATCH 4/4] fix: provision iOS Simulator runtime in CI --- .github/workflows/ci.yml | 6 +-- scripts/run-ios-simulator-tests.sh | 82 ++++++++++++++++++++++++++++++ test/ci-workflow.test.mjs | 17 +++++++ 3 files changed, 100 insertions(+), 5 deletions(-) create mode 100755 scripts/run-ios-simulator-tests.sh create mode 100644 test/ci-workflow.test.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a459654..671edf9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,8 +57,4 @@ jobs: run: xcodebuild -version - name: Run iOS Simulator tests - run: | - xcodebuild \ - -scheme astrolabe-runtime-ios \ - -destination 'platform=iOS Simulator,name=iPhone 16 Pro,OS=latest' \ - test + run: scripts/run-ios-simulator-tests.sh diff --git a/scripts/run-ios-simulator-tests.sh b/scripts/run-ios-simulator-tests.sh new file mode 100755 index 0000000..d6f48d3 --- /dev/null +++ b/scripts/run-ios-simulator-tests.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash + +set -euo pipefail + +select_runtime() { + xcrun simctl list runtimes --json | python3 -c ' +import json +import re +import sys + +runtimes = [ + runtime + for runtime in json.load(sys.stdin).get("runtimes", []) + if runtime.get("isAvailable") + and runtime.get("identifier", "").startswith( + "com.apple.CoreSimulator.SimRuntime.iOS-" + ) +] +if runtimes: + latest = max( + runtimes, + key=lambda runtime: tuple( + int(component) + for component in re.findall(r"\d+", runtime.get("version", "0")) + ), + ) + print(latest["identifier"]) +' +} + +select_device_type() { + xcrun simctl list devicetypes --json | python3 -c ' +import json +import sys + +device_types = [ + device_type + for device_type in json.load(sys.stdin).get("devicetypes", []) + if device_type.get("name", "").startswith("iPhone") +] +preferred = next( + ( + device_type + for device_type in device_types + if device_type.get("name") == "iPhone 16 Pro" + ), + None, +) +selected = preferred or (device_types[0] if device_types else None) +if selected: + print(selected["identifier"]) +' +} + +runtime_identifier="$(select_runtime)" +if [[ -z "$runtime_identifier" ]]; then + xcodebuild -downloadPlatform iOS + runtime_identifier="$(select_runtime)" +fi + +device_type_identifier="$(select_device_type)" +if [[ -z "$runtime_identifier" || -z "$device_type_identifier" ]]; then + printf 'Unable to resolve an available iOS Simulator runtime and device type\n' >&2 + exit 1 +fi + +device_udid="$( + xcrun simctl create \ + Astrolabe-CI \ + "$device_type_identifier" \ + "$runtime_identifier" +)" + +cleanup() { + xcrun simctl delete "$device_udid" >/dev/null 2>&1 || true +} +trap cleanup EXIT + +xcodebuild \ + -scheme astrolabe-runtime-ios \ + -destination "platform=iOS Simulator,id=${device_udid}" \ + test diff --git a/test/ci-workflow.test.mjs b/test/ci-workflow.test.mjs new file mode 100644 index 0000000..8ed0663 --- /dev/null +++ b/test/ci-workflow.test.mjs @@ -0,0 +1,17 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import test from "node:test"; + +test("iOS Simulator CI provisions and targets an available runtime", async () => { + const [workflow, runner] = await Promise.all([ + readFile(".github/workflows/ci.yml", "utf8"), + readFile("scripts/run-ios-simulator-tests.sh", "utf8") + ]); + + assert.match(workflow, /scripts\/run-ios-simulator-tests\.sh/); + assert.doesNotMatch(workflow, /name=iPhone/); + assert.match(runner, /xcodebuild -downloadPlatform iOS/); + assert.match(runner, /xcrun simctl create/); + assert.match(runner, /platform=iOS Simulator,id=\$\{device_udid\}/); + assert.match(runner, /xcrun simctl delete/); +});