@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.
- package/README.md +7 -1
- package/build/index.d.ts +2 -0
- package/build/index.js +5 -1
- package/build/runtime.d.ts +67 -0
- package/build/runtime.js +151 -0
- package/build/src/autolinking/link.js +3 -0
- package/build/src/autolinking/resolve.d.ts +1 -1
- package/build/src/autolinking/verify.js +21 -5
- package/build/src/config/constants.d.ts +1 -1
- package/build/src/config/constants.js +1 -1
- package/build/src/harmony/manifest/generate.js +4 -3
- package/build/src/harmony/manifest/index.js +21 -6
- package/build/src/harmony/manifest/runtime.d.ts +3 -0
- package/build/src/harmony/manifest/runtime.js +96 -0
- package/build/src/harmony/materialize.js +73 -44
- package/build/src/harmony/providers/host.js +42 -3
- package/build/src/harmony/rnoh/cli.d.ts +4 -1
- package/build/src/harmony/rnoh/cli.js +41 -18
- package/build/src/harmony/rnoh/link.js +56 -34
- package/build/src/metadata/descriptor.d.ts +1 -1
- package/build/src/metadata/descriptor.js +5 -9
- package/build/src/metadata/host.js +14 -1
- package/build/src/metadata/schema.js +13 -12
- package/build/src/types.d.ts +10 -9
- package/build/src/utilities/terminateProcess.d.ts +3 -0
- package/build/src/utilities/terminateProcess.js +58 -0
- package/build/src/utilities/toolCommand.d.ts +6 -0
- package/build/src/utilities/toolCommand.js +18 -0
- package/build/src/utilities/values.d.ts +1 -1
- package/harmony-module.schema.json +33 -3
- package/index.ts +3 -0
- package/package.json +15 -9
- package/runtime.ts +227 -0
- package/scripts/build.mjs +27 -0
- package/src/autolinking/link.ts +3 -0
- package/src/autolinking/verify.ts +22 -5
- package/src/config/constants.ts +1 -1
- package/src/harmony/manifest/generate.ts +4 -3
- package/src/harmony/manifest/index.ts +24 -8
- package/src/harmony/manifest/runtime.ts +111 -0
- package/src/harmony/materialize.ts +81 -50
- package/src/harmony/providers/host.ts +75 -4
- package/src/harmony/rnoh/cli.ts +48 -18
- package/src/harmony/rnoh/link.ts +50 -28
- package/src/metadata/descriptor.ts +16 -9
- package/src/metadata/host.ts +15 -1
- package/src/metadata/schema.ts +14 -13
- package/src/types.ts +11 -9
- package/src/utilities/terminateProcess.ts +53 -0
- package/src/utilities/toolCommand.ts +14 -0
- 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 [@
|
|
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;
|
package/build/runtime.js
ADDED
|
@@ -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 |
|
|
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
|
-
|
|
187
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
||
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
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
|
-
|| !
|
|
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
|
-
|
|
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,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
|
+
}
|