@expo-harmony/expo-modules-autolinking 55.0.25-harmony.2 → 55.0.25-harmony.4

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 (51) hide show
  1. package/README.md +7 -1
  2. package/build/index.d.ts +2 -0
  3. package/build/index.js +5 -1
  4. package/build/runtime.d.ts +67 -0
  5. package/build/runtime.js +151 -0
  6. package/build/src/autolinking/link.js +3 -0
  7. package/build/src/autolinking/resolve.d.ts +1 -1
  8. package/build/src/autolinking/verify.js +21 -5
  9. package/build/src/config/constants.d.ts +1 -1
  10. package/build/src/config/constants.js +1 -1
  11. package/build/src/harmony/manifest/generate.js +4 -3
  12. package/build/src/harmony/manifest/index.js +21 -6
  13. package/build/src/harmony/manifest/runtime.d.ts +3 -0
  14. package/build/src/harmony/manifest/runtime.js +96 -0
  15. package/build/src/harmony/materialize.js +73 -44
  16. package/build/src/harmony/providers/host.js +42 -3
  17. package/build/src/harmony/rnoh/cli.d.ts +4 -1
  18. package/build/src/harmony/rnoh/cli.js +41 -18
  19. package/build/src/harmony/rnoh/link.js +56 -34
  20. package/build/src/metadata/descriptor.d.ts +1 -1
  21. package/build/src/metadata/descriptor.js +5 -9
  22. package/build/src/metadata/host.js +14 -1
  23. package/build/src/metadata/schema.js +13 -12
  24. package/build/src/types.d.ts +10 -9
  25. package/build/src/utilities/terminateProcess.d.ts +3 -0
  26. package/build/src/utilities/terminateProcess.js +58 -0
  27. package/build/src/utilities/toolCommand.d.ts +6 -0
  28. package/build/src/utilities/toolCommand.js +18 -0
  29. package/build/src/utilities/values.d.ts +1 -1
  30. package/harmony-module.schema.json +33 -3
  31. package/index.ts +3 -0
  32. package/package.json +15 -9
  33. package/runtime.ts +227 -0
  34. package/scripts/build.mjs +27 -0
  35. package/src/autolinking/link.ts +3 -0
  36. package/src/autolinking/verify.ts +22 -5
  37. package/src/config/constants.ts +1 -1
  38. package/src/harmony/manifest/generate.ts +4 -3
  39. package/src/harmony/manifest/index.ts +24 -8
  40. package/src/harmony/manifest/runtime.ts +111 -0
  41. package/src/harmony/materialize.ts +81 -50
  42. package/src/harmony/providers/host.ts +75 -4
  43. package/src/harmony/rnoh/cli.ts +48 -18
  44. package/src/harmony/rnoh/link.ts +50 -28
  45. package/src/metadata/descriptor.ts +16 -9
  46. package/src/metadata/host.ts +15 -1
  47. package/src/metadata/schema.ts +14 -13
  48. package/src/types.ts +11 -9
  49. package/src/utilities/terminateProcess.ts +53 -0
  50. package/src/utilities/toolCommand.ts +14 -0
  51. package/tsconfig.json +2 -1
package/README.md CHANGED
@@ -4,9 +4,15 @@
4
4
 
5
5
  为 HarmonyOS 上的 React Native 应用提供 Expo Modules 和 RNOH 原生模块自动链接能力,与官方同版本的 expo-modules-autolinking 配套使用。
6
6
 
7
+ ## 安装
8
+
9
+ ```bash
10
+ npm install @expo-harmony/expo-modules-autolinking @react-native-oh/react-native-harmony-cli@^0.84.1
11
+ ```
12
+
7
13
  ## Author
8
14
 
