@expo-harmony/config-plugins 55.0.10-harmony.4 → 55.0.11-harmony.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.md +4 -4
- package/build/config.d.ts +6 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +4 -1
- package/build/normalizeConfig.d.ts +2 -1
- package/build/normalizeConfig.js +31 -54
- package/build/sdkVersion.d.ts +6 -0
- package/build/sdkVersion.js +52 -0
- package/package.json +2 -5
- package/src/config.ts +6 -0
- package/src/index.ts +3 -0
- package/src/normalizeConfig.ts +39 -88
- package/src/sdkVersion.ts +69 -0
package/README.md
CHANGED
|
@@ -40,9 +40,9 @@ module.exports = defineExpoHarmonyConfig({
|
|
|
40
40
|
| `vendor` | `expo-harmony` |
|
|
41
41
|
| `versionCode` | `1` |
|
|
42
42
|
| `versionName` | 配置里的 `version`,没有则 `1.0.0` |
|
|
43
|
-
| `targetApiVersion` | `
|
|
44
|
-
| `targetSdkVersion` | 数字或 SDK
|
|
45
|
-
| `compatibleSdkVersion` | `
|
|
43
|
+
| `targetApiVersion` | 旧版别名,建议改用 `expo-build-properties` 的 `targetSdkVersion` |
|
|
44
|
+
| `targetSdkVersion` | 数字或 SDK 字符串,默认 `24`,兼容旧 `targetApiVersion` |
|
|
45
|
+
| `compatibleSdkVersion` | `20`,不能低于 20,也不能大于 target |
|
|
46
46
|
| `deviceTypes` | `['phone', 'tablet']` |
|
|
47
47
|
| `permissions` | 权限列表,默认包含 `ohos.permission.INTERNET` |
|
|
48
48
|
| `skills` | 技能列表,`scheme` 会追加对应项 |
|
|
@@ -56,7 +56,7 @@ module.exports = defineExpoHarmonyConfig({
|
|
|
56
56
|
| `abiFilters` | `['arm64-v8a', 'x86_64']` |
|
|
57
57
|
| `signingConfigFile` | 签名配置文件路径,不设置则忽略 |
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
SDK/ABI 的这些旧字段继续兼容,新项目建议使用 `@expo-harmony/expo-build-properties`。`targetSdkVersion` 和 `compatibleSdkVersion` 可以写数字或带 API 级别的 SDK 标签;API 26 起使用 `26.0.0` 这样的分段版本。内置旧标签覆盖 API 13、14、20、21、23、24,已知 API 不接受错误的标签。`targetApiVersion` 与 `targetSdkVersion` 同时存在时须一致。`backgroundColor` 写 `#RRGGBB` 或 `#RRGGBBAA`,写入原生工程时转成 ARGB。`jsEngine` 目前只能是 `hermes`。
|
|
60
60
|
|
|
61
61
|
`normalizeHarmonyConfig(config)` 返回补齐默认值并校验后的配置。
|
|
62
62
|
|
package/build/config.d.ts
CHANGED
|
@@ -26,6 +26,10 @@ export interface HarmonyConfig {
|
|
|
26
26
|
vendor?: string;
|
|
27
27
|
versionCode?: number;
|
|
28
28
|
versionName?: string;
|
|
29
|
+
runtimeVersion?: string | {
|
|
30
|
+
policy: 'nativeVersion' | 'sdkVersion' | 'appVersion' | 'fingerprint';
|
|
31
|
+
};
|
|
32
|
+
/** @deprecated Configure targetSdkVersion in @expo-harmony/expo-build-properties. */
|
|
29
33
|
targetApiVersion?: number;
|
|
30
34
|
targetSdkVersion?: number | string;
|
|
31
35
|
compatibleSdkVersion?: number | string;
|
|
@@ -41,6 +45,8 @@ export interface HarmonyConfig {
|
|
|
41
45
|
jsEngine?: 'hermes';
|
|
42
46
|
abiFilters?: string[];
|
|
43
47
|
signingConfigFile?: string;
|
|
48
|
+
/** Native module build options supplied by config plugins. */
|
|
49
|
+
moduleBuilds?: Record<string, Record<string, string | number | boolean>>;
|
|
44
50
|
}
|
|
45
51
|
export type ExpoConfigWithHarmony = Omit<ExpoConfig, 'platforms'> & {
|
|
46
52
|
platforms?: ExpoHarmonyPlatform[];
|
package/build/index.d.ts
CHANGED
|
@@ -25,3 +25,5 @@ export type { HarmonyResourceFile, HarmonyResourceItem } from './resources';
|
|
|
25
25
|
export { withHarmonyGeneratedFiles, withRawfile } from './generatedFiles';
|
|
26
26
|
export type { HarmonyFileDescriptor, HarmonyFileMap, HarmonyGeneratedFilesOptions } from './generatedFiles';
|
|
27
27
|
export { HarmonyPermissions, withHarmonyPermissions } from './permissions';
|
|
28
|
+
export { parseHarmonySdkVersion, compareHarmonyApiVersions } from './sdkVersion';
|
|
29
|
+
export type { HarmonySdkVersion } from './sdkVersion';
|
package/build/index.js
CHANGED
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.withHarmonyPermissions = exports.HarmonyPermissions = exports.withRawfile = exports.withHarmonyGeneratedFiles = exports.HarmonyResources = exports.HarmonyManifest = exports.HarmonyPaths = exports.registerHarmonyConfigPlugin = exports.withWorker = exports.withStrings = exports.withRootOhPackage = exports.withRootHvigor = exports.withReactNativeConfig = exports.withProjectBuildProfile = exports.withProfiles = exports.withModuleJson = exports.withMedia = exports.withIndexPage = exports.withHvigorConfig = exports.withHarmonyResources = exports.withHarmonyMod = exports.withHarmonyDangerousMod = exports.withHarmonyBaseMods = exports.withHarmonyAutolinking = exports.withEntryOhPackage = exports.withEntryHvigor = exports.withEntryBuildProfile = exports.withEntryAbility = exports.withAbilityStage = exports.withCppPackageProvider = exports.withColors = exports.withCMakeLists = exports.withArkTSPackageProvider = exports.withAppJson = exports.recordManagedFile = exports.HARMONY_MOD_NAMES = exports.normalizeHarmonyConfig = exports.stableHarmonyJson = exports.atomicWrite = exports.HarmonyConfigPluginError = exports.withHarmonyJsonFile = exports.createHarmonyFileMod = exports.compileHarmonyModsAsync = exports.withStaticPlugin = exports.withRunOnce = exports.withPlugins = exports.createRunOncePlugin = exports.defineExpoHarmonyConfig = void 0;
|
|
36
|
+
exports.compareHarmonyApiVersions = exports.parseHarmonySdkVersion = exports.withHarmonyPermissions = exports.HarmonyPermissions = exports.withRawfile = exports.withHarmonyGeneratedFiles = exports.HarmonyResources = exports.HarmonyManifest = exports.HarmonyPaths = exports.registerHarmonyConfigPlugin = exports.withWorker = exports.withStrings = exports.withRootOhPackage = exports.withRootHvigor = exports.withReactNativeConfig = exports.withProjectBuildProfile = exports.withProfiles = exports.withModuleJson = exports.withMedia = exports.withIndexPage = exports.withHvigorConfig = exports.withHarmonyResources = exports.withHarmonyMod = exports.withHarmonyDangerousMod = exports.withHarmonyBaseMods = exports.withHarmonyAutolinking = exports.withEntryOhPackage = exports.withEntryHvigor = exports.withEntryBuildProfile = exports.withEntryAbility = exports.withAbilityStage = exports.withCppPackageProvider = exports.withColors = exports.withCMakeLists = exports.withArkTSPackageProvider = exports.withAppJson = exports.recordManagedFile = exports.HARMONY_MOD_NAMES = exports.normalizeHarmonyConfig = exports.stableHarmonyJson = exports.atomicWrite = exports.HarmonyConfigPluginError = exports.withHarmonyJsonFile = exports.createHarmonyFileMod = exports.compileHarmonyModsAsync = exports.withStaticPlugin = exports.withRunOnce = exports.withPlugins = exports.createRunOncePlugin = exports.defineExpoHarmonyConfig = void 0;
|
|
37
37
|
var config_1 = require("./config");
|
|
38
38
|
Object.defineProperty(exports, "defineExpoHarmonyConfig", { enumerable: true, get: function () { return config_1.defineExpoHarmonyConfig; } });
|
|
39
39
|
var composition_1 = require("./composition");
|
|
@@ -94,3 +94,6 @@ Object.defineProperty(exports, "withRawfile", { enumerable: true, get: function
|
|
|
94
94
|
var permissions_1 = require("./permissions");
|
|
95
95
|
Object.defineProperty(exports, "HarmonyPermissions", { enumerable: true, get: function () { return permissions_1.HarmonyPermissions; } });
|
|
96
96
|
Object.defineProperty(exports, "withHarmonyPermissions", { enumerable: true, get: function () { return permissions_1.withHarmonyPermissions; } });
|
|
97
|
+
var sdkVersion_1 = require("./sdkVersion");
|
|
98
|
+
Object.defineProperty(exports, "parseHarmonySdkVersion", { enumerable: true, get: function () { return sdkVersion_1.parseHarmonySdkVersion; } });
|
|
99
|
+
Object.defineProperty(exports, "compareHarmonyApiVersions", { enumerable: true, get: function () { return sdkVersion_1.compareHarmonyApiVersions; } });
|
|
@@ -10,13 +10,14 @@ interface NormalizedHarmonyConfig {
|
|
|
10
10
|
icon?: string;
|
|
11
11
|
label: string;
|
|
12
12
|
moduleName: string;
|
|
13
|
+
moduleBuilds?: NonNullable<HarmonyConfig['moduleBuilds']>;
|
|
13
14
|
nativeOrientation: Exclude<NonNullable<HarmonyConfig['orientation']>, 'default'> | 'unspecified';
|
|
14
15
|
permissions: NonNullable<HarmonyConfig['permissions']>;
|
|
15
16
|
productName: string;
|
|
16
17
|
querySchemes: string[];
|
|
17
18
|
signingConfigFile?: string;
|
|
18
19
|
skills: NonNullable<HarmonyConfig['skills']>;
|
|
19
|
-
targetApiVersion: number;
|
|
20
|
+
targetApiVersion: number | string;
|
|
20
21
|
targetSdkVersionString: string;
|
|
21
22
|
vendor: string;
|
|
22
23
|
versionCode: number;
|
package/build/normalizeConfig.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.normalizePermission = normalizePermission;
|
|
|
4
4
|
exports.normalizeHarmonyConfig = normalizeHarmonyConfig;
|
|
5
5
|
const errors_1 = require("./errors");
|
|
6
6
|
const resources_1 = require("./resources");
|
|
7
|
+
const sdkVersion_1 = require("./sdkVersion");
|
|
7
8
|
const BundlePattern = /^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*){2,}$/;
|
|
8
9
|
const IdentifierPattern = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
9
10
|
const ColorPattern = /^(#[0-9A-Fa-f]{6}|#[0-9A-Fa-f]{8})$/;
|
|
@@ -18,14 +19,6 @@ const Orientations = new Set([
|
|
|
18
19
|
const DeviceTypes = new Set(['phone', 'tablet', '2in1']);
|
|
19
20
|
const MinimumApi = 20;
|
|
20
21
|
const TargetApi = 24;
|
|
21
|
-
const SdkVersions = new Map([
|
|
22
|
-
[13, '5.0.1(13)'],
|
|
23
|
-
[14, '5.0.2(14)'],
|
|
24
|
-
[20, '6.0.0(20)'],
|
|
25
|
-
[21, '6.0.1(21)'],
|
|
26
|
-
[23, '6.1.0(23)'],
|
|
27
|
-
[24, '6.1.1(24)'],
|
|
28
|
-
]);
|
|
29
22
|
class HarmonyConfigError extends errors_1.HarmonyConfigPluginError {
|
|
30
23
|
constructor(message) {
|
|
31
24
|
super('ERR_HARMONY_CONFIG_INVALID', message, { operation: 'normalize-config' });
|
|
@@ -56,43 +49,6 @@ function normalizeColor(value, field, fallback) {
|
|
|
56
49
|
}
|
|
57
50
|
return (0, resources_1.toArgb)(color);
|
|
58
51
|
}
|
|
59
|
-
function parseSdkApi(value, field) {
|
|
60
|
-
if (value === undefined)
|
|
61
|
-
return null;
|
|
62
|
-
if (typeof value === 'number' && Number.isSafeInteger(value) && value > 0)
|
|
63
|
-
return value;
|
|
64
|
-
if (typeof value !== 'string') {
|
|
65
|
-
throw new HarmonyConfigError(`harmony.${field} must be a positive API number or an SDK label.`);
|
|
66
|
-
}
|
|
67
|
-
const match = /\((\d+)\)$/u.exec(value.trim());
|
|
68
|
-
const api = Number(match?.[1]);
|
|
69
|
-
if (!match || !Number.isSafeInteger(api) || api <= 0) {
|
|
70
|
-
throw new HarmonyConfigError(`harmony.${field} must end with its API level in parentheses, for example "6.0.0(20)".`);
|
|
71
|
-
}
|
|
72
|
-
return api;
|
|
73
|
-
}
|
|
74
|
-
function resolveSdkVersion(api, field, value) {
|
|
75
|
-
if (value !== undefined) {
|
|
76
|
-
if (typeof value === 'number' && Number.isSafeInteger(value) && value > 0) {
|
|
77
|
-
if (value !== api) {
|
|
78
|
-
throw new HarmonyConfigError(`harmony.${field} is API ${value}, but the configured API level is ${api}.`);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
const version = readString(value, `harmony.${field}`);
|
|
83
|
-
const parsed = parseSdkApi(version, field);
|
|
84
|
-
if (parsed !== api) {
|
|
85
|
-
throw new HarmonyConfigError(`harmony.${field} describes API ${parsed}, but the configured API level is ${api}.`);
|
|
86
|
-
}
|
|
87
|
-
return version;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
const version = SdkVersions.get(api);
|
|
91
|
-
if (!version) {
|
|
92
|
-
throw new HarmonyConfigError(`Harmony API ${api} has no built-in SDK label. Set harmony.${field} explicitly so new SDK releases do not require a package update.`);
|
|
93
|
-
}
|
|
94
|
-
return version;
|
|
95
|
-
}
|
|
96
52
|
function normalizeStringArray(value, field, allowed) {
|
|
97
53
|
if (!Array.isArray(value) || value.length === 0) {
|
|
98
54
|
throw new HarmonyConfigError(`${field} must be a non-empty array.`);
|
|
@@ -190,13 +146,19 @@ function normalizeHarmonyConfig(config) {
|
|
|
190
146
|
}
|
|
191
147
|
const version = readString(harmony.versionName, 'harmony.versionName', config.version || '1.0.0');
|
|
192
148
|
const code = readPositiveInteger(harmony.versionCode, 'versionCode', 1);
|
|
193
|
-
const target =
|
|
194
|
-
|
|
195
|
-
|
|
149
|
+
const target = (0, sdkVersion_1.parseHarmonySdkVersion)(harmony.targetSdkVersion ?? harmony.targetApiVersion ?? TargetApi, 'harmony.targetSdkVersion');
|
|
150
|
+
if (harmony.targetApiVersion !== undefined) {
|
|
151
|
+
const alias = (0, sdkVersion_1.parseHarmonySdkVersion)(harmony.targetApiVersion, 'harmony.targetApiVersion');
|
|
152
|
+
if ((0, sdkVersion_1.compareHarmonyApiVersions)(alias.api, target.api) !== 0) {
|
|
153
|
+
throw new HarmonyConfigError('harmony.targetApiVersion conflicts with harmony.targetSdkVersion.');
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const compatible = (0, sdkVersion_1.parseHarmonySdkVersion)(harmony.compatibleSdkVersion ?? MinimumApi, 'harmony.compatibleSdkVersion');
|
|
157
|
+
if ((0, sdkVersion_1.compareHarmonyApiVersions)(compatible.api, MinimumApi) < 0) {
|
|
196
158
|
throw new HarmonyConfigError(`Harmony compatible API must be ${MinimumApi} or newer.`);
|
|
197
159
|
}
|
|
198
|
-
if (compatible >
|
|
199
|
-
throw new HarmonyConfigError('Harmony compatibleSdkVersion cannot exceed
|
|
160
|
+
if ((0, sdkVersion_1.compareHarmonyApiVersions)(compatible.api, target.api) > 0) {
|
|
161
|
+
throw new HarmonyConfigError('Harmony compatibleSdkVersion cannot exceed targetSdkVersion.');
|
|
200
162
|
}
|
|
201
163
|
const orientation = harmony.orientation || config.orientation || 'default';
|
|
202
164
|
if (!Orientations.has(orientation)) {
|
|
@@ -211,7 +173,7 @@ function normalizeHarmonyConfig(config) {
|
|
|
211
173
|
? ['arm64-v8a', 'x86_64']
|
|
212
174
|
: normalizeStringArray(harmony.abiFilters, 'harmony.abiFilters');
|
|
213
175
|
for (const abi of abis) {
|
|
214
|
-
if (
|
|
176
|
+
if (!['arm64-v8a', 'x86_64'].includes(abi)) {
|
|
215
177
|
throw new HarmonyConfigError(`Invalid Harmony ABI filter: ${abi}`);
|
|
216
178
|
}
|
|
217
179
|
}
|
|
@@ -249,12 +211,27 @@ function normalizeHarmonyConfig(config) {
|
|
|
249
211
|
const devices = harmony.deviceTypes === undefined
|
|
250
212
|
? ['phone', 'tablet']
|
|
251
213
|
: normalizeStringArray(harmony.deviceTypes, 'harmony.deviceTypes', DeviceTypes);
|
|
214
|
+
const moduleBuilds = harmony.moduleBuilds;
|
|
215
|
+
if (moduleBuilds !== undefined) {
|
|
216
|
+
if (!moduleBuilds || typeof moduleBuilds !== 'object' || Array.isArray(moduleBuilds)) {
|
|
217
|
+
throw new HarmonyConfigError('harmony.moduleBuilds must be an object.');
|
|
218
|
+
}
|
|
219
|
+
for (const [name, options] of Object.entries(moduleBuilds)) {
|
|
220
|
+
if (!/^(@[a-z0-9._~-]+\/)?[a-z0-9._~-]+$/u.test(name)
|
|
221
|
+
|| !options || typeof options !== 'object' || Array.isArray(options)
|
|
222
|
+
|| Object.values(options).some(value => !['boolean', 'string', 'number'].includes(typeof value)
|
|
223
|
+
|| (typeof value === 'number' && !Number.isFinite(value)))) {
|
|
224
|
+
throw new HarmonyConfigError(`Invalid native module build options for ${name}.`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
252
228
|
return Object.freeze({
|
|
229
|
+
...(moduleBuilds && Object.keys(moduleBuilds).length ? { moduleBuilds: structuredClone(moduleBuilds) } : {}),
|
|
253
230
|
abiFilters: abis,
|
|
254
231
|
abilityName: ability,
|
|
255
232
|
backgroundColor: background,
|
|
256
233
|
bundleName: bundle,
|
|
257
|
-
compatibleSdkVersionString:
|
|
234
|
+
compatibleSdkVersionString: compatible.native,
|
|
258
235
|
deviceTypes: devices,
|
|
259
236
|
icon: harmony.icon || config.icon,
|
|
260
237
|
label: readString(harmony.label, 'harmony.label', config.name),
|
|
@@ -265,8 +242,8 @@ function normalizeHarmonyConfig(config) {
|
|
|
265
242
|
querySchemes: queries,
|
|
266
243
|
signingConfigFile: signing,
|
|
267
244
|
skills,
|
|
268
|
-
targetApiVersion: target,
|
|
269
|
-
targetSdkVersionString:
|
|
245
|
+
targetApiVersion: target.api,
|
|
246
|
+
targetSdkVersionString: target.native,
|
|
270
247
|
vendor: readString(harmony.vendor, 'harmony.vendor', 'expo-harmony'),
|
|
271
248
|
versionCode: code,
|
|
272
249
|
versionName: version,
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export interface HarmonySdkVersion {
|
|
2
|
+
api: number | string;
|
|
3
|
+
native: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function parseHarmonySdkVersion(value: unknown, field?: string): HarmonySdkVersion;
|
|
6
|
+
export declare function compareHarmonyApiVersions(left: number | string, right: number | string): number;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseHarmonySdkVersion = parseHarmonySdkVersion;
|
|
4
|
+
exports.compareHarmonyApiVersions = compareHarmonyApiVersions;
|
|
5
|
+
const errors_1 = require("./errors");
|
|
6
|
+
const Labels = new Map([
|
|
7
|
+
[13, '5.0.1(13)'], [14, '5.0.2(14)'], [20, '6.0.0(20)'],
|
|
8
|
+
[21, '6.0.1(21)'], [23, '6.1.0(23)'], [24, '6.1.1(24)'],
|
|
9
|
+
]);
|
|
10
|
+
function parseHarmonySdkVersion(value, field = 'SDK version') {
|
|
11
|
+
if (typeof value === 'number') {
|
|
12
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > 999) {
|
|
13
|
+
throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `${field}: expected a positive API level.`);
|
|
14
|
+
}
|
|
15
|
+
if (value >= 26)
|
|
16
|
+
return { api: `${value}.0.0`, native: `${value}.0.0` };
|
|
17
|
+
const native = Labels.get(value);
|
|
18
|
+
if (!native) {
|
|
19
|
+
throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `${field}: API ${value} requires an explicit HarmonyOS SDK label.`);
|
|
20
|
+
}
|
|
21
|
+
return { api: value, native };
|
|
22
|
+
}
|
|
23
|
+
if (typeof value !== 'string') {
|
|
24
|
+
throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `${field}: expected an API number or HarmonyOS SDK version.`);
|
|
25
|
+
}
|
|
26
|
+
const version = value.trim();
|
|
27
|
+
const dotted = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(version);
|
|
28
|
+
if (dotted && Number(dotted[1]) >= 26
|
|
29
|
+
&& dotted.slice(1).every(part => String(Number(part)) === part)) {
|
|
30
|
+
return { api: version, native: version };
|
|
31
|
+
}
|
|
32
|
+
const label = /^\d+\.\d+\.\d+\(([1-9]\d*)\)$/.exec(version);
|
|
33
|
+
if (label && Number(label[1]) < 26) {
|
|
34
|
+
const api = Number(label[1]);
|
|
35
|
+
const known = Labels.get(api);
|
|
36
|
+
if (known && known !== version) {
|
|
37
|
+
throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `${field}: API ${api} uses SDK label ${known}.`);
|
|
38
|
+
}
|
|
39
|
+
return { api, native: version };
|
|
40
|
+
}
|
|
41
|
+
throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `${field}: expected an SDK label such as "6.1.1(24)", or a dotted API version such as "26.0.0".`);
|
|
42
|
+
}
|
|
43
|
+
function compareHarmonyApiVersions(left, right) {
|
|
44
|
+
const a = String(left).split('.').map(Number);
|
|
45
|
+
const b = String(right).split('.').map(Number);
|
|
46
|
+
for (let i = 0; i < 3; i++) {
|
|
47
|
+
const delta = (a[i] ?? 0) - (b[i] ?? 0);
|
|
48
|
+
if (delta)
|
|
49
|
+
return delta;
|
|
50
|
+
}
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expo-harmony/config-plugins",
|
|
3
|
-
"version": "55.0.
|
|
3
|
+
"version": "55.0.11-harmony.2",
|
|
4
4
|
"keywords": [
|
|
5
5
|
"react-native",
|
|
6
6
|
"expo",
|
|
@@ -42,13 +42,10 @@
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@expo/config-plugins": "55.0.
|
|
45
|
+
"@expo/config-plugins": "55.0.11",
|
|
46
46
|
"@expo/config-types": "^55.0.5",
|
|
47
47
|
"json5": "2.2.3"
|
|
48
48
|
},
|
|
49
|
-
"peerDependencies": {
|
|
50
|
-
"@expo/config": "55.0.17"
|
|
51
|
-
},
|
|
52
49
|
"devDependencies": {
|
|
53
50
|
"@types/node": "18.19.0",
|
|
54
51
|
"typescript": "^6.0.3"
|
package/src/config.ts
CHANGED
|
@@ -34,6 +34,10 @@ export interface HarmonyConfig {
|
|
|
34
34
|
vendor?: string;
|
|
35
35
|
versionCode?: number;
|
|
36
36
|
versionName?: string;
|
|
37
|
+
runtimeVersion?: string | {
|
|
38
|
+
policy: 'nativeVersion' | 'sdkVersion' | 'appVersion' | 'fingerprint';
|
|
39
|
+
};
|
|
40
|
+
/** @deprecated Configure targetSdkVersion in @expo-harmony/expo-build-properties. */
|
|
37
41
|
targetApiVersion?: number;
|
|
38
42
|
targetSdkVersion?: number | string;
|
|
39
43
|
compatibleSdkVersion?: number | string;
|
|
@@ -49,6 +53,8 @@ export interface HarmonyConfig {
|
|
|
49
53
|
jsEngine?: 'hermes';
|
|
50
54
|
abiFilters?: string[];
|
|
51
55
|
signingConfigFile?: string;
|
|
56
|
+
/** Native module build options supplied by config plugins. */
|
|
57
|
+
moduleBuilds?: Record<string, Record<string, string | number | boolean>>;
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
export type ExpoConfigWithHarmony = Omit<ExpoConfig, 'platforms'> & {
|
package/src/index.ts
CHANGED
|
@@ -78,3 +78,6 @@ export type { HarmonyResourceFile, HarmonyResourceItem } from './resources';
|
|
|
78
78
|
export { withHarmonyGeneratedFiles, withRawfile } from './generatedFiles';
|
|
79
79
|
export type { HarmonyFileDescriptor, HarmonyFileMap, HarmonyGeneratedFilesOptions } from './generatedFiles';
|
|
80
80
|
export { HarmonyPermissions, withHarmonyPermissions } from './permissions';
|
|
81
|
+
|
|
82
|
+
export { parseHarmonySdkVersion, compareHarmonyApiVersions } from './sdkVersion';
|
|
83
|
+
export type { HarmonySdkVersion } from './sdkVersion';
|
package/src/normalizeConfig.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
} from './config';
|
|
8
8
|
import { HarmonyConfigPluginError } from './errors';
|
|
9
9
|
import { toArgb } from './resources';
|
|
10
|
+
import { compareHarmonyApiVersions, parseHarmonySdkVersion } from './sdkVersion';
|
|
10
11
|
|
|
11
12
|
type HarmonyExpoConfig = ExpoConfigWithHarmony;
|
|
12
13
|
|
|
@@ -20,13 +21,14 @@ interface NormalizedHarmonyConfig {
|
|
|
20
21
|
icon?: string;
|
|
21
22
|
label: string;
|
|
22
23
|
moduleName: string;
|
|
24
|
+
moduleBuilds?: NonNullable<HarmonyConfig['moduleBuilds']>;
|
|
23
25
|
nativeOrientation: Exclude<NonNullable<HarmonyConfig['orientation']>, 'default'> | 'unspecified';
|
|
24
26
|
permissions: NonNullable<HarmonyConfig['permissions']>;
|
|
25
27
|
productName: string;
|
|
26
28
|
querySchemes: string[];
|
|
27
29
|
signingConfigFile?: string;
|
|
28
30
|
skills: NonNullable<HarmonyConfig['skills']>;
|
|
29
|
-
targetApiVersion: number;
|
|
31
|
+
targetApiVersion: number | string;
|
|
30
32
|
targetSdkVersionString: string;
|
|
31
33
|
vendor: string;
|
|
32
34
|
versionCode: number;
|
|
@@ -47,14 +49,6 @@ const Orientations = new Set([
|
|
|
47
49
|
const DeviceTypes = new Set<HarmonyDeviceType>(['phone', 'tablet', '2in1']);
|
|
48
50
|
const MinimumApi = 20;
|
|
49
51
|
const TargetApi = 24;
|
|
50
|
-
const SdkVersions = new Map([
|
|
51
|
-
[13, '5.0.1(13)'],
|
|
52
|
-
[14, '5.0.2(14)'],
|
|
53
|
-
[20, '6.0.0(20)'],
|
|
54
|
-
[21, '6.0.1(21)'],
|
|
55
|
-
[23, '6.1.0(23)'],
|
|
56
|
-
[24, '6.1.1(24)'],
|
|
57
|
-
]);
|
|
58
52
|
|
|
59
53
|
class HarmonyConfigError extends HarmonyConfigPluginError {
|
|
60
54
|
constructor(message: string) {
|
|
@@ -96,58 +90,6 @@ function normalizeColor(value: unknown, field: string, fallback: string): string
|
|
|
96
90
|
return toArgb(color);
|
|
97
91
|
}
|
|
98
92
|
|
|
99
|
-
function parseSdkApi(value: unknown, field: string): number | null {
|
|
100
|
-
if (value === undefined) return null;
|
|
101
|
-
if (typeof value === 'number' && Number.isSafeInteger(value) && value > 0) return value;
|
|
102
|
-
if (typeof value !== 'string') {
|
|
103
|
-
throw new HarmonyConfigError(`harmony.${field} must be a positive API number or an SDK label.`);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const match = /\((\d+)\)$/u.exec(value.trim());
|
|
107
|
-
const api = Number(match?.[1]);
|
|
108
|
-
|
|
109
|
-
if (!match || !Number.isSafeInteger(api) || api <= 0) {
|
|
110
|
-
throw new HarmonyConfigError(
|
|
111
|
-
`harmony.${field} must end with its API level in parentheses, for example "6.0.0(20)".`
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return api;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function resolveSdkVersion(api: number, field: string, value?: unknown): string {
|
|
119
|
-
if (value !== undefined) {
|
|
120
|
-
if (typeof value === 'number' && Number.isSafeInteger(value) && value > 0) {
|
|
121
|
-
if (value !== api) {
|
|
122
|
-
throw new HarmonyConfigError(
|
|
123
|
-
`harmony.${field} is API ${value}, but the configured API level is ${api}.`
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
} else {
|
|
127
|
-
const version = readString(value, `harmony.${field}`);
|
|
128
|
-
const parsed = parseSdkApi(version, field);
|
|
129
|
-
|
|
130
|
-
if (parsed !== api) {
|
|
131
|
-
throw new HarmonyConfigError(
|
|
132
|
-
`harmony.${field} describes API ${parsed}, but the configured API level is ${api}.`
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return version;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const version = SdkVersions.get(api);
|
|
141
|
-
|
|
142
|
-
if (!version) {
|
|
143
|
-
throw new HarmonyConfigError(
|
|
144
|
-
`Harmony API ${api} has no built-in SDK label. Set harmony.${field} explicitly so new SDK releases do not require a package update.`
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return version;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
93
|
function normalizeStringArray<T extends string = string>(
|
|
152
94
|
value: unknown,
|
|
153
95
|
field: string,
|
|
@@ -286,24 +228,25 @@ function normalizeHarmonyConfig(config: HarmonyExpoConfig): NormalizedHarmonyCon
|
|
|
286
228
|
const version = readString(harmony.versionName, 'harmony.versionName', config.version || '1.0.0');
|
|
287
229
|
const code = readPositiveInteger(harmony.versionCode, 'versionCode', 1);
|
|
288
230
|
|
|
289
|
-
const target =
|
|
290
|
-
harmony.targetApiVersion,
|
|
291
|
-
'targetApiVersion',
|
|
292
|
-
parseSdkApi(harmony.targetSdkVersion, 'targetSdkVersion') ?? TargetApi
|
|
293
|
-
);
|
|
294
|
-
const compatible = readPositiveInteger(
|
|
295
|
-
parseSdkApi(harmony.compatibleSdkVersion, 'compatibleSdkVersion') ?? undefined,
|
|
296
|
-
'compatibleSdkVersion',
|
|
297
|
-
MinimumApi
|
|
231
|
+
const target = parseHarmonySdkVersion(
|
|
232
|
+
harmony.targetSdkVersion ?? harmony.targetApiVersion ?? TargetApi, 'harmony.targetSdkVersion'
|
|
298
233
|
);
|
|
299
234
|
|
|
300
|
-
if (
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
)
|
|
235
|
+
if (harmony.targetApiVersion !== undefined) {
|
|
236
|
+
const alias = parseHarmonySdkVersion(harmony.targetApiVersion, 'harmony.targetApiVersion');
|
|
237
|
+
|
|
238
|
+
if (compareHarmonyApiVersions(alias.api, target.api) !== 0) {
|
|
239
|
+
throw new HarmonyConfigError('harmony.targetApiVersion conflicts with harmony.targetSdkVersion.');
|
|
240
|
+
}
|
|
304
241
|
}
|
|
305
|
-
|
|
306
|
-
|
|
242
|
+
|
|
243
|
+
const compatible = parseHarmonySdkVersion(harmony.compatibleSdkVersion ?? MinimumApi, 'harmony.compatibleSdkVersion');
|
|
244
|
+
|
|
245
|
+
if (compareHarmonyApiVersions(compatible.api, MinimumApi) < 0) {
|
|
246
|
+
throw new HarmonyConfigError(`Harmony compatible API must be ${MinimumApi} or newer.`);
|
|
247
|
+
}
|
|
248
|
+
if (compareHarmonyApiVersions(compatible.api, target.api) > 0) {
|
|
249
|
+
throw new HarmonyConfigError('Harmony compatibleSdkVersion cannot exceed targetSdkVersion.');
|
|
307
250
|
}
|
|
308
251
|
|
|
309
252
|
const orientation = harmony.orientation || config.orientation || 'default';
|
|
@@ -327,7 +270,7 @@ function normalizeHarmonyConfig(config: HarmonyExpoConfig): NormalizedHarmonyCon
|
|
|
327
270
|
: normalizeStringArray(harmony.abiFilters, 'harmony.abiFilters');
|
|
328
271
|
|
|
329
272
|
for (const abi of abis) {
|
|
330
|
-
if (
|
|
273
|
+
if (!['arm64-v8a', 'x86_64'].includes(abi)) {
|
|
331
274
|
throw new HarmonyConfigError(`Invalid Harmony ABI filter: ${abi}`);
|
|
332
275
|
}
|
|
333
276
|
}
|
|
@@ -378,16 +321,28 @@ function normalizeHarmonyConfig(config: HarmonyExpoConfig): NormalizedHarmonyCon
|
|
|
378
321
|
DeviceTypes
|
|
379
322
|
);
|
|
380
323
|
|
|
324
|
+
const moduleBuilds = harmony.moduleBuilds;
|
|
325
|
+
if (moduleBuilds !== undefined) {
|
|
326
|
+
if (!moduleBuilds || typeof moduleBuilds !== 'object' || Array.isArray(moduleBuilds)) {
|
|
327
|
+
throw new HarmonyConfigError('harmony.moduleBuilds must be an object.');
|
|
328
|
+
}
|
|
329
|
+
for (const [name, options] of Object.entries(moduleBuilds)) {
|
|
330
|
+
if (!/^(@[a-z0-9._~-]+\/)?[a-z0-9._~-]+$/u.test(name)
|
|
331
|
+
|| !options || typeof options !== 'object' || Array.isArray(options)
|
|
332
|
+
|| Object.values(options).some(value => !['boolean', 'string', 'number'].includes(typeof value)
|
|
333
|
+
|| (typeof value === 'number' && !Number.isFinite(value)))) {
|
|
334
|
+
throw new HarmonyConfigError(`Invalid native module build options for ${name}.`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
381
339
|
return Object.freeze({
|
|
340
|
+
...(moduleBuilds && Object.keys(moduleBuilds).length ? { moduleBuilds: structuredClone(moduleBuilds) } : {}),
|
|
382
341
|
abiFilters: abis,
|
|
383
342
|
abilityName: ability,
|
|
384
343
|
backgroundColor: background,
|
|
385
344
|
bundleName: bundle,
|
|
386
|
-
compatibleSdkVersionString:
|
|
387
|
-
compatible,
|
|
388
|
-
'compatibleSdkVersion',
|
|
389
|
-
harmony.compatibleSdkVersion
|
|
390
|
-
),
|
|
345
|
+
compatibleSdkVersionString: compatible.native,
|
|
391
346
|
deviceTypes: devices,
|
|
392
347
|
icon: harmony.icon || config.icon,
|
|
393
348
|
label: readString(harmony.label, 'harmony.label', config.name),
|
|
@@ -398,12 +353,8 @@ function normalizeHarmonyConfig(config: HarmonyExpoConfig): NormalizedHarmonyCon
|
|
|
398
353
|
querySchemes: queries,
|
|
399
354
|
signingConfigFile: signing,
|
|
400
355
|
skills,
|
|
401
|
-
targetApiVersion: target,
|
|
402
|
-
targetSdkVersionString:
|
|
403
|
-
target,
|
|
404
|
-
'targetSdkVersion',
|
|
405
|
-
harmony.targetSdkVersion
|
|
406
|
-
),
|
|
356
|
+
targetApiVersion: target.api,
|
|
357
|
+
targetSdkVersionString: target.native,
|
|
407
358
|
vendor: readString(harmony.vendor, 'harmony.vendor', 'expo-harmony'),
|
|
408
359
|
versionCode: code,
|
|
409
360
|
versionName: version,
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { HarmonyConfigPluginError } from './errors';
|
|
2
|
+
|
|
3
|
+
export interface HarmonySdkVersion {
|
|
4
|
+
api: number | string;
|
|
5
|
+
native: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const Labels = new Map([
|
|
9
|
+
[13, '5.0.1(13)'], [14, '5.0.2(14)'], [20, '6.0.0(20)'],
|
|
10
|
+
[21, '6.0.1(21)'], [23, '6.1.0(23)'], [24, '6.1.1(24)'],
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
export function parseHarmonySdkVersion(value: unknown, field = 'SDK version'): HarmonySdkVersion {
|
|
14
|
+
if (typeof value === 'number') {
|
|
15
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > 999) {
|
|
16
|
+
throw new HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `${field}: expected a positive API level.`);
|
|
17
|
+
}
|
|
18
|
+
if (value >= 26) return { api: `${value}.0.0`, native: `${value}.0.0` };
|
|
19
|
+
|
|
20
|
+
const native = Labels.get(value);
|
|
21
|
+
|
|
22
|
+
if (!native) {
|
|
23
|
+
throw new HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `${field}: API ${value} requires an explicit HarmonyOS SDK label.`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return { api: value, native };
|
|
27
|
+
}
|
|
28
|
+
if (typeof value !== 'string') {
|
|
29
|
+
throw new HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `${field}: expected an API number or HarmonyOS SDK version.`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const version = value.trim();
|
|
33
|
+
const dotted = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(version);
|
|
34
|
+
|
|
35
|
+
if (dotted && Number(dotted[1]) >= 26
|
|
36
|
+
&& dotted.slice(1).every(part => String(Number(part)) === part)) {
|
|
37
|
+
return { api: version, native: version };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const label = /^\d+\.\d+\.\d+\(([1-9]\d*)\)$/.exec(version);
|
|
41
|
+
|
|
42
|
+
if (label && Number(label[1]) < 26) {
|
|
43
|
+
const api = Number(label[1]);
|
|
44
|
+
const known = Labels.get(api);
|
|
45
|
+
|
|
46
|
+
if (known && known !== version) {
|
|
47
|
+
throw new HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `${field}: API ${api} uses SDK label ${known}.`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return { api, native: version };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
throw new HarmonyConfigPluginError(
|
|
54
|
+
'ERR_HARMONY_CONFIG_INVALID',
|
|
55
|
+
`${field}: expected an SDK label such as "6.1.1(24)", or a dotted API version such as "26.0.0".`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function compareHarmonyApiVersions(left: number | string, right: number | string): number {
|
|
60
|
+
const a = String(left).split('.').map(Number);
|
|
61
|
+
const b = String(right).split('.').map(Number);
|
|
62
|
+
|
|
63
|
+
for (let i = 0; i < 3; i++) {
|
|
64
|
+
const delta = (a[i] ?? 0) - (b[i] ?? 0);
|
|
65
|
+
if (delta) return delta;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return 0;
|
|
69
|
+
}
|