expo-harmony-toolkit 1.7.0 → 1.7.1

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
@@ -9,7 +9,7 @@
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.7.0-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.1-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>
@@ -24,7 +24,7 @@
24
24
  </div>
25
25
 
26
26
  > [!IMPORTANT]
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
+ > `v1.7` keeps the `verified + preview + experimental` model and moves `expo-location` and `expo-camera` into `preview`. Starting with this documentation refresh, the public promise is tighter: `latest` only carries fully accepted `verified` capabilities, while `next` is reserved for preview fast-track work. The roadmap still targets `Managed/CNG Core Expo Coverage` first and long-tail extension coverage second.
28
28
 
29
29
  > [!TIP]
30
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.
@@ -47,17 +47,19 @@
47
47
 
48
48
  | Item | Status |
49
49
  | --- | --- |
50
- | Current version | `v1.7.0` |
50
+ | Current version | `v1.7.1` |
51
51
  | Support model | `verified + preview + experimental` |
52
52
  | Public `verified` matrix | `expo55-rnoh082-ui-stack` |
53
53
  | Supported input | Managed/CNG Expo projects |
54
54
  | `verified` JS/UI capabilities | `expo-router`, `expo-linking`, `expo-constants`, `react-native-reanimated`, `react-native-svg` |
55
55
  | `preview` native capabilities | `expo-file-system`, `expo-image-picker`, `expo-location`, `expo-camera` |
56
56
  | `experimental` capabilities | `expo-notifications`, `react-native-gesture-handler` |
57
+ | Release tracks | `latest` = fully accepted `verified` only; `next` = preview fast track |
58
+ | Capability telemetry | `runtimeMode` + `evidence(bundle/debugBuild/device/release)` |
57
59
  | Build path | `doctor -> init -> bundle -> build-hap` |
58
60
  | Primary sample | `examples/official-ui-stack-sample` |
59
61
  | Preview sample | `examples/official-native-capabilities-sample` |
60
- | Regression baselines | `examples/official-app-shell-sample`, `examples/official-minimal-sample` |
62
+ | Supporting onboarding samples | `examples/official-app-shell-sample`, `examples/official-minimal-sample` |
61
63
 
62
64
  <details>
63
65
  <summary><strong>Still outside the verified public promise</strong></summary>
@@ -168,7 +170,7 @@ Common decision points:
168
170
 
169
171
  ## Support Matrix
170
172
 
171
- `v1.7` keeps tiered support:
173
+ `v1.7` keeps tiered support and now exposes capability promotion distance in public reports:
172
174
 
173
175
  - `verified`: the only public matrix remains `expo55-rnoh082-ui-stack`
174
176
  - `preview`: `expo-file-system`, `expo-image-picker`, `expo-location`, `expo-camera`
@@ -176,18 +178,24 @@ Common decision points:
176
178
 
177
179
  `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
180
 
181
+ Starting in this refresh:
182
+
183
+ - `doctor-report.json` exposes `capabilities[].runtimeMode`
184
+ - `doctor-report.json` and `toolkit-config.json` expose `evidence.bundle`, `evidence.debugBuild`, `evidence.device`, and `evidence.release`
185
+ - `runtimeMode=shim` means the capability still has not reached a verified runtime path even if bundling and debug-build scaffolding already exist
186
+
179
187
  See [docs/support-matrix.md](./docs/support-matrix.md) for the full allowlist, pairing rules, exact specifiers, issue codes, and release gates.
180
188
 
181
189
  ## Official Samples
182
190
 
183
191
  - `examples/official-ui-stack-sample`
184
- The primary public sample for `v1.5.0`, covering router, linking, constants, SVG, reanimated, and Harmony sidecar build flow.
192
+ The current public main sample, covering router, linking, constants, SVG, reanimated, and the Harmony sidecar build flow.
185
193
  - `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.
