@expo-harmony/config-plugins 55.0.10-harmony.1 → 55.0.10-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/build/config.d.ts +64 -0
- package/build/config.js +6 -0
- package/build/errors.d.ts +11 -0
- package/build/errors.js +14 -0
- package/build/files.d.ts +6 -0
- package/build/files.js +69 -0
- package/build/index.d.ts +12 -0
- package/build/mods.d.ts +52 -0
- package/build/mods.js +400 -0
- package/build/nativeInputs.d.ts +14 -0
- package/build/nativeInputs.js +81 -0
- package/build/normalizeConfig.d.ts +28 -0
- package/build/normalizeConfig.js +276 -0
- package/build/ownership.d.ts +16 -0
- package/build/ownership.js +112 -0
- package/build/paths.d.ts +62 -0
- package/build/paths.js +129 -0
- package/package.json +1 -1
|
@@ -0,0 +1,81 @@
|
|
|
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.HarmonyNativeInputsFingerprintVersion = void 0;
|
|
7
|
+
exports.fingerprintHarmonyNativeInputsSync = fingerprintHarmonyNativeInputsSync;
|
|
8
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
9
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const json5_1 = __importDefault(require("json5"));
|
|
12
|
+
const HarmonyNativeInputsFingerprintVersion = 1;
|
|
13
|
+
exports.HarmonyNativeInputsFingerprintVersion = HarmonyNativeInputsFingerprintVersion;
|
|
14
|
+
function readOptionalFile(file) {
|
|
15
|
+
try {
|
|
16
|
+
return Uint8Array.from(node_fs_1.default.readFileSync(file));
|
|
17
|
+
}
|
|
18
|
+
catch (cause) {
|
|
19
|
+
if (cause && typeof cause === 'object' && 'code' in cause && cause.code === 'ENOENT')
|
|
20
|
+
return null;
|
|
21
|
+
throw new Error(cause instanceof Error ? cause.message : String(cause), { cause });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function resolveLocalHar(root, specifier) {
|
|
25
|
+
if (typeof specifier !== 'string' || !/^\.\.?[\\/]/u.test(specifier))
|
|
26
|
+
return null;
|
|
27
|
+
if (!specifier.toLowerCase().endsWith('.har'))
|
|
28
|
+
return null;
|
|
29
|
+
return node_path_1.default.resolve(root, specifier);
|
|
30
|
+
}
|
|
31
|
+
function findLocalHars(root, manifest) {
|
|
32
|
+
const specifiers = [
|
|
33
|
+
...Object.values(manifest.dependencies ?? {}),
|
|
34
|
+
...Object.values(manifest.overrides ?? {}),
|
|
35
|
+
];
|
|
36
|
+
return [...new Set(specifiers.map(specifier => resolveLocalHar(root, specifier)))]
|
|
37
|
+
.filter((value) => value !== null)
|
|
38
|
+
.sort();
|
|
39
|
+
}
|
|
40
|
+
function hashFile(hash, file) {
|
|
41
|
+
const handle = node_fs_1.default.openSync(file, 'r');
|
|
42
|
+
const buffer = new Uint8Array(1024 * 1024);
|
|
43
|
+
try {
|
|
44
|
+
while (true) {
|
|
45
|
+
const size = node_fs_1.default.readSync(handle, buffer, 0, buffer.length, null);
|
|
46
|
+
if (size === 0)
|
|
47
|
+
break;
|
|
48
|
+
hash.update(Uint8Array.from(buffer.subarray(0, size)));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
node_fs_1.default.closeSync(handle);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function fingerprintHarmonyNativeInputsSync(options) {
|
|
56
|
+
const data = node_fs_1.default.readFileSync(options.manifest);
|
|
57
|
+
const lock = readOptionalFile(options.lockfile);
|
|
58
|
+
const manifest = json5_1.default.parse(data.toString('utf8'));
|
|
59
|
+
const root = node_path_1.default.dirname(options.manifest);
|
|
60
|
+
const files = findLocalHars(root, manifest);
|
|
61
|
+
const hash = node_crypto_1.default.createHash('sha256');
|
|
62
|
+
hash.update(`expo-harmony-native-dependencies-v${HarmonyNativeInputsFingerprintVersion}\0`);
|
|
63
|
+
hash.update(Uint8Array.from(data));
|
|
64
|
+
if (lock)
|
|
65
|
+
hash.update(lock);
|
|
66
|
+
for (const file of files) {
|
|
67
|
+
if (!node_fs_1.default.statSync(file).isFile()) {
|
|
68
|
+
throw new Error(`Cannot fingerprint Harmony native artifact: ${file}`);
|
|
69
|
+
}
|
|
70
|
+
const relative = node_path_1.default.relative(options.projectRoot, file).split(node_path_1.default.sep).join('/');
|
|
71
|
+
hash.update('\0artifact\0');
|
|
72
|
+
hash.update(relative);
|
|
73
|
+
hash.update('\0');
|
|
74
|
+
hashFile(hash, file);
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
artifactCount: files.length,
|
|
78
|
+
fingerprint: hash.digest('hex'),
|
|
79
|
+
fingerprintVersion: HarmonyNativeInputsFingerprintVersion,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ExpoConfigWithHarmony, HarmonyConfig } from './config';
|
|
2
|
+
type HarmonyExpoConfig = ExpoConfigWithHarmony;
|
|
3
|
+
interface NormalizedHarmonyConfig {
|
|
4
|
+
abiFilters: string[];
|
|
5
|
+
abilityName: string;
|
|
6
|
+
backgroundColor: string;
|
|
7
|
+
bundleName: string;
|
|
8
|
+
compatibleSdkVersionString: string;
|
|
9
|
+
deviceTypes: NonNullable<HarmonyConfig['deviceTypes']>;
|
|
10
|
+
icon?: string;
|
|
11
|
+
label: string;
|
|
12
|
+
moduleName: string;
|
|
13
|
+
nativeOrientation: Exclude<NonNullable<HarmonyConfig['orientation']>, 'default'> | 'unspecified';
|
|
14
|
+
permissions: NonNullable<HarmonyConfig['permissions']>;
|
|
15
|
+
productName: string;
|
|
16
|
+
querySchemes: string[];
|
|
17
|
+
signingConfigFile?: string;
|
|
18
|
+
skills: NonNullable<HarmonyConfig['skills']>;
|
|
19
|
+
targetApiVersion: number;
|
|
20
|
+
targetSdkVersionString: string;
|
|
21
|
+
vendor: string;
|
|
22
|
+
versionCode: number;
|
|
23
|
+
versionName: string;
|
|
24
|
+
}
|
|
25
|
+
declare const HarmonySdkVersions: Map<number, string>;
|
|
26
|
+
declare function normalizeHarmonyConfig(config: HarmonyExpoConfig): NormalizedHarmonyConfig;
|
|
27
|
+
export { HarmonySdkVersions, normalizeHarmonyConfig };
|
|
28
|
+
export type { HarmonyExpoConfig, NormalizedHarmonyConfig };
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HarmonySdkVersions = void 0;
|
|
4
|
+
exports.normalizeHarmonyConfig = normalizeHarmonyConfig;
|
|
5
|
+
const errors_1 = require("./errors");
|
|
6
|
+
const BundleNamePattern = /^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*){2,}$/;
|
|
7
|
+
const IdentifierPattern = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
8
|
+
const ColorPattern = /^(#[0-9A-Fa-f]{6}|#[0-9A-Fa-f]{8})$/;
|
|
9
|
+
const ValidOrientations = new Set([
|
|
10
|
+
'default',
|
|
11
|
+
'portrait',
|
|
12
|
+
'landscape',
|
|
13
|
+
'portrait_inverted',
|
|
14
|
+
'landscape_inverted',
|
|
15
|
+
'auto_rotation',
|
|
16
|
+
]);
|
|
17
|
+
const ValidDeviceTypes = new Set(['phone', 'tablet', '2in1']);
|
|
18
|
+
const SupportedMinimumHarmonyApi = 13;
|
|
19
|
+
const DefaultHarmonyCompatibleApi = 23;
|
|
20
|
+
const DefaultHarmonyTargetApi = 24;
|
|
21
|
+
const HarmonySdkVersions = new Map([
|
|
22
|
+
[13, '5.0.1(13)'],
|
|
23
|
+
[20, '6.0.0(20)'],
|
|
24
|
+
[23, '6.1.0(23)'],
|
|
25
|
+
[24, '6.1.1(24)'],
|
|
26
|
+
]);
|
|
27
|
+
exports.HarmonySdkVersions = HarmonySdkVersions;
|
|
28
|
+
class HarmonyConfigError extends errors_1.HarmonyConfigPluginError {
|
|
29
|
+
constructor(message) {
|
|
30
|
+
super('ERR_HARMONY_CONFIG_INVALID', message, { operation: 'normalize-config' });
|
|
31
|
+
this.name = 'HarmonyConfigError';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function readPositiveInteger(value, field, fallback) {
|
|
35
|
+
const result = value === undefined ? fallback : value;
|
|
36
|
+
if (typeof result !== 'number' || !Number.isSafeInteger(result) || result <= 0) {
|
|
37
|
+
throw new HarmonyConfigError(`harmony.${field} must be a positive integer.`);
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
function readString(value, field, fallback) {
|
|
42
|
+
const result = value === undefined ? fallback : value;
|
|
43
|
+
if (typeof result !== 'string' || !result.trim()) {
|
|
44
|
+
throw new HarmonyConfigError(`${field} must be a non-empty string.`);
|
|
45
|
+
}
|
|
46
|
+
if (/\0|[\r\n]/.test(result)) {
|
|
47
|
+
throw new HarmonyConfigError(`${field} must not contain control characters.`);
|
|
48
|
+
}
|
|
49
|
+
return result.trim();
|
|
50
|
+
}
|
|
51
|
+
function normalizeColor(value, field, fallback) {
|
|
52
|
+
const color = value === undefined ? fallback : value;
|
|
53
|
+
if (typeof color !== 'string' || !ColorPattern.test(color)) {
|
|
54
|
+
throw new HarmonyConfigError(`${field} must be #RRGGBB or #RRGGBBAA.`);
|
|
55
|
+
}
|
|
56
|
+
const result = color.toUpperCase();
|
|
57
|
+
return result.length === 9
|
|
58
|
+
? `#${result.slice(7, 9)}${result.slice(1, 7)}`
|
|
59
|
+
: result;
|
|
60
|
+
}
|
|
61
|
+
function parseSdkApi(value, field) {
|
|
62
|
+
if (value === undefined)
|
|
63
|
+
return null;
|
|
64
|
+
if (typeof value === 'number' && Number.isSafeInteger(value) && value > 0)
|
|
65
|
+
return value;
|
|
66
|
+
if (typeof value !== 'string') {
|
|
67
|
+
throw new HarmonyConfigError(`harmony.${field} must be a positive API number or an SDK label.`);
|
|
68
|
+
}
|
|
69
|
+
const match = /\((\d+)\)$/u.exec(value.trim());
|
|
70
|
+
const api = Number(match?.[1]);
|
|
71
|
+
if (!match || !Number.isSafeInteger(api) || api <= 0) {
|
|
72
|
+
throw new HarmonyConfigError(`harmony.${field} must end with its API level in parentheses, for example "6.0.0(20)".`);
|
|
73
|
+
}
|
|
74
|
+
return api;
|
|
75
|
+
}
|
|
76
|
+
function resolveSdkVersion(api, field, value) {
|
|
77
|
+
if (value !== undefined) {
|
|
78
|
+
if (typeof value === 'number' && Number.isSafeInteger(value) && value > 0) {
|
|
79
|
+
if (value !== api) {
|
|
80
|
+
throw new HarmonyConfigError(`harmony.${field} is API ${value}, but the configured API level is ${api}.`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
const version = readString(value, `harmony.${field}`);
|
|
85
|
+
const versionApi = parseSdkApi(version, field);
|
|
86
|
+
if (versionApi !== api) {
|
|
87
|
+
throw new HarmonyConfigError(`harmony.${field} describes API ${versionApi}, but the configured API level is ${api}.`);
|
|
88
|
+
}
|
|
89
|
+
return version;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const version = HarmonySdkVersions.get(api);
|
|
93
|
+
if (!version) {
|
|
94
|
+
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.`);
|
|
95
|
+
}
|
|
96
|
+
return version;
|
|
97
|
+
}
|
|
98
|
+
function normalizeStringArray(value, field, allowed) {
|
|
99
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
100
|
+
throw new HarmonyConfigError(`${field} must be a non-empty array.`);
|
|
101
|
+
}
|
|
102
|
+
const result = [];
|
|
103
|
+
for (const item of value) {
|
|
104
|
+
if (typeof item !== 'string' || !item || (allowed && !allowed.has(item))) {
|
|
105
|
+
throw new HarmonyConfigError(`Invalid ${field} entry: ${item}`);
|
|
106
|
+
}
|
|
107
|
+
if (!result.includes(item))
|
|
108
|
+
result.push(item);
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
function normalizePermission(permission) {
|
|
113
|
+
if (!permission || typeof permission !== 'object') {
|
|
114
|
+
throw new HarmonyConfigError('harmony.permissions entries must be objects.');
|
|
115
|
+
}
|
|
116
|
+
const name = readString(permission.name, 'harmony.permissions[].name');
|
|
117
|
+
if (!/^ohos\.permission\.[A-Z0-9_]+$/.test(name)) {
|
|
118
|
+
throw new HarmonyConfigError(`Invalid Harmony permission: ${name}`);
|
|
119
|
+
}
|
|
120
|
+
const result = { name };
|
|
121
|
+
if (permission.reason !== undefined) {
|
|
122
|
+
result.reason = readString(permission.reason, 'harmony.permissions[].reason');
|
|
123
|
+
}
|
|
124
|
+
if (permission.usedScene !== undefined) {
|
|
125
|
+
if (!permission.usedScene || typeof permission.usedScene !== 'object') {
|
|
126
|
+
throw new HarmonyConfigError('permission.usedScene must be an object.');
|
|
127
|
+
}
|
|
128
|
+
const when = permission.usedScene.when === undefined
|
|
129
|
+
? 'inuse'
|
|
130
|
+
: permission.usedScene.when;
|
|
131
|
+
if (!['inuse', 'always'].includes(when)) {
|
|
132
|
+
throw new HarmonyConfigError('permission.usedScene.when must be inuse or always.');
|
|
133
|
+
}
|
|
134
|
+
const abilities = permission.usedScene.abilities === undefined
|
|
135
|
+
? undefined
|
|
136
|
+
: normalizeStringArray(permission.usedScene.abilities, 'permission.usedScene.abilities');
|
|
137
|
+
result.usedScene = { ...(abilities ? { abilities } : {}), when };
|
|
138
|
+
}
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
function normalizeSkill(skill) {
|
|
142
|
+
if (!skill || typeof skill !== 'object') {
|
|
143
|
+
throw new HarmonyConfigError('harmony.skills entries must be objects.');
|
|
144
|
+
}
|
|
145
|
+
const result = {};
|
|
146
|
+
for (const field of ['entities', 'actions']) {
|
|
147
|
+
if (skill[field] !== undefined)
|
|
148
|
+
result[field] = normalizeStringArray(skill[field], `harmony.skills[].${field}`);
|
|
149
|
+
}
|
|
150
|
+
if (skill.uris !== undefined) {
|
|
151
|
+
if (!Array.isArray(skill.uris)) {
|
|
152
|
+
throw new HarmonyConfigError('harmony.skills[].uris must be an array.');
|
|
153
|
+
}
|
|
154
|
+
result.uris = skill.uris.map((uri) => {
|
|
155
|
+
if (!uri || typeof uri !== 'object' || Array.isArray(uri)) {
|
|
156
|
+
throw new HarmonyConfigError('Harmony skill URI must be an object.');
|
|
157
|
+
}
|
|
158
|
+
const result = {};
|
|
159
|
+
for (const [key, value] of Object.entries(uri)) {
|
|
160
|
+
result[key] = readString(value, `harmony.skills[].uris[].${key}`);
|
|
161
|
+
}
|
|
162
|
+
return result;
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
if (Object.keys(result).length === 0) {
|
|
166
|
+
throw new HarmonyConfigError('Harmony skill must contain entities, actions, or uris.');
|
|
167
|
+
}
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
function readExpoSchemes(config) {
|
|
171
|
+
const values = Array.isArray(config.scheme) ? config.scheme : config.scheme ? [config.scheme] : [];
|
|
172
|
+
return values.map(value => readString(value, 'scheme'));
|
|
173
|
+
}
|
|
174
|
+
function normalizeHarmonyConfig(config) {
|
|
175
|
+
const harmony = config.harmony;
|
|
176
|
+
if (!harmony || typeof harmony !== 'object' || Array.isArray(harmony)) {
|
|
177
|
+
throw new HarmonyConfigError('Expo config must contain a harmony object.');
|
|
178
|
+
}
|
|
179
|
+
const bundleName = readString(harmony.bundleName, 'harmony.bundleName');
|
|
180
|
+
if (!BundleNamePattern.test(bundleName)) {
|
|
181
|
+
throw new HarmonyConfigError('harmony.bundleName must contain at least three valid dot-separated segments.');
|
|
182
|
+
}
|
|
183
|
+
const moduleName = readString(harmony.moduleName, 'harmony.moduleName', 'entry');
|
|
184
|
+
const abilityName = readString(harmony.abilityName, 'harmony.abilityName', 'EntryAbility');
|
|
185
|
+
if (!IdentifierPattern.test(moduleName)) {
|
|
186
|
+
throw new HarmonyConfigError('harmony.moduleName must be a valid Harmony identifier.');
|
|
187
|
+
}
|
|
188
|
+
if (!IdentifierPattern.test(abilityName)) {
|
|
189
|
+
throw new HarmonyConfigError('harmony.abilityName must be a valid Harmony identifier.');
|
|
190
|
+
}
|
|
191
|
+
const versionName = readString(harmony.versionName, 'harmony.versionName', config.version || '1.0.0');
|
|
192
|
+
const versionCode = readPositiveInteger(harmony.versionCode, 'versionCode', 1);
|
|
193
|
+
const targetSdkApi = parseSdkApi(harmony.targetSdkVersion, 'targetSdkVersion');
|
|
194
|
+
const targetApi = readPositiveInteger(harmony.targetApiVersion, 'targetApiVersion', targetSdkApi ?? DefaultHarmonyTargetApi);
|
|
195
|
+
const compatibleSdkApi = parseSdkApi(harmony.compatibleSdkVersion, 'compatibleSdkVersion');
|
|
196
|
+
const compatibleApi = readPositiveInteger(compatibleSdkApi ?? undefined, 'compatibleSdkVersion', DefaultHarmonyCompatibleApi);
|
|
197
|
+
if (compatibleApi < SupportedMinimumHarmonyApi) {
|
|
198
|
+
throw new HarmonyConfigError(`Harmony compatible API must be ${SupportedMinimumHarmonyApi} or newer.`);
|
|
199
|
+
}
|
|
200
|
+
if (compatibleApi > targetApi) {
|
|
201
|
+
throw new HarmonyConfigError('Harmony compatibleSdkVersion cannot exceed targetApiVersion.');
|
|
202
|
+
}
|
|
203
|
+
const orientation = harmony.orientation || config.orientation || 'default';
|
|
204
|
+
if (!ValidOrientations.has(orientation)) {
|
|
205
|
+
throw new HarmonyConfigError(`Unsupported Harmony orientation: ${orientation}`);
|
|
206
|
+
}
|
|
207
|
+
const style = harmony.userInterfaceStyle || config.userInterfaceStyle || 'light';
|
|
208
|
+
if (!['light', 'dark', 'automatic'].includes(style)) {
|
|
209
|
+
throw new HarmonyConfigError(`Unsupported Harmony UI style: ${style}`);
|
|
210
|
+
}
|
|
211
|
+
const background = normalizeColor(harmony.backgroundColor || config.backgroundColor, 'harmony.backgroundColor', '#FFFFFF');
|
|
212
|
+
const engine = harmony.jsEngine || 'hermes';
|
|
213
|
+
if (engine !== 'hermes') {
|
|
214
|
+
throw new HarmonyConfigError('Expo Harmony currently supports only the Hermes JavaScript engine.');
|
|
215
|
+
}
|
|
216
|
+
const abis = harmony.abiFilters === undefined
|
|
217
|
+
? ['arm64-v8a', 'x86_64']
|
|
218
|
+
: normalizeStringArray(harmony.abiFilters, 'harmony.abiFilters');
|
|
219
|
+
for (const abi of abis) {
|
|
220
|
+
if (!/^[A-Za-z0-9_-]+$/.test(abi)) {
|
|
221
|
+
throw new HarmonyConfigError(`Invalid Harmony ABI filter: ${abi}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const permissions = (harmony.permissions || []).map(normalizePermission);
|
|
225
|
+
if (!permissions.some(permission => permission.name === 'ohos.permission.INTERNET')) {
|
|
226
|
+
permissions.unshift({ name: 'ohos.permission.INTERNET' });
|
|
227
|
+
}
|
|
228
|
+
const schemes = readExpoSchemes(config);
|
|
229
|
+
let skills = (harmony.skills || []).map(normalizeSkill);
|
|
230
|
+
for (const scheme of schemes) {
|
|
231
|
+
skills.push({
|
|
232
|
+
actions: ['ohos.want.action.viewData'],
|
|
233
|
+
entities: ['entity.system.browsable'],
|
|
234
|
+
uris: [{ scheme }],
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
skills = [...new Map(skills.map(skill => [JSON.stringify(skill), skill])).values()];
|
|
238
|
+
const queries = [...new Set([
|
|
239
|
+
'http',
|
|
240
|
+
'https',
|
|
241
|
+
'tel',
|
|
242
|
+
'sms',
|
|
243
|
+
...schemes,
|
|
244
|
+
...(harmony.querySchemes === undefined
|
|
245
|
+
? []
|
|
246
|
+
: normalizeStringArray(harmony.querySchemes, 'harmony.querySchemes')),
|
|
247
|
+
])];
|
|
248
|
+
const signing = harmony.signingConfigFile === undefined
|
|
249
|
+
? undefined
|
|
250
|
+
: readString(harmony.signingConfigFile, 'harmony.signingConfigFile');
|
|
251
|
+
const devices = harmony.deviceTypes === undefined
|
|
252
|
+
? ['phone', 'tablet']
|
|
253
|
+
: normalizeStringArray(harmony.deviceTypes, 'harmony.deviceTypes', ValidDeviceTypes);
|
|
254
|
+
return Object.freeze({
|
|
255
|
+
abiFilters: abis,
|
|
256
|
+
abilityName,
|
|
257
|
+
backgroundColor: background,
|
|
258
|
+
bundleName,
|
|
259
|
+
compatibleSdkVersionString: resolveSdkVersion(compatibleApi, 'compatibleSdkVersion', harmony.compatibleSdkVersion),
|
|
260
|
+
deviceTypes: devices,
|
|
261
|
+
icon: harmony.icon || config.icon,
|
|
262
|
+
label: readString(harmony.label, 'harmony.label', config.name),
|
|
263
|
+
moduleName,
|
|
264
|
+
nativeOrientation: orientation === 'default' ? 'unspecified' : orientation,
|
|
265
|
+
permissions,
|
|
266
|
+
productName: readString(harmony.productName, 'harmony.productName', 'default'),
|
|
267
|
+
querySchemes: queries,
|
|
268
|
+
signingConfigFile: signing,
|
|
269
|
+
skills,
|
|
270
|
+
targetApiVersion: targetApi,
|
|
271
|
+
targetSdkVersionString: resolveSdkVersion(targetApi, 'targetSdkVersion', harmony.targetSdkVersion),
|
|
272
|
+
vendor: readString(harmony.vendor, 'harmony.vendor', 'expo-harmony'),
|
|
273
|
+
versionCode,
|
|
274
|
+
versionName,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ExpoConfig } from '@expo/config-types';
|
|
2
|
+
export interface HarmonyConfigPluginOwnership {
|
|
3
|
+
readonly owner: string;
|
|
4
|
+
readonly ability?: Readonly<Partial<{
|
|
5
|
+
startWindowBackground: string;
|
|
6
|
+
startWindowIcon: string;
|
|
7
|
+
}>>;
|
|
8
|
+
readonly resources?: Readonly<Partial<{
|
|
9
|
+
colors: Readonly<Partial<Record<'entry' | 'entryDark', readonly string[]>>>;
|
|
10
|
+
media: Readonly<Partial<Record<'app' | 'entry' | 'entryDark', readonly string[]>>>;
|
|
11
|
+
strings: Readonly<Partial<Record<'app' | 'entry', readonly string[]>>>;
|
|
12
|
+
}>>;
|
|
13
|
+
}
|
|
14
|
+
export declare function normalizeHarmonyConfigPlugins(value: unknown): readonly HarmonyConfigPluginOwnership[];
|
|
15
|
+
export declare function registerHarmonyConfigPlugin(config: ExpoConfig, owner: string, claims?: Omit<HarmonyConfigPluginOwnership, 'owner'>): ExpoConfig;
|
|
16
|
+
export declare function getHarmonyConfigPlugins(config: ExpoConfig): readonly HarmonyConfigPluginOwnership[];
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeHarmonyConfigPlugins = normalizeHarmonyConfigPlugins;
|
|
4
|
+
exports.registerHarmonyConfigPlugin = registerHarmonyConfigPlugin;
|
|
5
|
+
exports.getHarmonyConfigPlugins = getHarmonyConfigPlugins;
|
|
6
|
+
const ResourceScopes = {
|
|
7
|
+
colors: new Set(['entry', 'entryDark']),
|
|
8
|
+
media: new Set(['app', 'entry', 'entryDark']),
|
|
9
|
+
strings: new Set(['app', 'entry']),
|
|
10
|
+
};
|
|
11
|
+
const AbilityFields = new Set(['startWindowBackground', 'startWindowIcon']);
|
|
12
|
+
const ResourceName = /^[A-Za-z0-9_.-]+$/u;
|
|
13
|
+
function isRecord(value) {
|
|
14
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
15
|
+
}
|
|
16
|
+
function normalizeStringList(value, field) {
|
|
17
|
+
if (!Array.isArray(value)) {
|
|
18
|
+
throw new TypeError(`Invalid Harmony config-plugin ownership: ${field} must be an array.`);
|
|
19
|
+
}
|
|
20
|
+
const items = [];
|
|
21
|
+
for (const item of value) {
|
|
22
|
+
if (typeof item !== 'string' || !item || !ResourceName.test(item) || item.includes('..')) {
|
|
23
|
+
throw new TypeError(`Invalid Harmony config-plugin ownership: ${field} contains an invalid resource name.`);
|
|
24
|
+
}
|
|
25
|
+
if (!items.includes(item))
|
|
26
|
+
items.push(item);
|
|
27
|
+
}
|
|
28
|
+
return items.sort((left, right) => left.localeCompare(right, 'en'));
|
|
29
|
+
}
|
|
30
|
+
function normalizeResources(value) {
|
|
31
|
+
if (value === undefined)
|
|
32
|
+
return {};
|
|
33
|
+
if (!isRecord(value)) {
|
|
34
|
+
throw new TypeError('Invalid Harmony config-plugin ownership: resources must be an object.');
|
|
35
|
+
}
|
|
36
|
+
const resources = {};
|
|
37
|
+
for (const kind of Object.keys(value).sort()) {
|
|
38
|
+
if (!Object.hasOwn(ResourceScopes, kind)) {
|
|
39
|
+
throw new TypeError(`Invalid Harmony config-plugin ownership: resources.${kind} is not supported.`);
|
|
40
|
+
}
|
|
41
|
+
const scopes = value[kind];
|
|
42
|
+
if (!isRecord(scopes)) {
|
|
43
|
+
throw new TypeError(`Invalid Harmony config-plugin ownership: resources.${kind} must be an object.`);
|
|
44
|
+
}
|
|
45
|
+
const allowed = ResourceScopes[kind];
|
|
46
|
+
resources[kind] = {};
|
|
47
|
+
for (const scope of Object.keys(scopes).sort()) {
|
|
48
|
+
if (!allowed.has(scope)) {
|
|
49
|
+
throw new TypeError(`Invalid Harmony config-plugin ownership: resources.${kind}.${scope} is not a supported scope.`);
|
|
50
|
+
}
|
|
51
|
+
resources[kind][scope] = normalizeStringList(scopes[scope], `resources.${kind}.${scope}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return resources;
|
|
55
|
+
}
|
|
56
|
+
function normalizeAbility(value) {
|
|
57
|
+
if (value === undefined)
|
|
58
|
+
return {};
|
|
59
|
+
if (!isRecord(value)) {
|
|
60
|
+
throw new TypeError('Invalid Harmony config-plugin ownership: ability must be an object.');
|
|
61
|
+
}
|
|
62
|
+
const ability = {};
|
|
63
|
+
for (const field of Object.keys(value).sort()) {
|
|
64
|
+
if (!AbilityFields.has(field)) {
|
|
65
|
+
throw new TypeError(`Invalid Harmony config-plugin ownership: ability.${field} is not supported.`);
|
|
66
|
+
}
|
|
67
|
+
if (typeof value[field] !== 'string' || !value[field]) {
|
|
68
|
+
throw new TypeError(`Invalid Harmony config-plugin ownership: ability.${field} must be a non-empty string.`);
|
|
69
|
+
}
|
|
70
|
+
ability[field] = value[field];
|
|
71
|
+
}
|
|
72
|
+
return ability;
|
|
73
|
+
}
|
|
74
|
+
function normalizeHarmonyConfigPlugins(value) {
|
|
75
|
+
if (value === undefined)
|
|
76
|
+
return [];
|
|
77
|
+
if (!Array.isArray(value)) {
|
|
78
|
+
throw new TypeError('Invalid Harmony config-plugin ownership: the plugin list must be an array.');
|
|
79
|
+
}
|
|
80
|
+
const owners = new Set();
|
|
81
|
+
const plugins = value.map((plugin, index) => {
|
|
82
|
+
if (!isRecord(plugin)) {
|
|
83
|
+
throw new TypeError(`Invalid Harmony config-plugin ownership: plugin ${index} must be an object.`);
|
|
84
|
+
}
|
|
85
|
+
if (typeof plugin.owner !== 'string' || !plugin.owner.trim()) {
|
|
86
|
+
throw new TypeError(`Invalid Harmony config-plugin ownership: plugin ${index}.owner must be a non-empty string.`);
|
|
87
|
+
}
|
|
88
|
+
if (owners.has(plugin.owner)) {
|
|
89
|
+
throw new TypeError(`Invalid Harmony config-plugin ownership: owner ${plugin.owner} is duplicated.`);
|
|
90
|
+
}
|
|
91
|
+
owners.add(plugin.owner);
|
|
92
|
+
return {
|
|
93
|
+
ability: normalizeAbility(plugin.ability),
|
|
94
|
+
owner: plugin.owner,
|
|
95
|
+
resources: normalizeResources(plugin.resources),
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
return plugins.sort((left, right) => left.owner.localeCompare(right.owner, 'en'));
|
|
99
|
+
}
|
|
100
|
+
function registerHarmonyConfigPlugin(config, owner, claims = {}) {
|
|
101
|
+
const plugin = normalizeHarmonyConfigPlugins([{ ...claims, owner }])[0];
|
|
102
|
+
config._internal ??= {};
|
|
103
|
+
const plugins = normalizeHarmonyConfigPlugins(config._internal.harmonyConfigPlugins);
|
|
104
|
+
config._internal.harmonyConfigPlugins = normalizeHarmonyConfigPlugins([
|
|
105
|
+
...plugins.filter(item => item.owner !== owner),
|
|
106
|
+
plugin,
|
|
107
|
+
]);
|
|
108
|
+
return config;
|
|
109
|
+
}
|
|
110
|
+
function getHarmonyConfigPlugins(config) {
|
|
111
|
+
return normalizeHarmonyConfigPlugins(config?._internal?.harmonyConfigPlugins);
|
|
112
|
+
}
|
package/build/paths.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export interface HarmonyManagedPaths {
|
|
2
|
+
readonly appJson: string;
|
|
3
|
+
readonly projectBuildProfile: string;
|
|
4
|
+
readonly rootOhPackage: string;
|
|
5
|
+
readonly rootHvigor: string;
|
|
6
|
+
readonly nativeInputsStamp: string;
|
|
7
|
+
readonly hvigorConfig: string;
|
|
8
|
+
readonly entryBuildProfile: string;
|
|
9
|
+
readonly entryOhPackage: string;
|
|
10
|
+
readonly entryHvigor: string;
|
|
11
|
+
readonly moduleJson: string;
|
|
12
|
+
readonly profiles: string;
|
|
13
|
+
readonly entryAbility: string;
|
|
14
|
+
readonly indexPage: string;
|
|
15
|
+
readonly worker: string;
|
|
16
|
+
readonly arktsPackageProvider: string;
|
|
17
|
+
readonly cppPackageProvider: string;
|
|
18
|
+
readonly cmakeLists: string;
|
|
19
|
+
}
|
|
20
|
+
export interface HarmonyProjectPaths {
|
|
21
|
+
readonly reactNativeConfig: string;
|
|
22
|
+
}
|
|
23
|
+
export interface HarmonyProjectPathCandidates {
|
|
24
|
+
readonly reactNativeConfig: readonly string[];
|
|
25
|
+
}
|
|
26
|
+
export interface HarmonyResourcePaths {
|
|
27
|
+
readonly strings: Readonly<{
|
|
28
|
+
app: string;
|
|
29
|
+
entry: string;
|
|
30
|
+
}>;
|
|
31
|
+
readonly colors: Readonly<{
|
|
32
|
+
entry: string;
|
|
33
|
+
entryDark: string;
|
|
34
|
+
}>;
|
|
35
|
+
readonly media: Readonly<{
|
|
36
|
+
app: string;
|
|
37
|
+
entry: string;
|
|
38
|
+
entryDark: string;
|
|
39
|
+
}>;
|
|
40
|
+
}
|
|
41
|
+
export interface HarmonyPathsApi {
|
|
42
|
+
readonly HARMONY_PATHS: HarmonyManagedPaths;
|
|
43
|
+
readonly PROJECT_PATH_CANDIDATES: HarmonyProjectPathCandidates;
|
|
44
|
+
readonly PROJECT_PATHS: HarmonyProjectPaths;
|
|
45
|
+
readonly RESOURCE_PATHS: HarmonyResourcePaths;
|
|
46
|
+
assertNoExternalSymlink(root: string, target: string): Promise<void>;
|
|
47
|
+
isInside(root: string, target: string): boolean;
|
|
48
|
+
resolveHarmonyPath(platformProjectRoot: string, relativePath: string): Promise<string>;
|
|
49
|
+
resolveProjectPath(projectRoot: string, modName: keyof HarmonyProjectPaths): Promise<string>;
|
|
50
|
+
toPosixRelative(projectRoot: string, target: string): string;
|
|
51
|
+
}
|
|
52
|
+
declare const ManagedPaths: HarmonyManagedPaths;
|
|
53
|
+
declare const ProjectPaths: HarmonyProjectPaths;
|
|
54
|
+
declare const ProjectPathCandidates: HarmonyProjectPathCandidates;
|
|
55
|
+
declare const ResourcePaths: HarmonyResourcePaths;
|
|
56
|
+
export declare function isInside(root: string, target: string): boolean;
|
|
57
|
+
export declare function assertNoExternalSymlink(root: string, target: string): Promise<void>;
|
|
58
|
+
export declare function resolveHarmonyPath(platformProjectRoot: string, relativePath: string): Promise<string>;
|
|
59
|
+
export declare function resolveProjectPath(projectRoot: string, modName: keyof HarmonyProjectPaths): Promise<string>;
|
|
60
|
+
export declare function toPosixRelative(projectRoot: string, target: string): string;
|
|
61
|
+
export declare const HarmonyPaths: HarmonyPathsApi;
|
|
62
|
+
export { ManagedPaths, ProjectPathCandidates, ProjectPaths, ResourcePaths };
|