@expo-harmony/config-plugins 55.0.10-harmony.2 → 55.0.10-harmony.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +203 -11
  2. package/build/compiler.d.ts +7 -0
  3. package/build/compiler.js +12 -0
  4. package/build/composition.d.ts +24 -0
  5. package/build/composition.js +11 -0
  6. package/build/config.d.ts +2 -15
  7. package/build/customFiles.d.ts +15 -0
  8. package/build/customFiles.js +108 -0
  9. package/build/files.d.ts +1 -1
  10. package/build/files.js +2 -2
  11. package/build/generatedFiles.d.ts +15 -0
  12. package/build/generatedFiles.js +70 -0
  13. package/build/index.d.ts +20 -5
  14. package/build/index.js +53 -6
  15. package/build/internal.d.ts +3 -0
  16. package/build/internal.js +13 -0
  17. package/build/manifest.d.ts +38 -0
  18. package/build/manifest.js +71 -0
  19. package/build/mods.d.ts +37 -31
  20. package/build/mods.js +118 -86
  21. package/build/normalizeConfig.d.ts +10 -3
  22. package/build/normalizeConfig.js +45 -47
  23. package/build/ownership.d.ts +4 -3
  24. package/build/ownership.js +17 -0
  25. package/build/paths.d.ts +13 -10
  26. package/build/paths.js +32 -31
  27. package/build/permissions.d.ts +12 -0
  28. package/build/permissions.js +60 -0
  29. package/build/pluginTypes.d.ts +15 -0
  30. package/build/pluginTypes.js +2 -0
  31. package/build/resources.d.ts +13 -0
  32. package/build/resources.js +55 -0
  33. package/package.json +9 -9
  34. package/src/compiler.ts +21 -0
  35. package/src/composition.ts +45 -0
  36. package/src/config.ts +3 -19
  37. package/src/customFiles.ts +142 -0
  38. package/src/files.ts +3 -4
  39. package/src/generatedFiles.ts +85 -0
  40. package/src/index.ts +20 -11
  41. package/src/internal.ts +3 -0
  42. package/src/manifest.ts +151 -0
  43. package/src/mods.ts +180 -128
  44. package/src/nativeInputs.ts +0 -1
  45. package/src/normalizeConfig.ts +56 -59
  46. package/src/ownership.ts +25 -5
  47. package/src/paths.ts +41 -45
  48. package/src/permissions.ts +68 -0
  49. package/src/pluginTypes.ts +24 -0
  50. 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.withAbilityStage = 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");