194
+ The `v1.7.x` Batch A+B preview walkthrough sample, covering the supported core subsets for `expo-file-system`, `expo-image-picker`, `expo-location`, and `expo-camera` plus permission, bundle, and debug-build validation.
187
195
  - `examples/official-app-shell-sample`
188
- The `v1.1` App Shell regression baseline that protects router behavior while UI-stack support is finalized.
196
+ The minimal App Shell onboarding sample that demonstrates router, linking, constants, pathname, observed URL, and a generated deep-link flow.
189
197
  - `examples/official-minimal-sample`
190
- The smallest smoke baseline for sidecar templates and the shortest bundle path.
198
+ The smallest onboarding sample, explaining the shortest `doctor -> init -> bundle -> build-hap` chain and what it intentionally does not cover.
191
199
 
192
200
  See:
193
201
 
@@ -230,14 +238,16 @@ Pre-publish checks:
230
238
  - `pnpm build`
231
239
  - `pnpm test`
232
240
  - `npm pack --dry-run`
233
- - tarball smoke: `doctor --strict`, `init --force`, `bundle`
241
+ - tarball smoke:
242
+ `latest` runs `doctor --strict`, `init --force`, `bundle`
243
+ `next` runs `doctor --target-tier preview`, `init --force`, `bundle`
234
244
 
235
- Automatic publishing defaults to hosted CI only:
245
+ Automatic publishing still defaults to hosted CI only, but now splits into two tracks:
236
246
 
237
- - GitHub workflow runs `build/test/pack/tarball smoke`
238
- - `build-hap --mode debug` does not block npm publish
239
- - GitHub auto-publish uses the `latest` dist-tag and provenance
240
- - local manual publishing uses the `latest` dist-tag
247
+ - `stable/latest`: only verified samples and fully accepted capabilities
248
+ - `fast-track/next`: preview sample smoke and preview capability validation
249
+ - GitHub auto-publish selects `latest` or `next` based on the tag and keeps provenance enabled
250
+ - `build-hap --mode debug` still does not block hosted npm publishing
241
251
 
242
252
  Manual Harmony acceptance still requires:
243
253
 
@@ -248,6 +258,12 @@ Manual Harmony acceptance still requires:
248
258
  - `Build Debug Hap(s)` succeeds
249
259
  - `official-native-capabilities-sample` at least proves Batch A+B preview route bundling, generated Harmony permissions, and the debug build path
250
260
 
261
+ Verified promotion still additionally requires:
262
+
263
+ - device-side acceptance
264
+ - release signing plus `build-hap --mode release`
265
+ - roadmap, support matrix, README, and acceptance records updated in the same PR
266
+
251
267
  See [docs/npm-release.md](./docs/npm-release.md) and [docs/signing-and-release.md](./docs/signing-and-release.md).
252
268
 
253
269
  ## Documentation
