expo-harmony-toolkit 1.5.2 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -1,7 +1,7 @@
1
1
  <div align="center">
2
2
  <h1>Expo Harmony Toolkit</h1>
3
3
  <p><strong>A HarmonyOS migration, admission, and UI-stack build toolkit for Managed/CNG Expo projects.</strong></p>
4
- <p>One validated UI-stack matrix, explicit dependency admission rules, managed Harmony sidecar scaffolding, and a toolkit-driven <code>doctor → init → bundle → build-hap</code> path.</p>
4
+ <p>One verified UI-stack matrix, additive preview/experimental capability tiers, managed Harmony sidecar scaffolding, and a toolkit-driven <code>doctor → init → bundle → build-hap</code> path.</p>
5
5
  <p>
6
6
  <a href="./README.md">简体中文</a> ·
7
7
  <a href="./README.en.md">English</a>
@@ -9,13 +9,14 @@
9
9
  <p>
10
10
  <a href="https://github.com/BlackishGreen33/Expo-Harmony-Toolkit/actions/workflows/ci.yml"><img alt="Checks" src="https://img.shields.io/badge/checks-passing-16a34a?style=flat-square&logo=githubactions&logoColor=white"></a>
11
11
  <a href="./LICENSE"><img alt="License" src="https://img.shields.io/badge/license-MIT-0f766e?style=flat-square"></a>
12
- <a href="https://github.com/BlackishGreen33/Expo-Harmony-Toolkit/releases"><img alt="Version" src="https://img.shields.io/badge/version-v1.5.2-111827?style=flat-square"></a>
12
+ <a href="https://github.com/BlackishGreen33/Expo-Harmony-Toolkit/releases"><img alt="Version" src="https://img.shields.io/badge/version-v1.7.0-111827?style=flat-square"></a>
13
13
  <a href="./docs/support-matrix.md"><img alt="Matrix" src="https://img.shields.io/badge/matrix-expo55--rnoh082--ui--stack-2563eb?style=flat-square"></a>
14
14
  <img alt="Input" src="https://img.shields.io/badge/input-Managed%2FCNG-059669?style=flat-square">
15
15
  </p>
16
16
  <p>
17
17
  <a href="./docs/support-matrix.md">Support Matrix</a> ·
18
18
  <a href="./docs/cli-build.md">CLI Build Guide</a> ·
19
+ <a href="./docs/official-native-capabilities-sample.md">Official Native Capabilities Sample</a> ·
19
20
  <a href="./docs/official-ui-stack-sample.md">Official UI Stack Sample</a> ·
20
21
  <a href="./docs/npm-release.md">npm Release Notes</a> ·
21
22
  <a href="./docs/roadmap.md">Roadmap</a>
@@ -23,14 +24,14 @@
23
24
  </div>
24
25
 
25
26
  > [!IMPORTANT]
26
- > `v1.5.2` continues to make one formal public promise only: `expo55-rnoh082-ui-stack`. This is not a claim that arbitrary Expo applications can be published to HarmonyOS unchanged.
27
+ > `v1.7` keeps the `verified + preview + experimental` model and moves `expo-location` and `expo-camera` into `preview`. The public path is still "Core Expo Full Coverage first, long-tail third-party native modules second"; this is still not a claim that arbitrary Expo apps can be published to HarmonyOS unchanged.
27
28
 
28
29
  > [!TIP]
29
30
  > The two validated `@react-native-oh-tpl/*` adapters in the public matrix are currently consumed via exact Git URLs and commits. For repository development and the official UI-stack sample, prefer `pnpm install --ignore-scripts` so adapter prepare hooks do not fail on private upstream resources.
30
31
 
31
32
  ## Overview
32
33
 
33
- `expo-harmony-toolkit` provides a constrained, verifiable Expo-to-Harmony toolchain:
34
+ `expo-harmony-toolkit` provides a constrained, verifiable Expo-to-Harmony toolchain and now starts exposing preview-tier native capability bridges:
34
35
 
35
36
  - Expo config plugin entrypoint `app.plugin.js`
36
37
  - `expo-harmony doctor`
@@ -46,23 +47,25 @@
46
47
 
47
48
  | Item | Status |
48
49
  | --- | --- |
49
- | Current version | `v1.5.2` |
50
- | Public matrix | `expo55-rnoh082-ui-stack` |
50
+ | Current version | `v1.7.0` |
51
+ | Support model | `verified + preview + experimental` |
52
+ | Public `verified` matrix | `expo55-rnoh082-ui-stack` |
51
53
  | Supported input | Managed/CNG Expo projects |
52
- | Validated JS/UI capabilities | `expo-router`, `expo-linking`, `expo-constants`, `react-native-reanimated`, `react-native-svg` |
54
+ | `verified` JS/UI capabilities | `expo-router`, `expo-linking`, `expo-constants`, `react-native-reanimated`, `react-native-svg` |
55
+ | `preview` native capabilities | `expo-file-system`, `expo-image-picker`, `expo-location`, `expo-camera` |
56
+ | `experimental` capabilities | `expo-notifications`, `react-native-gesture-handler` |
53
57
  | Build path | `doctor -> init -> bundle -> build-hap` |
54
58
  | Primary sample | `examples/official-ui-stack-sample` |