@@ -28,6 +29,7 @@ const TextMods = new Set([
28
29
  'rootHvigor',
29
30
  'nativeInputsStamp',
30
31
  'entryHvigor',
32
+ 'abilityStage',
31
33
  'entryAbility',
32
34
  'indexPage',
33
35
  'worker',
@@ -36,10 +38,11 @@ const TextMods = new Set([
36
38
  'cmakeLists',
37
39
  ]);
38
40
  const ResourceMods = new Set(['strings', 'colors', 'media']);
39
- const VirtualMods = new Set(['dangerous', 'autolinking', 'manifest']);
41
+ const VirtualMods = new Set(['dangerous', 'autolinking', 'manifest', 'prepare', 'patch']);
40
42
  const ResourceModNames = ['strings', 'colors', 'media'];
41
- exports.HarmonyModNames = Object.freeze([
43
+ const AllModNames = Object.freeze([
42
44
  'dangerous',
45
+ 'prepare',
43
46
  'reactNativeConfig',
44
47
  'appJson',
45
48
  'projectBuildProfile',
@@ -55,6 +58,7 @@ exports.HarmonyModNames = Object.freeze([
55
58
  'colors',
56
59
  'media',
57
60
  'profiles',
61
+ 'abilityStage',
58
62
  'entryAbility',
59
63
  'indexPage',
60
64
  'worker',
@@ -62,8 +66,10 @@ exports.HarmonyModNames = Object.freeze([
62
66
  'cppPackageProvider',
63
67
  'cmakeLists',
64
68
  'autolinking',
69
+ 'patch',
65
70
  'manifest',
66
71
  ]);
72
+ exports.HarmonyModNames = Object.freeze(AllModNames.filter((name) => name !== 'manifest' && name !== 'nativeInputsStamp' && name !== 'prepare' && name !== 'patch'));
67
73
  function recordManagedFile(config, file, owner) {
68
74
  config._internal ??= {};
69
75
  config._internal.harmonyManagedFiles ??= [];
@@ -73,35 +79,41 @@ function recordManagedFile(config, file, owner) {
73
79
  files.sort((left, right) => left.path.localeCompare(right.path, 'en'));
74
80
  config._internal.harmonyManagedFiles = files;
75
81
  }
76
- function assertModResults(modName, value, kind) {
82
+ function assertModResults(name, value, kind) {
77
83
  const valid = kind === 'text'
78
84
  ? typeof value === 'string'
79
85
  : value !== null && typeof value === 'object' && !Array.isArray(value);
80
86
  if (!valid) {
81
- throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_MOD_RESULTS_INVALID', `harmony.${modName} must return ${kind === 'text' ? 'a string' : 'an object'} modResults.`, { operation: `harmony.${modName}.write` });
87
+ 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
88
  }
83
89
  }
84
- function withFileProvider(config, modName) {
90
+ function withFileProvider(config, name) {
85
91
  return (0, config_plugins_1.withBaseMod)(config, {
86
92
  platform: 'harmony',
87
- mod: modName,
93
+ mod: name,
88
94
  isProvider: true,
89
- isIntrospective: JsonMods.has(modName),
90
- saveToInternal: JsonMods.has(modName),
95
+ isIntrospective: JsonMods.has(name),
96
+ saveToInternal: JsonMods.has(name),
91
97
  async action(value) {
92
- const { nextMod, ...modRequest } = value.modRequest;
93
- const isProjectFile = modName in paths_1.ProjectPaths;
94
- const root = isProjectFile ? modRequest.projectRoot : modRequest.platformProjectRoot;
95
- const file = isProjectFile
96
- ? await (0, paths_1.resolveProjectPath)(root, modName)
97
- : await (0, paths_1.resolveHarmonyPath)(root, paths_1.ManagedPaths[modName]);
98
- const exists = node_fs_1.default.existsSync(file);
99
- const data = JsonMods.has(modName)
100
- ? await (0, files_1.readJson5)(file, {}, modName)
101
- : await (0, files_1.readText)(file);
102
- const request = { ...modRequest, modFile: file, modFileExists: exists };
103
- const result = await nextMod({ ...value, modRequest: request, modResults: data });
104
- assertModResults(modName, result.modResults, JsonMods.has(modName) ? 'json' : 'text');
98
+ const { nextMod: next, ...request } = value.modRequest;
99
+ const project = name in paths_1.ProjectPaths;
100
+ const root = project ? request.projectRoot : request.platformProjectRoot;
101
+ const file = project
102
+ ? await (0, paths_1.resolveProjectPath)(root, name)
103
+ : await (0, paths_1.resolveHarmonyPath)(root, paths_1.ManagedPaths[name]);
104
+ const exists = !request.ignoreExistingNativeFiles && node_fs_1.default.existsSync(file);
105
+ const template = value._internal?.harmonyTemplateDirectory;
106
+ const input = !exists && !project && typeof template === 'string'
107
+ ? await (0, paths_1.resolveHarmonyPath)(template, paths_1.ManagedPaths[name])
108
+ : file;
109
+ const data = request.ignoreExistingNativeFiles && input === file
110
+ ? (JsonMods.has(name) ? {} : '')
111
+ : JsonMods.has(name)
112
+ ? await (0, files_1.readJson5)(input, {}, name)
113
+ : await (0, files_1.readText)(file);
114
+ const context = { ...request, modFile: file, modFileExists: exists };
115
+ const result = await next({ ...value, modRequest: context, modResults: data });
116
+ assertModResults(name, result.modResults, JsonMods.has(name) ? 'json' : 'text');
105
117
  if (!result.modRequest.introspect) {
106
118
  if (typeof result.modResults === 'string') {
107
119
  await (0, files_1.atomicWrite)(file, result.modResults.replace(/\r\n?/g, '\n').replace(/\n?$/, '\n'));
@@ -109,17 +121,19 @@ function withFileProvider(config, modName) {
109
121
  else {
110
122
  await (0, files_1.writeJson5)(file, result.modResults);
111
123
  }
112
- recordManagedFile(result, file, modName);
124
+ recordManagedFile(result, file, name);
113
125
  }
114
126
  return result;
115
127
  },
116
128
  });
117
129
  }
118
- async function readResourceMap(root, entries, modName) {
130
+ async function readResourceMap(root, entries, name, template, ignore = false) {
119
131
  const resources = {};
120
132
  for (const [scope, relative] of Object.entries(entries)) {
121
133
  const file = await (0, paths_1.resolveHarmonyPath)(root, relative);
122
- resources[scope] = await (0, files_1.readJson5)(file, {}, modName);
134
+ const fallback = ignore || !node_fs_1.default.existsSync(file);
135
+ const input = fallback && template ? await (0, paths_1.resolveHarmonyPath)(template, relative) : file;
136
+ resources[scope] = ignore && input === file ? {} : await (0, files_1.readJson5)(input, {}, name);
123
137
  }
124
138
  return resources;
125
139
  }
@@ -162,16 +176,14 @@ async function writeMediaMap(root, media, config, previous = {}) {
162
176
  throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `Harmony media ${scope}/${name} must use a descriptor object.`, { operation: 'harmony.media.write' });
163
177
  }
164
178
  const item = descriptor;
165
- const hasSource = Object.hasOwn(item, 'source');
166
- const hasContent = Object.hasOwn(item, 'content');
167
- if (hasSource === hasContent) {
179
+ if (Object.hasOwn(item, 'source') === Object.hasOwn(item, 'content')) {
168
180
  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
181
  }
170
182
  if (item.replaceBase !== undefined && typeof item.replaceBase !== 'boolean') {
171
183
  throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `Harmony media ${scope}/${name} replaceBase must be a boolean.`, { operation: 'harmony.media.write' });
172
184
  }
173
185
  let content;
174
- if (hasSource) {
186
+ if (Object.hasOwn(item, 'source')) {
175
187
  if (typeof item.source !== 'string' || item.source.length === 0) {
176
188
  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
189
  }
@@ -191,9 +203,6 @@ async function writeMediaMap(root, media, config, previous = {}) {
191
203
  });
192
204
  }
193
205
  }
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
206
  for (const [scope, names] of Object.entries(previous)) {
198
207
  if (!(scope in paths_1.ResourcePaths.media))
199
208
  continue;
@@ -210,9 +219,9 @@ async function writeMediaMap(root, media, config, previous = {}) {
210
219
  await node_fs_1.default.promises.mkdir(directory, { recursive: true });
211
220
  if (replaceBase) {
212
221
  const base = node_path_1.default.parse(name).name;
213
- for (const oldName of await node_fs_1.default.promises.readdir(directory)) {
214
- if (node_path_1.default.parse(oldName).name === base && oldName !== name) {
215
- await node_fs_1.default.promises.rm(node_path_1.default.join(directory, oldName), { force: true });
222
+ for (const entry of await node_fs_1.default.promises.readdir(directory)) {
223
+ if (node_path_1.default.parse(entry).name === base && entry !== name) {
224
+ await node_fs_1.default.promises.rm(node_path_1.default.join(directory, entry), { force: true });
216
225
  }
217
226
  }
218
227
  }
@@ -221,47 +230,47 @@ async function writeMediaMap(root, media, config, previous = {}) {
221
230
  recordManagedFile(config, file, 'media');
222
231
  }
223
232
  }
224
- function withResourceProvider(config, modName) {
225
- const isIntrospective = modName !== 'media';
233
+ function withResourceProvider(config, name) {
234
+ const introspective = name !== 'media';
226
235
  return (0, config_plugins_1.withBaseMod)(config, {
227
236
  platform: 'harmony',
228
- mod: modName,
237
+ mod: name,
229
238
  isProvider: true,
230
- isIntrospective,
231
- saveToInternal: isIntrospective,
239
+ isIntrospective: introspective,
240
+ saveToInternal: introspective,
232
241
  async action(value) {
233
- const { nextMod, ...modRequest } = value.modRequest;
234
- const root = modRequest.platformProjectRoot;
235
- const data = modName === 'media'
236
- ? await readMediaMap(root)
237
- : await readResourceMap(root, paths_1.ResourcePaths[modName], modName);
238
- const previous = modName === 'media'
242
+ const { nextMod: next, ...request } = value.modRequest;
243
+ const root = request.platformProjectRoot;
244
+ const data = name === 'media'
245
+ ? (request.ignoreExistingNativeFiles
246
+ ? Object.fromEntries(Object.keys(paths_1.ResourcePaths.media).map(scope => [scope, {}]))
247
+ : await readMediaMap(root))
248
+ : await readResourceMap(root, paths_1.ResourcePaths[name], name, value._internal?.harmonyTemplateDirectory, request.ignoreExistingNativeFiles);
249
+ const previous = name === 'media'
239
250
  ? Object.fromEntries(Object.entries(data).map(([scope, files]) => [scope, Object.keys(files)]))
240
251
  : {};
241
- const result = await nextMod({ ...value, modRequest, modResults: data });
242
- assertModResults(modName, result.modResults, 'resource');
252
+ const result = await next({ ...value, modRequest: request, modResults: data });
253
+ assertModResults(name, result.modResults, 'resource');
243
254
  if (!result.modRequest.introspect) {
244
- if (modName === 'media') {
255
+ if (name === 'media') {
245
256
  await writeMediaMap(root, result.modResults, result, previous);
246
257
  }
247
258
  else {
248
- const paths = paths_1.ResourcePaths[modName];
259
+ const paths = paths_1.ResourcePaths[name];
249
260
  for (const [scope, resource] of Object.entries(result.modResults)) {
250
261
  if (!(scope in paths))
251
262
  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
263
  if (!resource || typeof resource !== 'object' || Array.isArray(resource)) {
256
- throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `Harmony ${modName} scope ${scope} must be an object.`, { operation: `harmony.${modName}.write` });
264
+ throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `Harmony ${name} scope ${scope} must be an object.`, { operation: `harmony.${name}.write` });
257
265
  }
258
266
  const file = await (0, paths_1.resolveHarmonyPath)(root, paths[scope]);
267
+ // Harmony's resource compiler rejects an empty JSON root.
259
268
  if (Object.keys(resource).length === 0) {
260
269
  await node_fs_1.default.promises.rm(file, { force: true });
261
270
  continue;
262
271
  }
263
272
  await (0, files_1.writeJson5)(file, resource);
264
- recordManagedFile(result, file, modName);
273
+ recordManagedFile(result, file, name);
265
274
  }
266
275
  }
267
276
  }
@@ -269,61 +278,63 @@ function withResourceProvider(config, modName) {
269
278
  },
270
279
  });
271
280
  }
272
- function withVirtualProvider(config, modName) {
281
+ function withVirtualProvider(config, name) {
273
282
  return (0, config_plugins_1.withBaseMod)(config, {
274
283
  platform: 'harmony',
275
- mod: modName,
284
+ mod: name,
276
285
  isProvider: true,
286
+ isIntrospective: name === 'prepare',
277
287
  async action(value) {
278
- const { nextMod, ...modRequest } = value.modRequest;
279
- return nextMod({ ...value, modRequest, modResults: null });
288
+ const { nextMod: next, ...request } = value.modRequest;
289
+ return next({ ...value, modRequest: request, modResults: null });
280
290
  },
281
291
  });
282
292
  }
283
- function withProvider(config, modName) {
293
+ function withProvider(config, name) {
284
294
  const actions = [];
285
295
  config = (0, config_plugins_1.withMod)(config, {
286
296
  platform: 'harmony',
287
- mod: modName,
297
+ mod: name,
288
298
  async action(value) {
289
299
  let result = value;
290
- // withMod is a stack: the last registered plugin runs first. Preserve
291
- // that ordering for Harmony plugins registered after the provider.
300
+ // Late mods must follow Expo's last-registered-first order.
292
301
  for (let index = actions.length - 1; index >= 0; index -= 1) {
293
302
  result = await actions[index](result);
294
303
  }
295
304
  return result;
296
305
  },
297
306
  });
298
- if (JsonMods.has(modName) || TextMods.has(modName)) {
299
- config = withFileProvider(config, modName);
307
+ if (JsonMods.has(name) || TextMods.has(name)) {
308
+ config = withFileProvider(config, name);
300
309
  }
301
- else if (ResourceMods.has(modName)) {
302
- config = withResourceProvider(config, modName);
310
+ else if (ResourceMods.has(name)) {
311
+ config = withResourceProvider(config, name);
303
312
  }
304
- else if (VirtualMods.has(modName)) {
305
- config = withVirtualProvider(config, modName);
313
+ else if (VirtualMods.has(name)) {
314
+ config = withVirtualProvider(config, name);
306
315
  }
307
316
  else {
308
- throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_MOD_NOT_REGISTERED', `Unknown Harmony mod: ${modName}`, { operation: 'register-base-mods' });
317
+ throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_MOD_NOT_REGISTERED', `Unknown Harmony mod: ${name}`, { operation: 'register-base-mods' });
309
318
  }
310
319
  const mods = config.mods;
311
- const provider = mods?.harmony?.[modName];
320
+ const provider = mods?.harmony?.[name];
312
321
  if (provider?.isProvider)
313
322
  provider.expoHarmonyLateActions = actions;
314
323
  return config;
315
324
  }
316
325
  function withHarmonyBaseMods(config) {
317
- for (const modName of exports.HarmonyModNames) {
326
+ for (const name of AllModNames) {
318
327
  const mods = config.mods;
319
- if (!mods?.harmony?.[modName]?.isProvider)
320
- config = withProvider(config, modName);
328
+ if (!mods?.harmony?.[name]?.isProvider)
329
+ config = withProvider(config, name);
321
330
  }
322
331
  const mods = config.mods;
323
332
  const table = mods.harmony;
324
333
  mods.harmony = Object.fromEntries([
325
- ...exports.HarmonyModNames.filter(name => table[name]).map(name => [name, table[name]]),
326
- ...Object.entries(table).filter(([name]) => !exports.HarmonyModNames.includes(name)),
334
+ ...AllModNames.filter(name => table[name] && name !== 'manifest' && name !== 'patch').map(name => [name, table[name]]),
335
+ ...Object.entries(table).filter(([name]) => !AllModNames.includes(name)),
336
+ ['patch', table.patch],
337
+ ['manifest', table.manifest],
327
338
  ]);
328
339
  return config;
329
340
  }
@@ -331,17 +342,32 @@ function withHarmonyMod(config, tuple) {
331
342
  if (!Array.isArray(tuple) || tuple.length !== 2) {
332
343
  throw new TypeError('withHarmonyMod expects [modName, action].');
333
344
  }
334
- const [modName, action] = tuple;
335
- if (!exports.HarmonyModNames.includes(modName) || typeof action !== 'function') {
336
- throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_MOD_NOT_REGISTERED', `Cannot register Harmony mod: ${modName}`, { operation: 'register-mod' });
345
+ const [name, action] = tuple;
346
+ if (!exports.HarmonyModNames.includes(name) || typeof action !== 'function') {
347
+ throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_MOD_NOT_REGISTERED', `Cannot register Harmony mod: ${name}`, { operation: 'register-mod' });
337
348
  }
349
+ return registerMod(config, name, action);
350
+ }
351
+ function registerMod(config, name, action) {
338
352
  const mods = config.mods;
339
- const provider = mods?.harmony?.[modName];
353
+ const provider = mods?.harmony?.[name];
340
354
  if (provider?.isProvider && provider.expoHarmonyLateActions) {
341
- provider.expoHarmonyLateActions.push(action);
355
+ // Build an ordinary Expo interceptor on an isolated mod table, preserving
356
+ // its registration stack, error behavior and execution trace for late mods.
357
+ const carrier = (0, config_plugins_1.withMod)({ ...config, mods: {} }, {
358
+ platform: 'harmony',
359
+ mod: name,
360
+ action: action,
361
+ });
362
+ const interceptor = carrier.mods.harmony[name];
363
+ provider.expoHarmonyLateActions.push(interceptor);
342
364
  return config;
343
365
  }
344
- return (0, config_plugins_1.withMod)(config, { platform: 'harmony', mod: modName, action });
366
+ return (0, config_plugins_1.withMod)(config, {
367
+ platform: 'harmony',
368
+ mod: name,
369
+ action: action,
370
+ });
345
371
  }
346
372
  const withReactNativeConfig = (config, action) => withHarmonyMod(config, ['reactNativeConfig', action]);
347
373
  exports.withReactNativeConfig = withReactNativeConfig;
@@ -353,7 +379,7 @@ const withRootOhPackage = (config, action) => withHarmonyMod(config, ['rootOhPac
353
379
  exports.withRootOhPackage = withRootOhPackage;
354
380
  const withRootHvigor = (config, action) => withHarmonyMod(config, ['rootHvigor', action]);
355
381
  exports.withRootHvigor = withRootHvigor;
356
- const withNativeInputsStamp = (config, action) => withHarmonyMod(config, ['nativeInputsStamp', action]);
382
+ const withNativeInputsStamp = (config, action) => registerMod(config, 'nativeInputsStamp', action);
357
383
  exports.withNativeInputsStamp = withNativeInputsStamp;
358
384
  const withHvigorConfig = (config, action) => withHarmonyMod(config, ['hvigorConfig', action]);
359
385
  exports.withHvigorConfig = withHvigorConfig;
@@ -373,6 +399,8 @@ const withMedia = (config, action) => withHarmonyMod(config, ['media', action]);
373
399
  exports.withMedia = withMedia;
374
400
  const withProfiles = (config, action) => withHarmonyMod(config, ['profiles', action]);
375
401
  exports.withProfiles = withProfiles;
402
+ const withAbilityStage = (config, action) => withHarmonyMod(config, ['abilityStage', action]);
403
+ exports.withAbilityStage = withAbilityStage;
376
404
  const withEntryAbility = (config, action) => withHarmonyMod(config, ['entryAbility', action]);
377
405
  exports.withEntryAbility = withEntryAbility;
378
406
  const withIndexPage = (config, action) => withHarmonyMod(config, ['indexPage', action]);
@@ -387,13 +415,17 @@ const withCMakeLists = (config, action) => withHarmonyMod(config, ['cmakeLists',
387
415
  exports.withCMakeLists = withCMakeLists;
388
416
  const withHarmonyAutolinking = (config, action) => withHarmonyMod(config, ['autolinking', action]);
389
417
  exports.withHarmonyAutolinking = withHarmonyAutolinking;
390
- const withCngManifest = (config, action) => withHarmonyMod(config, ['manifest', action]);
418
+ const withCngManifest = (config, action) => registerMod(config, 'manifest', action);
391
419
  exports.withCngManifest = withCngManifest;
420
+ const withPreparation = (config, action) => registerMod(config, 'prepare', action);
421
+ exports.withPreparation = withPreparation;
422
+ const withProjectPatch = (config, action) => registerMod(config, 'patch', action);
423
+ exports.withProjectPatch = withProjectPatch;
392
424
  const withHarmonyDangerousMod = (config, action) => withHarmonyMod(config, ['dangerous', action]);
393
425
  exports.withHarmonyDangerousMod = withHarmonyDangerousMod;
394
426
  const withHarmonyResources = (config, action) => {
395
- for (const modName of ResourceModNames) {
396
- withHarmonyMod(config, [modName, action]);
427
+ for (const name of ResourceModNames) {
428
+ withHarmonyMod(config, [name, action]);
397
429
  }
398
430
  return config;
399
431
  };
@@ -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 const HarmonySdkVersions: Map<number, string>;
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 { HarmonySdkVersions, normalizeHarmonyConfig };
34
+ export { normalizeHarmonyConfig };
28
35
  export type { HarmonyExpoConfig, NormalizedHarmonyConfig };
@@ -1,12 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.HarmonySdkVersions = void 0;
3
+ exports.normalizePermission = normalizePermission;
4
4
  exports.normalizeHarmonyConfig = normalizeHarmonyConfig;
5
5
  const errors_1 = require("./errors");
6
- const BundleNamePattern = /^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*){2,}$/;
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 ValidOrientations = new Set([
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 ValidDeviceTypes = new Set(['phone', 'tablet', '2in1']);
18
- const SupportedMinimumHarmonyApi = 13;
19
- const DefaultHarmonyCompatibleApi = 23;
20
- const DefaultHarmonyTargetApi = 24;
21
- const HarmonySdkVersions = new Map([
18
+ const DeviceTypes = new Set(['phone', 'tablet', '2in1']);
19
+ const MinimumApi = 20;
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
- const result = color.toUpperCase();
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 versionApi = parseSdkApi(version, field);
86
- if (versionApi !== api) {
87
- throw new HarmonyConfigError(`harmony.${field} describes API ${versionApi}, but the configured API level is ${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 = HarmonySdkVersions.get(api);
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] = readString(value, `harmony.skills[].uris[].${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 bundleName = readString(harmony.bundleName, 'harmony.bundleName');
180
- if (!BundleNamePattern.test(bundleName)) {
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 moduleName = readString(harmony.moduleName, 'harmony.moduleName', 'entry');
184
- const abilityName = readString(harmony.abilityName, 'harmony.abilityName', 'EntryAbility');
185
- if (!IdentifierPattern.test(moduleName)) {
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(abilityName)) {
188
+ if (!IdentifierPattern.test(ability)) {
189
189
  throw new HarmonyConfigError('harmony.abilityName must be a valid Harmony identifier.');
190
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.`);
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 (compatibleApi > targetApi) {
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 (!ValidOrientations.has(orientation)) {
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') {
@@ -225,6 +219,10 @@ function normalizeHarmonyConfig(config) {
225
219
  if (!permissions.some(permission => permission.name === 'ohos.permission.INTERNET')) {
226
220
  permissions.unshift({ name: 'ohos.permission.INTERNET' });
227
221
  }
222
+ if (config.updates?.checkAutomatically === 'WIFI_ONLY'
223
+ && !permissions.some(permission => permission.name === 'ohos.permission.GET_NETWORK_INFO')) {
224
+ permissions.push({ name: 'ohos.permission.GET_NETWORK_INFO' });
225
+ }
228
226
  const schemes = readExpoSchemes(config);
229
227
  let skills = (harmony.skills || []).map(normalizeSkill);
230
228
  for (const scheme of schemes) {
@@ -250,27 +248,27 @@ function normalizeHarmonyConfig(config) {
250
248
  : readString(harmony.signingConfigFile, 'harmony.signingConfigFile');
251
249
  const devices = harmony.deviceTypes === undefined
252
250
  ? ['phone', 'tablet']
253
- : normalizeStringArray(harmony.deviceTypes, 'harmony.deviceTypes', ValidDeviceTypes);
251
+ : normalizeStringArray(harmony.deviceTypes, 'harmony.deviceTypes', DeviceTypes);
254
252
  return Object.freeze({
255
253
  abiFilters: abis,
256
- abilityName,
254
+ abilityName: ability,
257
255
  backgroundColor: background,
258
- bundleName,
259
- compatibleSdkVersionString: resolveSdkVersion(compatibleApi, 'compatibleSdkVersion', harmony.compatibleSdkVersion),
256
+ bundleName: bundle,
257
+ compatibleSdkVersionString: resolveSdkVersion(compatible, 'compatibleSdkVersion', harmony.compatibleSdkVersion),
260
258
  deviceTypes: devices,
261
259
  icon: harmony.icon || config.icon,
262
260
  label: readString(harmony.label, 'harmony.label', config.name),
263
- moduleName,
261
+ moduleName: module,
264
262
  nativeOrientation: orientation === 'default' ? 'unspecified' : orientation,
265
263
  permissions,
266
264
  productName: readString(harmony.productName, 'harmony.productName', 'default'),
267
265
  querySchemes: queries,
268
266
  signingConfigFile: signing,
269
267
  skills,
270
- targetApiVersion: targetApi,
271
- targetSdkVersionString: resolveSdkVersion(targetApi, 'targetSdkVersion', harmony.targetSdkVersion),
268
+ targetApiVersion: target,
269
+ targetSdkVersionString: resolveSdkVersion(target, 'targetSdkVersion', harmony.targetSdkVersion),
272
270
  vendor: readString(harmony.vendor, 'harmony.vendor', 'expo-harmony'),
273
- versionCode,
274
- versionName,
271
+ versionCode: code,
272
+ versionName: version,
275
273
  });
276
274
  }
@@ -1,6 +1,7 @@
1
- import type { ExpoConfig } from '@expo/config-types';
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: ExpoConfig, owner: string, claims?: Omit<HarmonyConfigPluginOwnership, 'owner'>): ExpoConfig;
16
- export declare function getHarmonyConfigPlugins(config: ExpoConfig): readonly HarmonyConfigPluginOwnership[];
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[];