@@ -260,7 +276,7 @@ See [docs/npm-release.md](./docs/npm-release.md) and [docs/signing-and-release.m
260
276
  - [Official Minimal Sample Guide](./docs/official-minimal-sample.md)
261
277
  - [npm Release Notes](./docs/npm-release.md)
262
278
  - [Signing and Release Notes](./docs/signing-and-release.md)
263
- - [v1.7.0 Acceptance Log (In Progress)](./docs/v1.7.0-acceptance.md)
279
+ - [v1.7.1 Acceptance Log](./docs/v1.7.1-acceptance.md)
264
280
  - [Roadmap](./docs/roadmap.md)
265
281
 
266
282
  ## License
package/README.md CHANGED
@@ -9,7 +9,7 @@
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.7.0-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.1-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>
@@ -24,7 +24,7 @@
24
24
  </div>
25
25
 
26
26
  > [!IMPORTANT]
27
- > `v1.7` 延续 `verified + preview + experimental` 三层支持模型,并把 `expo-location`、`expo-camera` 推进到 `preview`。当前对外路线仍然是先做到 `Core Expo Full Coverage`,再去覆盖长尾第三方 native module;这依然不是“任意 Expo 项目都能原样发布到 HarmonyOS”的声明。
27
+ > `v1.7` 延续 `verified + preview + experimental` 三层支持模型,并把 `expo-location`、`expo-camera` 推进到 `preview`。从这版文档开始,对外承诺进一步收紧为:`latest` 只承诺完整验收的 `verified` 能力,`next` 用于 preview fast track;路线仍然是先做到 `Managed/CNG Core Expo Coverage`,再通过 extension model 逼近“任意 Expo 项目”。
28
28
 
29
29
  > [!TIP]
30
30
  > 由于当前公开矩阵内的两套 `@react-native-oh-tpl/*` adapter 依赖以 Git URL + exact commit 形式接入,仓库开发和官方 UI-stack sample 推荐使用 `pnpm install --ignore-scripts`,避免 Git adapter 在 prepare 阶段拉取私有资源而中断安装。
@@ -47,17 +47,19 @@
47
47
 
48
48
  | 项目 | 说明 |
49
49
  | --- | --- |
50
- | 当前版本 | `v1.7.0` |
50
+ | 当前版本 | `v1.7.1` |
51
51
  | 支持模型 | `verified + preview + experimental` |
52
52
  | 唯一 `verified` 公开矩阵 | `expo55-rnoh082-ui-stack` |
53
53
  | 输入范围 | Managed/CNG Expo 项目 |
54
54
  | `verified` JS/UI 能力 | `expo-router`、`expo-linking`、`expo-constants`、`react-native-reanimated`、`react-native-svg` |
55
55
  | `preview` 原生能力 | `expo-file-system`、`expo-image-picker`、`expo-location`、`expo-camera` |
56
56
  | `experimental` 能力 | `expo-notifications`、`react-native-gesture-handler` |
57
+ | 发布轨 | `latest` = fully accepted verified only;`next` = preview fast track |
58
+ | capability 遥测 | `runtimeMode` + `evidence(bundle/debugBuild/device/release)` |
57
59
  | 构建链 | `doctor -> init -> bundle -> build-hap` |
58
60
  | 主 sample | `examples/official-ui-stack-sample` |
59
61
  | preview sample | `examples/official-native-capabilities-sample` |
60
- | 回归基线 | `examples/official-app-shell-sample`、`examples/official-minimal-sample` |
62
+ | 辅助 onboarding samples | `examples/official-app-shell-sample`、`examples/official-minimal-sample` |
61
63
 
62
64
  <details>
63
65
  <summary><strong>当前仍不在 verified 正式承诺范围</strong></summary>
@@ -168,7 +170,7 @@ pnpm exec expo-harmony build-hap --mode release
168
170
 
169
171
  ## 支持矩阵
170
172
 
171
- `v1.7` 继续采用支持分层:
173
+ `v1.7` 继续采用支持分层,并开始把 capability 晋升距离写进公开报告:
172
174
 
173
175
  - `verified`:唯一公开矩阵仍是 `expo55-rnoh082-ui-stack`
174
176
  - `preview`:`expo-file-system`、`expo-image-picker`、`expo-location`、`expo-camera`
@@ -176,6 +178,18 @@ pnpm exec expo-harmony build-hap --mode release
176
178
 
177
179
  `doctor --strict` 继续只代表 `verified`。`doctor --target-tier preview` 会在同一 runtime matrix 下额外放行 preview 能力,但这不等于它们已经进入正式承诺。
178
180
 
181
+ 从本版开始:
182
+
183
+ - `doctor-report.json` 的 `capabilities[]` 会带出 `runtimeMode`
184
+ - `doctor-report.json` 与 `toolkit-config.json` 会带出 `evidence.bundle`、`evidence.debugBuild`、`evidence.device`、`evidence.release`
185
+ - `runtimeMode=shim` 说明当前仍未进入 verified runtime path,即使 bundle / debug build 已经可走通
186
+
187
+ 文档里的状态标记额外约定为:
188
+
189
+ - `🟡`:当前子集已经实现,主要只差真机 / release 证据
190
+ - `🟠`:包已经进入 `preview`,但这个具体子 API 还没到可信实现,不能简单理解成“只差真机”
191
+ - `⛔`:完全不在当前公开支持范围
192
+
179
193
  完整白名单、配对规则、exact specifier、issue code 与 release gate 见 [docs/support-matrix.md](./docs/support-matrix.md)。
180
194
 
181
195
  ## 官方 Samples
@@ -183,11 +197,11 @@ pnpm exec expo-harmony build-hap --mode release
183
197
  - `examples/official-ui-stack-sample`
184
198
  当前唯一对外主 sample,同时覆盖 router、linking、constants、SVG、reanimated 和 Harmony sidecar 构建链。
185
199
  - `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 验收。
200
+ `v1.7.x` 的 Batch A+B preview walkthrough sample,用来承接 `expo-file-system`、`expo-image-picker`、`expo-location`、`expo-camera` 的核心支持子集、permission、bundle 与 debug build 验收。
187
201
  - `examples/official-app-shell-sample`
188
- `v1.1` App Shell 回归基线,用来防止 UI-stack 收口引入 router 退化。
202
+ 最小可理解的 App Shell onboarding sample,用来展示 router、linking、constants、pathname、observed URL 与 generated deep link。
189
203
  - `examples/official-minimal-sample`
190
- 最小 smoke baseline,用来回归 sidecar 模板与最短 bundle 路径。
204
+ 最小 onboarding sample,用来说明最短 `doctor -> init -> bundle -> build-hap` 链路,以及它故意不覆盖的范围。
191
205
 
192
206
  详见:
193
207
 
@@ -230,14 +244,16 @@ pnpm exec expo-harmony build-hap --mode release
230
244
  - `pnpm build`
231
245
  - `pnpm test`
232
246
  - `npm pack --dry-run`
233
- - tarball 安装 smoke:`doctor --strict`、`init --force`、`bundle`
247
+ - tarball 安装 smoke
248
+ `latest` 走 `doctor --strict`、`init --force`、`bundle`
249
+ `next` 走 `doctor --target-tier preview`、`init --force`、`bundle`
234
250
 
235
- 自动发布默认走 hosted CI only
251
+ 自动发布默认走 hosted CI only,并区分双轨:
236
252
 
237
- - GitHub workflow `build/test/pack/tarball smoke`
238
- - `build-hap --mode debug` 不阻塞 npm publish
239
- - GitHub 自动发布使用 `latest` dist-tag provenance
240
- - 本地手动发布使用 `latest` dist-tag
253
+ - `stable/latest`:只承接 verified sample 与完整验收能力
254
+ - `fast-track/next`:承接 preview sample preview capability smoke
255
+ - GitHub 自动发布按 tag 选择 `latest` 或 `next` dist-tag,并保留 provenance
256
+ - `build-hap --mode debug` 继续不作为 hosted npm publish 的硬阻塞条件
241
257
 
242
258
  手动 Harmony 验收继续要求:
243
259
 
@@ -248,6 +264,12 @@ pnpm exec expo-harmony build-hap --mode release
248
264
  - `Build Debug Hap(s)` 成功
249
265
  - `official-native-capabilities-sample` 至少完成 Batch A+B preview route 的 bundle、permission 与 debug build 检查
250
266
 
267
+ verified capability 晋升还必须补齐:
268
+
269
+ - device-side 验收
270
+ - release signing / `build-hap --mode release`
271
+ - roadmap、support matrix、README、acceptance 记录同 PR 更新
272
+
251
273
  详见 [docs/npm-release.md](./docs/npm-release.md) 与 [docs/signing-and-release.md](./docs/signing-and-release.md)。
252
274
 
253
275
  ## 文档索引
@@ -260,7 +282,7 @@ pnpm exec expo-harmony build-hap --mode release
260
282
  - [官方最小 sample 指南](./docs/official-minimal-sample.md)
261
283
  - [npm 发布说明](./docs/npm-release.md)
262
284
  - [签名与 Release 说明](./docs/signing-and-release.md)
263
- - [v1.7.0 验收记录(进行中)](./docs/v1.7.0-acceptance.md)
285
+ - [v1.7.1 验收记录](./docs/v1.7.1-acceptance.md)
264
286
  - [路线图](./docs/roadmap.md)
265
287
 
266
288
  ## License
@@ -200,7 +200,7 @@ async function buildHapProject(projectRoot, options) {
200
200
  if (options.mode === 'release' && !envReport.signingConfigured) {
201
201
  blockingIssues.push({
202
202
  code: 'env.signing.missing',
203
- message: 'Release HAP builds require signingConfigs in harmony/build-profile.json5.',
203
+ message: 'Release HAP builds require signingConfigs from .expo-harmony/signing.local.json or harmony/build-profile.json5.',
204
204
  });
205
205
  }
206
206
  if (blockingIssues.length > 0) {
@@ -1,12 +1,13 @@
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.7.0";
3
+ export declare const TOOLKIT_VERSION = "1.7.1";
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";
7
7
  export declare const SUPPORTED_EXPO_SDKS: number[];
8
8
  export declare const GENERATED_DIR = ".expo-harmony";
9
9
  export declare const GENERATED_SHIMS_DIR = ".expo-harmony/shims";
10
+ export declare const SIGNING_LOCAL_FILENAME = "signing.local.json";
10
11
  export declare const MANIFEST_FILENAME = "manifest.json";
11
12
  export declare const DOCTOR_REPORT_FILENAME = "doctor-report.json";
12
13
  export declare const ENV_REPORT_FILENAME = "env-report.json";
@@ -1,15 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
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;
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.SIGNING_LOCAL_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.7.0';
6
+ exports.TOOLKIT_VERSION = '1.7.1';
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';
10
10
  exports.SUPPORTED_EXPO_SDKS = [53, 55];
11
11
  exports.GENERATED_DIR = '.expo-harmony';
12
12
  exports.GENERATED_SHIMS_DIR = `${exports.GENERATED_DIR}/shims`;
13
+ exports.SIGNING_LOCAL_FILENAME = 'signing.local.json';
13
14
  exports.MANIFEST_FILENAME = 'manifest.json';
14
15
  exports.DOCTOR_REPORT_FILENAME = 'doctor-report.json';
15
16
  exports.ENV_REPORT_FILENAME = 'env-report.json';
package/build/core/env.js CHANGED
@@ -10,6 +10,7 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
10
10
  const path_1 = __importDefault(require("path"));
11
11
  const constants_1 = require("./constants");
12
12
  const project_1 = require("./project");
13
+ const signing_1 = require("./signing");
13
14
  const DEFAULT_DEVECO_STUDIO_CANDIDATES = [
14
15
  '/Applications/DevEco-Studio.app',
15
16
  path_1.default.join(process.env.HOME ?? '', 'Applications', 'DevEco-Studio.app'),
@@ -56,9 +57,11 @@ async function buildEnvReport(projectRoot, options = {}) {
56
57
  const harmonyProjectRoot = (await fs_extra_1.default.pathExists(harmonyProjectRootCandidate))
57
58
  ? harmonyProjectRootCandidate
58
59
  : null;
59
- const signingConfigured = harmonyProjectRoot
60
- ? await detectSigningConfiguration(path_1.default.join(harmonyProjectRoot, 'build-profile.json5'))
61
- : false;
60
+ const signingLocalConfig = await (0, signing_1.readSigningLocalConfig)(resolvedProjectRoot);
61
+ const buildProfileContents = harmonyProjectRoot
62
+ ? await readBuildProfileContents(path_1.default.join(harmonyProjectRoot, 'build-profile.json5'))
63
+ : null;
64
+ const signingConfigured = (0, signing_1.hasSigningConfiguration)(buildProfileContents, signingLocalConfig);
62
65
  const blockingIssues = [];
63
66
  const advisories = [];
64
67
  const warnings = [];
@@ -92,7 +95,7 @@ async function buildEnvReport(projectRoot, options = {}) {
92
95
  else if (!signingConfigured) {
93
96
  advisories.push({
94
97
  code: 'env.signing.missing',
95
- message: 'Harmony build-profile.json5 does not declare any signingConfigs yet. Debug GUI flows may still work, but release builds require signing.',
98
+ message: 'Neither .expo-harmony/signing.local.json nor harmony/build-profile.json5 declares usable signingConfigs yet. Debug GUI flows may still work, but release builds require signing.',
96
99
  });
97
100
  }
98
101
  return {
@@ -169,16 +172,11 @@ function findExecutableInPath(executableName, runtimeEnv) {
169
172
  }
170
173
  return null;
171
174
  }
172
- async function detectSigningConfiguration(buildProfilePath) {
175
+ async function readBuildProfileContents(buildProfilePath) {
173
176
  if (!(await fs_extra_1.default.pathExists(buildProfilePath))) {
174
- return false;
175
- }
176
- const contents = await fs_extra_1.default.readFile(buildProfilePath, 'utf8');
177
- const signingConfigsMatch = contents.match(/signingConfigs\s*:\s*\[([\s\S]*?)\]/m);
178
- if (!signingConfigsMatch) {
179
- return false;
177
+ return null;
180
178
  }
181
- return signingConfigsMatch[1].trim().length > 0;
179
+ return fs_extra_1.default.readFile(buildProfilePath, 'utf8');
182
180
  }
183
181
  function renderIssueLine(issue) {
184
182
  return `- ${issue.code}: ${issue.message}${issue.subject ? ` (${issue.subject})` : ''}`;
@@ -46,6 +46,8 @@ async function buildDoctorReport(projectRoot, options = {}) {
46
46
  packageName: definition.packageName,
47
47
  status: definition.status,
48
48
  supportTier: definition.supportTier,
49
+ runtimeMode: definition.runtimeMode,
50
+ evidence: { ...definition.evidence },
49
51
  note: definition.note,
50
52
  docsUrl: definition.docsUrl,
51
53
  nativePackageNames: [...definition.nativePackageNames],
@@ -135,7 +137,13 @@ function renderDoctorReport(report) {
135
137
  const permissions = capability.harmonyPermissions.length > 0
136
138
  ? ` | permissions: ${capability.harmonyPermissions.join(', ')}`
137
139
  : '';
138
- return `- [${capability.status}/${capability.supportTier}] ${capability.packageName} -> ${capability.nativePackageNames.join(', ') || 'toolkit-managed bridge'} | sample: ${capability.sampleRoute}${permissions}`;
140
+ const missingEvidence = getMissingCapabilityEvidence(capability.evidence);
141
+ const evidence = ` | evidence: ${renderCapabilityEvidence(capability.evidence)}`;
142
+ const promotionGaps = buildCapabilityPromotionGaps(capability.runtimeMode, missingEvidence);
143
+ const gapSuffix = promotionGaps.length > 0
144
+ ? ` | verified gaps: ${promotionGaps.join(', ')}`
145
+ : '';
146
+ return `- [${capability.status}/${capability.supportTier}] ${capability.packageName} -> ${capability.nativePackageNames.join(', ') || 'toolkit-managed bridge'} | runtime: ${capability.runtimeMode} | sample: ${capability.sampleRoute}${permissions}${evidence}${gapSuffix}`;
139
147
  }));
140
148
  }
141
149
  if (report.blockingIssues.length > 0) {
@@ -149,6 +157,26 @@ function renderDoctorReport(report) {
149
157
  }
150
158
  return sections.join('\n');
151
159
  }
160
+ function renderCapabilityEvidence(evidence) {
161
+ return [
162
+ `bundle=${evidence.bundle ? 'yes' : 'no'}`,
163
+ `debugBuild=${evidence.debugBuild ? 'yes' : 'no'}`,
164
+ `device=${evidence.device ? 'yes' : 'no'}`,
165
+ `release=${evidence.release ? 'yes' : 'no'}`,
166
+ ].join(', ');
167
+ }
168
+ function getMissingCapabilityEvidence(evidence) {
169
+ return Object.entries(evidence)
170
+ .filter(([, present]) => !present)
171
+ .map(([key]) => key);
172
+ }
173
+ function buildCapabilityPromotionGaps(runtimeMode, missingEvidence) {
174
+ const gaps = missingEvidence.map((entry) => String(entry));
175
+ if (runtimeMode !== 'verified') {
176
+ gaps.unshift(`runtimeMode:${runtimeMode}->verified`);
177
+ }
178
+ return gaps;
179
+ }
152
180
  function createDependencyRecord(name, version, source) {
153
181
  const matrixRecord = dependencyCatalog_1.DEPENDENCY_CATALOG[name] ?? DEFAULT_RECORD;
154
182
  return {
@@ -0,0 +1,11 @@
1
+ type JsonRecord = Record<string, unknown>;
2
+ type SigningLocalAppFragment = {
3
+ signingConfigs?: JsonRecord[];
4
+ products?: JsonRecord[];
5
+ };
6
+ export declare function getSigningLocalPath(projectRoot: string): string;
7
+ export declare function readSigningLocalConfig(projectRoot: string): Promise<SigningLocalAppFragment | null>;
8
+ export declare function mergeSigningLocalConfigIntoBuildProfile(rawBuildProfileContents: string, signingLocalConfig: SigningLocalAppFragment | null): string;
9
+ export declare function hasSigningConfiguration(rawBuildProfileContents: string | null, signingLocalConfig?: SigningLocalAppFragment | null): boolean;
10
+ export declare function hasSigningLocalConfiguration(signingLocalConfig: SigningLocalAppFragment | null): boolean;
11
+ export {};
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getSigningLocalPath = getSigningLocalPath;
7
+ exports.readSigningLocalConfig = readSigningLocalConfig;
8
+ exports.mergeSigningLocalConfigIntoBuildProfile = mergeSigningLocalConfigIntoBuildProfile;
9
+ exports.hasSigningConfiguration = hasSigningConfiguration;
10
+ exports.hasSigningLocalConfiguration = hasSigningLocalConfiguration;
11
+ const fs_extra_1 = __importDefault(require("fs-extra"));
12
+ const json5_1 = __importDefault(require("json5"));
13
+ const path_1 = __importDefault(require("path"));
14
+ const constants_1 = require("./constants");
15
+ function getSigningLocalPath(projectRoot) {
16
+ return path_1.default.join(projectRoot, constants_1.GENERATED_DIR, constants_1.SIGNING_LOCAL_FILENAME);
17
+ }
18
+ async function readSigningLocalConfig(projectRoot) {
19
+ const signingLocalPath = getSigningLocalPath(projectRoot);
20
+ if (!(await fs_extra_1.default.pathExists(signingLocalPath))) {
21
+ return null;
22
+ }
23
+ const rawConfig = (await fs_extra_1.default.readJson(signingLocalPath));
24
+ return normalizeSigningLocalConfig(rawConfig);
25
+ }
26
+ function mergeSigningLocalConfigIntoBuildProfile(rawBuildProfileContents, signingLocalConfig) {
27
+ if (!signingLocalConfig) {
28
+ return rawBuildProfileContents;
29
+ }
30
+ const parsed = json5_1.default.parse(rawBuildProfileContents);
31
+ const merged = mergeSigningLocalConfig(parsed, signingLocalConfig);
32
+ return JSON.stringify(merged, null, 2) + '\n';
33
+ }
34
+ function hasSigningConfiguration(rawBuildProfileContents, signingLocalConfig = null) {
35
+ if (hasSigningLocalConfiguration(signingLocalConfig)) {
36
+ return true;
37
+ }
38
+ if (!rawBuildProfileContents) {
39
+ return false;
40
+ }
41
+ try {
42
+ const parsed = json5_1.default.parse(rawBuildProfileContents);
43
+ return hasNonEmptySigningConfigList(parsed.app?.signingConfigs);
44
+ }
45
+ catch (_error) {
46
+ return false;
47
+ }
48
+ }
49
+ function hasSigningLocalConfiguration(signingLocalConfig) {
50
+ return hasNonEmptySigningConfigList(signingLocalConfig?.signingConfigs);
51
+ }
52
+ function normalizeSigningLocalConfig(rawConfig) {
53
+ const candidate = rawConfig && typeof rawConfig === 'object' && !Array.isArray(rawConfig)
54
+ ? 'app' in rawConfig &&
55
+ rawConfig.app &&
56
+ typeof rawConfig.app === 'object' &&
57
+ !Array.isArray(rawConfig.app)
58
+ ? rawConfig.app
59
+ : rawConfig
60
+ : {};
61
+ const signingConfigs = Array.isArray(candidate.signingConfigs)
62
+ ? candidate.signingConfigs.filter(isJsonRecord)
63
+ : undefined;
64
+ const products = Array.isArray(candidate.products)
65
+ ? candidate.products.filter(isJsonRecord)
66
+ : undefined;
67
+ return {
68
+ ...(signingConfigs ? { signingConfigs } : {}),
69
+ ...(products ? { products } : {}),
70
+ };
71
+ }
72
+ function mergeSigningLocalConfig(buildProfile, signingLocalConfig) {
73
+ const nextBuildProfile = {
74
+ ...buildProfile,
75
+ app: isJsonRecord(buildProfile.app)
76
+ ? {
77
+ ...buildProfile.app,
78
+ }
79
+ : {},
80
+ };
81
+ if (Array.isArray(signingLocalConfig.signingConfigs)) {
82
+ nextBuildProfile.app.signingConfigs = signingLocalConfig.signingConfigs.map((entry) => ({ ...entry }));
83
+ }
84
+ if (Array.isArray(signingLocalConfig.products)) {
85
+ const currentProducts = Array.isArray(nextBuildProfile.app.products)
86
+ ? nextBuildProfile.app.products.map((entry) => ({ ...entry }))
87
+ : [];
88
+ for (const incomingProduct of signingLocalConfig.products) {
89
+ const incomingName = typeof incomingProduct.name === 'string' && incomingProduct.name.length > 0
90
+ ? incomingProduct.name
91
+ : null;
92
+ const existingIndex = incomingName === null
93
+ ? -1
94
+ : currentProducts.findIndex((entry) => entry.name === incomingName);
95
+ if (existingIndex >= 0) {
96
+ currentProducts[existingIndex] = {
97
+ ...currentProducts[existingIndex],
98
+ ...incomingProduct,
99
+ };
100
+ continue;
101
+ }
102
+ currentProducts.push({ ...incomingProduct });
103
+ }
104
+ nextBuildProfile.app.products = currentProducts;
105
+ }
106
+ return nextBuildProfile;
107
+ }
108
+ function hasNonEmptySigningConfigList(signingConfigs) {
109
+ return Array.isArray(signingConfigs) && signingConfigs.some(isJsonRecord);
110
+ }
111
+ function isJsonRecord(value) {
112
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
113
+ }