@expo-harmony/config-plugins 55.0.10-harmony.2 → 55.0.10-harmony.3
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 +203 -11
- package/build/compiler.d.ts +7 -0
- package/build/compiler.js +12 -0
- package/build/composition.d.ts +24 -0
- package/build/composition.js +11 -0
- package/build/config.d.ts +2 -15
- package/build/customFiles.d.ts +15 -0
- package/build/customFiles.js +108 -0
- package/build/files.d.ts +1 -1
- package/build/files.js +2 -2
- package/build/generatedFiles.d.ts +15 -0
- package/build/generatedFiles.js +70 -0
- package/build/index.d.ts +20 -5
- package/build/index.js +52 -6
- package/build/internal.d.ts +3 -0
- package/build/internal.js +13 -0
- package/build/manifest.d.ts +38 -0
- package/build/manifest.js +71 -0
- package/build/mods.d.ts +36 -31
- package/build/mods.js +114 -86
- package/build/normalizeConfig.d.ts +10 -3
- package/build/normalizeConfig.js +41 -47
- package/build/ownership.d.ts +4 -3
- package/build/ownership.js +17 -0
- package/build/paths.d.ts +12 -10
- package/build/paths.js +31 -31
- package/build/permissions.d.ts +12 -0
- package/build/permissions.js +60 -0
- package/build/pluginTypes.d.ts +15 -0
- package/build/pluginTypes.js +2 -0
- package/build/resources.d.ts +13 -0
- package/build/resources.js +55 -0
- package/package.json +9 -9
- package/src/compiler.ts +21 -0
- package/src/composition.ts +45 -0
- package/src/config.ts +3 -19
- package/src/customFiles.ts +142 -0
- package/src/files.ts +3 -4
- package/src/generatedFiles.ts +85 -0
- package/src/index.ts +19 -11
- package/src/internal.ts +3 -0
- package/src/manifest.ts +151 -0
- package/src/mods.ts +170 -129
- package/src/nativeInputs.ts +0 -1
- package/src/normalizeConfig.ts +51 -59
- package/src/ownership.ts +25 -5
- package/src/paths.ts +38 -46
- package/src/permissions.ts +68 -0
- package/src/pluginTypes.ts +24 -0
- package/src/resources.ts +65 -0
package/build/mods.js
CHANGED
|
@@ -3,10 +3,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.withHarmonyResources = exports.withHarmonyDangerousMod = exports.withCngManifest = exports.withHarmonyAutolinking = exports.withCMakeLists = exports.withCppPackageProvider = exports.withArkTSPackageProvider = exports.withWorker = exports.withIndexPage = exports.withEntryAbility = exports.withProfiles = exports.withMedia = exports.withColors = exports.withStrings = exports.withModuleJson = exports.withEntryHvigor = exports.withEntryOhPackage = exports.withEntryBuildProfile = exports.withHvigorConfig = exports.withNativeInputsStamp = exports.withRootHvigor = exports.withRootOhPackage = exports.withProjectBuildProfile = exports.withAppJson = exports.withReactNativeConfig = exports.HarmonyModNames = void 0;
|
|
6
|
+
exports.withHarmonyResources = exports.withHarmonyDangerousMod = exports.withProjectPatch = exports.withPreparation = exports.withCngManifest = exports.withHarmonyAutolinking = exports.withCMakeLists = exports.withCppPackageProvider = exports.withArkTSPackageProvider = exports.withWorker = exports.withIndexPage = exports.withEntryAbility = exports.withProfiles = exports.withMedia = exports.withColors = exports.withStrings = exports.withModuleJson = exports.withEntryHvigor = exports.withEntryOhPackage = exports.withEntryBuildProfile = exports.withHvigorConfig = exports.withNativeInputsStamp = exports.withRootHvigor = exports.withRootOhPackage = exports.withProjectBuildProfile = exports.withAppJson = exports.withReactNativeConfig = exports.HarmonyModNames = void 0;
|
|
7
7
|
exports.recordManagedFile = recordManagedFile;
|
|
8
8
|
exports.withHarmonyBaseMods = withHarmonyBaseMods;
|
|
9
9
|
exports.withHarmonyMod = withHarmonyMod;
|
|
10
|
+
exports.registerMod = registerMod;
|
|
10
11
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
11
12
|
const node_path_1 = __importDefault(require("node:path"));
|
|
12
13
|
const config_plugins_1 = require("@expo/config-plugins");
|
|
@@ -36,10 +37,11 @@ const TextMods = new Set([
|
|
|
36
37
|
'cmakeLists',
|
|
37
38
|
]);
|
|
38
39
|
const ResourceMods = new Set(['strings', 'colors', 'media']);
|
|
39
|
-
const VirtualMods = new Set(['dangerous', 'autolinking', 'manifest']);
|
|
40
|
+
const VirtualMods = new Set(['dangerous', 'autolinking', 'manifest', 'prepare', 'patch']);
|
|
40
41
|
const ResourceModNames = ['strings', 'colors', 'media'];
|
|
41
|
-
|
|
42
|
+
const AllModNames = Object.freeze([
|
|
42
43
|
'dangerous',
|
|
44
|
+
'prepare',
|
|
43
45
|
'reactNativeConfig',
|
|
44
46
|
'appJson',
|
|
45
47
|
'projectBuildProfile',
|
|
@@ -62,8 +64,10 @@ exports.HarmonyModNames = Object.freeze([
|
|
|
62
64
|
'cppPackageProvider',
|
|
63
65
|
'cmakeLists',
|
|
64
66
|
'autolinking',
|
|
67
|
+
'patch',
|
|
65
68
|
'manifest',
|
|
66
69
|
]);
|
|
70
|
+
exports.HarmonyModNames = Object.freeze(AllModNames.filter((name) => name !== 'manifest' && name !== 'nativeInputsStamp' && name !== 'prepare' && name !== 'patch'));
|
|
67
71
|
function recordManagedFile(config, file, owner) {
|
|
68
72
|
config._internal ??= {};
|
|
69
73
|
config._internal.harmonyManagedFiles ??= [];
|
|
@@ -73,35 +77,41 @@ function recordManagedFile(config, file, owner) {
|
|
|
73
77
|
files.sort((left, right) => left.path.localeCompare(right.path, 'en'));
|
|
74
78
|
config._internal.harmonyManagedFiles = files;
|
|
75
79
|
}
|
|
76
|
-
function assertModResults(
|
|
80
|
+
function assertModResults(name, value, kind) {
|
|
77
81
|
const valid = kind === 'text'
|
|
78
82
|
? typeof value === 'string'
|
|
79
83
|
: value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
80
84
|
if (!valid) {
|
|
81
|
-
throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_MOD_RESULTS_INVALID', `harmony.${
|
|
85
|
+
throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_MOD_RESULTS_INVALID', `harmony.${name} must return ${kind === 'text' ? 'a string' : 'an object'} modResults.`, { operation: `harmony.${name}.write` });
|
|
82
86
|
}
|
|
83
87
|
}
|
|
84
|
-
function withFileProvider(config,
|
|
88
|
+
function withFileProvider(config, name) {
|
|
85
89
|
return (0, config_plugins_1.withBaseMod)(config, {
|
|
86
90
|
platform: 'harmony',
|
|
87
|
-
mod:
|
|
91
|
+
mod: name,
|
|
88
92
|
isProvider: true,
|
|
89
|
-
isIntrospective: JsonMods.has(
|
|
90
|
-
saveToInternal: JsonMods.has(
|
|
93
|
+
isIntrospective: JsonMods.has(name),
|
|
94
|
+
saveToInternal: JsonMods.has(name),
|
|
91
95
|
async action(value) {
|
|
92
|
-
const { nextMod, ...
|
|
93
|
-
const
|
|
94
|
-
const root =
|
|
95
|
-
const file =
|
|
96
|
-
? await (0, paths_1.resolveProjectPath)(root,
|
|
97
|
-
: await (0, paths_1.resolveHarmonyPath)(root, paths_1.ManagedPaths[
|
|
98
|
-
const exists = node_fs_1.default.existsSync(file);
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
|
|
96
|
+
const { nextMod: next, ...request } = value.modRequest;
|
|
97
|
+
const project = name in paths_1.ProjectPaths;
|
|
98
|
+
const root = project ? request.projectRoot : request.platformProjectRoot;
|
|
99
|
+
const file = project
|
|
100
|
+
? await (0, paths_1.resolveProjectPath)(root, name)
|
|
101
|
+
: await (0, paths_1.resolveHarmonyPath)(root, paths_1.ManagedPaths[name]);
|
|
102
|
+
const exists = !request.ignoreExistingNativeFiles && node_fs_1.default.existsSync(file);
|
|
103
|
+
const template = value._internal?.harmonyTemplateDirectory;
|
|
104
|
+
const input = !exists && !project && typeof template === 'string'
|
|
105
|
+
? await (0, paths_1.resolveHarmonyPath)(template, paths_1.ManagedPaths[name])
|
|
106
|
+
: file;
|
|
107
|
+
const data = request.ignoreExistingNativeFiles && input === file
|
|
108
|
+
? (JsonMods.has(name) ? {} : '')
|
|
109
|
+
: JsonMods.has(name)
|
|
110
|
+
? await (0, files_1.readJson5)(input, {}, name)
|
|
111
|
+
: await (0, files_1.readText)(file);
|
|
112
|
+
const context = { ...request, modFile: file, modFileExists: exists };
|
|
113
|
+
const result = await next({ ...value, modRequest: context, modResults: data });
|
|
114
|
+
assertModResults(name, result.modResults, JsonMods.has(name) ? 'json' : 'text');
|
|
105
115
|
if (!result.modRequest.introspect) {
|
|
106
116
|
if (typeof result.modResults === 'string') {
|
|
107
117
|
await (0, files_1.atomicWrite)(file, result.modResults.replace(/\r\n?/g, '\n').replace(/\n?$/, '\n'));
|
|
@@ -109,17 +119,19 @@ function withFileProvider(config, modName) {
|
|
|
109
119
|
else {
|
|
110
120
|
await (0, files_1.writeJson5)(file, result.modResults);
|
|
111
121
|
}
|
|
112
|
-
recordManagedFile(result, file,
|
|
122
|
+
recordManagedFile(result, file, name);
|
|
113
123
|
}
|
|
114
124
|
return result;
|
|
115
125
|
},
|
|
116
126
|
});
|
|
117
127
|
}
|
|
118
|
-
async function readResourceMap(root, entries,
|
|
128
|
+
async function readResourceMap(root, entries, name, template, ignore = false) {
|
|
119
129
|
const resources = {};
|
|
120
130
|
for (const [scope, relative] of Object.entries(entries)) {
|
|
121
131
|
const file = await (0, paths_1.resolveHarmonyPath)(root, relative);
|
|
122
|
-
|
|
132
|
+
const fallback = ignore || !node_fs_1.default.existsSync(file);
|
|
133
|
+
const input = fallback && template ? await (0, paths_1.resolveHarmonyPath)(template, relative) : file;
|
|
134
|
+
resources[scope] = ignore && input === file ? {} : await (0, files_1.readJson5)(input, {}, name);
|
|
123
135
|
}
|
|
124
136
|
return resources;
|
|
125
137
|
}
|
|
@@ -162,16 +174,14 @@ async function writeMediaMap(root, media, config, previous = {}) {
|
|
|
162
174
|
throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `Harmony media ${scope}/${name} must use a descriptor object.`, { operation: 'harmony.media.write' });
|
|
163
175
|
}
|
|
164
176
|
const item = descriptor;
|
|
165
|
-
|
|
166
|
-
const hasContent = Object.hasOwn(item, 'content');
|
|
167
|
-
if (hasSource === hasContent) {
|
|
177
|
+
if (Object.hasOwn(item, 'source') === Object.hasOwn(item, 'content')) {
|
|
168
178
|
throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `Harmony media ${scope}/${name} must define exactly one of source or content.`, { operation: 'harmony.media.write' });
|
|
169
179
|
}
|
|
170
180
|
if (item.replaceBase !== undefined && typeof item.replaceBase !== 'boolean') {
|
|
171
181
|
throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `Harmony media ${scope}/${name} replaceBase must be a boolean.`, { operation: 'harmony.media.write' });
|
|
172
182
|
}
|
|
173
183
|
let content;
|
|
174
|
-
if (
|
|
184
|
+
if (Object.hasOwn(item, 'source')) {
|
|
175
185
|
if (typeof item.source !== 'string' || item.source.length === 0) {
|
|
176
186
|
throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `Harmony media ${scope}/${name} source must be a non-empty path.`, { operation: 'harmony.media.write' });
|
|
177
187
|
}
|
|
@@ -191,9 +201,6 @@ async function writeMediaMap(root, media, config, previous = {}) {
|
|
|
191
201
|
});
|
|
192
202
|
}
|
|
193
203
|
}
|
|
194
|
-
// Resolve and read every desired input before removing stale resources. A
|
|
195
|
-
// malformed descriptor or missing source therefore cannot partially mutate
|
|
196
|
-
// an otherwise valid generated project.
|
|
197
204
|
for (const [scope, names] of Object.entries(previous)) {
|
|
198
205
|
if (!(scope in paths_1.ResourcePaths.media))
|
|
199
206
|
continue;
|
|
@@ -210,9 +217,9 @@ async function writeMediaMap(root, media, config, previous = {}) {
|
|
|
210
217
|
await node_fs_1.default.promises.mkdir(directory, { recursive: true });
|
|
211
218
|
if (replaceBase) {
|
|
212
219
|
const base = node_path_1.default.parse(name).name;
|
|
213
|
-
for (const
|
|
214
|
-
if (node_path_1.default.parse(
|
|
215
|
-
await node_fs_1.default.promises.rm(node_path_1.default.join(directory,
|
|
220
|
+
for (const entry of await node_fs_1.default.promises.readdir(directory)) {
|
|
221
|
+
if (node_path_1.default.parse(entry).name === base && entry !== name) {
|
|
222
|
+
await node_fs_1.default.promises.rm(node_path_1.default.join(directory, entry), { force: true });
|
|
216
223
|
}
|
|
217
224
|
}
|
|
218
225
|
}
|
|
@@ -221,47 +228,47 @@ async function writeMediaMap(root, media, config, previous = {}) {
|
|
|
221
228
|
recordManagedFile(config, file, 'media');
|
|
222
229
|
}
|
|
223
230
|
}
|
|
224
|
-
function withResourceProvider(config,
|
|
225
|
-
const
|
|
231
|
+
function withResourceProvider(config, name) {
|
|
232
|
+
const introspective = name !== 'media';
|
|
226
233
|
return (0, config_plugins_1.withBaseMod)(config, {
|
|
227
234
|
platform: 'harmony',
|
|
228
|
-
mod:
|
|
235
|
+
mod: name,
|
|
229
236
|
isProvider: true,
|
|
230
|
-
isIntrospective,
|
|
231
|
-
saveToInternal:
|
|
237
|
+
isIntrospective: introspective,
|
|
238
|
+
saveToInternal: introspective,
|
|
232
239
|
async action(value) {
|
|
233
|
-
const { nextMod, ...
|
|
234
|
-
const root =
|
|
235
|
-
const data =
|
|
236
|
-
?
|
|
237
|
-
|
|
238
|
-
|
|
240
|
+
const { nextMod: next, ...request } = value.modRequest;
|
|
241
|
+
const root = request.platformProjectRoot;
|
|
242
|
+
const data = name === 'media'
|
|
243
|
+
? (request.ignoreExistingNativeFiles
|
|
244
|
+
? Object.fromEntries(Object.keys(paths_1.ResourcePaths.media).map(scope => [scope, {}]))
|
|
245
|
+
: await readMediaMap(root))
|
|
246
|
+
: await readResourceMap(root, paths_1.ResourcePaths[name], name, value._internal?.harmonyTemplateDirectory, request.ignoreExistingNativeFiles);
|
|
247
|
+
const previous = name === 'media'
|
|
239
248
|
? Object.fromEntries(Object.entries(data).map(([scope, files]) => [scope, Object.keys(files)]))
|
|
240
249
|
: {};
|
|
241
|
-
const result = await
|
|
242
|
-
assertModResults(
|
|
250
|
+
const result = await next({ ...value, modRequest: request, modResults: data });
|
|
251
|
+
assertModResults(name, result.modResults, 'resource');
|
|
243
252
|
if (!result.modRequest.introspect) {
|
|
244
|
-
if (
|
|
253
|
+
if (name === 'media') {
|
|
245
254
|
await writeMediaMap(root, result.modResults, result, previous);
|
|
246
255
|
}
|
|
247
256
|
else {
|
|
248
|
-
const paths = paths_1.ResourcePaths[
|
|
257
|
+
const paths = paths_1.ResourcePaths[name];
|
|
249
258
|
for (const [scope, resource] of Object.entries(result.modResults)) {
|
|
250
259
|
if (!(scope in paths))
|
|
251
260
|
continue;
|
|
252
|
-
// Harmony's resource compiler requires the JSON root to contain
|
|
253
|
-
// exactly one resource-kind member. An empty scope serialized as
|
|
254
|
-
// `{}` is invalid, so omit it until a plugin contributes content.
|
|
255
261
|
if (!resource || typeof resource !== 'object' || Array.isArray(resource)) {
|
|
256
|
-
throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `Harmony ${
|
|
262
|
+
throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `Harmony ${name} scope ${scope} must be an object.`, { operation: `harmony.${name}.write` });
|
|
257
263
|
}
|
|
258
264
|
const file = await (0, paths_1.resolveHarmonyPath)(root, paths[scope]);
|
|
265
|
+
// Harmony's resource compiler rejects an empty JSON root.
|
|
259
266
|
if (Object.keys(resource).length === 0) {
|
|
260
267
|
await node_fs_1.default.promises.rm(file, { force: true });
|
|
261
268
|
continue;
|
|
262
269
|
}
|
|
263
270
|
await (0, files_1.writeJson5)(file, resource);
|
|
264
|
-
recordManagedFile(result, file,
|
|
271
|
+
recordManagedFile(result, file, name);
|
|
265
272
|
}
|
|
266
273
|
}
|
|
267
274
|
}
|
|
@@ -269,61 +276,63 @@ function withResourceProvider(config, modName) {
|
|
|
269
276
|
},
|
|
270
277
|
});
|
|
271
278
|
}
|
|
272
|
-
function withVirtualProvider(config,
|
|
279
|
+
function withVirtualProvider(config, name) {
|
|
273
280
|
return (0, config_plugins_1.withBaseMod)(config, {
|
|
274
281
|
platform: 'harmony',
|
|
275
|
-
mod:
|
|
282
|
+
mod: name,
|
|
276
283
|
isProvider: true,
|
|
284
|
+
isIntrospective: name === 'prepare',
|
|
277
285
|
async action(value) {
|
|
278
|
-
const { nextMod, ...
|
|
279
|
-
return
|
|
286
|
+
const { nextMod: next, ...request } = value.modRequest;
|
|
287
|
+
return next({ ...value, modRequest: request, modResults: null });
|
|
280
288
|
},
|
|
281
289
|
});
|
|
282
290
|
}
|
|
283
|
-
function withProvider(config,
|
|
291
|
+
function withProvider(config, name) {
|
|
284
292
|
const actions = [];
|
|
285
293
|
config = (0, config_plugins_1.withMod)(config, {
|
|
286
294
|
platform: 'harmony',
|
|
287
|
-
mod:
|
|
295
|
+
mod: name,
|
|
288
296
|
async action(value) {
|
|
289
297
|
let result = value;
|
|
290
|
-
//
|
|
291
|
-
// that ordering for Harmony plugins registered after the provider.
|
|
298
|
+
// Late mods must follow Expo's last-registered-first order.
|
|
292
299
|
for (let index = actions.length - 1; index >= 0; index -= 1) {
|
|
293
300
|
result = await actions[index](result);
|
|
294
301
|
}
|
|
295
302
|
return result;
|
|
296
303
|
},
|
|
297
304
|
});
|
|
298
|
-
if (JsonMods.has(
|
|
299
|
-
config = withFileProvider(config,
|
|
305
|
+
if (JsonMods.has(name) || TextMods.has(name)) {
|
|
306
|
+
config = withFileProvider(config, name);
|
|
300
307
|
}
|
|
301
|
-
else if (ResourceMods.has(
|
|
302
|
-
config = withResourceProvider(config,
|
|
308
|
+
else if (ResourceMods.has(name)) {
|
|
309
|
+
config = withResourceProvider(config, name);
|
|
303
310
|
}
|
|
304
|
-
else if (VirtualMods.has(
|
|
305
|
-
config = withVirtualProvider(config,
|
|
311
|
+
else if (VirtualMods.has(name)) {
|
|
312
|
+
config = withVirtualProvider(config, name);
|
|
306
313
|
}
|
|
307
314
|
else {
|
|
308
|
-
throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_MOD_NOT_REGISTERED', `Unknown Harmony mod: ${
|
|
315
|
+
throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_MOD_NOT_REGISTERED', `Unknown Harmony mod: ${name}`, { operation: 'register-base-mods' });
|
|
309
316
|
}
|
|
310
317
|
const mods = config.mods;
|
|
311
|
-
const provider = mods?.harmony?.[
|
|
318
|
+
const provider = mods?.harmony?.[name];
|
|
312
319
|
if (provider?.isProvider)
|
|
313
320
|
provider.expoHarmonyLateActions = actions;
|
|
314
321
|
return config;
|
|
315
322
|
}
|
|
316
323
|
function withHarmonyBaseMods(config) {
|
|
317
|
-
for (const
|
|
324
|
+
for (const name of AllModNames) {
|
|
318
325
|
const mods = config.mods;
|
|
319
|
-
if (!mods?.harmony?.[
|
|
320
|
-
config = withProvider(config,
|
|
326
|
+
if (!mods?.harmony?.[name]?.isProvider)
|
|
327
|
+
config = withProvider(config, name);
|
|
321
328
|
}
|
|
322
329
|
const mods = config.mods;
|
|
323
330
|
const table = mods.harmony;
|
|
324
331
|
mods.harmony = Object.fromEntries([
|
|
325
|
-
...
|
|
326
|
-
...Object.entries(table).filter(([name]) => !
|
|
332
|
+
...AllModNames.filter(name => table[name] && name !== 'manifest' && name !== 'patch').map(name => [name, table[name]]),
|
|
333
|
+
...Object.entries(table).filter(([name]) => !AllModNames.includes(name)),
|
|
334
|
+
['patch', table.patch],
|
|
335
|
+
['manifest', table.manifest],
|
|
327
336
|
]);
|
|
328
337
|
return config;
|
|
329
338
|
}
|
|
@@ -331,17 +340,32 @@ function withHarmonyMod(config, tuple) {
|
|
|
331
340
|
if (!Array.isArray(tuple) || tuple.length !== 2) {
|
|
332
341
|
throw new TypeError('withHarmonyMod expects [modName, action].');
|
|
333
342
|
}
|
|
334
|
-
const [
|
|
335
|
-
if (!exports.HarmonyModNames.includes(
|
|
336
|
-
throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_MOD_NOT_REGISTERED', `Cannot register Harmony mod: ${
|
|
343
|
+
const [name, action] = tuple;
|
|
344
|
+
if (!exports.HarmonyModNames.includes(name) || typeof action !== 'function') {
|
|
345
|
+
throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_MOD_NOT_REGISTERED', `Cannot register Harmony mod: ${name}`, { operation: 'register-mod' });
|
|
337
346
|
}
|
|
347
|
+
return registerMod(config, name, action);
|
|
348
|
+
}
|
|
349
|
+
function registerMod(config, name, action) {
|
|
338
350
|
const mods = config.mods;
|
|
339
|
-
const provider = mods?.harmony?.[
|
|
351
|
+
const provider = mods?.harmony?.[name];
|
|
340
352
|
if (provider?.isProvider && provider.expoHarmonyLateActions) {
|
|
341
|
-
|
|
353
|
+
// Build an ordinary Expo interceptor on an isolated mod table, preserving
|
|
354
|
+
// its registration stack, error behavior and execution trace for late mods.
|
|
355
|
+
const carrier = (0, config_plugins_1.withMod)({ ...config, mods: {} }, {
|
|
356
|
+
platform: 'harmony',
|
|
357
|
+
mod: name,
|
|
358
|
+
action: action,
|
|
359
|
+
});
|
|
360
|
+
const interceptor = carrier.mods.harmony[name];
|
|
361
|
+
provider.expoHarmonyLateActions.push(interceptor);
|
|
342
362
|
return config;
|
|
343
363
|
}
|
|
344
|
-
return (0, config_plugins_1.withMod)(config, {
|
|
364
|
+
return (0, config_plugins_1.withMod)(config, {
|
|
365
|
+
platform: 'harmony',
|
|
366
|
+
mod: name,
|
|
367
|
+
action: action,
|
|
368
|
+
});
|
|
345
369
|
}
|
|
346
370
|
const withReactNativeConfig = (config, action) => withHarmonyMod(config, ['reactNativeConfig', action]);
|
|
347
371
|
exports.withReactNativeConfig = withReactNativeConfig;
|
|
@@ -353,7 +377,7 @@ const withRootOhPackage = (config, action) => withHarmonyMod(config, ['rootOhPac
|
|
|
353
377
|
exports.withRootOhPackage = withRootOhPackage;
|
|
354
378
|
const withRootHvigor = (config, action) => withHarmonyMod(config, ['rootHvigor', action]);
|
|
355
379
|
exports.withRootHvigor = withRootHvigor;
|
|
356
|
-
const withNativeInputsStamp = (config, action) =>
|
|
380
|
+
const withNativeInputsStamp = (config, action) => registerMod(config, 'nativeInputsStamp', action);
|
|
357
381
|
exports.withNativeInputsStamp = withNativeInputsStamp;
|
|
358
382
|
const withHvigorConfig = (config, action) => withHarmonyMod(config, ['hvigorConfig', action]);
|
|
359
383
|
exports.withHvigorConfig = withHvigorConfig;
|
|
@@ -387,13 +411,17 @@ const withCMakeLists = (config, action) => withHarmonyMod(config, ['cmakeLists',
|
|
|
387
411
|
exports.withCMakeLists = withCMakeLists;
|
|
388
412
|
const withHarmonyAutolinking = (config, action) => withHarmonyMod(config, ['autolinking', action]);
|
|
389
413
|
exports.withHarmonyAutolinking = withHarmonyAutolinking;
|
|
390
|
-
const withCngManifest = (config, action) =>
|
|
414
|
+
const withCngManifest = (config, action) => registerMod(config, 'manifest', action);
|
|
391
415
|
exports.withCngManifest = withCngManifest;
|
|
416
|
+
const withPreparation = (config, action) => registerMod(config, 'prepare', action);
|
|
417
|
+
exports.withPreparation = withPreparation;
|
|
418
|
+
const withProjectPatch = (config, action) => registerMod(config, 'patch', action);
|
|
419
|
+
exports.withProjectPatch = withProjectPatch;
|
|
392
420
|
const withHarmonyDangerousMod = (config, action) => withHarmonyMod(config, ['dangerous', action]);
|
|
393
421
|
exports.withHarmonyDangerousMod = withHarmonyDangerousMod;
|
|
394
422
|
const withHarmonyResources = (config, action) => {
|
|
395
|
-
for (const
|
|
396
|
-
withHarmonyMod(config, [
|
|
423
|
+
for (const name of ResourceModNames) {
|
|
424
|
+
withHarmonyMod(config, [name, action]);
|
|
397
425
|
}
|
|
398
426
|
return config;
|
|
399
427
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExpoConfigWithHarmony, HarmonyConfig } from './config';
|
|
1
|
+
import type { ExpoConfigWithHarmony, HarmonyConfig, HarmonyPermission } from './config';
|
|
2
2
|
type HarmonyExpoConfig = ExpoConfigWithHarmony;
|
|
3
3
|
interface NormalizedHarmonyConfig {
|
|
4
4
|
abiFilters: string[];
|
|
@@ -22,7 +22,14 @@ interface NormalizedHarmonyConfig {
|
|
|
22
22
|
versionCode: number;
|
|
23
23
|
versionName: string;
|
|
24
24
|
}
|
|
25
|
-
declare
|
|
25
|
+
export declare function normalizePermission(permission: HarmonyPermission): {
|
|
26
|
+
name: string;
|
|
27
|
+
reason?: string;
|
|
28
|
+
usedScene?: {
|
|
29
|
+
abilities?: string[];
|
|
30
|
+
when: "always" | "inuse";
|
|
31
|
+
};
|
|
32
|
+
};
|
|
26
33
|
declare function normalizeHarmonyConfig(config: HarmonyExpoConfig): NormalizedHarmonyConfig;
|
|
27
|
-
export {
|
|
34
|
+
export { normalizeHarmonyConfig };
|
|
28
35
|
export type { HarmonyExpoConfig, NormalizedHarmonyConfig };
|
package/build/normalizeConfig.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.normalizePermission = normalizePermission;
|
|
4
4
|
exports.normalizeHarmonyConfig = normalizeHarmonyConfig;
|
|
5
5
|
const errors_1 = require("./errors");
|
|
6
|
-
const
|
|
6
|
+
const resources_1 = require("./resources");
|
|
7
|
+
const BundlePattern = /^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*){2,}$/;
|
|
7
8
|
const IdentifierPattern = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
8
9
|
const ColorPattern = /^(#[0-9A-Fa-f]{6}|#[0-9A-Fa-f]{8})$/;
|
|
9
|
-
const
|
|
10
|
+
const Orientations = new Set([
|
|
10
11
|
'default',
|
|
11
12
|
'portrait',
|
|
12
13
|
'landscape',
|
|
@@ -14,17 +15,17 @@ const ValidOrientations = new Set([
|
|
|
14
15
|
'landscape_inverted',
|
|
15
16
|
'auto_rotation',
|
|
16
17
|
]);
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const HarmonySdkVersions = new Map([
|
|
18
|
+
const DeviceTypes = new Set(['phone', 'tablet', '2in1']);
|
|
19
|
+
const MinimumApi = 13;
|
|
20
|
+
const TargetApi = 24;
|
|
21
|
+
const SdkVersions = new Map([
|
|
22
22
|
[13, '5.0.1(13)'],
|
|
23
|
+
[14, '5.0.2(14)'],
|
|
23
24
|
[20, '6.0.0(20)'],
|
|
25
|
+
[21, '6.0.1(21)'],
|
|
24
26
|
[23, '6.1.0(23)'],
|
|
25
27
|
[24, '6.1.1(24)'],
|
|
26
28
|
]);
|
|
27
|
-
exports.HarmonySdkVersions = HarmonySdkVersions;
|
|
28
29
|
class HarmonyConfigError extends errors_1.HarmonyConfigPluginError {
|
|
29
30
|
constructor(message) {
|
|
30
31
|
super('ERR_HARMONY_CONFIG_INVALID', message, { operation: 'normalize-config' });
|
|
@@ -53,10 +54,7 @@ function normalizeColor(value, field, fallback) {
|
|
|
53
54
|
if (typeof color !== 'string' || !ColorPattern.test(color)) {
|
|
54
55
|
throw new HarmonyConfigError(`${field} must be #RRGGBB or #RRGGBBAA.`);
|
|
55
56
|
}
|
|
56
|
-
|
|
57
|
-
return result.length === 9
|
|
58
|
-
? `#${result.slice(7, 9)}${result.slice(1, 7)}`
|
|
59
|
-
: result;
|
|
57
|
+
return (0, resources_1.toArgb)(color);
|
|
60
58
|
}
|
|
61
59
|
function parseSdkApi(value, field) {
|
|
62
60
|
if (value === undefined)
|
|
@@ -82,14 +80,14 @@ function resolveSdkVersion(api, field, value) {
|
|
|
82
80
|
}
|
|
83
81
|
else {
|
|
84
82
|
const version = readString(value, `harmony.${field}`);
|
|
85
|
-
const
|
|
86
|
-
if (
|
|
87
|
-
throw new HarmonyConfigError(`harmony.${field} describes API ${
|
|
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}.`);
|
|
88
86
|
}
|
|
89
87
|
return version;
|
|
90
88
|
}
|
|
91
89
|
}
|
|
92
|
-
const version =
|
|
90
|
+
const version = SdkVersions.get(api);
|
|
93
91
|
if (!version) {
|
|
94
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.`);
|
|
95
93
|
}
|
|
@@ -157,7 +155,9 @@ function normalizeSkill(skill) {
|
|
|
157
155
|
}
|
|
158
156
|
const result = {};
|
|
159
157
|
for (const [key, value] of Object.entries(uri)) {
|
|
160
|
-
result[key] =
|
|
158
|
+
result[key] = typeof value === 'boolean' || (typeof value === 'number' && Number.isFinite(value))
|
|
159
|
+
? value
|
|
160
|
+
: readString(value, `harmony.skills[].uris[].${key}`);
|
|
161
161
|
}
|
|
162
162
|
return result;
|
|
163
163
|
});
|
|
@@ -176,38 +176,32 @@ function normalizeHarmonyConfig(config) {
|
|
|
176
176
|
if (!harmony || typeof harmony !== 'object' || Array.isArray(harmony)) {
|
|
177
177
|
throw new HarmonyConfigError('Expo config must contain a harmony object.');
|
|
178
178
|
}
|
|
179
|
-
const
|
|
180
|
-
if (!
|
|
179
|
+
const bundle = readString(harmony.bundleName, 'harmony.bundleName');
|
|
180
|
+
if (!BundlePattern.test(bundle)) {
|
|
181
181
|
throw new HarmonyConfigError('harmony.bundleName must contain at least three valid dot-separated segments.');
|
|
182
182
|
}
|
|
183
|
-
const
|
|
184
|
-
const
|
|
185
|
-
if (!IdentifierPattern.test(
|
|
183
|
+
const module = readString(harmony.moduleName, 'harmony.moduleName', 'entry');
|
|
184
|
+
const ability = readString(harmony.abilityName, 'harmony.abilityName', 'EntryAbility');
|
|
185
|
+
if (!IdentifierPattern.test(module)) {
|
|
186
186
|
throw new HarmonyConfigError('harmony.moduleName must be a valid Harmony identifier.');
|
|
187
187
|
}
|
|
188
|
-
if (!IdentifierPattern.test(
|
|
188
|
+
if (!IdentifierPattern.test(ability)) {
|
|
189
189
|
throw new HarmonyConfigError('harmony.abilityName must be a valid Harmony identifier.');
|
|
190
190
|
}
|
|
191
|
-
const
|
|
192
|
-
const
|
|
193
|
-
const
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if (compatibleApi < SupportedMinimumHarmonyApi) {
|
|
198
|
-
throw new HarmonyConfigError(`Harmony compatible API must be ${SupportedMinimumHarmonyApi} or newer.`);
|
|
191
|
+
const version = readString(harmony.versionName, 'harmony.versionName', config.version || '1.0.0');
|
|
192
|
+
const code = readPositiveInteger(harmony.versionCode, 'versionCode', 1);
|
|
193
|
+
const target = readPositiveInteger(harmony.targetApiVersion, 'targetApiVersion', parseSdkApi(harmony.targetSdkVersion, 'targetSdkVersion') ?? TargetApi);
|
|
194
|
+
const compatible = readPositiveInteger(parseSdkApi(harmony.compatibleSdkVersion, 'compatibleSdkVersion') ?? undefined, 'compatibleSdkVersion', MinimumApi);
|
|
195
|
+
if (compatible < MinimumApi) {
|
|
196
|
+
throw new HarmonyConfigError(`Harmony compatible API must be ${MinimumApi} or newer.`);
|
|
199
197
|
}
|
|
200
|
-
if (
|
|
198
|
+
if (compatible > target) {
|
|
201
199
|
throw new HarmonyConfigError('Harmony compatibleSdkVersion cannot exceed targetApiVersion.');
|
|
202
200
|
}
|
|
203
201
|
const orientation = harmony.orientation || config.orientation || 'default';
|
|
204
|
-
if (!
|
|
202
|
+
if (!Orientations.has(orientation)) {
|
|
205
203
|
throw new HarmonyConfigError(`Unsupported Harmony orientation: ${orientation}`);
|
|
206
204
|
}
|
|
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
205
|
const background = normalizeColor(harmony.backgroundColor || config.backgroundColor, 'harmony.backgroundColor', '#FFFFFF');
|
|
212
206
|
const engine = harmony.jsEngine || 'hermes';
|
|
213
207
|
if (engine !== 'hermes') {
|
|
@@ -250,27 +244,27 @@ function normalizeHarmonyConfig(config) {
|
|
|
250
244
|
: readString(harmony.signingConfigFile, 'harmony.signingConfigFile');
|
|
251
245
|
const devices = harmony.deviceTypes === undefined
|
|
252
246
|
? ['phone', 'tablet']
|
|
253
|
-
: normalizeStringArray(harmony.deviceTypes, 'harmony.deviceTypes',
|
|
247
|
+
: normalizeStringArray(harmony.deviceTypes, 'harmony.deviceTypes', DeviceTypes);
|
|
254
248
|
return Object.freeze({
|
|
255
249
|
abiFilters: abis,
|
|
256
|
-
abilityName,
|
|
250
|
+
abilityName: ability,
|
|
257
251
|
backgroundColor: background,
|
|
258
|
-
bundleName,
|
|
259
|
-
compatibleSdkVersionString: resolveSdkVersion(
|
|
252
|
+
bundleName: bundle,
|
|
253
|
+
compatibleSdkVersionString: resolveSdkVersion(compatible, 'compatibleSdkVersion', harmony.compatibleSdkVersion),
|
|
260
254
|
deviceTypes: devices,
|
|
261
255
|
icon: harmony.icon || config.icon,
|
|
262
256
|
label: readString(harmony.label, 'harmony.label', config.name),
|
|
263
|
-
moduleName,
|
|
257
|
+
moduleName: module,
|
|
264
258
|
nativeOrientation: orientation === 'default' ? 'unspecified' : orientation,
|
|
265
259
|
permissions,
|
|
266
260
|
productName: readString(harmony.productName, 'harmony.productName', 'default'),
|
|
267
261
|
querySchemes: queries,
|
|
268
262
|
signingConfigFile: signing,
|
|
269
263
|
skills,
|
|
270
|
-
targetApiVersion:
|
|
271
|
-
targetSdkVersionString: resolveSdkVersion(
|
|
264
|
+
targetApiVersion: target,
|
|
265
|
+
targetSdkVersionString: resolveSdkVersion(target, 'targetSdkVersion', harmony.targetSdkVersion),
|
|
272
266
|
vendor: readString(harmony.vendor, 'harmony.vendor', 'expo-harmony'),
|
|
273
|
-
versionCode,
|
|
274
|
-
versionName,
|
|
267
|
+
versionCode: code,
|
|
268
|
+
versionName: version,
|
|
275
269
|
});
|
|
276
270
|
}
|
package/build/ownership.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ExpoConfigWithHarmony } from './config';
|
|
2
2
|
export interface HarmonyConfigPluginOwnership {
|
|
3
3
|
readonly owner: string;
|
|
4
|
+
readonly files?: readonly string[];
|
|
4
5
|
readonly ability?: Readonly<Partial<{
|
|
5
6
|
startWindowBackground: string;
|
|
6
7
|
startWindowIcon: string;
|
|
@@ -12,5 +13,5 @@ export interface HarmonyConfigPluginOwnership {
|
|
|
12
13
|
}>>;
|
|
13
14
|
}
|
|
14
15
|
export declare function normalizeHarmonyConfigPlugins(value: unknown): readonly HarmonyConfigPluginOwnership[];
|
|
15
|
-
export declare function registerHarmonyConfigPlugin(config:
|
|
16
|
-
export declare function getHarmonyConfigPlugins(config:
|
|
16
|
+
export declare function registerHarmonyConfigPlugin<Config extends ExpoConfigWithHarmony>(config: Config, owner: string, claims?: Omit<HarmonyConfigPluginOwnership, 'owner'>): Config;
|
|
17
|
+
export declare function getHarmonyConfigPlugins(config: ExpoConfigWithHarmony): readonly HarmonyConfigPluginOwnership[];
|
package/build/ownership.js
CHANGED
|
@@ -89,7 +89,17 @@ function normalizeHarmonyConfigPlugins(value) {
|
|
|
89
89
|
throw new TypeError(`Invalid Harmony config-plugin ownership: owner ${plugin.owner} is duplicated.`);
|
|
90
90
|
}
|
|
91
91
|
owners.add(plugin.owner);
|
|
92
|
+
let files;
|
|
93
|
+
if (plugin.files !== undefined) {
|
|
94
|
+
if (!Array.isArray(plugin.files) || plugin.files.some(file => typeof file !== 'string'
|
|
95
|
+
|| !file || file.includes('\\') || file.includes('\0') || /^[A-Za-z]:/.test(file)
|
|
96
|
+
|| file.split('/').some(part => !part || part === '.' || part === '..'))) {
|
|
97
|
+
throw new TypeError(`Invalid Harmony config-plugin ownership: ${plugin.owner}.files must contain relative file paths.`);
|
|
98
|
+
}
|
|
99
|
+
files = [...new Set(plugin.files)].sort();
|
|
100
|
+
}
|
|
92
101
|
return {
|
|
102
|
+
...(files === undefined ? {} : { files }),
|
|
93
103
|
ability: normalizeAbility(plugin.ability),
|
|
94
104
|
owner: plugin.owner,
|
|
95
105
|
resources: normalizeResources(plugin.resources),
|
|
@@ -101,6 +111,13 @@ function registerHarmonyConfigPlugin(config, owner, claims = {}) {
|
|
|
101
111
|
const plugin = normalizeHarmonyConfigPlugins([{ ...claims, owner }])[0];
|
|
102
112
|
config._internal ??= {};
|
|
103
113
|
const plugins = normalizeHarmonyConfigPlugins(config._internal.harmonyConfigPlugins);
|
|
114
|
+
for (const other of plugins) {
|
|
115
|
+
if (other.owner === owner)
|
|
116
|
+
continue;
|
|
117
|
+
const duplicate = plugin.files?.find(file => other.files?.includes(file));
|
|
118
|
+
if (duplicate)
|
|
119
|
+
throw new TypeError(`Harmony file ${duplicate} is claimed by both ${other.owner} and ${owner}.`);
|
|
120
|
+
}
|
|
104
121
|
config._internal.harmonyConfigPlugins = normalizeHarmonyConfigPlugins([
|
|
105
122
|
...plugins.filter(item => item.owner !== owner),
|
|
106
123
|
plugin,
|