9
15
  **expo-harmony** © [Baoshuo](https://github.com/renbaoshuo), Released under the MIT License.<br>
10
16
  Authored and maintained by Baoshuo with help from [contributors](https://github.com/renbaoshuo/expo-harmony/contributors).
11
17
 
12
- > [Personal Website](https://baoshuo.ren) · [Blog](https://blog.baoshuo.ren) · GitHub [@renbaoshuo](https://github.com/renbaoshuo) · Twitter [@baoshuo](https://twitter.com/baoshuo)
18
+ > [Personal Website](https://baoshuo.ren) · [Blog](https://blog.baoshuo.ren) · GitHub [@renbaoshuo](https://github.com/renbaoshuo) · Twitter [@renbaoshuo](https://twitter.com/renbaoshuo)
package/build/index.d.ts CHANGED
@@ -6,3 +6,5 @@ import { verifyModulesAsync } from './src/autolinking/verify';
6
6
  import { normalizeHarmonyModuleMetadata } from './src/metadata/schema';
7
7
  export { canonicalizeAutolinkingArtifacts, linkModulesAsync, normalizeHarmonyModuleMetadata, resolveModulesAsync, searchModulesAsync, verifyModulesAsync, };
8
8
  export type { BuildType, CanonicalizeAutolinkingArtifactsOptions, CanonicalizedAutolinkingArtifacts, Diagnostic, DiagnosticSource, ExpoMetadata, HostMetadata, HarmonyModuleMetadata, LinkOptions, LinkResult, Logger, Manifest, ModuleDescriptor, ModuleArtifactDescriptor, ModuleSource, Platform, ResolveOptions, RnohMetadata, SearchOptions, SearchRecord, SearchResult, VerificationResult, VerifyOptions, } from './src/types';
9
+ export { createNativeModuleContracts } from './src/harmony/manifest/runtime';
10
+ export { ohpmDependenciesFromManifest } from './src/harmony/manifest';
package/build/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.verifyModulesAsync = exports.searchModulesAsync = exports.resolveModulesAsync = exports.normalizeHarmonyModuleMetadata = exports.linkModulesAsync = exports.canonicalizeAutolinkingArtifacts = void 0;
3
+ exports.ohpmDependenciesFromManifest = exports.createNativeModuleContracts = exports.verifyModulesAsync = exports.searchModulesAsync = exports.resolveModulesAsync = exports.normalizeHarmonyModuleMetadata = exports.linkModulesAsync = exports.canonicalizeAutolinkingArtifacts = void 0;
4
4
  const link_1 = require("./src/autolinking/link");
5
5
  Object.defineProperty(exports, "linkModulesAsync", { enumerable: true, get: function () { return link_1.linkModulesAsync; } });
6
6
  const canonicalize_1 = require("./src/harmony/persistence/canonicalize");
@@ -13,3 +13,7 @@ const verify_1 = require("./src/autolinking/verify");
13
13
  Object.defineProperty(exports, "verifyModulesAsync", { enumerable: true, get: function () { return verify_1.verifyModulesAsync; } });
14
14
  const schema_1 = require("./src/metadata/schema");
15
15
  Object.defineProperty(exports, "normalizeHarmonyModuleMetadata", { enumerable: true, get: function () { return schema_1.normalizeHarmonyModuleMetadata; } });
16
+ var runtime_1 = require("./src/harmony/manifest/runtime");
17
+ Object.defineProperty(exports, "createNativeModuleContracts", { enumerable: true, get: function () { return runtime_1.createNativeModuleContracts; } });
18
+ var manifest_1 = require("./src/harmony/manifest");
19
+ Object.defineProperty(exports, "ohpmDependenciesFromManifest", { enumerable: true, get: function () { return manifest_1.ohpmDependenciesFromManifest; } });
@@ -0,0 +1,67 @@
1
+ export interface HarmonyNativeModule {
2
+ packageName: string;
3
+ packageVersion: string;
4
+ modules: string[];
5
+ services: string[];
6
+ registrations: string[];
7
+ artifacts: string[];
8
+ }
9
+ export interface HarmonyRuntimeContract {
10
+ schemaVersion: 1;
11
+ platform: 'harmony';
12
+ runtimeVersion: string;
13
+ development: boolean;
14
+ expo: string;
15
+ expoModulesCore: string;
16
+ rnoh: string;
17
+ react: string;
18
+ hermes: string;
19
+ engine: 'hermes-v1';
20
+ bundleFormat: 'js-source';
21
+ renderer: 'rnoh';
22
+ modules: HarmonyNativeModule[];
23
+ config: {
24
+ nativeCompiler: string;
25
+ targetApiVersion: number;
26
+ compatibleApiVersion: number;
27
+ permissions: string[];
28
+ querySchemes: string[];
29
+ backgroundModes: string[];
30
+ abiFilters: string[];
31
+ };
32
+ }
33
+ export interface HarmonyCompatibilityIssue {
34
+ code: string;
35
+ field: string;
36
+ message: string;
37
+ }
38
+ export declare class HarmonyCompatibilityError extends Error {
39
+ readonly issues: HarmonyCompatibilityIssue[];
40
+ readonly code = "ERR_HARMONY_INCOMPATIBLE";
41
+ constructor(issues: HarmonyCompatibilityIssue[]);
42
+ }
43
+ export declare function validateHarmonyRuntime(value: unknown): HarmonyCompatibilityIssue[];
44
+ export declare function checkHarmonyCompatibility(client: unknown, requirements: unknown): HarmonyCompatibilityIssue[];
45
+ export declare function assertHarmonyCompatibility(client: unknown, requirements: unknown): asserts requirements is HarmonyRuntimeContract;
46
+ export interface HarmonyDevelopmentManifest {
47
+ id: string;
48
+ createdAt: string;
49
+ metadata: Record<string, string>;
50
+ assets: unknown[];
51
+ runtimeVersion: string;
52
+ launchAsset: {
53
+ key: 'bundle';
54
+ contentType: 'application/javascript';
55
+ url: string;
56
+ };
57
+ extra: {
58
+ expoClient: Record<string, unknown>;
59
+ expoGo: Record<string, unknown>;
60
+ harmony: {
61
+ projectId: string;
62
+ manifestUrl: string;
63
+ requirements: HarmonyRuntimeContract;
64
+ };
65
+ };
66
+ }
67
+ export declare function assertHarmonyDevelopmentManifest(client: unknown, value: unknown): asserts value is HarmonyDevelopmentManifest;
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HarmonyCompatibilityError = void 0;
4
+ exports.validateHarmonyRuntime = validateHarmonyRuntime;
5
+ exports.checkHarmonyCompatibility = checkHarmonyCompatibility;
6
+ exports.assertHarmonyCompatibility = assertHarmonyCompatibility;
7
+ exports.assertHarmonyDevelopmentManifest = assertHarmonyDevelopmentManifest;
8
+ class HarmonyCompatibilityError extends Error {
9
+ issues;
10
+ code = 'ERR_HARMONY_INCOMPATIBLE';
11
+ constructor(issues) {
12
+ super(issues.map(issue => `[${issue.code}] ${issue.message}`).join('\n'));
13
+ this.issues = issues;
14
+ this.name = 'HarmonyCompatibilityError';
15
+ }
16
+ }
17
+ exports.HarmonyCompatibilityError = HarmonyCompatibilityError;
18
+ function record(value) {
19
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
20
+ }
21
+ function token(value) {
22
+ return typeof value === 'string' && /^[A-Za-z0-9@][A-Za-z0-9@._+:()/-]{0,199}$/.test(value)
23
+ && !value.includes('://') && !value.includes('..') && !/^[A-Za-z]:/.test(value);
24
+ }
25
+ function strings(value) {
26
+ return Array.isArray(value) && value.every(token) && new Set(value).size === value.length;
27
+ }
28
+ const Versions = ['runtimeVersion', 'expo', 'expoModulesCore', 'rnoh', 'react', 'hermes'];
29
+ const Settings = ['permissions', 'querySchemes', 'backgroundModes', 'abiFilters'];
30
+ function validateHarmonyRuntime(value) {
31
+ if (!record(value))
32
+ return [{ code: 'INVALID_CONTRACT', field: '', message: 'Runtime contract must be an object.' }];
33
+ const issues = [];
34
+ for (const [field, expected] of Object.entries({ schemaVersion: 1, platform: 'harmony', engine: 'hermes-v1', bundleFormat: 'js-source', renderer: 'rnoh' })) {
35
+ if (value[field] !== expected) {
36
+ issues.push({ code: field === 'platform' ? 'PLATFORM_MISMATCH' : 'INVALID_CONTRACT', field, message: `${field} must be ${expected}.` });
37
+ }
38
+ }
39
+ if (typeof value.development !== 'boolean') {
40
+ issues.push({ code: 'INVALID_CONTRACT', field: 'development', message: 'development must describe native development support.' });
41
+ }
42
+ for (const field of Versions) {
43
+ if (field === 'runtimeVersion' ? typeof value[field] !== 'string' || !value[field] : !token(value[field])) {
44
+ issues.push({ code: 'INVALID_CONTRACT', field, message: `${field} must be an explicit version.` });
45
+ }
46
+ }
47
+ if (!Array.isArray(value.modules)) {
48
+ issues.push({ code: 'INVALID_CONTRACT', field: 'modules', message: 'Native modules must be an array.' });
49
+ }
50
+ else {
51
+ const names = new Set();
52
+ for (const module of value.modules) {
53
+ if (!record(module) || !token(module.packageName) || !token(module.packageVersion)
54
+ || !strings(module.modules) || !strings(module.services) || !strings(module.registrations)
55
+ || !strings(module.artifacts) || module.artifacts.some(hash => !/^[a-f0-9]{64}$/.test(hash))
56
+ || names.has(module.packageName)) {
57
+ issues.push({ code: 'INVALID_CONTRACT', field: 'modules', message: 'Invalid or duplicate native module contract.' });
58
+ }
59
+ else {
60
+ names.add(module.packageName);
61
+ }
62
+ }
63
+ }
64
+ const config = value.config;
65
+ if (!record(config) || !token(config.nativeCompiler) || !Settings.every(field => strings(config[field]))
66
+ || !Number.isInteger(config.targetApiVersion) || Number(config.targetApiVersion) < 13
67
+ || !Number.isInteger(config.compatibleApiVersion) || Number(config.compatibleApiVersion) < 13
68
+ || Number(config.compatibleApiVersion) > Number(config.targetApiVersion)
69
+ || (Array.isArray(config.abiFilters) && config.abiFilters.length === 0)) {
70
+ issues.push({ code: 'INVALID_CONTRACT', field: 'config', message: 'Invalid native build configuration.' });
71
+ }
72
+ return issues;
73
+ }
74
+ function checkHarmonyCompatibility(client, requirements) {
75
+ const issues = [
76
+ ...validateHarmonyRuntime(client).map(issue => ({ ...issue, field: `client.${issue.field}` })),
77
+ ...validateHarmonyRuntime(requirements).map(issue => ({ ...issue, field: `requirements.${issue.field}` })),
78
+ ];
79
+ if (issues.length > 0)
80
+ return issues;
81
+ const available = client;
82
+ const required = requirements;
83
+ for (const field of Versions) {
84
+ if (available[field] !== required[field]) {
85
+ issues.push({ code: 'RUNTIME_MISMATCH', field, message: `${field}: project requires ${required[field]}, client provides ${available[field]}. Rebuild or select a matching client.` });
86
+ }
87
+ }
88
+ if (required.development && !available.development) {
89
+ issues.push({ code: 'CONFIG_MISMATCH', field: 'development', message: 'This client was built without native development support. Use a debug client for development manifests.' });
90
+ }
91
+ for (const module of required.modules) {
92
+ const found = available.modules.find(item => item.packageName === module.packageName);
93
+ if (!found) {
94
+ issues.push({ code: 'MISSING_NATIVE_MODULE', field: module.packageName, message: `Client is missing native package ${module.packageName}. Rebuild the client with this package.` });
95
+ continue;
96
+ }
97
+ if (found.packageVersion !== module.packageVersion
98
+ || ['modules', 'services', 'registrations', 'artifacts'].some((field) => {
99
+ const left = found[field];
100
+ const right = module[field];
101
+ return left.length !== right.length || right.some(item => !left.includes(item));
102
+ })) {
103
+ issues.push({ code: 'NATIVE_MODULE_MISMATCH', field: module.packageName, message: `Native contract differs for ${module.packageName}. Rebuild or select a matching client.` });
104
+ }
105
+ }
106
+ for (const field of ['nativeCompiler', 'targetApiVersion', 'compatibleApiVersion']) {
107
+ if (available.config[field] !== required.config[field]) {
108
+ issues.push({ code: 'CONFIG_MISMATCH', field, message: `${field}: project requires ${required.config[field]}, client provides ${available.config[field]}.` });
109
+ }
110
+ }
111
+ for (const field of Settings) {
112
+ for (const item of required.config[field]) {
113
+ if (!available.config[field].includes(item)) {
114
+ issues.push({ code: 'CONFIG_MISMATCH', field, message: `Client build is missing ${field}: ${item}. Rebuild the client with this configuration.` });
115
+ }
116
+ }
117
+ }
118
+ return issues;
119
+ }
120
+ function assertHarmonyCompatibility(client, requirements) {
121
+ const issues = checkHarmonyCompatibility(client, requirements);
122
+ if (issues.length > 0)
123
+ throw new HarmonyCompatibilityError(issues);
124
+ }
125
+ function assertHarmonyDevelopmentManifest(client, value) {
126
+ if (!record(value) || typeof value.id !== 'string'
127
+ || !/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(value.id)
128
+ || typeof value.createdAt !== 'string' || !Number.isFinite(Date.parse(value.createdAt))
129
+ || !record(value.metadata) || !Object.values(value.metadata).every(item => typeof item === 'string')
130
+ || !Array.isArray(value.assets) || value.assets.length !== 0 || !record(value.launchAsset)
131
+ || value.launchAsset.key !== 'bundle' || value.launchAsset.contentType !== 'application/javascript'
132
+ || typeof value.launchAsset.url !== 'string' || !record(value.extra) || !record(value.extra.expoClient)
133
+ || !record(value.extra.expoGo) || !record(value.extra.harmony)
134
+ || typeof value.extra.harmony.projectId !== 'string' || !value.extra.harmony.projectId
135
+ || typeof value.extra.harmony.manifestUrl !== 'string') {
136
+ throw new HarmonyCompatibilityError([{ code: 'INVALID_MANIFEST', field: '', message: 'Expected an Expo development JS source manifest with Harmony requirements.' }]);
137
+ }
138
+ const harmony = value.extra.harmony;
139
+ assertHarmonyCompatibility(client, harmony.requirements);
140
+ const origin = /^(https?:\/\/(?:[A-Za-z0-9.-]+|\[[A-Fa-f0-9:.]+\])(?::[0-9]{1,5})?)\/(?:manifest|index\.exp)?\?platform=harmony$/.exec(harmony.manifestUrl)?.[1];
141
+ const bundle = value.launchAsset.url;
142
+ const query = bundle.split('?')[1]?.split('&') || [];
143
+ if (value.runtimeVersion !== harmony.requirements.runtimeVersion || !origin
144
+ || !bundle.startsWith(`${origin}/`) || !bundle.split('?')[0].endsWith('.bundle')
145
+ || bundle.includes('#') || query.filter(item => item.startsWith('platform=')).join() !== 'platform=harmony'
146
+ || query.filter(item => item.startsWith('transform.bytecode=')).join() !== 'transform.bytecode=0'
147
+ || query.filter(item => item.startsWith('dev=')).join() !== 'dev=true'
148
+ || query.filter(item => item.startsWith('transform.engine=')).join() !== 'transform.engine=hermes') {
149
+ throw new HarmonyCompatibilityError([{ code: 'INVALID_MANIFEST', field: 'launchAsset', message: 'Manifest and bundle must use the same HTTP(S) origin, runtime, Harmony platform, and development JS source format.' }]);
150
+ }
151
+ }
@@ -8,6 +8,7 @@ const node_path_1 = __importDefault(require("node:path"));
8
8
  const constants_1 = require("../config/constants");
9
9
  const host_1 = require("../harmony/providers/host");
10
10
  const generate_1 = require("../harmony/manifest/generate");
11
+ const runtime_1 = require("../harmony/manifest/runtime");
11
12
  const errors_1 = require("../errors");
12
13
  const options_1 = require("../config/options");
13
14
  const link_1 = require("../harmony/rnoh/link");
@@ -49,6 +50,7 @@ async function linkModulesAsync(rawOptions) {
49
50
  throw new errors_1.HarmonyAutolinkingError('SOURCE_ARTIFACT_MATERIALIZATION_REQUIRED', 'Only nativeModulesDir modules may build a local Harmony HAR.', { stage: 'link', details: pendingSource.map(module => module.packageName).sort() });
50
51
  }
51
52
  await (0, materialize_1.materializeLocalSourcesAsync)(pendingSource, {
53
+ dependencies: modules,
52
54
  projectRoot: options.projectRoot,
53
55
  timeoutMs: rawOptions.timeoutMs,
54
56
  outputLimit: rawOptions.outputLimit,
@@ -93,6 +95,7 @@ async function linkModulesAsync(rawOptions) {
93
95
  const manifest = (0, generate_1.serializeManifest)(modules, {
94
96
  buildType: options.buildType,
95
97
  managedArtifacts: managed,
98
+ runtimeModules: (0, runtime_1.createNativeModuleContracts)(modules),
96
99
  });
97
100
  const published = await (0, publish_1.publishArtifactsAsync)({
98
101
  allowedRoot: staging.projectRoot,
@@ -1,4 +1,4 @@
1
1
  import type { ModuleDescriptor, ResolveOptions } from '../types';
2
- declare function compareModuleDescriptors(left: any, right: any): 1 | -1 | 0;
2
+ declare function compareModuleDescriptors(left: any, right: any): 1 | 0 | -1;
3
3
  declare function resolveModulesAsync(options?: ResolveOptions): Promise<ModuleDescriptor[]>;
4
4
  export { compareModuleDescriptors, resolveModulesAsync, };
@@ -183,9 +183,14 @@ function searchRecordFromDescriptor(descriptor, declared) {
183
183
  });
184
184
  }
185
185
  const host = host_1.HostMetadataFields.some(field => descriptor.expo[field]?.length > 0);
186
- const modules = Array.isArray(descriptor.harmony.modules)
187
- ? [...descriptor.harmony.modules]
188
- : [];
186
+ if (!Array.isArray(descriptor.harmony.modules) || !Array.isArray(descriptor.harmony.services)) {
187
+ throw new errors_1.HarmonyAutolinkingError('INVALID_METADATA', 'descriptor.harmony must declare module and service class lists.', {
188
+ packageName: descriptor.packageName,
189
+ stage: 'verify',
190
+ });
191
+ }
192
+ const modules = [...descriptor.harmony.modules];
193
+ const services = [...descriptor.harmony.services];
189
194
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
190
195
  const rnoh = {};
191
196
  if (descriptor.rnoh.ohPackageName !== undefined) {
@@ -214,12 +219,13 @@ function searchRecordFromDescriptor(descriptor, declared) {
214
219
  packageVersion: descriptor.packageVersion,
215
220
  packageRoot: descriptor.packageRoot,
216
221
  source: descriptor.source,
217
- supportsHarmony: modules.length > 0 || host,
218
- expoModuleConfig: modules.length > 0 || host
222
+ supportsHarmony: modules.length > 0 || services.length > 0 || host,
223
+ expoModuleConfig: modules.length > 0 || services.length > 0 || host
219
224
  ? {
220
225
  platforms: ['harmony'],
221
226
  harmony: {
222
227
  modules,
228
+ services,
223
229
  ...Object.fromEntries(host_1.HostMetadataFields.map(field => [
224
230
  field,
225
231
  [...(descriptor.expo[field] || [])],
@@ -318,6 +324,16 @@ async function verifyModulesAsync(options = {}) {
318
324
  }
319
325
  descriptors.sort(resolve_1.compareModuleDescriptors);
320
326
  diagnostics.push(...collectDescriptorConflicts(descriptors));
327
+ if (descriptors.some(module => module.packageName === '@expo-harmony/expo-modules-core')
328
+ && !descriptors.some(module => module.packageName === '@expo-harmony/expo')) {
329
+ diagnostics.push({
330
+ severity: 'error',
331
+ code: 'ERR_EXPO_HARMONY_HOST_NOT_FOUND',
332
+ message: 'Install @expo-harmony/expo to provide the native Expo host and module registration entry.',
333
+ packageName: '@expo-harmony/expo',
334
+ stage: 'verify',
335
+ });
336
+ }
321
337
  if (searchResult) {
322
338
  const seen = new Map();
323
339
  for (const descriptor of descriptors) {
@@ -1,7 +1,7 @@
1
1
  declare const Platform = "harmony";
2
2
  declare const RnohCliPackage = "@react-native-oh/react-native-harmony-cli";
3
3
  declare const ToolVersion: any;
4
- declare const ManifestSchemaVersion = 3;
4
+ declare const ManifestSchemaVersion = 4;
5
5
  declare const RnohArtifacts: Readonly<{
6
6
  etsFactory: "entry/src/main/ets/RNOHPackagesFactory.ets";
7
7
  cppFactory: "entry/src/main/cpp/RNOHPackagesFactory.h";
@@ -13,7 +13,7 @@ const RnohCliPackage = '@react-native-oh/react-native-harmony-cli';
13
13
  exports.RnohCliPackage = RnohCliPackage;
14
14
  const ToolVersion = JSON.parse((0, node_fs_1.readFileSync)(node_path_1.default.resolve(__dirname, '../../../package.json'), 'utf8')).version;
15
15
  exports.ToolVersion = ToolVersion;
16
- const ManifestSchemaVersion = 3;
16
+ const ManifestSchemaVersion = 4;
17
17
  exports.ManifestSchemaVersion = ManifestSchemaVersion;
18
18
  const RnohArtifacts = Object.freeze({
19
19
  etsFactory: 'entry/src/main/ets/RNOHPackagesFactory.ets',
@@ -4,6 +4,7 @@ exports.createManifest = createManifest;
4
4
  exports.serializeManifest = serializeManifest;
5
5
  const constants_1 = require("../../config/constants");
6
6
  const values_1 = require("../../utilities/values");
7
+ const host_1 = require("../../metadata/host");
7
8
  function cloneOhPackageName(value) {
8
9
  if (Array.isArray(value))
9
10
  return value.map(mapping => ({ ...mapping }));
@@ -18,11 +19,10 @@ function createManifestEntry(descriptor) {
18
19
  source: descriptor.source,
19
20
  harmony: {
20
21
  modules: [...descriptor.harmony.modules],
22
+ services: [...descriptor.harmony.services],
21
23
  },
22
24
  ...(descriptor.arkTs ? { arkTs: { ...descriptor.arkTs } } : {}),
23
- expo: {
24
- rootViewComponents: [...descriptor.expo.rootViewComponents],
25
- },
25
+ expo: Object.fromEntries(host_1.HostMetadataFields.map(field => [field, [...descriptor.expo[field]]])),
26
26
  rnoh: {
27
27
  ...(descriptor.rnoh.ohPackageName !== undefined
28
28
  ? { ohPackageName: cloneOhPackageName(descriptor.rnoh.ohPackageName) }
@@ -45,6 +45,7 @@ function createManifest(modules, options = {}) {
45
45
  schemaVersion: constants_1.ManifestSchemaVersion,
46
46
  platform: constants_1.Platform,
47
47
  buildType,
48
+ ...(options.runtimeModules ? { runtimeModules: options.runtimeModules } : {}),
48
49
  modules: modules.map(descriptor => createManifestEntry(descriptor)),
49
50
  managedArtifacts: [...(options.managedArtifacts || constants_1.ManagedArtifacts)],
50
51
  };
@@ -11,7 +11,13 @@ const errors_1 = require("../../errors");
11
11
  const dependencies_1 = require("../ohpm/dependencies");
12
12
  const schema_1 = require("../../metadata/schema");
13
13
  const values_1 = require("../../utilities/values");
14
+ const host_1 = require("../../metadata/host");
14
15
  const ModuleSources = new Set(['dependency', 'searchPath', 'nativeModulesDir', 'reactNativeProjectConfig']);
16
+ function isClassList(value) {
17
+ return Array.isArray(value)
18
+ && value.every(name => typeof name === 'string' && /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name))
19
+ && new Set(value).size === value.length;
20
+ }
15
21
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
22
  function validateManifest(manifest, options = {}) {
17
23
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -38,21 +44,30 @@ function validateManifest(manifest, options = {}) {
38
44
  || typeof entry.packageLinkPath !== 'string'
39
45
  || !node_path_1.default.isAbsolute(entry.packageLinkPath)
40
46
  || !ModuleSources.has(entry.source)
41
- || (entry.harmony !== undefined && (!(0, values_1.isObject)(entry.harmony)
42
- || !Array.isArray(entry.harmony.modules)
43
- || entry.harmony.modules.some(moduleClass => typeof moduleClass !== 'string' || !moduleClass)))
47
+ || !(0, values_1.isObject)(entry.harmony)
48
+ || !isClassList(entry.harmony.modules)
49
+ || !isClassList(entry.harmony.services)
44
50
  || (entry.arkTs !== undefined && (!(0, values_1.isObject)(entry.arkTs)
45
51
  || entry.arkTs.harPath !== 'harmony/library.har'
46
52
  || !(0, schema_1.isValidOhpmPackageName)(entry.arkTs.ohPackageName)))
47
- || (entry.harmony?.modules.length > 0 && !(0, values_1.isObject)(entry.arkTs))
53
+ || ((entry.harmony.modules.length > 0 || entry.harmony.services.length > 0) && !(0, values_1.isObject)(entry.arkTs))
48
54
  || !(0, values_1.isObject)(entry.expo)
49
- || !Array.isArray(entry.expo.rootViewComponents)
55
+ || !isClassList(entry.expo.rootViewComponents)
56
+ || !host_1.HostMetadataFields.every(field => isClassList(entry.expo[field] ?? []))
57
+ || (host_1.HostMetadataFields.some(field => entry.expo[field]?.length > 0) && !(0, values_1.isObject)(entry.arkTs))
50
58
  || !(0, values_1.isObject)(entry.rnoh)
51
59
  || !Array.isArray(entry.rnoh.harPaths)) {
52
60
  throw new errors_1.HarmonyAutolinkingError('INVALID_MANIFEST', `Harmony autolinking manifest module ${index} is invalid.`, { details: options.file ? { file: options.file } : undefined, stage: 'manifest' });
53
61
  }
54
62
  }
55
- return structuredClone(candidate);
63
+ // Schema 4 manifests generated before lifecycle contributions remain readable
64
+ // for stale dependency cleanup. New manifests always contain the full lists.
65
+ const normalized = structuredClone(candidate);
66
+ normalized.modules.forEach((entry) => {
67
+ for (const field of host_1.HostMetadataFields)
68
+ entry.expo[field] ??= [];
69
+ });
70
+ return normalized;
56
71
  }
57
72
  function ohpmDependenciesFromManifest(manifest, options = {}) {
58
73
  const validated = validateManifest(manifest);
@@ -0,0 +1,3 @@
1
+ import type { HarmonyNativeModule } from '../../../runtime';
2
+ import type { ModuleDescriptor } from '../../types';
3
+ export declare function createNativeModuleContracts(modules: readonly ModuleDescriptor[]): HarmonyNativeModule[];
@@ -0,0 +1,96 @@
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.createNativeModuleContracts = createNativeModuleContracts;
7
+ const node_crypto_1 = __importDefault(require("node:crypto"));
8
+ const node_fs_1 = __importDefault(require("node:fs"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const tar_1 = __importDefault(require("tar"));
11
+ const errors_1 = require("../../errors");
12
+ function digest(file) {
13
+ if (!node_fs_1.default.statSync(file).isFile()) {
14
+ throw new errors_1.HarmonyAutolinkingError('RUNTIME_ARTIFACT', 'Native runtime artifacts must be regular files.', { stage: 'runtime-contract' });
15
+ }
16
+ const entries = [];
17
+ const names = new Set();
18
+ tar_1.default.t({
19
+ file,
20
+ sync: true,
21
+ strict: true,
22
+ onentry(entry) {
23
+ if (entry.type === 'Directory')
24
+ return;
25
+ if (entry.type !== 'File' || names.has(entry.path)) {
26
+ throw new errors_1.HarmonyAutolinkingError('RUNTIME_ARTIFACT', 'Native HARs must contain unique regular files.', { stage: 'runtime-contract' });
27
+ }
28
+ names.add(entry.path);
29
+ const hash = node_crypto_1.default.createHash('sha256');
30
+ entry.on('data', chunk => hash.update(chunk));
31
+ entry.on('end', () => entries.push(`${entry.path}\0${hash.digest('hex')}`));
32
+ },
33
+ });
34
+ if (entries.length === 0) {
35
+ throw new errors_1.HarmonyAutolinkingError('RUNTIME_ARTIFACT', 'Native HARs must not be empty.', { stage: 'runtime-contract' });
36
+ }
37
+ // Archive timestamps, owners, and entry order are not native runtime inputs.
38
+ return node_crypto_1.default.createHash('sha256').update(entries.sort().join('\0')).digest('hex');
39
+ }
40
+ function sourceDigest(root) {
41
+ const hash = node_crypto_1.default.createHash('sha256');
42
+ const ignored = new Set(['build', '.hvigor', '.cxx', 'oh_modules', 'node_modules', '.git', 'oh-package-lock.json5']);
43
+ const visit = (directory) => {
44
+ const entries = node_fs_1.default.readdirSync(directory, { withFileTypes: true })
45
+ .sort((left, right) => left.name < right.name ? -1 : 1);
46
+ for (const entry of entries) {
47
+ if (ignored.has(entry.name))
48
+ continue;
49
+ const file = node_path_1.default.join(directory, entry.name);
50
+ if (entry.isDirectory())
51
+ visit(file);
52
+ else if (entry.isFile() && /\.(ets|ts|cpp|c|h|hpp|cmake|json|json5|so|a)$|CMakeLists\.txt$/.test(entry.name)) {
53
+ hash.update(node_path_1.default.relative(root, file).split(node_path_1.default.sep).join('/'));
54
+ hash.update('\0');
55
+ hash.update(Uint8Array.from(node_fs_1.default.readFileSync(file)));
56
+ hash.update('\0');
57
+ }
58
+ }
59
+ };
60
+ visit(root);
61
+ return hash.digest('hex');
62
+ }
63
+ function createNativeModuleContracts(modules) {
64
+ return modules.map((module) => {
65
+ const files = [...module.rnoh.harPaths, ...(module.arkTs ? [module.arkTs.harPath] : [])]
66
+ .map(file => node_path_1.default.resolve(module.packageRoot, file));
67
+ let artifacts;
68
+ try {
69
+ artifacts = [...new Set(files.map(digest))];
70
+ if (module.source === 'nativeModulesDir')
71
+ artifacts.push(sourceDigest(node_path_1.default.join(module.packageRoot, 'harmony')));
72
+ artifacts.sort();
73
+ }
74
+ catch (cause) {
75
+ throw new errors_1.HarmonyAutolinkingError('RUNTIME_ARTIFACT', `Build the native HAR for ${module.packageName} before publishing or requesting its runtime contract.`, { cause, packageName: module.packageName, stage: 'runtime-contract' });
76
+ }
77
+ const registrations = [
78
+ ...module.expo.rootViewComponents.map(name => `view:${name}`),
79
+ ...module.expo.appLifecycleSubscribers.map(name => `app:${name}`),
80
+ ...module.expo.abilityLifecycleSubscribers.map(name => `ability:${name}`),
81
+ ...module.expo.reactNativeHostHandlers.map(name => `host:${name}`),
82
+ ...module.expo.reactActivityHandlers.map(name => `activity:${name}`),
83
+ ...module.expo.runtimeBindings.map(name => `binding:${name}`),
84
+ ...['etsPackageClassName', 'etsPackageImport', 'cppPackageClassName', 'cmakeLibraryTargetName']
85
+ .flatMap(field => module.rnoh[field] ? [`${field}:${module.rnoh[field]}`] : []),
86
+ ];
87
+ return {
88
+ packageName: module.packageName,
89
+ packageVersion: module.packageVersion,
90
+ modules: [...module.harmony.modules].sort(),
91
+ services: [...module.harmony.services].sort(),
92
+ registrations: registrations.sort(),
93
+ artifacts,
94
+ };
95
+ }).sort((left, right) => left.packageName < right.packageName ? -1 : left.packageName > right.packageName ? 1 : 0);
96
+ }