expo-harmony-toolkit 1.7.1 → 1.7.3

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.
Files changed (67) hide show
  1. package/README.en.md +20 -9
  2. package/README.md +22 -11
  3. package/build/core/autolinking.d.ts +10 -0
  4. package/build/core/autolinking.js +421 -0
  5. package/build/core/build/commands.d.ts +19 -0
  6. package/build/core/build/commands.js +61 -0
  7. package/build/core/build/compatibilityShims.d.ts +1 -0
  8. package/build/core/build/compatibilityShims.js +272 -0
  9. package/build/core/build/localHar.d.ts +13 -0
  10. package/build/core/build/localHar.js +337 -0
  11. package/build/core/build/reporting.d.ts +19 -0
  12. package/build/core/build/reporting.js +67 -0
  13. package/build/core/build/rnohCompatibility.d.ts +1 -0
  14. package/build/core/build/rnohCompatibility.js +38 -0
  15. package/build/core/build.d.ts +4 -12
  16. package/build/core/build.js +70 -762
  17. package/build/core/constants.d.ts +2 -1
  18. package/build/core/constants.js +3 -2
  19. package/build/core/dependencyInspection.d.ts +2 -0
  20. package/build/core/dependencyInspection.js +112 -0
  21. package/build/core/env.js +1 -1
  22. package/build/core/report.js +33 -10
  23. package/build/core/signing.d.ts +2 -0
  24. package/build/core/signing.js +30 -0
  25. package/build/core/template/capabilityRegistry.d.ts +9 -0
  26. package/build/core/template/capabilityRegistry.js +84 -0
  27. package/build/core/template/expoModulesCoreShim.d.ts +2 -0
  28. package/build/core/template/expoModulesCoreShim.js +279 -0
  29. package/build/core/template/metro.d.ts +2 -0
  30. package/build/core/template/metro.js +213 -0
  31. package/build/core/template/nativeFiles.d.ts +8 -0
  32. package/build/core/template/nativeFiles.js +333 -0
  33. package/build/core/template/renderers/camera.d.ts +4 -0
  34. package/build/core/template/renderers/camera.js +612 -0
  35. package/build/core/template/renderers/fileSystem.d.ts +4 -0
  36. package/build/core/template/renderers/fileSystem.js +814 -0
  37. package/build/core/template/renderers/imagePicker.d.ts +4 -0
  38. package/build/core/template/renderers/imagePicker.js +753 -0
  39. package/build/core/template/renderers/location.d.ts +4 -0
  40. package/build/core/template/renderers/location.js +836 -0
  41. package/build/core/template/runtimeShims.d.ts +2 -0
  42. package/build/core/template/runtimeShims.js +192 -0
  43. package/build/core/template/support.d.ts +11 -0
  44. package/build/core/template/support.js +108 -0
  45. package/build/core/template.d.ts +2 -6
  46. package/build/core/template.js +47 -3966
  47. package/build/data/capabilities.js +45 -19
  48. package/build/data/dependencyCatalog.js +10 -0
  49. package/build/data/publicDocs.d.ts +11 -0
  50. package/build/data/publicDocs.js +28 -0
  51. package/build/data/validatedMatrices.js +9 -1
  52. package/build/docs/render.d.ts +9 -0
  53. package/build/docs/render.js +248 -0
  54. package/build/types.d.ts +12 -0
  55. package/docs/cli-build.md +1 -1
  56. package/docs/npm-release.md +1 -0
  57. package/docs/official-app-shell-sample.md +2 -1
  58. package/docs/official-minimal-sample.md +2 -1
  59. package/docs/official-native-capabilities-sample.md +28 -47
  60. package/docs/official-ui-stack-sample.md +2 -1
  61. package/docs/signing-and-release.md +3 -0
  62. package/docs/support-matrix.md +38 -16
  63. package/package.json +13 -5
  64. package/docs/v1.5.1-acceptance.md +0 -385
  65. package/docs/v1.6.0-acceptance.md +0 -193
  66. package/docs/v1.7.0-acceptance.md +0 -111
  67. package/docs/v1.7.1-acceptance.md +0 -111
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.1-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.3-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>
@@ -45,9 +45,10 @@
45
45
 
46
46
  ## Current Status
47
47
 
48
+ <!-- GENERATED:readme-current-status:start -->
48
49
  | Item | Status |
49
50
  | --- | --- |
50
- | Current version | `v1.7.1` |
51
+ | Current version | `v1.7.3` |
51
52
  | Support model | `verified + preview + experimental` |
52
53
  | Public `verified` matrix | `expo55-rnoh082-ui-stack` |
53
54
  | Supported input | Managed/CNG Expo projects |
@@ -55,11 +56,12 @@
55
56
  | `preview` native capabilities | `expo-file-system`, `expo-image-picker`, `expo-location`, `expo-camera` |
56
57
  | `experimental` capabilities | `expo-notifications`, `react-native-gesture-handler` |
57
58
  | Release tracks | `latest` = fully accepted `verified` only; `next` = preview fast track |
58
- | Capability telemetry | `runtimeMode` + `evidence(bundle/debugBuild/device/release)` |
59
+ | Capability telemetry | `runtimeMode` + `evidence(...)` + `evidenceSource(...)` |
59
60
  | Build path | `doctor -> init -> bundle -> build-hap` |
60
61
  | Primary sample | `examples/official-ui-stack-sample` |
