expo-harmony-toolkit 1.8.0 → 1.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +3 -2
- package/README.md +3 -2
- package/build/core/autolinking.js +66 -44
- package/build/core/build/compatibilityShims.js +64 -8
- package/build/core/build/localHar.d.ts +2 -0
- package/build/core/build/localHar.js +57 -0
- package/build/core/build/rnohCompatibility.js +34 -1
- package/build/core/build.js +2 -0
- package/build/core/constants.d.ts +1 -1
- package/build/core/constants.js +1 -1
- package/build/core/project.d.ts +3 -1
- package/build/core/project.js +47 -0
- package/build/core/report.js +47 -11
- package/build/core/template.js +5 -1
- package/build/data/capabilities.d.ts +6 -2
- package/build/data/capabilities.js +8 -4
- package/build/data/dependencyCatalog.js +248 -0
- package/build/data/publicDocs.d.ts +1 -1
- package/build/data/uiStack.d.ts +166 -0
- package/build/data/uiStack.js +142 -1
- package/build/data/validatedMatrices.js +4 -1
- package/build/plugin.js +4 -0
- package/build/types.d.ts +12 -0
- package/docs/npm-release.md +1 -0
- package/docs/signing-and-release.md +42 -0
- package/docs/support-matrix.md +3 -0
- package/package.json +1 -1
- package/templates/harmony/entry/build-profile.json5 +11 -0
package/build/core/project.js
CHANGED
|
@@ -10,6 +10,8 @@ exports.findAppConfigPath = findAppConfigPath;
|
|
|
10
10
|
exports.collectDeclaredDependencies = collectDeclaredDependencies;
|
|
11
11
|
exports.hasDeclaredDependency = hasDeclaredDependency;
|
|
12
12
|
exports.collectExpoPlugins = collectExpoPlugins;
|
|
13
|
+
exports.resolveExpoHarmonyDoctorConfig = resolveExpoHarmonyDoctorConfig;
|
|
14
|
+
exports.applyDependencyExclusions = applyDependencyExclusions;
|
|
13
15
|
exports.collectExpoSchemes = collectExpoSchemes;
|
|
14
16
|
exports.deriveHarmonyIdentifiers = deriveHarmonyIdentifiers;
|
|
15
17
|
exports.detectExpoSdkVersion = detectExpoSdkVersion;
|
|
@@ -100,6 +102,27 @@ function collectExpoPlugins(expoConfig) {
|
|
|
100
102
|
}
|
|
101
103
|
return [...names].sort((left, right) => left.localeCompare(right));
|
|
102
104
|
}
|
|
105
|
+
function resolveExpoHarmonyDoctorConfig(expoConfig) {
|
|
106
|
+
const rawDoctorConfig = expoConfig.extra?.expoHarmony?.doctor;
|
|
107
|
+
const coverageProfile = normalizeCoverageProfile(rawDoctorConfig?.coverageProfile);
|
|
108
|
+
return {
|
|
109
|
+
excludeDependencies: normalizeStringArray(rawDoctorConfig?.excludeDependencies),
|
|
110
|
+
excludePlugins: normalizeStringArray(rawDoctorConfig?.excludePlugins),
|
|
111
|
+
coverageProfile,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function applyDependencyExclusions(packageJson, excludedDependencies) {
|
|
115
|
+
if (excludedDependencies.length === 0) {
|
|
116
|
+
return packageJson;
|
|
117
|
+
}
|
|
118
|
+
const excludedDependencySet = new Set(excludedDependencies);
|
|
119
|
+
return {
|
|
120
|
+
...packageJson,
|
|
121
|
+
dependencies: filterDependencySection(packageJson.dependencies, excludedDependencySet),
|
|
122
|
+
devDependencies: filterDependencySection(packageJson.devDependencies, excludedDependencySet),
|
|
123
|
+
peerDependencies: filterDependencySection(packageJson.peerDependencies, excludedDependencySet),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
103
126
|
function collectExpoSchemes(expoConfig) {
|
|
104
127
|
const rawScheme = expoConfig.scheme;
|
|
105
128
|
if (typeof rawScheme === 'string' && rawScheme.trim().length > 0) {
|
|
@@ -204,3 +227,27 @@ function sanitizeIdentifierSegment(value) {
|
|
|
204
227
|
.replace(/^_+/, '');
|
|
205
228
|
return cleaned.length > 0 ? cleaned : 'entry';
|
|
206
229
|
}
|
|
230
|
+
function normalizeStringArray(value) {
|
|
231
|
+
if (!Array.isArray(value)) {
|
|
232
|
+
return [];
|
|
233
|
+
}
|
|
234
|
+
return [...new Set(value
|
|
235
|
+
.filter((entry) => typeof entry === 'string')
|
|
236
|
+
.map((entry) => entry.trim())
|
|
237
|
+
.filter((entry) => entry.length > 0))].sort((left, right) => left.localeCompare(right));
|
|
238
|
+
}
|
|
239
|
+
function normalizeCoverageProfile(value) {
|
|
240
|
+
if (value === 'managed-core' ||
|
|
241
|
+
value === 'managed-native-heavy' ||
|
|
242
|
+
value === 'bare' ||
|
|
243
|
+
value === 'third-party-native-heavy') {
|
|
244
|
+
return value;
|
|
245
|
+
}
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
function filterDependencySection(section, excludedDependencies) {
|
|
249
|
+
if (!section) {
|
|
250
|
+
return section;
|
|
251
|
+
}
|
|
252
|
+
return Object.fromEntries(Object.entries(section).filter(([dependencyName]) => !excludedDependencies.has(dependencyName)));
|
|
253
|
+
}
|
package/build/core/report.js
CHANGED
|
@@ -27,13 +27,16 @@ const BARE_WORKFLOW_DIRECTORY_NAMES = ['android', 'ios'];
|
|
|
27
27
|
const BARE_WORKFLOW_DEPENDENCIES = new Set(['expo-build-properties', 'expo-dev-client']);
|
|
28
28
|
async function buildDoctorReport(projectRoot, options = {}) {
|
|
29
29
|
const loadedProject = await (0, project_1.loadProject)(projectRoot);
|
|
30
|
+
const doctorConfig = (0, project_1.resolveExpoHarmonyDoctorConfig)(loadedProject.expoConfig);
|
|
31
|
+
const filteredPackageJson = (0, project_1.applyDependencyExclusions)(loadedProject.packageJson, doctorConfig.excludeDependencies);
|
|
30
32
|
const expoSdkVersion = (0, project_1.detectExpoSdkVersion)(loadedProject.packageJson);
|
|
31
33
|
const matrix = validatedMatrices_1.VALIDATED_RELEASE_MATRICES[validatedMatrices_1.DEFAULT_VALIDATED_MATRIX_ID];
|
|
32
34
|
const targetTier = options.targetTier ?? 'verified';
|
|
33
|
-
const
|
|
35
|
+
const excludedPlugins = new Set(doctorConfig.excludePlugins);
|
|
36
|
+
const expoPlugins = (0, project_1.collectExpoPlugins)(loadedProject.expoConfig).filter((pluginName) => !excludedPlugins.has(pluginName));
|
|
34
37
|
const expoSchemes = (0, project_1.collectExpoSchemes)(loadedProject.expoConfig);
|
|
35
38
|
const dependencyRecords = new Map();
|
|
36
|
-
const declaredDependencies = (0, project_1.collectDeclaredDependencies)(
|
|
39
|
+
const declaredDependencies = (0, project_1.collectDeclaredDependencies)(filteredPackageJson);
|
|
37
40
|
for (const dependency of declaredDependencies) {
|
|
38
41
|
dependencyRecords.set(dependency.name, createDependencyRecord(dependency.name, dependency.version, dependency.source));
|
|
39
42
|
}
|
|
@@ -43,8 +46,11 @@ async function buildDoctorReport(projectRoot, options = {}) {
|
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
48
|
const dependencies = await (0, dependencyInspection_1.annotateDependencyBuildability)(loadedProject.projectRoot, [...dependencyRecords.values()].sort((left, right) => left.name.localeCompare(right.name)));
|
|
46
|
-
const blockingIssues = await collectBlockingIssues(loadedProject.projectRoot, loadedProject.expoConfig,
|
|
47
|
-
const
|
|
49
|
+
const blockingIssues = await collectBlockingIssues(loadedProject.projectRoot, loadedProject.expoConfig, filteredPackageJson, expoPlugins, expoSchemes, expoSdkVersion, dependencies, matrix, targetTier);
|
|
50
|
+
const excludedDependencies = new Set(doctorConfig.excludeDependencies);
|
|
51
|
+
const capabilities = (0, capabilities_1.getCapabilityDefinitionsForProject)(filteredPackageJson, {
|
|
52
|
+
excludedDependencies,
|
|
53
|
+
}).map((definition) => ({
|
|
48
54
|
id: definition.id,
|
|
49
55
|
packageName: definition.packageName,
|
|
50
56
|
status: definition.status,
|
|
@@ -59,7 +65,7 @@ async function buildDoctorReport(projectRoot, options = {}) {
|
|
|
59
65
|
sampleRoute: definition.sampleRoute,
|
|
60
66
|
acceptanceChecklist: [...definition.acceptanceChecklist],
|
|
61
67
|
}));
|
|
62
|
-
const coverageProfile = await detectCoverageProfile(loadedProject.projectRoot,
|
|
68
|
+
const coverageProfile = await detectCoverageProfile(loadedProject.projectRoot, filteredPackageJson, dependencies, doctorConfig.coverageProfile);
|
|
63
69
|
const blockingDependencyNames = new Set(blockingIssues
|
|
64
70
|
.filter((issue) => issue.code.startsWith('dependency.') && issue.subject)
|
|
65
71
|
.map((issue) => issue.subject));
|
|
@@ -76,7 +82,7 @@ async function buildDoctorReport(projectRoot, options = {}) {
|
|
|
76
82
|
capabilities,
|
|
77
83
|
});
|
|
78
84
|
const warnings = buildWarnings(loadedProject.expoConfig, expoSdkVersion, resolvedDependencies);
|
|
79
|
-
const advisories = buildAdvisories(loadedProject.expoConfig);
|
|
85
|
+
const advisories = buildAdvisories(loadedProject.expoConfig, doctorConfig);
|
|
80
86
|
return {
|
|
81
87
|
generatedAt: new Date().toISOString(),
|
|
82
88
|
projectRoot: loadedProject.projectRoot,
|
|
@@ -248,9 +254,17 @@ async function collectBlockingIssues(projectRoot, expoConfig, packageJson, expoP
|
|
|
248
254
|
subject: dependencyName,
|
|
249
255
|
});
|
|
250
256
|
}
|
|
257
|
+
if (rule.specifiers &&
|
|
258
|
+
!rule.specifiers.some((specifier) => matchesDependencySpecifier(dependency.version, specifier))) {
|
|
259
|
+
issues.push({
|
|
260
|
+
code: 'dependency.specifier_mismatch',
|
|
261
|
+
message: `Dependency ${dependencyName} does not match any validated dependency spec: ${rule.specifiers.join(', ')}.`,
|
|
262
|
+
subject: dependencyName,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
251
265
|
}
|
|
252
266
|
for (const dependency of dependencies) {
|
|
253
|
-
if (
|
|
267
|
+
if (shouldBlockDependencyForTargetTier(dependency, matrix, targetTier)) {
|
|
254
268
|
issues.push({
|
|
255
269
|
code: 'dependency.not_allowed',
|
|
256
270
|
message: `${dependency.name} is outside the ${targetTier} support tier for ${matrix.id}.`,
|
|
@@ -353,7 +367,10 @@ async function collectBlockingIssues(projectRoot, expoConfig, packageJson, expoP
|
|
|
353
367
|
}
|
|
354
368
|
return dedupeIssues(issues);
|
|
355
369
|
}
|
|
356
|
-
async function detectCoverageProfile(projectRoot, packageJson, dependencies) {
|
|
370
|
+
async function detectCoverageProfile(projectRoot, packageJson, dependencies, coverageProfileOverride) {
|
|
371
|
+
if (coverageProfileOverride) {
|
|
372
|
+
return coverageProfileOverride;
|
|
373
|
+
}
|
|
357
374
|
for (const directoryName of BARE_WORKFLOW_DIRECTORY_NAMES) {
|
|
358
375
|
if (await fs_extra_1.default.pathExists(path_1.default.join(projectRoot, directoryName))) {
|
|
359
376
|
return 'bare';
|
|
@@ -490,8 +507,17 @@ function buildWarnings(expoConfig, expoSdkVersion, dependencies) {
|
|
|
490
507
|
}
|
|
491
508
|
return warnings;
|
|
492
509
|
}
|
|
493
|
-
function buildAdvisories(expoConfig) {
|
|
510
|
+
function buildAdvisories(expoConfig, doctorConfig) {
|
|
494
511
|
const advisories = [];
|
|
512
|
+
if (doctorConfig.excludeDependencies.length > 0) {
|
|
513
|
+
advisories.push(`Expo Harmony doctor excluded dependencies: ${doctorConfig.excludeDependencies.join(', ')}.`);
|
|
514
|
+
}
|
|
515
|
+
if (doctorConfig.excludePlugins.length > 0) {
|
|
516
|
+
advisories.push(`Expo Harmony doctor excluded plugins: ${doctorConfig.excludePlugins.join(', ')}.`);
|
|
517
|
+
}
|
|
518
|
+
if (doctorConfig.coverageProfile) {
|
|
519
|
+
advisories.push(`Expo Harmony doctor forced coverage profile to ${doctorConfig.coverageProfile}.`);
|
|
520
|
+
}
|
|
495
521
|
if (!expoConfig.android?.package && expoConfig.ios?.bundleIdentifier) {
|
|
496
522
|
advisories.push('The project is using ios.bundleIdentifier as the only explicit native identifier. Prefer setting android.package as well before claiming release readiness.');
|
|
497
523
|
}
|
|
@@ -501,11 +527,21 @@ function isDependencyAllowedForTargetTier(dependencyName, matrix, targetTier) {
|
|
|
501
527
|
if (matrix.allowedDependencies.includes(dependencyName)) {
|
|
502
528
|
return true;
|
|
503
529
|
}
|
|
530
|
+
const catalogRecord = dependencyCatalog_1.DEPENDENCY_CATALOG[dependencyName];
|
|
531
|
+
if (catalogRecord) {
|
|
532
|
+
return (0, capabilities_1.isSupportTierAllowed)(catalogRecord.supportTier, targetTier);
|
|
533
|
+
}
|
|
504
534
|
const capability = capabilities_1.CAPABILITY_BY_PACKAGE[dependencyName];
|
|
505
|
-
|
|
535
|
+
return capability ? (0, capabilities_1.isSupportTierAllowed)(capability.supportTier, targetTier) : false;
|
|
536
|
+
}
|
|
537
|
+
function shouldBlockDependencyForTargetTier(dependency, matrix, targetTier) {
|
|
538
|
+
if (dependency.source === 'devDependency') {
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
if (dependency.buildabilityRisk === 'js-only-unknown') {
|
|
506
542
|
return false;
|
|
507
543
|
}
|
|
508
|
-
return (
|
|
544
|
+
return !isDependencyAllowedForTargetTier(dependency.name, matrix, targetTier);
|
|
509
545
|
}
|
|
510
546
|
function matchesVersionRange(rawVersion, range) {
|
|
511
547
|
const coerced = semver_1.default.coerce(rawVersion);
|
package/build/core/template.js
CHANGED
|
@@ -135,9 +135,12 @@ async function syncProjectTemplate(projectRoot, force = false, options = {}) {
|
|
|
135
135
|
}
|
|
136
136
|
async function buildManagedFiles(loadedProject, identifiers, previousToolkitConfig, doctorReport) {
|
|
137
137
|
const hasExpoRouter = (0, support_1.usesExpoRouter)(loadedProject.packageJson);
|
|
138
|
+
const doctorConfig = (0, project_1.resolveExpoHarmonyDoctorConfig)(loadedProject.expoConfig);
|
|
138
139
|
const enabledCapabilities = doctorReport.capabilities;
|
|
139
140
|
const hasManagedExpoHarmonyPackage = enabledCapabilities.some((capability) => capability.runtimeMode !== 'shim');
|
|
140
|
-
const requestedHarmonyPermissions = (0, capabilities_1.collectCapabilityHarmonyPermissions)(loadedProject.packageJson
|
|
141
|
+
const requestedHarmonyPermissions = (0, capabilities_1.collectCapabilityHarmonyPermissions)(loadedProject.packageJson, {
|
|
142
|
+
excludedDependencies: new Set(doctorConfig.excludeDependencies),
|
|
143
|
+
});
|
|
141
144
|
const signingLocalConfig = await (0, signing_1.readSigningLocalConfig)(loadedProject.projectRoot);
|
|
142
145
|
const hvigorPluginFilename = await (0, project_1.resolveRnohHvigorPluginFilename)(loadedProject.projectRoot);
|
|
143
146
|
const renderedHarmonyRootPackage = renderTemplate(await fs_extra_1.default.readFile(path_1.default.join(TEMPLATE_ROOT, 'oh-package.json5'), 'utf8'), loadedProject, identifiers, hvigorPluginFilename);
|
|
@@ -179,6 +182,7 @@ async function buildManagedFiles(loadedProject, identifiers, previousToolkitConf
|
|
|
179
182
|
bundleName: identifiers.bundleName,
|
|
180
183
|
entryModuleName: identifiers.entryModuleName,
|
|
181
184
|
coverageProfile: doctorReport.coverageProfile,
|
|
185
|
+
doctorConfig,
|
|
182
186
|
capabilities: enabledCapabilities.map((capability) => ({
|
|
183
187
|
id: capability.id,
|
|
184
188
|
packageName: capability.packageName,
|
|
@@ -3,5 +3,9 @@ export declare const CAPABILITY_DEFINITIONS: readonly CapabilityDefinition[];
|
|
|
3
3
|
export declare const CAPABILITY_BY_PACKAGE: Record<string, CapabilityDefinition>;
|
|
4
4
|
export declare function compareSupportTiers(left: SupportTier, right: SupportTier): number;
|
|
5
5
|
export declare function isSupportTierAllowed(dependencyTier: SupportTier, targetTier: DoctorTargetTier): boolean;
|
|
6
|
-
export declare function getCapabilityDefinitionsForProject(packageJson: PackageJson
|
|
7
|
-
|
|
6
|
+
export declare function getCapabilityDefinitionsForProject(packageJson: PackageJson, options?: {
|
|
7
|
+
excludedDependencies?: ReadonlySet<string>;
|
|
8
|
+
}): CapabilityDefinition[];
|
|
9
|
+
export declare function collectCapabilityHarmonyPermissions(packageJson: PackageJson, options?: {
|
|
10
|
+
excludedDependencies?: ReadonlySet<string>;
|
|
11
|
+
}): string[];
|
|
@@ -149,9 +149,13 @@ function compareSupportTiers(left, right) {
|
|
|
149
149
|
function isSupportTierAllowed(dependencyTier, targetTier) {
|
|
150
150
|
return compareSupportTiers(dependencyTier, targetTier) <= 0;
|
|
151
151
|
}
|
|
152
|
-
function getCapabilityDefinitionsForProject(packageJson) {
|
|
153
|
-
|
|
152
|
+
function getCapabilityDefinitionsForProject(packageJson, options = {}) {
|
|
153
|
+
const excludedDependencies = options.excludedDependencies ?? new Set();
|
|
154
|
+
return exports.CAPABILITY_DEFINITIONS.filter((definition) => !excludedDependencies.has(definition.packageName) &&
|
|
155
|
+
(0, project_1.hasDeclaredDependency)(packageJson, definition.packageName)).sort((left, right) => left.packageName.localeCompare(right.packageName));
|
|
154
156
|
}
|
|
155
|
-
function collectCapabilityHarmonyPermissions(packageJson) {
|
|
156
|
-
return Array.from(new Set(getCapabilityDefinitionsForProject(packageJson
|
|
157
|
+
function collectCapabilityHarmonyPermissions(packageJson, options = {}) {
|
|
158
|
+
return Array.from(new Set(getCapabilityDefinitionsForProject(packageJson, options)
|
|
159
|
+
.filter((definition) => definition.runtimeMode !== 'shim')
|
|
160
|
+
.flatMap((definition) => definition.harmonyPermissions))).sort((left, right) => left.localeCompare(right));
|
|
157
161
|
}
|
|
@@ -26,6 +26,29 @@ const UI_STACK_COMPATIBILITY_RECORDS = Object.fromEntries(uiStack_1.UI_STACK_VAL
|
|
|
26
26
|
},
|
|
27
27
|
],
|
|
28
28
|
]));
|
|
29
|
+
const UI_STACK_ADAPTER_PACKAGE_NAME_SET = new Set(uiStack_1.UI_STACK_VALIDATED_ADAPTERS.map((entry) => entry.adapterPackageName));
|
|
30
|
+
const HARMONY_NATIVE_ADAPTER_COMPATIBILITY_RECORDS = Object.fromEntries(uiStack_1.HARMONY_NATIVE_ADAPTERS.filter((entry) => !UI_STACK_ADAPTER_PACKAGE_NAME_SET.has(entry.adapterPackageName)).flatMap((entry) => [
|
|
31
|
+
[
|
|
32
|
+
entry.canonicalPackageName,
|
|
33
|
+
{
|
|
34
|
+
status: 'manual',
|
|
35
|
+
supportTier: 'experimental',
|
|
36
|
+
note: `Harmony runtime support is tracked through ${entry.adapterPackageName}. Keep it on the experimental lane until bundle, debug HAP, and device evidence are recorded for the consuming app.`,
|
|
37
|
+
replacement: entry.adapterPackageName,
|
|
38
|
+
docsUrl: entry.docsUrl,
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
[
|
|
42
|
+
entry.adapterPackageName,
|
|
43
|
+
{
|
|
44
|
+
status: 'manual',
|
|
45
|
+
supportTier: 'experimental',
|
|
46
|
+
note: `Harmony adapter package pinned by the ccnubox intake catalog. The toolkit can link its HAR when ${entry.harmonyHarFileName} is installed, but runtime behavior still needs app-level evidence.`,
|
|
47
|
+
replacement: entry.canonicalPackageName,
|
|
48
|
+
docsUrl: entry.docsUrl,
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
]));
|
|
29
52
|
exports.DEPENDENCY_CATALOG = {
|
|
30
53
|
[constants_1.TOOLKIT_PACKAGE_NAME]: {
|
|
31
54
|
status: 'supported',
|
|
@@ -112,7 +135,32 @@ exports.DEPENDENCY_CATALOG = {
|
|
|
112
135
|
supportTier: 'verified',
|
|
113
136
|
note: 'File-based routing is treated as part of the validated App Shell matrix when router peers, scheme, and plugin config are present.',
|
|
114
137
|
},
|
|
138
|
+
'@react-native-async-storage/async-storage': {
|
|
139
|
+
status: 'manual',
|
|
140
|
+
supportTier: 'experimental',
|
|
141
|
+
note: 'Async storage is a high-frequency startup dependency. Treat it as an experimental Harmony intake dependency until the managed adapter path has stable doctor, sample, and build coverage.',
|
|
142
|
+
docsUrl: 'https://github.com/react-native-oh-library',
|
|
143
|
+
},
|
|
144
|
+
'react-native-safe-area-context': {
|
|
145
|
+
status: 'supported',
|
|
146
|
+
supportTier: 'experimental',
|
|
147
|
+
note: 'Safe-area handling is shimmed by the toolkit for Harmony so App Shell layouts can keep rendering while the native path remains outside the verified matrix.',
|
|
148
|
+
docsUrl: 'https://github.com/react-native-oh-library',
|
|
149
|
+
},
|
|
150
|
+
'react-native-screens': {
|
|
151
|
+
status: 'manual',
|
|
152
|
+
supportTier: 'experimental',
|
|
153
|
+
note: 'Navigation stacks frequently depend on react-native-screens. Keep it on the experimental lane until the Harmony adapter path has repeatable sample and device coverage.',
|
|
154
|
+
docsUrl: 'https://github.com/react-native-oh-library',
|
|
155
|
+
},
|
|
156
|
+
'react-native-webview': {
|
|
157
|
+
status: 'manual',
|
|
158
|
+
supportTier: 'experimental',
|
|
159
|
+
note: 'WebView is a common app-shell blocker. Treat it as an experimental intake dependency until the Harmony runtime path is validated on device.',
|
|
160
|
+
docsUrl: 'https://github.com/react-native-oh-library',
|
|
161
|
+
},
|
|
115
162
|
...UI_STACK_COMPATIBILITY_RECORDS,
|
|
163
|
+
...HARMONY_NATIVE_ADAPTER_COMPATIBILITY_RECORDS,
|
|
116
164
|
'react-native-gesture-handler': {
|
|
117
165
|
status: 'manual',
|
|
118
166
|
supportTier: 'experimental',
|
|
@@ -127,6 +175,206 @@ exports.DEPENDENCY_CATALOG = {
|
|
|
127
175
|
replacement: 'react-native-gesture-handler',
|
|
128
176
|
docsUrl: 'https://github.com/react-native-oh-library/react-native-harmony-gesture-handler',
|
|
129
177
|
},
|
|
178
|
+
'@ant-design/icons-react-native': {
|
|
179
|
+
status: 'supported',
|
|
180
|
+
supportTier: 'experimental',
|
|
181
|
+
note: 'Icon font rendering is accepted for the ccnubox intake path; font asset registration still needs bundle/debug validation on Harmony.',
|
|
182
|
+
},
|
|
183
|
+
'@ant-design/react-native': {
|
|
184
|
+
status: 'manual',
|
|
185
|
+
supportTier: 'experimental',
|
|
186
|
+
note: 'React Native UI components are JS-heavy, but the app must still validate modal, picker, and gesture surfaces on Harmony.',
|
|
187
|
+
},
|
|
188
|
+
'@bacons/apple-targets': {
|
|
189
|
+
status: 'manual',
|
|
190
|
+
supportTier: 'experimental',
|
|
191
|
+
note: 'iOS widget build tooling may be excluded from Harmony doctor because it does not participate in the Harmony runtime.',
|
|
192
|
+
},
|
|
193
|
+
'@expo/vector-icons': {
|
|
194
|
+
status: 'supported',
|
|
195
|
+
supportTier: 'experimental',
|
|
196
|
+
note: 'Vector icon rendering is accepted when paired with bundled icon fonts and a working Expo font path.',
|
|
197
|
+
},
|
|
198
|
+
'@lottiefiles/dotlottie-react': {
|
|
199
|
+
status: 'manual',
|
|
200
|
+
supportTier: 'experimental',
|
|
201
|
+
note: 'dotLottie usage remains an experimental animation surface until the app records Harmony runtime evidence.',
|
|
202
|
+
},
|
|
203
|
+
'@react-navigation/bottom-tabs': {
|
|
204
|
+
status: 'supported',
|
|
205
|
+
supportTier: 'experimental',
|
|
206
|
+
note: 'React Navigation tabs are accepted for the ccnubox app-shell path when paired with screens, safe-area, gesture, and reanimated validation.',
|
|
207
|
+
},
|
|
208
|
+
'@react-navigation/elements': {
|
|
209
|
+
status: 'supported',
|
|
210
|
+
supportTier: 'experimental',
|
|
211
|
+
note: 'React Navigation elements stay in the JS/UI layer but inherit the navigation-stack runtime evidence requirements.',
|
|
212
|
+
},
|
|
213
|
+
'@react-navigation/native': {
|
|
214
|
+
status: 'supported',
|
|
215
|
+
supportTier: 'experimental',
|
|
216
|
+
note: 'React Navigation native is accepted for managed Harmony intake when linking, safe-area, screens, and gesture dependencies are present.',
|
|
217
|
+
},
|
|
218
|
+
'@rneui/base': {
|
|
219
|
+
status: 'supported',
|
|
220
|
+
supportTier: 'experimental',
|
|
221
|
+
note: 'RNE base components are accepted as JS/UI dependencies and should be covered by app-level visual smoke tests.',
|
|
222
|
+
},
|
|
223
|
+
'@rneui/themed': {
|
|
224
|
+
status: 'supported',
|
|
225
|
+
supportTier: 'experimental',
|
|
226
|
+
note: 'RNE themed components are accepted as JS/UI dependencies and should be covered by app-level visual smoke tests.',
|
|
227
|
+
},
|
|
228
|
+
axios: {
|
|
229
|
+
status: 'supported',
|
|
230
|
+
supportTier: 'verified',
|
|
231
|
+
note: 'HTTP client dependency with no native surface.',
|
|
232
|
+
},
|
|
233
|
+
'expo-application': {
|
|
234
|
+
status: 'manual',
|
|
235
|
+
supportTier: 'experimental',
|
|
236
|
+
note: 'Application metadata reads require Harmony verification because Expo native modules are outside the verified UI-stack matrix.',
|
|
237
|
+
},
|
|
238
|
+
'expo-blur': {
|
|
239
|
+
status: 'manual',
|
|
240
|
+
supportTier: 'experimental',
|
|
241
|
+
note: 'Blur effects need visual verification on Harmony and are not part of the verified UI-stack matrix.',
|
|
242
|
+
},
|
|
243
|
+
'expo-clipboard': {
|
|
244
|
+
status: 'manual',
|
|
245
|
+
supportTier: 'experimental',
|
|
246
|
+
note: 'Clipboard support should be routed through the Harmony clipboard adapter where the app needs real pasteboard access.',
|
|
247
|
+
replacement: '@react-native-oh-tpl/clipboard',
|
|
248
|
+
},
|
|
249
|
+
'expo-dev-client': {
|
|
250
|
+
status: 'manual',
|
|
251
|
+
supportTier: 'experimental',
|
|
252
|
+
note: 'Dev-client native behavior is outside release eligibility; it may exist in the project without blocking experimental intake.',
|
|
253
|
+
},
|
|
254
|
+
'expo-font': {
|
|
255
|
+
status: 'manual',
|
|
256
|
+
supportTier: 'experimental',
|
|
257
|
+
note: 'Font loading is accepted for ccnubox intake but must be validated with bundled icon/font assets in the Harmony HAP.',
|
|
258
|
+
},
|
|
259
|
+
'expo-haptics': {
|
|
260
|
+
status: 'manual',
|
|
261
|
+
supportTier: 'experimental',
|
|
262
|
+
note: 'Haptics require a Harmony runtime implementation before the app can claim device parity.',
|
|
263
|
+
},
|
|
264
|
+
'expo-image': {
|
|
265
|
+
status: 'manual',
|
|
266
|
+
supportTier: 'experimental',
|
|
267
|
+
note: 'Image rendering and cache behavior require Harmony bundle and device validation.',
|
|
268
|
+
},
|
|
269
|
+
'expo-image-manipulator': {
|
|
270
|
+
status: 'manual',
|
|
271
|
+
supportTier: 'experimental',
|
|
272
|
+
note: 'Image manipulation remains an experimental ccnubox requirement for timetable screenshot/export flows.',
|
|
273
|
+
},
|
|
274
|
+
'expo-insights': {
|
|
275
|
+
status: 'manual',
|
|
276
|
+
supportTier: 'experimental',
|
|
277
|
+
note: 'Telemetry should not block bundle intake, but Harmony runtime behavior remains outside the verified matrix.',
|
|
278
|
+
},
|
|
279
|
+
'expo-linear-gradient': {
|
|
280
|
+
status: 'manual',
|
|
281
|
+
supportTier: 'experimental',
|
|
282
|
+
note: 'Gradient rendering requires visual smoke validation on Harmony.',
|
|
283
|
+
},
|
|
284
|
+
'expo-media-library': {
|
|
285
|
+
status: 'manual',
|
|
286
|
+
supportTier: 'experimental',
|
|
287
|
+
note: 'Media-library write/read flows should be backed by the Harmony camera-roll adapter for screenshot save acceptance.',
|
|
288
|
+
replacement: '@react-native-oh-tpl/camera-roll',
|
|
289
|
+
},
|
|
290
|
+
'expo-secure-store': {
|
|
291
|
+
status: 'manual',
|
|
292
|
+
supportTier: 'experimental',
|
|
293
|
+
note: 'Secure storage must use a real Harmony secure backend or equivalent encrypted wrapper before release parity is claimed.',
|
|
294
|
+
},
|
|
295
|
+
'expo-splash-screen': {
|
|
296
|
+
status: 'manual',
|
|
297
|
+
supportTier: 'experimental',
|
|
298
|
+
note: 'Splash behavior is accepted for ccnubox intake but remains native-side runtime evidence.',
|
|
299
|
+
},
|
|
300
|
+
'expo-symbols': {
|
|
301
|
+
status: 'manual',
|
|
302
|
+
supportTier: 'experimental',
|
|
303
|
+
note: 'Symbols are platform-specific visual assets and require Harmony fallback/runtime verification.',
|
|
304
|
+
},
|
|
305
|
+
'expo-system-ui': {
|
|
306
|
+
status: 'manual',
|
|
307
|
+
supportTier: 'experimental',
|
|
308
|
+
note: 'System UI updates touch native chrome and need Harmony runtime validation.',
|
|
309
|
+
},
|
|
310
|
+
'expo-updates': {
|
|
311
|
+
status: 'manual',
|
|
312
|
+
supportTier: 'experimental',
|
|
313
|
+
note: 'OTA update checks remain experimental on Harmony until update metadata and launch behavior are validated.',
|
|
314
|
+
},
|
|
315
|
+
'expo-web-browser': {
|
|
316
|
+
status: 'manual',
|
|
317
|
+
supportTier: 'experimental',
|
|
318
|
+
note: 'In-app browser flows need Harmony runtime validation; they must not replace WebView parity for pages that require injected scripts.',
|
|
319
|
+
},
|
|
320
|
+
'jcore-react-native': {
|
|
321
|
+
status: 'manual',
|
|
322
|
+
supportTier: 'experimental',
|
|
323
|
+
note: 'JCore is accepted as part of the JPush Harmony intake path, but device-side SDK evidence is required.',
|
|
324
|
+
docsUrl: 'https://docs.jiguang.cn/jpush/client/HarmonyOS/hmos_guide',
|
|
325
|
+
},
|
|
326
|
+
'jpush-react-native': {
|
|
327
|
+
status: 'manual',
|
|
328
|
+
supportTier: 'experimental',
|
|
329
|
+
note: 'JPush is a real runtime requirement for ccnubox. Keep it in the catalog and require a Harmony SDK bridge plus device evidence for registrationId, arrival, click, and cold-start payloads.',
|
|
330
|
+
docsUrl: 'https://docs.jiguang.cn/jpush/client/HarmonyOS/hmos_guide',
|
|
331
|
+
},
|
|
332
|
+
'mx-jpush-expo': {
|
|
333
|
+
status: 'manual',
|
|
334
|
+
supportTier: 'experimental',
|
|
335
|
+
note: 'The Expo config plugin may be excluded from Harmony config processing, but the JPush runtime dependency must stay visible in doctor reports.',
|
|
336
|
+
docsUrl: 'https://docs.jiguang.cn/jpush/client/HarmonyOS/hmos_guide',
|
|
337
|
+
},
|
|
338
|
+
'react-native-draggable-grid': {
|
|
339
|
+
status: 'supported',
|
|
340
|
+
supportTier: 'experimental',
|
|
341
|
+
note: 'Drag-grid behavior is accepted for the ccnubox homepage intake and requires gesture/runtime smoke validation.',
|
|
342
|
+
},
|
|
343
|
+
'react-native-edge-to-edge': {
|
|
344
|
+
status: 'manual',
|
|
345
|
+
supportTier: 'experimental',
|
|
346
|
+
note: 'Android edge-to-edge native configuration can be excluded as a config plugin; runtime chrome behavior remains Harmony-specific.',
|
|
347
|
+
},
|
|
348
|
+
'react-native-keyboard-aware-scroll-view': {
|
|
349
|
+
status: 'supported',
|
|
350
|
+
supportTier: 'experimental',
|
|
351
|
+
note: 'Keyboard-aware scrolling is accepted as JS/UI behavior but requires form-flow validation on Harmony.',
|
|
352
|
+
},
|
|
353
|
+
'react-native-pdf-renderer': {
|
|
354
|
+
status: 'manual',
|
|
355
|
+
supportTier: 'experimental',
|
|
356
|
+
note: 'PDF rendering is a native-looking app requirement and remains experimental until a Harmony renderer or adapter is validated.',
|
|
357
|
+
},
|
|
358
|
+
'react-native-reanimated-carousel': {
|
|
359
|
+
status: 'supported',
|
|
360
|
+
supportTier: 'experimental',
|
|
361
|
+
note: 'Carousel behavior is accepted when paired with the validated reanimated Harmony adapter and homepage smoke tests.',
|
|
362
|
+
},
|
|
363
|
+
'react-native-svg-transformer': {
|
|
364
|
+
status: 'supported',
|
|
365
|
+
supportTier: 'experimental',
|
|
366
|
+
note: 'Metro SVG transformation is accepted for bundling when paired with the validated react-native-svg Harmony adapter.',
|
|
367
|
+
},
|
|
368
|
+
'react-native-web': {
|
|
369
|
+
status: 'supported',
|
|
370
|
+
supportTier: 'verified',
|
|
371
|
+
note: 'Web support dependency has no Harmony native runtime surface.',
|
|
372
|
+
},
|
|
373
|
+
zustand: {
|
|
374
|
+
status: 'supported',
|
|
375
|
+
supportTier: 'verified',
|
|
376
|
+
note: 'State management dependency with no native surface.',
|
|
377
|
+
},
|
|
130
378
|
...Object.fromEntries(capabilities_1.CAPABILITY_DEFINITIONS.map((definition) => [
|
|
131
379
|
definition.packageName,
|
|
132
380
|
{
|
|
@@ -8,4 +8,4 @@ export declare const SUPPORTING_SAMPLE_PATHS: readonly ["examples/official-app-s
|
|
|
8
8
|
export declare const VERIFIED_JS_UI_CAPABILITY_NAMES: readonly ["expo-router", "expo-linking", "expo-constants", ...("react-native-reanimated" | "react-native-svg")[]];
|
|
9
9
|
export declare const PREVIEW_CAPABILITY_DEFINITIONS: import("..").CapabilityDefinition[];
|
|
10
10
|
export declare const EXPERIMENTAL_CAPABILITY_NAMES: readonly [...string[], "react-native-gesture-handler"];
|
|
11
|
-
export declare const PUBLIC_CURRENT_VERSION = "1.8.
|
|
11
|
+
export declare const PUBLIC_CURRENT_VERSION = "1.8.2";
|