59
+ | Preview sample | `examples/official-native-capabilities-sample` |
55
60
  | Regression baselines | `examples/official-app-shell-sample`, `examples/official-minimal-sample` |
56
61
 
57
62
  <details>
58
- <summary><strong>Currently out of scope</strong></summary>
63
+ <summary><strong>Still outside the verified public promise</strong></summary>
59
64
 
60
65
  - bare Expo
61
- - `expo-image-picker`
62
- - `expo-file-system`
63
- - `expo-location`
64
- - `expo-camera`
66
+ - `expo-file-system`, `expo-image-picker`, `expo-location`, and `expo-camera` remain `preview`
65
67
  - `expo-notifications`
68
+ - `react-native-gesture-handler`
66
69
  - multiple public matrices
67
70
 
68
71
  </details>
@@ -130,6 +133,7 @@ Notes:
130
133
  cd /path/to/app
131
134
  pnpm exec expo-harmony doctor --project-root .
132
135
  pnpm exec expo-harmony doctor --project-root . --strict
136
+ pnpm exec expo-harmony doctor --project-root . --target-tier preview
133
137
  ```
134
138
 
135
139
  2. Generate or refresh the managed Harmony sidecar:
@@ -157,24 +161,20 @@ pnpm exec expo-harmony build-hap --mode release
157
161
  Common decision points:
158
162
 
159
163
  - Want to know whether the current project still matches the public matrix: run `doctor --strict`
164
+ - Want to know whether the project at least falls into preview / experimental tiers: run `doctor --target-tier preview` or `doctor --target-tier experimental`
160
165
  - Changed dependencies, Expo config, or plugin wiring: run `sync-template`
161
166
  - Only want to verify JavaScript/UI portability: run `bundle`
162
167
  - About to open DevEco Studio or build a HAP locally: run `env` first
163
168
 
164
169
  ## Support Matrix
165
170
 
166
- `v1.5.2` stays on one public matrix: `expo55-rnoh082-ui-stack`.
171
+ `v1.7` keeps tiered support:
167
172
 
168
- - Expo SDK: `55`
169
- - React: `19.1.1`
170
- - React Native: `0.82.1`
171
- - RNOH and `@react-native-oh/react-native-harmony-cli`: `0.82.18`
172
- - App Shell packages: `expo-router`, `expo-linking`, `expo-constants`
173
- - UI stack packages: `react-native-reanimated`, `react-native-svg`
174
- - Harmony adapters: the matching `@react-native-oh-tpl/*` exact Git specifiers
175
- - Native identifier: at least `android.package` or `ios.bundleIdentifier`
173
+ - `verified`: the only public matrix remains `expo55-rnoh082-ui-stack`
174
+ - `preview`: `expo-file-system`, `expo-image-picker`, `expo-location`, `expo-camera`
175
+ - `experimental`: `expo-notifications`, `react-native-gesture-handler`
176
176
 
177
- `react-native-gesture-handler` is no longer part of the public matrix. It remains a manual exploration path until the current `@react-native-oh-tpl/react-native-gesture-handler` and `@react-native-oh/react-native-harmony@0.82.18` runtime pairing passes on-device validation.
177
+ `doctor --strict` still means `verified` only. `doctor --target-tier preview` allows the same runtime matrix plus preview-tier capabilities, but that does not promote them into the formal public promise.
178
178
 
179
179
  See [docs/support-matrix.md](./docs/support-matrix.md) for the full allowlist, pairing rules, exact specifiers, issue codes, and release gates.
180
180
 
@@ -182,6 +182,8 @@ See [docs/support-matrix.md](./docs/support-matrix.md) for the full allowlist, p
182
182
 
183
183
  - `examples/official-ui-stack-sample`
184
184
  The primary public sample for `v1.5.0`, covering router, linking, constants, SVG, reanimated, and Harmony sidecar build flow.
185
+ - `examples/official-native-capabilities-sample`
186
+ The `v1.7` Batch A+B preview sample, covering `expo-file-system`, `expo-image-picker`, `expo-location`, and `expo-camera` bridge, permission, bundle, and debug-build validation.
185
187
  - `examples/official-app-shell-sample`
186
188
  The `v1.1` App Shell regression baseline that protects router behavior while UI-stack support is finalized.
187
189
  - `examples/official-minimal-sample`
@@ -189,6 +191,7 @@ See [docs/support-matrix.md](./docs/support-matrix.md) for the full allowlist, p
189
191
 
190
192
  See:
191
193
 
194
+ - [Official Native Capabilities Sample Guide](./docs/official-native-capabilities-sample.md)
192
195
  - [Official UI Stack Sample Guide](./docs/official-ui-stack-sample.md)
193
196
  - [Official App Shell Sample Guide](./docs/official-app-shell-sample.md)
194
197
  - [Official Minimal Sample Guide](./docs/official-minimal-sample.md)
@@ -199,6 +202,7 @@ See:
199
202
  | --- | --- |
200
203
  | `expo-harmony doctor` | Inspect Expo config and dependencies and produce a migration report |
201
204
  | `expo-harmony doctor --strict` | Run the formal matrix admission gate |
205
+ | `expo-harmony doctor --target-tier preview` | Evaluate whether the project fits at least the preview support tier |
202
206
  | `expo-harmony init` | Generate Harmony sidecar files, autolinking artifacts, metadata, and package scripts |
203
207
  | `expo-harmony sync-template` | Reapply managed templates and report drift |
204
208
  | `expo-harmony env` | Check the local DevEco / hvigor / hdc / signing environment |
@@ -242,6 +246,7 @@ Manual Harmony acceptance still requires:
242
246
  - pressing the home-screen motion rail triggers visible animation
243
247
  - routing still works after the animation completes
244
248
  - `Build Debug Hap(s)` succeeds
249
+ - `official-native-capabilities-sample` at least proves Batch A+B preview route bundling, generated Harmony permissions, and the debug build path
245
250
 
246
251
  See [docs/npm-release.md](./docs/npm-release.md) and [docs/signing-and-release.md](./docs/signing-and-release.md).
247
252
 
@@ -249,11 +254,13 @@ See [docs/npm-release.md](./docs/npm-release.md) and [docs/signing-and-release.m
249
254
 
250
255
  - [Support Matrix](./docs/support-matrix.md)
251
256
  - [CLI Build Guide](./docs/cli-build.md)
257
+ - [Official Native Capabilities Sample Guide](./docs/official-native-capabilities-sample.md)
252
258
  - [Official UI Stack Sample Guide](./docs/official-ui-stack-sample.md)
253
259
  - [Official App Shell Sample Guide](./docs/official-app-shell-sample.md)
254
260
  - [Official Minimal Sample Guide](./docs/official-minimal-sample.md)
255
261
  - [npm Release Notes](./docs/npm-release.md)
256
262
  - [Signing and Release Notes](./docs/signing-and-release.md)
263
+ - [v1.7.0 Acceptance Log (In Progress)](./docs/v1.7.0-acceptance.md)
257
264
  - [Roadmap](./docs/roadmap.md)
258
265
 
259
266
  ## License
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  <div align="center">
2
2
  <h1>Expo Harmony Toolkit</h1>
3
3
  <p><strong>面向 Managed/CNG Expo 项目的 HarmonyOS 迁移、准入检查与 UI-stack 构建工具链。</strong></p>
4
- <p>One validated UI-stack matrix, explicit dependency admission rules, managed Harmony sidecar scaffolding, and a toolkit-driven <code>doctor → init → bundle → build-hap</code> path.</p>
4
+ <p>One verified UI-stack matrix, additive preview/experimental capability tiers, managed Harmony sidecar scaffolding, and a toolkit-driven <code>doctor → init → bundle → build-hap</code> path.</p>
5
5
  <p>
6
6
  <a href="./README.md">简体中文</a> ·
7
7
  <a href="./README.en.md">English</a>
@@ -9,13 +9,14 @@
9
9
  <p>
10
10
  <a href="https://github.com/BlackishGreen33/Expo-Harmony-Toolkit/actions/workflows/ci.yml"><img alt="Checks" src="https://img.shields.io/badge/checks-passing-16a34a?style=flat-square&logo=githubactions&logoColor=white"></a>
11
11
  <a href="./LICENSE"><img alt="License" src="https://img.shields.io/badge/license-MIT-0f766e?style=flat-square"></a>
12
- <a href="https://github.com/BlackishGreen33/Expo-Harmony-Toolkit/releases"><img alt="Version" src="https://img.shields.io/badge/version-v1.5.2-111827?style=flat-square"></a>
12
+ <a href="https://github.com/BlackishGreen33/Expo-Harmony-Toolkit/releases"><img alt="Version" src="https://img.shields.io/badge/version-v1.7.0-111827?style=flat-square"></a>
13
13
  <a href="./docs/support-matrix.md"><img alt="Matrix" src="https://img.shields.io/badge/matrix-expo55--rnoh082--ui--stack-2563eb?style=flat-square"></a>
14
14
  <img alt="Input" src="https://img.shields.io/badge/input-Managed%2FCNG-059669?style=flat-square">
15
15
  </p>
16
16
  <p>
17
17
  <a href="./docs/support-matrix.md">支持矩阵</a> ·
18
18
  <a href="./docs/cli-build.md">CLI 构建指南</a> ·
19
+ <a href="./docs/official-native-capabilities-sample.md">官方 Native Capabilities Sample</a> ·
19
20
  <a href="./docs/official-ui-stack-sample.md">官方 UI Stack Sample</a> ·
20
21
  <a href="./docs/npm-release.md">npm 发布说明</a> ·
21
22
  <a href="./docs/roadmap.md">路线图</a>
@@ -23,14 +24,14 @@
23
24
  </div>
24
25
 
25
26
  > [!IMPORTANT]
26
- > `v1.5.2` 继续只对 `expo55-rnoh082-ui-stack` 做正式公开承诺。这不是“任意 Expo 项目都能原样发布到 HarmonyOS”的声明,而是对一条受限、可验证矩阵的稳定承诺。
27
+ > `v1.7` 延续 `verified + preview + experimental` 三层支持模型,并把 `expo-location`、`expo-camera` 推进到 `preview`。当前对外路线仍然是先做到 `Core Expo Full Coverage`,再去覆盖长尾第三方 native module;这依然不是“任意 Expo 项目都能原样发布到 HarmonyOS”的声明。
27
28
 
28
29
  > [!TIP]
29
30
  > 由于当前公开矩阵内的两套 `@react-native-oh-tpl/*` adapter 依赖以 Git URL + exact commit 形式接入,仓库开发和官方 UI-stack sample 推荐使用 `pnpm install --ignore-scripts`,避免 Git adapter 在 prepare 阶段拉取私有资源而中断安装。
30
31
 
31
32
  ## 概览
32
33
 
33
- `expo-harmony-toolkit` 提供一条围绕 Expo 到 HarmonyOS 迁移的受限、可验证工具链:
34
+ `expo-harmony-toolkit` 提供一条围绕 Expo 到 HarmonyOS 迁移的受限、可验证工具链,并开始公开 preview 层的原生能力桥接骨架:
34
35
 
35
36
  - Expo config plugin 根入口 `app.plugin.js`
36
37
  - `expo-harmony doctor`
@@ -46,23 +47,25 @@
46
47
 
47
48
  | 项目 | 说明 |
48
49
  | --- | --- |
49
- | 当前版本 | `v1.5.2` |
50
- | 唯一公开矩阵 | `expo55-rnoh082-ui-stack` |
50
+ | 当前版本 | `v1.7.0` |
51
+ | 支持模型 | `verified + preview + experimental` |
52
+ | 唯一 `verified` 公开矩阵 | `expo55-rnoh082-ui-stack` |
51
53
  | 输入范围 | Managed/CNG Expo 项目 |
52
- | 已验证 JS/UI 能力 | `expo-router`、`expo-linking`、`expo-constants`、`react-native-reanimated`、`react-native-svg` |
54
+ | `verified` JS/UI 能力 | `expo-router`、`expo-linking`、`expo-constants`、`react-native-reanimated`、`react-native-svg` |
55
+ | `preview` 原生能力 | `expo-file-system`、`expo-image-picker`、`expo-location`、`expo-camera` |
56
+ | `experimental` 能力 | `expo-notifications`、`react-native-gesture-handler` |
53
57
  | 构建链 | `doctor -> init -> bundle -> build-hap` |
54
58
  | 主 sample | `examples/official-ui-stack-sample` |
59
+ | preview sample | `examples/official-native-capabilities-sample` |
55
60
  | 回归基线 | `examples/official-app-shell-sample`、`examples/official-minimal-sample` |
56
61
 
57
62
  <details>
58
- <summary><strong>当前不在承诺范围</strong></summary>
63
+ <summary><strong>当前仍不在 verified 正式承诺范围</strong></summary>
59
64
 
60
65
  - bare Expo
61
- - `expo-image-picker`
62
- - `expo-file-system`
63
- - `expo-location`
64
- - `expo-camera`
66
+ - `expo-file-system`、`expo-image-picker`、`expo-location`、`expo-camera` 仍只属于 `preview`
65
67
  - `expo-notifications`
68
+ - `react-native-gesture-handler`
66
69
  - 多矩阵并行支持
67
70
 
68
71
  </details>
@@ -130,6 +133,7 @@ pnpm install --ignore-scripts
130
133
  cd /path/to/app
131
134
  pnpm exec expo-harmony doctor --project-root .
132
135
  pnpm exec expo-harmony doctor --project-root . --strict
136
+ pnpm exec expo-harmony doctor --project-root . --target-tier preview
133
137
  ```
134
138
 
135
139
  2. 生成或刷新受管 Harmony sidecar:
@@ -157,24 +161,20 @@ pnpm exec expo-harmony build-hap --mode release
157
161
  常见使用判断:
158
162
 
159
163
  - 想知道当前项目是否还在公开矩阵里:跑 `doctor --strict`
164
+ - 想知道项目是否只落在 preview / experimental:跑 `doctor --target-tier preview` 或 `doctor --target-tier experimental`
160
165
  - 刚改过依赖、Expo 配置或插件:先跑 `sync-template`
161
166
  - 只想验证 JS/UI 侧是否能打包:跑 `bundle`
162
167
  - 准备进 DevEco Studio 或本机构建 HAP:先跑 `env`
163
168
 
164
169
  ## 支持矩阵
165
170
 
166
- `v1.5.2` 继续坚持单矩阵路线:`expo55-rnoh082-ui-stack`。
171
+ `v1.7` 继续采用支持分层:
167
172
 
168
- - Expo SDK:`55`
169
- - React:`19.1.1`
170
- - React Native:`0.82.1`
171
- - RNOH / `@react-native-oh/react-native-harmony-cli`:`0.82.18`
172
- - App Shell 依赖:`expo-router`、`expo-linking`、`expo-constants`
173
- - UI stack 依赖:`react-native-reanimated`、`react-native-svg`
174
- - Harmony adapter:对应两项 `@react-native-oh-tpl/*` exact Git specifier
175
- - 原生标识:至少配置 `android.package` 或 `ios.bundleIdentifier`
173
+ - `verified`:唯一公开矩阵仍是 `expo55-rnoh082-ui-stack`
174
+ - `preview`:`expo-file-system`、`expo-image-picker`、`expo-location`、`expo-camera`
175
+ - `experimental`:`expo-notifications`、`react-native-gesture-handler`
176
176
 
177
- 当前不再把 `react-native-gesture-handler` 放进公开矩阵。它仍可作为手动探索项,但当前 `@react-native-oh-tpl/react-native-gesture-handler` `@react-native-oh/react-native-harmony@0.82.18` 的设备侧 runtime 组合还没有通过正式验收。
177
+ `doctor --strict` 继续只代表 `verified`。`doctor --target-tier preview` 会在同一 runtime matrix 下额外放行 preview 能力,但这不等于它们已经进入正式承诺。
178
178
 
179
179
  完整白名单、配对规则、exact specifier、issue code 与 release gate 见 [docs/support-matrix.md](./docs/support-matrix.md)。
180
180
 
@@ -182,6 +182,8 @@ pnpm exec expo-harmony build-hap --mode release
182
182
 
183
183
  - `examples/official-ui-stack-sample`
184
184
  当前唯一对外主 sample,同时覆盖 router、linking、constants、SVG、reanimated 和 Harmony sidecar 构建链。
185
+ - `examples/official-native-capabilities-sample`
186
+ `v1.7` 的 Batch A+B preview sample,用来承接 `expo-file-system`、`expo-image-picker`、`expo-location`、`expo-camera` 的 bridge、permission、bundle 与 debug build 验收。
185
187
  - `examples/official-app-shell-sample`
186
188
  `v1.1` App Shell 回归基线,用来防止 UI-stack 收口引入 router 退化。
187
189
  - `examples/official-minimal-sample`
@@ -190,6 +192,7 @@ pnpm exec expo-harmony build-hap --mode release
190
192
  详见:
191
193
 
192
194
  - [官方 UI Stack sample 指南](./docs/official-ui-stack-sample.md)
195
+ - [官方 Native Capabilities sample 指南](./docs/official-native-capabilities-sample.md)
193
196
  - [官方 App Shell sample 指南](./docs/official-app-shell-sample.md)
194
197
  - [官方最小 sample 指南](./docs/official-minimal-sample.md)
195
198
 
@@ -199,6 +202,7 @@ pnpm exec expo-harmony build-hap --mode release
199
202
  | --- | --- |
200
203
  | `expo-harmony doctor` | 扫描 Expo 配置与依赖,输出迁移报告 |
201
204
  | `expo-harmony doctor --strict` | 将当前矩阵准入检查作为正式 gate 执行 |
205
+ | `expo-harmony doctor --target-tier preview` | 在同一 runtime matrix 下评估项目是否至少落在 `preview` 能力层 |
202
206
  | `expo-harmony init` | 生成 Harmony sidecar、autolinking 产物、metadata 与 package scripts |
203
207
  | `expo-harmony sync-template` | 再次应用受管模板并检查 drift |
204
208
  | `expo-harmony env` | 检查 DevEco / hvigor / hdc / signing 本地环境 |
@@ -242,6 +246,7 @@ pnpm exec expo-harmony build-hap --mode release
242
246
  - 点击首页 motion rail 后能触发可见动画
243
247
  - 动画完成后路由跳转仍正常
244
248
  - `Build Debug Hap(s)` 成功
249
+ - `official-native-capabilities-sample` 至少完成 Batch A+B preview route 的 bundle、permission 与 debug build 检查
245
250
 
246
251
  详见 [docs/npm-release.md](./docs/npm-release.md) 与 [docs/signing-and-release.md](./docs/signing-and-release.md)。
247
252
 
@@ -249,11 +254,13 @@ pnpm exec expo-harmony build-hap --mode release
249
254
 
250
255
  - [支持矩阵](./docs/support-matrix.md)
251
256
  - [CLI 构建指南](./docs/cli-build.md)
257
+ - [官方 Native Capabilities sample 指南](./docs/official-native-capabilities-sample.md)
252
258
  - [官方 UI Stack sample 指南](./docs/official-ui-stack-sample.md)
253
259
  - [官方 App Shell sample 指南](./docs/official-app-shell-sample.md)
254
260
  - [官方最小 sample 指南](./docs/official-minimal-sample.md)
255
261
  - [npm 发布说明](./docs/npm-release.md)
256
262
  - [签名与 Release 说明](./docs/signing-and-release.md)
263
+ - [v1.7.0 验收记录(进行中)](./docs/v1.7.0-acceptance.md)
257
264
  - [路线图](./docs/roadmap.md)
258
265
 
259
266
  ## License
package/build/cli.js CHANGED
@@ -25,7 +25,8 @@ async function run(argv = process.argv) {
25
25
  .command('doctor')
26
26
  .description('Inspect an Expo project and classify dependencies against the Harmony migration matrix')
27
27
  .option('-p, --project-root <path>', 'path to the Expo project')
28
- .option('--strict', 'return a non-zero exit code when the project falls outside the validated v1.5 matrix')
28
+ .option('--strict', 'return a non-zero exit code when the project falls outside the validated verified matrix')
29
+ .option('--target-tier <tier>', 'evaluate the project against verified, preview, or experimental support tiers')
29
30
  .option('--json', 'print JSON instead of a human-readable report')
30
31
  .option('-o, --output <path>', 'write the JSON report to a file')
31
32
  .action(doctor_1.runDoctorCommand);
@@ -1,7 +1,9 @@
1
+ import { DoctorTargetTier } from '../types';
1
2
  export interface DoctorCommandOptions {
2
3
  projectRoot?: string;
3
4
  strict?: boolean;
4
5
  json?: boolean;
5
6
  output?: string;
7
+ targetTier?: DoctorTargetTier | string;
6
8
  }
7
9
  export declare function runDoctorCommand(options: DoctorCommandOptions): Promise<void>;
@@ -9,7 +9,8 @@ const constants_1 = require("../core/constants");
9
9
  const report_1 = require("../core/report");
10
10
  async function runDoctorCommand(options) {
11
11
  const projectRoot = path_1.default.resolve(options.projectRoot ?? process.cwd());
12
- const report = await (0, report_1.buildDoctorReport)(projectRoot);
12
+ const targetTier = resolveDoctorTargetTier(options.targetTier, Boolean(options.strict));
13
+ const report = await (0, report_1.buildDoctorReport)(projectRoot, { targetTier });
13
14
  if (options.output) {
14
15
  await (0, report_1.writeDoctorReport)(projectRoot, report, path_1.default.resolve(options.output));
15
16
  }
@@ -22,3 +23,15 @@ async function runDoctorCommand(options) {
22
23
  }
23
24
  process.stdout.write((0, report_1.renderDoctorReport)(report) + '\n');
24
25
  }
26
+ function resolveDoctorTargetTier(targetTier, strict) {
27
+ if (strict) {
28
+ return 'verified';
29
+ }
30
+ if (!targetTier) {
31
+ return 'verified';
32
+ }
33
+ if (targetTier === 'verified' || targetTier === 'preview' || targetTier === 'experimental') {
34
+ return targetTier;
35
+ }
36
+ throw new Error(`Unsupported doctor target tier: ${targetTier}`);
37
+ }
@@ -1,6 +1,6 @@
1
1
  export declare const TOOLKIT_PACKAGE_NAME = "expo-harmony-toolkit";
2
2
  export declare const CLI_NAME = "expo-harmony";
3
- export declare const TOOLKIT_VERSION = "1.5.2";
3
+ export declare const TOOLKIT_VERSION = "1.7.0";
4
4
  export declare const TEMPLATE_VERSION = "rnoh-0.82.18";
5
5
  export declare const RNOH_VERSION = "0.82.18";
6
6
  export declare const RNOH_CLI_VERSION = "0.82.18";
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DESIRED_PACKAGE_SCRIPTS = exports.HARMONY_RUNTIME_PRELUDE_RELATIVE_PATH = exports.HARMONY_ROUTER_ENTRY_FILENAME = exports.STRICT_ENV_EXIT_CODE = exports.STRICT_DOCTOR_EXIT_CODE = exports.DEFAULT_HVIGOR_PLUGIN_FILENAME = exports.PREBUILD_METADATA_FILENAME = exports.TOOLKIT_CONFIG_FILENAME = exports.BUILD_REPORT_FILENAME = exports.ENV_REPORT_FILENAME = exports.DOCTOR_REPORT_FILENAME = exports.MANIFEST_FILENAME = exports.GENERATED_SHIMS_DIR = exports.GENERATED_DIR = exports.SUPPORTED_EXPO_SDKS = exports.RNOH_CLI_VERSION = exports.RNOH_VERSION = exports.TEMPLATE_VERSION = exports.TOOLKIT_VERSION = exports.CLI_NAME = exports.TOOLKIT_PACKAGE_NAME = void 0;
4
4
  exports.TOOLKIT_PACKAGE_NAME = 'expo-harmony-toolkit';
5
5
  exports.CLI_NAME = 'expo-harmony';
6
- exports.TOOLKIT_VERSION = '1.5.2';
6
+ exports.TOOLKIT_VERSION = '1.7.0';
7
7
  exports.TEMPLATE_VERSION = 'rnoh-0.82.18';
8
8
  exports.RNOH_VERSION = '0.82.18';
9
9
  exports.RNOH_CLI_VERSION = '0.82.18';
@@ -1,4 +1,6 @@
1
- import { DoctorReport } from '../types';
2
- export declare function buildDoctorReport(projectRoot: string): Promise<DoctorReport>;
1
+ import { DoctorTargetTier, DoctorReport } from '../types';
2
+ export declare function buildDoctorReport(projectRoot: string, options?: {
3
+ targetTier?: DoctorTargetTier;
4
+ }): Promise<DoctorReport>;
3
5
  export declare function writeDoctorReport(projectRoot: string, report: DoctorReport, outputPath?: string): Promise<string>;
4
6
  export declare function renderDoctorReport(report: DoctorReport): string;
@@ -12,18 +12,21 @@ const semver_1 = __importDefault(require("semver"));
12
12
  const constants_1 = require("./constants");
13
13
  const dependencyCatalog_1 = require("../data/dependencyCatalog");
14
14
  const validatedMatrices_1 = require("../data/validatedMatrices");
15
+ const capabilities_1 = require("../data/capabilities");
15
16
  const uiStack_1 = require("../data/uiStack");
16
17
  const metadata_1 = require("./metadata");
17
18
  const project_1 = require("./project");
18
19
  const constants_2 = require("./constants");
19
20
  const DEFAULT_RECORD = {
20
21
  status: 'unknown',
22
+ supportTier: 'unsupported',
21
23
  note: 'This dependency is not in the current compatibility catalog yet.',
22
24
  };
23
- async function buildDoctorReport(projectRoot) {
25
+ async function buildDoctorReport(projectRoot, options = {}) {
24
26
  const loadedProject = await (0, project_1.loadProject)(projectRoot);
25
27
  const expoSdkVersion = (0, project_1.detectExpoSdkVersion)(loadedProject.packageJson);
26
28
  const matrix = validatedMatrices_1.VALIDATED_RELEASE_MATRICES[validatedMatrices_1.DEFAULT_VALIDATED_MATRIX_ID];
29
+ const targetTier = options.targetTier ?? 'verified';
27
30
  const expoPlugins = (0, project_1.collectExpoPlugins)(loadedProject.expoConfig);
28
31
  const expoSchemes = (0, project_1.collectExpoSchemes)(loadedProject.expoConfig);
29
32
  const dependencyRecords = new Map();
@@ -37,7 +40,19 @@ async function buildDoctorReport(projectRoot) {
37
40
  }
38
41
  }
39
42
  const dependencies = [...dependencyRecords.values()].sort((left, right) => left.name.localeCompare(right.name));
40
- const blockingIssues = await collectBlockingIssues(loadedProject.projectRoot, loadedProject.expoConfig, loadedProject.packageJson, expoPlugins, expoSchemes, expoSdkVersion, dependencies, matrix);
43
+ const blockingIssues = await collectBlockingIssues(loadedProject.projectRoot, loadedProject.expoConfig, loadedProject.packageJson, expoPlugins, expoSchemes, expoSdkVersion, dependencies, matrix, targetTier);
44
+ const capabilities = (0, capabilities_1.getCapabilityDefinitionsForProject)(loadedProject.packageJson).map((definition) => ({
45
+ id: definition.id,
46
+ packageName: definition.packageName,
47
+ status: definition.status,
48
+ supportTier: definition.supportTier,
49
+ note: definition.note,
50
+ docsUrl: definition.docsUrl,
51
+ nativePackageNames: [...definition.nativePackageNames],
52
+ harmonyPermissions: [...definition.harmonyPermissions],
53
+ sampleRoute: definition.sampleRoute,
54
+ acceptanceChecklist: [...definition.acceptanceChecklist],
55
+ }));
41
56
  const blockingDependencyNames = new Set(blockingIssues
42
57
  .filter((issue) => issue.code.startsWith('dependency.') && issue.subject)
43
58
  .map((issue) => issue.subject));
@@ -58,6 +73,7 @@ async function buildDoctorReport(projectRoot) {
58
73
  rnohVersion: constants_1.RNOH_VERSION,
59
74
  rnohCliVersion: constants_1.RNOH_CLI_VERSION,
60
75
  expoSdkVersion,
76
+ targetTier,
61
77
  expoConfig: {
62
78
  name: loadedProject.expoConfig.name ?? null,
63
79
  slug: loadedProject.expoConfig.slug ?? null,
@@ -74,6 +90,13 @@ async function buildDoctorReport(projectRoot) {
74
90
  manual: resolvedDependencies.filter((dependency) => dependency.status === 'manual').length,
75
91
  unknown: resolvedDependencies.filter((dependency) => dependency.status === 'unknown').length,
76
92
  },
93
+ supportSummary: {
94
+ verified: resolvedDependencies.filter((dependency) => dependency.supportTier === 'verified').length,
95
+ preview: resolvedDependencies.filter((dependency) => dependency.supportTier === 'preview').length,
96
+ experimental: resolvedDependencies.filter((dependency) => dependency.supportTier === 'experimental').length,
97
+ unsupported: resolvedDependencies.filter((dependency) => dependency.supportTier === 'unsupported').length,
98
+ },
99
+ capabilities,
77
100
  blockingIssues,
78
101
  advisories,
79
102
  warnings,
@@ -92,19 +115,29 @@ function renderDoctorReport(report) {
92
115
  `Config: ${report.appConfigPath ?? 'not found'}`,
93
116
  `Expo SDK: ${report.expoSdkVersion ?? 'unknown'} (recognized ${constants_1.SUPPORTED_EXPO_SDKS.join(', ')})`,
94
117
  `Matrix: ${report.matrixId ?? 'none'}`,
118
+ `Target tier: ${report.targetTier}`,
95
119
  `Eligibility: ${report.eligibility}`,
96
120
  `Schemes: ${report.expoConfig.schemes.join(', ') || 'none'}`,
97
121
  `Plugins: ${report.expoConfig.plugins.join(', ') || 'none'}`,
98
122
  `RNOH template: ${report.templateVersion} / runtime ${report.rnohVersion}`,
99
123
  `Summary: ${report.summary.supported} supported, ${report.summary.manual} manual, ${report.summary.unknown} unknown (${report.summary.total} total)`,
124
+ `Support tiers: ${report.supportSummary.verified} verified, ${report.supportSummary.preview} preview, ${report.supportSummary.experimental} experimental, ${report.supportSummary.unsupported} unsupported`,
100
125
  '',
101
126
  `Dependencies:`,
102
127
  ...report.dependencies.map((dependency) => {
103
128
  const replacement = dependency.replacement ? ` | replacement: ${dependency.replacement}` : '';
104
129
  const blocking = dependency.blocking ? ' | blocking: yes' : '';
105
- return `- [${dependency.status}] ${dependency.name}@${dependency.version} (${dependency.source}) - ${dependency.note}${replacement}${blocking}`;
130
+ return `- [${dependency.status}/${dependency.supportTier}] ${dependency.name}@${dependency.version} (${dependency.source}) - ${dependency.note}${replacement}${blocking}`;
106
131
  }),
107
132
  ];
133
+ if (report.capabilities.length > 0) {
134
+ sections.push('', 'Capabilities:', ...report.capabilities.map((capability) => {
135
+ const permissions = capability.harmonyPermissions.length > 0
136
+ ? ` | permissions: ${capability.harmonyPermissions.join(', ')}`
137
+ : '';
138
+ return `- [${capability.status}/${capability.supportTier}] ${capability.packageName} -> ${capability.nativePackageNames.join(', ') || 'toolkit-managed bridge'} | sample: ${capability.sampleRoute}${permissions}`;
139
+ }));
140
+ }
108
141
  if (report.blockingIssues.length > 0) {
109
142
  sections.push('', 'Blocking issues:', ...report.blockingIssues.map((issue) => `- ${issue.code}: ${issue.message}${issue.subject ? ` (${issue.subject})` : ''}`));
110
143
  }
@@ -123,13 +156,14 @@ function createDependencyRecord(name, version, source) {
123
156
  version,
124
157
  source,
125
158
  status: matrixRecord.status,
159
+ supportTier: matrixRecord.supportTier,
126
160
  blocking: false,
127
161
  note: matrixRecord.note,
128
162
  replacement: matrixRecord.replacement,
129
163
  docsUrl: matrixRecord.docsUrl,
130
164
  };
131
165
  }
132
- async function collectBlockingIssues(projectRoot, expoConfig, packageJson, expoPlugins, expoSchemes, expoSdkVersion, dependencies, matrix) {
166
+ async function collectBlockingIssues(projectRoot, expoConfig, packageJson, expoPlugins, expoSchemes, expoSdkVersion, dependencies, matrix, targetTier) {
133
167
  const issues = [];
134
168
  const dependencyMap = new Map(dependencies.map((dependency) => [dependency.name, dependency]));
135
169
  if (expoSdkVersion !== matrix.expoSdkVersion) {
@@ -167,10 +201,10 @@ async function collectBlockingIssues(projectRoot, expoConfig, packageJson, expoP
167
201
  }
168
202
  }
169
203
  for (const dependency of dependencies) {
170
- if (!matrix.allowedDependencies.includes(dependency.name)) {
204
+ if (!isDependencyAllowedForTargetTier(dependency.name, matrix, targetTier)) {
171
205
  issues.push({
172
206
  code: 'dependency.not_allowed',
173
- message: `${dependency.name} is outside the validated ${matrix.id} allowlist.`,
207
+ message: `${dependency.name} is outside the ${targetTier} support tier for ${matrix.id}.`,
174
208
  subject: dependency.name,
175
209
  });
176
210
  }
@@ -282,6 +316,12 @@ function buildWarnings(expoConfig, expoSdkVersion, dependencies) {
282
316
  if (dependencies.some((dependency) => dependency.status === 'manual')) {
283
317
  warnings.push('Manual-review dependencies were detected. They remain outside the current validated matrix even though the toolkit can still scaffold exploratory files.');
284
318
  }
319
+ if (dependencies.some((dependency) => dependency.supportTier === 'preview')) {
320
+ warnings.push('Preview-tier dependencies were detected. The toolkit can scaffold and bundle them, but runtime behavior is not part of the verified public promise yet.');
321
+ }
322
+ if (dependencies.some((dependency) => dependency.supportTier === 'experimental')) {
323
+ warnings.push('Experimental-tier dependencies were detected. Expect bridge drift, runtime gaps, or additional manual validation before claiming release readiness.');
324
+ }
285
325
  if (dependencies.some((dependency) => dependency.status === 'unknown')) {
286
326
  warnings.push('Unknown dependencies were detected. The toolkit can scaffold the project, but runtime portability is not guaranteed.');
287
327
  }
@@ -294,6 +334,16 @@ function buildAdvisories(expoConfig) {
294
334
  }
295
335
  return advisories;
296
336
  }
337
+ function isDependencyAllowedForTargetTier(dependencyName, matrix, targetTier) {
338
+ if (matrix.allowedDependencies.includes(dependencyName)) {
339
+ return true;
340
+ }
341
+ const capability = capabilities_1.CAPABILITY_BY_PACKAGE[dependencyName];
342
+ if (!capability) {
343
+ return false;
344
+ }
345
+ return (0, capabilities_1.isSupportTierAllowed)(capability.supportTier, targetTier);
346
+ }
297
347
  function matchesVersionRange(rawVersion, range) {
298
348
  const coerced = semver_1.default.coerce(rawVersion);
299
349
  if (!coerced) {