61
62
  | Preview sample | `examples/official-native-capabilities-sample` |
62
63
  | Supporting onboarding samples | `examples/official-app-shell-sample`, `examples/official-minimal-sample` |
64
+ <!-- GENERATED:readme-current-status:end -->
63
65
 
64
66
  <details>
65
67
  <summary><strong>Still outside the verified public promise</strong></summary>
@@ -170,19 +172,21 @@ Common decision points:
170
172
 
171
173
  ## Support Matrix
172
174
 
173
- `v1.7` keeps tiered support and now exposes capability promotion distance in public reports:
174
-
175
+ <!-- GENERATED:readme-support-matrix:start -->
175
176
  - `verified`: the only public matrix remains `expo55-rnoh082-ui-stack`
176
177
  - `preview`: `expo-file-system`, `expo-image-picker`, `expo-location`, `expo-camera`
177
178
  - `experimental`: `expo-notifications`, `react-native-gesture-handler`
178
179
 
179
180
  `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.
180
181
 
181
- Starting in this refresh:
182
-
183
182
  - `doctor-report.json` exposes `capabilities[].runtimeMode`
184
183
  - `doctor-report.json` and `toolkit-config.json` expose `evidence.bundle`, `evidence.debugBuild`, `evidence.device`, and `evidence.release`
184
+ - `doctor-report.json` and `toolkit-config.json` expose `evidenceSource.bundle`, `evidenceSource.debugBuild`, `evidenceSource.device`, and `evidenceSource.release`
185
185
  - `runtimeMode=shim` means the capability still has not reached a verified runtime path even if bundling and debug-build scaffolding already exist
186
+ - `evidenceSource.device=manual-doc` means the current device signal comes from manual acceptance records, not automated verification
187
+ <!-- GENERATED:readme-support-matrix:end -->
188
+
189
+ Starting in this refresh, `doctor` also emits `buildabilityRisk` so matrix-out dependencies that still look JavaScript-only no longer get described the same way as packages with clear native risk. This does not relax any gate; it only improves diagnosis.
186
190
 
187
191
  See [docs/support-matrix.md](./docs/support-matrix.md) for the full allowlist, pairing rules, exact specifiers, issue codes, and release gates.
188
192
 
@@ -226,10 +230,10 @@ Key managed outputs include:
226
230
  - `harmony/entry/src/main/cpp/autolinking.cmake`
227
231
  - `metro.harmony.config.js`
228
232
  - `.expo-harmony/manifest.json`
233
+ - `.expo-harmony/toolkit-config.json`
229
234
  - `.expo-harmony/doctor-report.json`
230
235
  - `.expo-harmony/env-report.json`
231
236
  - `.expo-harmony/build-report.json`
232
- - `.expo-harmony/toolkit-config.json`
233
237
 
234
238
  ## Release And Validation
235
239
 
@@ -249,6 +253,12 @@ Automatic publishing still defaults to hosted CI only, but now splits into two t
249
253
  - GitHub auto-publish selects `latest` or `next` based on the tag and keeps provenance enabled
250
254
  - `build-hap --mode debug` still does not block hosted npm publishing
251
255
 
256
+ Additional preview evidence semantics:
257
+
258
+ - `bundle/debugBuild` are marked as `automated`
259
+ - `device` is marked as `manual-doc`, which means a human acceptance record exists rather than CI automation
260
+ - `release` is marked as `none`, which means no release evidence exists yet
261
+
252
262
  Manual Harmony acceptance still requires:
253
263
 
254
264
  - `official-ui-stack-sample` launches successfully
@@ -276,9 +286,10 @@ See [docs/npm-release.md](./docs/npm-release.md) and [docs/signing-and-release.m
276
286
  - [Official Minimal Sample Guide](./docs/official-minimal-sample.md)
277
287
  - [npm Release Notes](./docs/npm-release.md)
278
288
  - [Signing and Release Notes](./docs/signing-and-release.md)
279
- - [v1.7.1 Acceptance Log](./docs/v1.7.1-acceptance.md)
280
289
  - [Roadmap](./docs/roadmap.md)
281
290
 
291
+ Acceptance logs stay in the repo-only [`acceptance/`](./acceptance/) directory and are not shipped inside the npm tarball.
292
+
282
293
  ## License
283
294
 
284
295
  Released under the [MIT License](./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.1-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.3-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>
@@ -45,9 +45,10 @@
45
45
 
46
46
  ## 当前状态
47
47
 
48
+ <!-- GENERATED:readme-current-status:start -->
48
49
  | 项目 | 说明 |
49
50
  | --- | --- |
50
- | 当前版本 | `v1.7.1` |
51
+ | 当前版本 | `v1.7.3` |
51
52
  | 支持模型 | `verified + preview + experimental` |
52
53
  | 唯一 `verified` 公开矩阵 | `expo55-rnoh082-ui-stack` |
53
54
  | 输入范围 | Managed/CNG Expo 项目 |
@@ -55,11 +56,12 @@
55
56
  | `preview` 原生能力 | `expo-file-system`、`expo-image-picker`、`expo-location`、`expo-camera` |
56
57
  | `experimental` 能力 | `expo-notifications`、`react-native-gesture-handler` |
57
58
  | 发布轨 | `latest` = fully accepted verified only;`next` = preview fast track |
58
- | capability 遥测 | `runtimeMode` + `evidence(bundle/debugBuild/device/release)` |
59
+ | capability 遥测 | `runtimeMode` + `evidence(...)` + `evidenceSource(...)` |
59
60
  | 构建链 | `doctor -> init -> bundle -> build-hap` |
60
61
  | 主 sample | `examples/official-ui-stack-sample` |
61
62
  | preview sample | `examples/official-native-capabilities-sample` |
62
63
  | 辅助 onboarding samples | `examples/official-app-shell-sample`、`examples/official-minimal-sample` |
64
+ <!-- GENERATED:readme-current-status:end -->
63
65
 
64
66
  <details>
65
67
  <summary><strong>当前仍不在 verified 正式承诺范围</strong></summary>
@@ -170,19 +172,21 @@ pnpm exec expo-harmony build-hap --mode release
170
172
 
171
173
  ## 支持矩阵
172
174
 
173
- `v1.7` 继续采用支持分层,并开始把 capability 晋升距离写进公开报告:
174
-
175
+ <!-- GENERATED:readme-support-matrix:start -->
175
176
  - `verified`:唯一公开矩阵仍是 `expo55-rnoh082-ui-stack`
176
- - `preview`:`expo-file-system`、`expo-image-picker`、`expo-location`、`expo-camera`
177
- - `experimental`:`expo-notifications`、`react-native-gesture-handler`
177
+ - `preview`:`expo-file-system`, `expo-image-picker`, `expo-location`, `expo-camera`
178
+ - `experimental`:`expo-notifications`, `react-native-gesture-handler`
178
179
 
179
180
  `doctor --strict` 继续只代表 `verified`。`doctor --target-tier preview` 会在同一 runtime matrix 下额外放行 preview 能力,但这不等于它们已经进入正式承诺。
180
181
 
181
- 从本版开始:
182
-
183
182
  - `doctor-report.json` 的 `capabilities[]` 会带出 `runtimeMode`
184
183
  - `doctor-report.json` 与 `toolkit-config.json` 会带出 `evidence.bundle`、`evidence.debugBuild`、`evidence.device`、`evidence.release`
184
+ - `doctor-report.json` 与 `toolkit-config.json` 会带出 `evidenceSource.bundle`、`evidenceSource.debugBuild`、`evidenceSource.device`、`evidenceSource.release`
185
185
  - `runtimeMode=shim` 说明当前仍未进入 verified runtime path,即使 bundle / debug build 已经可走通
186
+ - `evidenceSource.device=manual-doc` 表示当前只有人工设备验收记录,不代表机器自动验证
187
+ <!-- GENERATED:readme-support-matrix:end -->
188
+
189
+ 从本版开始,`doctor` 还会额外输出 `buildabilityRisk`,把“超出公开矩阵但看起来更像纯 JS 层”的依赖,与“存在明确原生风险”的依赖区分开来;这不会放宽 gate,只是让构建可诊断性更强。
186
190
 
187
191
  文档里的状态标记额外约定为:
188
192
 
@@ -233,9 +237,9 @@ pnpm exec expo-harmony build-hap --mode release
233
237
  - `metro.harmony.config.js`
234
238
  - `.expo-harmony/manifest.json`
235
239
  - `.expo-harmony/doctor-report.json`
240
+ - `.expo-harmony/toolkit-config.json`
236
241
  - `.expo-harmony/env-report.json`
237
242
  - `.expo-harmony/build-report.json`
238
- - `.expo-harmony/toolkit-config.json`
239
243
 
240
244
  ## 发布与验收
241
245
 
@@ -255,6 +259,12 @@ pnpm exec expo-harmony build-hap --mode release
255
259
  - GitHub 自动发布按 tag 选择 `latest` 或 `next` dist-tag,并保留 provenance
256
260
  - `build-hap --mode debug` 继续不作为 hosted npm publish 的硬阻塞条件
257
261
 
262
+ preview 证据的额外说明:
263
+
264
+ - `bundle/debugBuild` 标记为 `automated`
265
+ - `device` 标记为 `manual-doc`,表示已有人工验收记录,不等于 CI 自动验收
266
+ - `release` 标记为 `none`,表示当前仍没有 release 证据
267
+
258
268
  手动 Harmony 验收继续要求:
259
269
 
260
270
  - `official-ui-stack-sample` 成功启动
@@ -282,9 +292,10 @@ verified capability 晋升还必须补齐:
282
292
  - [官方最小 sample 指南](./docs/official-minimal-sample.md)
283
293
  - [npm 发布说明](./docs/npm-release.md)
284
294
  - [签名与 Release 说明](./docs/signing-and-release.md)
285
- - [v1.7.1 验收记录](./docs/v1.7.1-acceptance.md)
286
295
  - [路线图](./docs/roadmap.md)
287
296
 
297
+ 验收记录继续保留在 repo-only 的 [`acceptance/`](./acceptance/) 目录中,不随 npm tarball 一起发布。
298
+
288
299
  ## License
289
300
 
290
301
  本项目基于 [MIT License](./LICENSE) 发布。
@@ -0,0 +1,10 @@
1
+ import { TemplateFileDefinition } from '../types';
2
+ export declare const AUTOLINKED_FILE_PATHS: readonly [string, string, string, string];
3
+ export declare class AutolinkingFailureError extends Error {
4
+ constructor(stage: string, command: string, options?: {
5
+ cause?: string;
6
+ missingFiles?: string[];
7
+ });
8
+ }
9
+ export declare function buildAutolinkedManagedFiles(projectRoot: string, harmonyRootPackageContents: string): Promise<TemplateFileDefinition[]>;
10
+ export declare function normalizeKnownHarmonyPackageJsons(projectRoot: string): Promise<() => Promise<void>>;
@@ -0,0 +1,421 @@
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.AutolinkingFailureError = exports.AUTOLINKED_FILE_PATHS = void 0;
7
+ exports.buildAutolinkedManagedFiles = buildAutolinkedManagedFiles;
8
+ exports.normalizeKnownHarmonyPackageJsons = normalizeKnownHarmonyPackageJsons;
9
+ const fs_extra_1 = __importDefault(require("fs-extra"));
10
+ const json5_1 = __importDefault(require("json5"));
11
+ const os_1 = __importDefault(require("os"));
12
+ const path_1 = __importDefault(require("path"));
13
+ const uiStack_1 = require("../data/uiStack");
14
+ exports.AUTOLINKED_FILE_PATHS = [
15
+ path_1.default.join('harmony', 'oh-package.json5'),
16
+ path_1.default.join('harmony', 'entry', 'src', 'main', 'ets', 'RNOHPackagesFactory.ets'),
17
+ path_1.default.join('harmony', 'entry', 'src', 'main', 'cpp', 'RNOHPackagesFactory.h'),
18
+ path_1.default.join('harmony', 'entry', 'src', 'main', 'cpp', 'autolinking.cmake'),
19
+ ];
20
+ const HARMONY_PACKAGE_JSON_NORMALIZERS = {
21
+ '@react-native-oh-tpl/react-native-gesture-handler': (packageJson) => {
22
+ const harmony = packageJson.harmony && typeof packageJson.harmony === 'object' && !Array.isArray(packageJson.harmony)
23
+ ? { ...packageJson.harmony }
24
+ : null;
25
+ if (!harmony || !('codegenConfig' in harmony)) {
26
+ return null;
27
+ }
28
+ delete harmony.codegenConfig;
29
+ return {
30
+ ...packageJson,
31
+ harmony,
32
+ };
33
+ },
34
+ };
35
+ class AutolinkingFailureError extends Error {
36
+ constructor(stage, command, options = {}) {
37
+ const details = [
38
+ `Harmony autolinking failed during ${stage}.`,
39
+ `Command: ${command}`,
40
+ ];
41
+ if (options.missingFiles && options.missingFiles.length > 0) {
42
+ details.push(`Missing files: ${options.missingFiles.join(', ')}`);
43
+ }
44
+ if (options.cause) {
45
+ details.push(`Cause: ${options.cause}`);
46
+ }
47
+ super(details.join(' '));
48
+ this.name = 'AutolinkingFailureError';
49
+ }
50
+ }
51
+ exports.AutolinkingFailureError = AutolinkingFailureError;
52
+ async function buildAutolinkedManagedFiles(projectRoot, harmonyRootPackageContents) {
53
+ const generated = await generateAutolinkingArtifacts(projectRoot, harmonyRootPackageContents);
54
+ return [
55
+ {
56
+ relativePath: exports.AUTOLINKED_FILE_PATHS[0],
57
+ contents: generated.ohPackageContents,
58
+ },
59
+ {
60
+ relativePath: exports.AUTOLINKED_FILE_PATHS[1],
61
+ contents: generated.etsFactoryContents,
62
+ },
63
+ {
64
+ relativePath: exports.AUTOLINKED_FILE_PATHS[2],
65
+ contents: generated.cppFactoryContents,
66
+ },
67
+ {
68
+ relativePath: exports.AUTOLINKED_FILE_PATHS[3],
69
+ contents: generated.cmakeContents,
70
+ },
71
+ ];
72
+ }
73
+ async function normalizeKnownHarmonyPackageJsons(projectRoot) {
74
+ const originalContentsByPath = new Map();
75
+ for (const [packageName, normalizePackageJson] of Object.entries(HARMONY_PACKAGE_JSON_NORMALIZERS)) {
76
+ const packageJsonPath = resolveProjectPackageJson(projectRoot, packageName);
77
+ if (!packageJsonPath || originalContentsByPath.has(packageJsonPath)) {
78
+ continue;
79
+ }
80
+ try {
81
+ const currentContents = await fs_extra_1.default.readFile(packageJsonPath, 'utf8');
82
+ const packageJson = JSON.parse(currentContents);
83
+ const normalizedPackageJson = normalizePackageJson(packageJson);
84
+ if (!normalizedPackageJson) {
85
+ continue;
86
+ }
87
+ originalContentsByPath.set(packageJsonPath, currentContents);
88
+ await fs_extra_1.default.writeFile(packageJsonPath, JSON.stringify(normalizedPackageJson, null, 2) + '\n');
89
+ }
90
+ catch {
91
+ // Ignore malformed adapter package metadata and let downstream tooling surface the failure.
92
+ }
93
+ }
94
+ return async () => {
95
+ for (const [packageJsonPath, originalContents] of originalContentsByPath) {
96
+ await fs_extra_1.default.writeFile(packageJsonPath, originalContents);
97
+ }
98
+ };
99
+ }
100
+ async function generateAutolinkingArtifacts(projectRoot, harmonyRootPackageContents) {
101
+ const rnohCliPackageJsonPath = resolveProjectPackageJson(projectRoot, '@react-native-oh/react-native-harmony-cli');
102
+ const managedOhPackageContents = await buildManagedHarmonyRootPackageContents(projectRoot, harmonyRootPackageContents);
103
+ const managedAutolinkingEntries = await resolveManagedAutolinkingEntries(projectRoot);
104
+ if (!rnohCliPackageJsonPath) {
105
+ return createEmptyAutolinkingArtifacts(managedOhPackageContents, managedAutolinkingEntries);
106
+ }
107
+ const restoreNormalizedHarmonyPackageJsons = await normalizeKnownHarmonyPackageJsons(projectRoot);
108
+ try {
109
+ const temporaryRoot = await fs_extra_1.default.mkdtemp(path_1.default.join(os_1.default.tmpdir(), 'expo-harmony-autolinking-'));
110
+ try {
111
+ const temporaryHarmonyRoot = path_1.default.join(temporaryRoot, 'harmony');
112
+ const commandDescription = buildRnohLinkHarmonyCommand(projectRoot, temporaryHarmonyRoot);
113
+ await fs_extra_1.default.ensureDir(path_1.default.join(temporaryHarmonyRoot, 'entry', 'src', 'main', 'ets'));
114
+ await fs_extra_1.default.ensureDir(path_1.default.join(temporaryHarmonyRoot, 'entry', 'src', 'main', 'cpp'));
115
+ await fs_extra_1.default.writeFile(path_1.default.join(temporaryHarmonyRoot, 'oh-package.json5'), harmonyRootPackageContents);
116
+ await runRnohLinkHarmonyCommand(projectRoot, rnohCliPackageJsonPath, temporaryHarmonyRoot, commandDescription);
117
+ await assertGeneratedAutolinkingFilesExist(temporaryHarmonyRoot, commandDescription);
118
+ let normalizedEtsFactoryContents;
119
+ let normalizedCppFactoryContents;
120
+ let normalizedCmakeContents;
121
+ try {
122
+ normalizedEtsFactoryContents = await normalizeAutolinkingEtsFactoryContents(projectRoot, await fs_extra_1.default.readFile(path_1.default.join(temporaryHarmonyRoot, 'entry', 'src', 'main', 'ets', 'RNOHPackagesFactory.ets'), 'utf8'));
123
+ normalizedCppFactoryContents = await normalizeAutolinkingCppFactoryContents(projectRoot, await fs_extra_1.default.readFile(path_1.default.join(temporaryHarmonyRoot, 'entry', 'src', 'main', 'cpp', 'RNOHPackagesFactory.h'), 'utf8'));
124
+ normalizedCmakeContents = await fs_extra_1.default.readFile(path_1.default.join(temporaryHarmonyRoot, 'entry', 'src', 'main', 'cpp', 'autolinking.cmake'), 'utf8');
125
+ }
126
+ catch (error) {
127
+ throw new AutolinkingFailureError('normalize-generated-files', commandDescription, {
128
+ cause: getErrorMessage(error),
129
+ });
130
+ }
131
+ return {
132
+ ohPackageContents: managedOhPackageContents,
133
+ etsFactoryContents: injectManagedAutolinkingIntoEtsFactory(normalizedEtsFactoryContents, managedAutolinkingEntries),
134
+ cppFactoryContents: injectManagedAutolinkingIntoCppFactory(normalizedCppFactoryContents, managedAutolinkingEntries),
135
+ cmakeContents: injectManagedAutolinkingIntoCmake(normalizedCmakeContents, managedAutolinkingEntries),
136
+ };
137
+ }
138
+ finally {
139
+ await fs_extra_1.default.remove(temporaryRoot);
140
+ }
141
+ }
142
+ finally {
143
+ await restoreNormalizedHarmonyPackageJsons();
144
+ }
145
+ }
146
+ async function resolveManagedAutolinkingEntries(projectRoot) {
147
+ const entries = await Promise.all(uiStack_1.UI_STACK_VALIDATED_ADAPTERS.map(async (adapter) => {
148
+ if (adapter.supportsAutolinking || !adapter.managedAutolinking) {
149
+ return null;
150
+ }
151
+ const dependencySpecifier = await resolveHarmonyAdapterHarDependency(projectRoot, adapter.adapterPackageName);
152
+ if (!dependencySpecifier) {
153
+ return null;
154
+ }
155
+ return {
156
+ adapterPackageName: adapter.adapterPackageName,
157
+ ...adapter.managedAutolinking,
158
+ };
159
+ }));
160
+ return entries.filter((entry) => entry !== null);
161
+ }
162
+ async function normalizeAutolinkingEtsFactoryContents(projectRoot, contents) {
163
+ let normalizedContents = contents
164
+ .replace(/import type \{\s*RNPackageContext\s*,\s*RNOHPackage\s*\} from '@rnoh\/react-native-openharmony';/, "import type { RNPackageContext, RNPackage } from '@rnoh/react-native-openharmony';")
165
+ .replace(/export function createRNOHPackages\(([^)]*)\):\s*RNOHPackage\[\]/, 'export function createRNOHPackages($1): RNPackage[]');
166
+ const gestureHandlerPackageJsonPath = resolveProjectPackageJson(projectRoot, '@react-native-oh-tpl/react-native-gesture-handler');
167
+ if (!gestureHandlerPackageJsonPath) {
168
+ return normalizedContents;
169
+ }
170
+ try {
171
+ const gestureHandlerPackageJson = (await fs_extra_1.default.readJson(gestureHandlerPackageJsonPath));
172
+ if (gestureHandlerPackageJson.harmony?.codegenConfig) {
173
+ return normalizedContents;
174
+ }
175
+ }
176
+ catch {
177
+ return normalizedContents;
178
+ }
179
+ let gestureHandlerPackageImportName = null;
180
+ normalizedContents = normalizedContents.replace(/import\s+([A-Za-z_$][\w$]*)\s+from ['"]@react-native-oh-tpl\/react-native-gesture-handler['"];?/, (_match, importName) => {
181
+ gestureHandlerPackageImportName = importName;
182
+ return "import { GestureHandlerPackage } from '@react-native-oh-tpl/react-native-gesture-handler/ts';";
183
+ });
184
+ if (!gestureHandlerPackageImportName) {
185
+ return normalizedContents;
186
+ }
187
+ return normalizedContents.replace(new RegExp(`new\\s+${escapeRegExp(gestureHandlerPackageImportName)}\\(ctx\\)`, 'g'), 'new GestureHandlerPackage(ctx)');
188
+ }
189
+ function injectManagedAutolinkingIntoEtsFactory(contents, entries) {
190
+ if (entries.length === 0) {
191
+ return contents;
192
+ }
193
+ const missingImports = entries
194
+ .filter((entry) => !contents.includes(`from '${entry.etsImportPath}'`))
195
+ .map((entry) => `import { ${entry.etsPackageName} } from '${entry.etsImportPath}';`);
196
+ if (missingImports.length > 0) {
197
+ contents = contents.replace("import type { RNPackageContext, RNPackage } from '@rnoh/react-native-openharmony';", `import type { RNPackageContext, RNPackage } from '@rnoh/react-native-openharmony';\n${missingImports.join('\n')}`);
198
+ }
199
+ const missingFactoryEntries = entries
200
+ .filter((entry) => !contents.includes(`new ${entry.etsPackageName}(ctx)`))
201
+ .map((entry) => ` new ${entry.etsPackageName}(ctx),`);
202
+ if (missingFactoryEntries.length > 0) {
203
+ contents = contents.replace(/return \[\n/, `return [\n${missingFactoryEntries.join('\n')}\n`);
204
+ }
205
+ return contents;
206
+ }
207
+ async function normalizeAutolinkingCppFactoryContents(projectRoot, contents) {
208
+ const gestureHandlerPackageJsonPath = resolveProjectPackageJson(projectRoot, '@react-native-oh-tpl/react-native-gesture-handler');
209
+ if (!gestureHandlerPackageJsonPath) {
210
+ return contents;
211
+ }
212
+ try {
213
+ const gestureHandlerPackageJson = (await fs_extra_1.default.readJson(gestureHandlerPackageJsonPath));
214
+ if (gestureHandlerPackageJson.harmony?.codegenConfig) {
215
+ return contents;
216
+ }
217
+ }
218
+ catch {
219
+ return contents;
220
+ }
221
+ return contents
222
+ .replace(/#include "ReactNativeOhTplReactNativeGestureHandlerPackage\.h"/, '#include "RnohReactNativeHarmonyGestureHandlerPackage.h"')
223
+ .replace(/\brnoh::ReactNativeOhTplReactNativeGestureHandlerPackage\b/g, 'rnoh::RnohReactNativeHarmonyGestureHandlerPackage');
224
+ }
225
+ function injectManagedAutolinkingIntoCppFactory(contents, entries) {
226
+ if (entries.length === 0) {
227
+ return contents;
228
+ }
229
+ const missingIncludes = entries
230
+ .filter((entry) => !contents.includes(`#include "${entry.cppHeaderName}"`))
231
+ .map((entry) => `#include "${entry.cppHeaderName}"`);
232
+ if (missingIncludes.length > 0) {
233
+ contents = contents.replace('#include "RNOH/Package.h"', `#include "RNOH/Package.h"\n${missingIncludes.join('\n')}`);
234
+ }
235
+ const missingFactoryEntries = entries
236
+ .filter((entry) => !contents.includes(`std::make_shared<rnoh::${entry.cppPackageName}>(ctx)`))
237
+ .map((entry) => ` std::make_shared<rnoh::${entry.cppPackageName}>(ctx),`);
238
+ if (missingFactoryEntries.length > 0) {
239
+ contents = contents.replace(/return \{\n/, `return {\n${missingFactoryEntries.join('\n')}\n`);
240
+ }
241
+ return contents;
242
+ }
243
+ function injectManagedAutolinkingIntoCmake(contents, entries) {
244
+ if (entries.length === 0) {
245
+ return contents;
246
+ }
247
+ const missingSubdirectories = entries
248
+ .filter((entry) => !contents.includes(`./${entry.cmakeTargetName}`))
249
+ .map((entry) => ` add_subdirectory("\${OH_MODULES_DIR}/${entry.adapterPackageName}/src/main/cpp" ./${entry.cmakeTargetName})`);
250
+ if (missingSubdirectories.length > 0) {
251
+ contents = contents.replace(/function\(autolink_libraries target\)\n/, `function(autolink_libraries target)\n${missingSubdirectories.join('\n')}\n`);
252
+ }
253
+ const missingLibraryTargets = entries
254
+ .filter((entry) => !contents.includes(` ${entry.cmakeTargetName}`))
255
+ .map((entry) => ` ${entry.cmakeTargetName}`);
256
+ if (missingLibraryTargets.length > 0) {
257
+ contents = contents.replace(/set\(AUTOLINKED_LIBRARIES\n/, `set(AUTOLINKED_LIBRARIES\n${missingLibraryTargets.join('\n')}\n`);
258
+ }
259
+ return contents;
260
+ }
261
+ async function runRnohLinkHarmonyCommand(projectRoot, rnohCliPackageJsonPath, harmonyProjectPath, commandDescription) {
262
+ const rnohCliRoot = path_1.default.dirname(rnohCliPackageJsonPath);
263
+ let commandLinkHarmony;
264
+ try {
265
+ ({ commandLinkHarmony } = require(path_1.default.join(rnohCliRoot, 'dist', 'commands', 'link-harmony.js')));
266
+ }
267
+ catch (error) {
268
+ throw new AutolinkingFailureError('load-link-harmony', commandDescription, {
269
+ cause: getErrorMessage(error),
270
+ });
271
+ }
272
+ try {
273
+ await commandLinkHarmony.func([], {}, {
274
+ harmonyProjectPath,
275
+ nodeModulesPath: path_1.default.join(projectRoot, 'node_modules'),
276
+ cmakeAutolinkPathRelativeToHarmony: './entry/src/main/cpp/autolinking.cmake',
277
+ cppRnohPackagesFactoryPathRelativeToHarmony: './entry/src/main/cpp/RNOHPackagesFactory.h',
278
+ etsRnohPackagesFactoryPathRelativeToHarmony: './entry/src/main/ets/RNOHPackagesFactory.ets',
279
+ ohPackagePathRelativeToHarmony: './oh-package.json5',
280
+ includeNpmPackages: uiStack_1.UI_STACK_ADAPTER_PACKAGE_NAMES,
281
+ });
282
+ }
283
+ catch (error) {
284
+ throw new AutolinkingFailureError('link-harmony', commandDescription, {
285
+ cause: getErrorMessage(error),
286
+ });
287
+ }
288
+ }
289
+ function buildRnohLinkHarmonyCommand(projectRoot, harmonyProjectPath) {
290
+ return [
291
+ 'link-harmony',
292
+ `--harmonyProjectPath ${harmonyProjectPath}`,
293
+ `--nodeModulesPath ${path_1.default.join(projectRoot, 'node_modules')}`,
294
+ '--cmakeAutolinkPathRelativeToHarmony ./entry/src/main/cpp/autolinking.cmake',
295
+ '--cppRnohPackagesFactoryPathRelativeToHarmony ./entry/src/main/cpp/RNOHPackagesFactory.h',
296
+ '--etsRnohPackagesFactoryPathRelativeToHarmony ./entry/src/main/ets/RNOHPackagesFactory.ets',
297
+ '--ohPackagePathRelativeToHarmony ./oh-package.json5',
298
+ ].join(' ');
299
+ }
300
+ async function assertGeneratedAutolinkingFilesExist(harmonyProjectPath, commandDescription) {
301
+ const requiredFiles = [
302
+ path_1.default.join(harmonyProjectPath, 'entry', 'src', 'main', 'ets', 'RNOHPackagesFactory.ets'),
303
+ path_1.default.join(harmonyProjectPath, 'entry', 'src', 'main', 'cpp', 'RNOHPackagesFactory.h'),
304
+ path_1.default.join(harmonyProjectPath, 'entry', 'src', 'main', 'cpp', 'autolinking.cmake'),
305
+ ];
306
+ const missingFiles = requiredFiles.filter((filePath) => !fs_extra_1.default.existsSync(filePath));
307
+ if (missingFiles.length > 0) {
308
+ throw new AutolinkingFailureError('validate-generated-files', commandDescription, {
309
+ missingFiles: missingFiles.map((filePath) => path_1.default.relative(harmonyProjectPath, filePath)),
310
+ });
311
+ }
312
+ }
313
+ async function buildManagedHarmonyRootPackageContents(projectRoot, harmonyRootPackageContents) {
314
+ const parsedPackageJson = json5_1.default.parse(harmonyRootPackageContents);
315
+ const dependencies = {
316
+ ...(parsedPackageJson.dependencies ?? {}),
317
+ ...(await readPreservedHarmonyRootDependencies(projectRoot)),
318
+ };
319
+ for (const adapter of uiStack_1.UI_STACK_VALIDATED_ADAPTERS) {
320
+ const dependencySpecifier = await resolveHarmonyAdapterHarDependency(projectRoot, adapter.adapterPackageName);
321
+ if (dependencySpecifier) {
322
+ dependencies[adapter.adapterPackageName] = dependencySpecifier;
323
+ }
324
+ }
325
+ parsedPackageJson.dependencies = sortRecordByKey(dependencies);
326
+ return json5_1.default.stringify(parsedPackageJson, null, 2) + '\n';
327
+ }
328
+ async function readPreservedHarmonyRootDependencies(projectRoot) {
329
+ const harmonyRootPackagePath = path_1.default.join(projectRoot, 'harmony', 'oh-package.json5');
330
+ if (!(await fs_extra_1.default.pathExists(harmonyRootPackagePath))) {
331
+ return {};
332
+ }
333
+ const currentHarmonyRootPackage = json5_1.default.parse(await fs_extra_1.default.readFile(harmonyRootPackagePath, 'utf8'));
334
+ const preservedDependencies = {};
335
+ const validatedAdapterPackageNames = new Set(uiStack_1.UI_STACK_ADAPTER_PACKAGE_NAMES);
336
+ for (const [packageName, specifier] of Object.entries(currentHarmonyRootPackage.dependencies ?? {})) {
337
+ if (typeof specifier !== 'string') {
338
+ continue;
339
+ }
340
+ if (validatedAdapterPackageNames.has(packageName)) {
341
+ continue;
342
+ }
343
+ preservedDependencies[packageName] = specifier;
344
+ }
345
+ return preservedDependencies;
346
+ }
347
+ async function resolveHarmonyAdapterHarDependency(projectRoot, adapterPackageName) {
348
+ const adapterEntry = uiStack_1.UI_STACK_VALIDATED_ADAPTERS.find((candidate) => candidate.adapterPackageName === adapterPackageName);
349
+ if (!adapterEntry) {
350
+ return null;
351
+ }
352
+ const adapterRoot = path_1.default.join(projectRoot, 'node_modules', ...adapterPackageName.split('/'));
353
+ const harPath = path_1.default.join(adapterRoot, 'harmony', adapterEntry.harmonyHarFileName);
354
+ if (!(await fs_extra_1.default.pathExists(harPath))) {
355
+ return null;
356
+ }
357
+ const relativeHarPath = path_1.default.relative(path_1.default.join(projectRoot, 'harmony'), harPath).replace(/\\/g, '/');
358
+ return `file:${relativeHarPath}`;
359
+ }
360
+ function createEmptyAutolinkingArtifacts(harmonyRootPackageContents, managedAutolinkingEntries) {
361
+ return {
362
+ ohPackageContents: harmonyRootPackageContents,
363
+ etsFactoryContents: injectManagedAutolinkingIntoEtsFactory(`/*
364
+ * This file was generated by Expo Harmony Toolkit autolinking.
365
+ * DO NOT modify it manually, your changes WILL be overwritten.
366
+ */
367
+ import type { RNPackageContext, RNPackage } from '@rnoh/react-native-openharmony';
368
+
369
+ export function createRNOHPackages(_ctx: RNPackageContext): RNPackage[] {
370
+ return [];
371
+ }
372
+ `, managedAutolinkingEntries),
373
+ cppFactoryContents: injectManagedAutolinkingIntoCppFactory(`/*
374
+ * This file was generated by Expo Harmony Toolkit autolinking.
375
+ * DO NOT modify it manually, your changes WILL be overwritten.
376
+ */
377
+ #pragma once
378
+ #include "RNOH/Package.h"
379
+
380
+ std::vector<rnoh::Package::Shared> createRNOHPackages(const rnoh::Package::Context &_ctx) {
381
+ return {};
382
+ }
383
+ `, managedAutolinkingEntries),
384
+ cmakeContents: injectManagedAutolinkingIntoCmake(`# This file was generated by Expo Harmony Toolkit autolinking.
385
+ # DO NOT modify it manually, your changes WILL be overwritten.
386
+ cmake_minimum_required(VERSION 3.5)
387
+
388
+ function(autolink_libraries target)
389
+ set(AUTOLINKED_LIBRARIES
390
+ )
391
+
392
+ foreach(lib \${AUTOLINKED_LIBRARIES})
393
+ target_link_libraries(\${target} PUBLIC \${lib})
394
+ endforeach()
395
+ endfunction()
396
+ `, managedAutolinkingEntries),
397
+ };
398
+ }
399
+ function resolveProjectPackageJson(projectRoot, request) {
400
+ const directPackageJsonPath = path_1.default.join(projectRoot, 'node_modules', ...request.split('/'), 'package.json');
401
+ if (fs_extra_1.default.existsSync(directPackageJsonPath)) {
402
+ return directPackageJsonPath;
403
+ }
404
+ try {
405
+ return require.resolve(path_1.default.join(request, 'package.json'), {
406
+ paths: [projectRoot],
407
+ });
408
+ }
409
+ catch {
410
+ return null;
411
+ }
412
+ }
413
+ function sortRecordByKey(record) {
414
+ return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
415
+ }
416
+ function escapeRegExp(value) {
417
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
418
+ }
419
+ function getErrorMessage(error) {
420
+ return error instanceof Error ? error.message : String(error);
421
+ }
@@ -0,0 +1,19 @@
1
+ import { buildEnvReport } from '../env';
2
+ export interface CommandRunnerResult {
3
+ exitCode: number;
4
+ stdout: string;
5
+ stderr: string;
6
+ }
7
+ export type CommandRunner = (file: string, args: string[], options: {
8
+ cwd: string;
9
+ env: NodeJS.ProcessEnv;
10
+ }) => Promise<CommandRunnerResult>;
11
+ export declare function buildInvocation(file: string, args: string[]): {
12
+ file: string;
13
+ args: string[];
14
+ };
15
+ export declare function createHarmonyBuildEnvironment(runtimeEnv: NodeJS.ProcessEnv, envReport: Awaited<ReturnType<typeof buildEnvReport>>): NodeJS.ProcessEnv;
16
+ export declare function defaultCommandRunner(file: string, args: string[], options: {
17
+ cwd: string;
18
+ env: NodeJS.ProcessEnv;
19
+ }): Promise<CommandRunnerResult>;