@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
@@ -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,
package/build/paths.d.ts CHANGED
@@ -3,13 +3,13 @@ export interface HarmonyManagedPaths {
3
3
  readonly projectBuildProfile: string;
4
4
  readonly rootOhPackage: string;
5
5
  readonly rootHvigor: string;
6
- readonly nativeInputsStamp: string;
7
6
  readonly hvigorConfig: string;
8
7
  readonly entryBuildProfile: string;
9
8
  readonly entryOhPackage: string;
10
9
  readonly entryHvigor: string;
11
10
  readonly moduleJson: string;
12
11
  readonly profiles: string;
12
+ readonly abilityStage: string;
13
13
  readonly entryAbility: string;
14
14
  readonly indexPage: string;
15
15
  readonly worker: string;
@@ -45,18 +45,21 @@ export interface HarmonyPathsApi {
45
45
  readonly RESOURCE_PATHS: HarmonyResourcePaths;
46
46
  assertNoExternalSymlink(root: string, target: string): Promise<void>;
47
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;
48
+ resolveHarmonyPath(directory: string, relative: string): Promise<string>;
49
+ resolveProjectPath(root: string, name: keyof HarmonyProjectPaths): Promise<string>;
50
+ toPosixRelative(root: string, target: string): string;
51
51
  }
52
- declare const ManagedPaths: HarmonyManagedPaths;
52
+ declare const ManagedPaths: HarmonyManagedPaths & {
53
+ readonly nativeInputsStamp: string;
54
+ };
53
55
  declare const ProjectPaths: HarmonyProjectPaths;
54
56
  declare const ProjectPathCandidates: HarmonyProjectPathCandidates;
55
57
  declare const ResourcePaths: HarmonyResourcePaths;
56
58
  export declare function isInside(root: string, target: string): boolean;
57
59
  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 };
60
+ export declare function resolveHarmonyPath(directory: string, relative: string): Promise<string>;
61
+ export declare function resolveProjectPath(root: string, name: keyof HarmonyProjectPaths): Promise<string>;
62
+ export declare function toPosixRelative(root: string, target: string): string;
63
+ declare const NativeInputsStampPath: string;
64
+ export declare const HarmonyPaths: Readonly<HarmonyPathsApi>;
65
+ export { ManagedPaths, NativeInputsStampPath, ProjectPathCandidates, ProjectPaths, ResourcePaths };
package/build/paths.js CHANGED
@@ -3,7 +3,7 @@ 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.ResourcePaths = exports.ProjectPaths = exports.ProjectPathCandidates = exports.ManagedPaths = exports.HarmonyPaths = void 0;
6
+ exports.ResourcePaths = exports.ProjectPaths = exports.ProjectPathCandidates = exports.NativeInputsStampPath = exports.ManagedPaths = exports.HarmonyPaths = void 0;
7
7
  exports.isInside = isInside;
8
8
  exports.assertNoExternalSymlink = assertNoExternalSymlink;
9
9
  exports.resolveHarmonyPath = resolveHarmonyPath;
@@ -24,6 +24,7 @@ const ManagedPaths = Object.freeze({
24
24
  entryHvigor: 'entry/hvigorfile.ts',
25
25
  moduleJson: 'entry/src/main/module.json5',
26
26
  profiles: 'entry/src/main/resources/base/profile/main_pages.json',
27
+ abilityStage: 'entry/src/main/ets/abilitystage/EntryAbilityStage.ets',
27
28
  entryAbility: 'entry/src/main/ets/entryability/EntryAbility.ets',
28
29
  indexPage: 'entry/src/main/ets/pages/Index.ets',
29
30
  worker: 'entry/src/main/ets/workers/RNOHWorker.ets',
@@ -37,8 +38,7 @@ const ProjectPaths = Object.freeze({
37
38
  });
38
39
  exports.ProjectPaths = ProjectPaths;
39
40
  const ProjectPathCandidates = Object.freeze({
40
- // Keep this order aligned with @react-native-community/cli-config's async
41
- // search places. The first entry is also the file created for new projects.
41
+ // Match React Native CLI lookup precedence; new projects use the first path.
42
42
  reactNativeConfig: Object.freeze([
43
43
  'react-native.config.js',
44
44
  'react-native.config.cjs',
@@ -69,55 +69,56 @@ function isInside(root, target) {
69
69
  || (!relative.startsWith(`..${node_path_1.default.sep}`) && relative !== '..' && !node_path_1.default.isAbsolute(relative));
70
70
  }
71
71
  async function assertNoExternalSymlink(root, target) {
72
- const absoluteRoot = node_path_1.default.resolve(root);
73
- const realRoot = await node_fs_1.default.promises.realpath(absoluteRoot).catch(() => absoluteRoot);
74
- const relative = node_path_1.default.relative(absoluteRoot, target);
75
- let cursor = absoluteRoot;
72
+ const absolute = node_path_1.default.resolve(root);
73
+ const canonical = await node_fs_1.default.promises.realpath(absolute).catch(() => absolute);
74
+ const relative = node_path_1.default.relative(absolute, target);
75
+ let current = absolute;
76
76
  for (const segment of relative.split(node_path_1.default.sep).filter(Boolean)) {
77
- cursor = node_path_1.default.join(cursor, segment);
77
+ current = node_path_1.default.join(current, segment);
78
78
  let stat;
79
79
  try {
80
- stat = await node_fs_1.default.promises.lstat(cursor);
80
+ stat = await node_fs_1.default.promises.lstat(current);
81
81
  }
82
82
  catch (cause) {
83
83
  if (cause.code === 'ENOENT')
84
84
  break;
85
- throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_PATH_INVALID', `Cannot inspect managed Harmony path ${cursor}: ${cause.message}`, { cause, file: cursor, operation: 'resolve-path' });
85
+ throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_PATH_INVALID', `Cannot inspect managed Harmony path ${current}: ${cause.message}`, { cause, file: current, operation: 'resolve-path' });
86
86
  }
87
87
  if (stat.isSymbolicLink()) {
88
- const real = await node_fs_1.default.promises.realpath(cursor);
89
- if (!isInside(realRoot, real)) {
90
- throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_PATH_ESCAPE', `Refusing to follow a symlink outside the Harmony project: ${cursor}`, { file: cursor, operation: 'resolve-path' });
88
+ const real = await node_fs_1.default.promises.realpath(current);
89
+ if (!isInside(canonical, real)) {
90
+ throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_PATH_ESCAPE', `Refusing to follow a symlink outside the Harmony project: ${current}`, { file: current, operation: 'resolve-path' });
91
91
  }
92
92
  }
93
93
  }
94
94
  }
95
- async function resolveHarmonyPath(platformProjectRoot, relativePath) {
96
- if (typeof relativePath !== 'string' || !relativePath || node_path_1.default.isAbsolute(relativePath)) {
97
- throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_PATH_ESCAPE', `Invalid managed path: ${relativePath}`, { operation: 'resolve-path' });
95
+ async function resolveHarmonyPath(directory, relative) {
96
+ if (typeof relative !== 'string' || !relative || node_path_1.default.isAbsolute(relative)) {
97
+ throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_PATH_ESCAPE', `Invalid managed path: ${relative}`, { operation: 'resolve-path' });
98
98
  }
99
- const root = node_path_1.default.resolve(platformProjectRoot);
100
- const target = node_path_1.default.resolve(root, relativePath);
99
+ const root = node_path_1.default.resolve(directory);
100
+ const target = node_path_1.default.resolve(root, relative);
101
101
  if (!isInside(root, target)) {
102
- throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_PATH_ESCAPE', `Managed path escapes the Harmony project: ${relativePath}`, { file: target, operation: 'resolve-path' });
102
+ throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_PATH_ESCAPE', `Managed path escapes the Harmony project: ${relative}`, { file: target, operation: 'resolve-path' });
103
103
  }
104
104
  await assertNoExternalSymlink(root, target);
105
105
  return target;
106
106
  }
107
- async function resolveProjectPath(projectRoot, modName) {
108
- const candidates = ProjectPathCandidates[modName] || [ProjectPaths[modName]];
109
- const resolved = await Promise.all(candidates.map(candidate => resolveHarmonyPath(projectRoot, candidate)));
110
- const existing = resolved.filter(file => node_fs_1.default.existsSync(file));
111
- if (existing.length > 1) {
112
- throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `Multiple files provide harmony.${modName}: ${existing.map(file => node_path_1.default.basename(file)).join(', ')}`, { file: existing[0], operation: `harmony.${modName}.read` });
107
+ async function resolveProjectPath(root, name) {
108
+ const candidates = ProjectPathCandidates[name] || [ProjectPaths[name]];
109
+ for (const candidate of candidates) {
110
+ if (node_fs_1.default.existsSync(node_path_1.default.join(root, candidate)))
111
+ return resolveHarmonyPath(root, candidate);
113
112
  }
114
- return existing[0] || resolved[0];
113
+ return resolveHarmonyPath(root, candidates[0]);
115
114
  }
116
- function toPosixRelative(projectRoot, target) {
117
- return node_path_1.default.relative(projectRoot, target).split(node_path_1.default.sep).join('/');
115
+ function toPosixRelative(root, target) {
116
+ return node_path_1.default.relative(root, target).split(node_path_1.default.sep).join('/');
118
117
  }
119
- exports.HarmonyPaths = {
120
- HARMONY_PATHS: ManagedPaths,
118
+ const { nativeInputsStamp: NativeInputsStampPath, ...PublicManagedPaths } = ManagedPaths;
119
+ exports.NativeInputsStampPath = NativeInputsStampPath;
120
+ exports.HarmonyPaths = Object.freeze({
121
+ HARMONY_PATHS: Object.freeze(PublicManagedPaths),
121
122
  PROJECT_PATH_CANDIDATES: ProjectPathCandidates,
122
123
  PROJECT_PATHS: ProjectPaths,
123
124
  RESOURCE_PATHS: ResourcePaths,
@@ -126,4 +127,4 @@ exports.HarmonyPaths = {
126
127
  resolveHarmonyPath,
127
128
  resolveProjectPath,
128
129
  toPosixRelative,
129
- };
130
+ });
@@ -0,0 +1,12 @@
1
+ import type { HarmonyPermission } from './config';
2
+ import { type HarmonyModuleJson } from './manifest';
3
+ import type { HarmonyConfigPlugin } from './pluginTypes';
4
+ export declare function mergePermissions(current: readonly HarmonyPermission[], additions: readonly HarmonyPermission[]): HarmonyPermission[];
5
+ export declare function ensurePermissions(manifest: HarmonyModuleJson, permissions: readonly HarmonyPermission[]): HarmonyModuleJson;
6
+ export declare function removePermissions(manifest: HarmonyModuleJson, names: readonly string[]): HarmonyModuleJson;
7
+ export declare const withHarmonyPermissions: HarmonyConfigPlugin<readonly HarmonyPermission[]>;
8
+ export declare const HarmonyPermissions: Readonly<{
9
+ ensurePermissions: typeof ensurePermissions;
10
+ mergePermissions: typeof mergePermissions;
11
+ removePermissions: typeof removePermissions;
12
+ }>;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HarmonyPermissions = exports.withHarmonyPermissions = void 0;
4
+ exports.mergePermissions = mergePermissions;
5
+ exports.ensurePermissions = ensurePermissions;
6
+ exports.removePermissions = removePermissions;
7
+ const manifest_1 = require("./manifest");
8
+ const mods_1 = require("./mods");
9
+ const normalizeConfig_1 = require("./normalizeConfig");
10
+ function mergePermissions(current, additions) {
11
+ const result = new Map();
12
+ for (const input of [...current, ...additions]) {
13
+ const permission = (0, normalizeConfig_1.normalizePermission)(input);
14
+ const existing = result.get(permission.name);
15
+ const scene = existing?.usedScene || permission.usedScene;
16
+ const abilities = [...new Set([
17
+ ...(existing?.usedScene?.abilities ?? []),
18
+ ...(permission.usedScene?.abilities ?? []),
19
+ ])];
20
+ result.set(permission.name, {
21
+ ...existing,
22
+ ...permission,
23
+ ...(scene
24
+ ? {
25
+ usedScene: {
26
+ ...(abilities.length ? { abilities } : {}),
27
+ when: existing?.usedScene?.when === 'always' || permission.usedScene?.when === 'always' ? 'always' : 'inuse',
28
+ },
29
+ }
30
+ : {}),
31
+ });
32
+ }
33
+ return [...result.values()];
34
+ }
35
+ function ensurePermissions(manifest, permissions) {
36
+ const module = (0, manifest_1.getModuleOrThrow)(manifest);
37
+ module.requestPermissions = mergePermissions(module.requestPermissions ?? [], permissions);
38
+ return manifest;
39
+ }
40
+ function removePermissions(manifest, names) {
41
+ const module = (0, manifest_1.getModuleOrThrow)(manifest);
42
+ if (module.requestPermissions) {
43
+ module.requestPermissions = module.requestPermissions.filter(permission => !names.includes(permission.name));
44
+ }
45
+ return manifest;
46
+ }
47
+ const withHarmonyPermissions = (config, permissions) => {
48
+ const additions = mergePermissions([], permissions);
49
+ if (config.harmony) {
50
+ config.harmony = { ...config.harmony, permissions: mergePermissions(config.harmony.permissions ?? [], additions) };
51
+ }
52
+ return (0, mods_1.withModuleJson)(config, (mod) => {
53
+ if (mod.modResults.module === undefined)
54
+ mod.modResults.module = {};
55
+ ensurePermissions(mod.modResults, additions);
56
+ return mod;
57
+ });
58
+ };
59
+ exports.withHarmonyPermissions = withHarmonyPermissions;
60
+ exports.HarmonyPermissions = Object.freeze({ ensurePermissions, mergePermissions, removePermissions });
@@ -0,0 +1,15 @@
1
+ import type { ExportedConfigWithProps } from '@expo/config-plugins';
2
+ import type { ExpoConfigWithHarmony, ExpoHarmonyPlatform, HarmonyConfig } from './config';
3
+ export type HarmonyConfigPlugin<Props = void> = <Config extends ExpoConfigWithHarmony>(config: Config, props: Props) => Config;
4
+ export type HarmonyModConfig<T> = Omit<ExportedConfigWithProps<T>, 'platforms' | 'modRawConfig' | 'modRequest'> & {
5
+ platforms?: ExpoHarmonyPlatform[];
6
+ harmony?: HarmonyConfig;
7
+ modRawConfig: ExpoConfigWithHarmony;
8
+ modRequest: Omit<ExportedConfigWithProps<T>['modRequest'], 'platform' | 'nextMod'> & {
9
+ platform: 'harmony';
10
+ nextMod?: HarmonyModAction<T>;
11
+ modFile?: string;
12
+ modFileExists?: boolean;
13
+ };
14
+ };
15
+ export type HarmonyModAction<T = unknown> = (config: HarmonyModConfig<T>) => HarmonyModConfig<T> | Promise<HarmonyModConfig<T>>;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,13 @@
1
+ export interface HarmonyResourceItem extends Record<string, unknown> {
2
+ name: string;
3
+ value: string;
4
+ }
5
+ export interface HarmonyResourceFile extends Record<string, unknown> {
6
+ string?: HarmonyResourceItem[];
7
+ color?: HarmonyResourceItem[];
8
+ }
9
+ export declare function setString(file: HarmonyResourceFile, item: HarmonyResourceItem): HarmonyResourceFile;
10
+ export declare function removeString(file: HarmonyResourceFile, name: string): HarmonyResourceFile;
11
+ export declare function setColor(file: HarmonyResourceFile, item: HarmonyResourceItem): HarmonyResourceFile;
12
+ export declare function removeColor(file: HarmonyResourceFile, name: string): HarmonyResourceFile;
13
+ export declare function toArgb(value: string | number): string;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setString = setString;
4
+ exports.removeString = removeString;
5
+ exports.setColor = setColor;
6
+ exports.removeColor = removeColor;
7
+ exports.toArgb = toArgb;
8
+ const errors_1 = require("./errors");
9
+ function setItem(file, kind, item) {
10
+ if (!item || typeof item.name !== 'string' || !/^[A-Za-z0-9_.-]+$/.test(item.name)
11
+ || item.name.includes('..') || typeof item.value !== 'string') {
12
+ throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_RESOURCE_INVALID', `Invalid Harmony ${kind} resource.`, { operation: 'edit-resource' });
13
+ }
14
+ const items = readItems(file, kind);
15
+ const existing = items.find(value => value?.name === item.name);
16
+ file[kind] = [...items.filter(value => value?.name !== item.name), { ...existing, ...item }];
17
+ return file;
18
+ }
19
+ function readItems(file, kind) {
20
+ if (file[kind] !== undefined && !Array.isArray(file[kind])) {
21
+ throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_RESOURCE_INVALID', `Harmony ${kind} resources must be an array.`, { operation: 'edit-resource' });
22
+ }
23
+ return file[kind] ?? [];
24
+ }
25
+ function setString(file, item) {
26
+ return setItem(file, 'string', item);
27
+ }
28
+ function removeString(file, name) {
29
+ const items = readItems(file, 'string');
30
+ if (file.string)
31
+ file.string = items.filter(item => item?.name !== name);
32
+ return file;
33
+ }
34
+ function setColor(file, item) {
35
+ return setItem(file, 'color', item);
36
+ }
37
+ function removeColor(file, name) {
38
+ const items = readItems(file, 'color');
39
+ if (file.color)
40
+ file.color = items.filter(item => item?.name !== name);
41
+ return file;
42
+ }
43
+ function toArgb(value) {
44
+ let hex;
45
+ if (typeof value === 'number' && Number.isInteger(value) && value >= 0 && value <= 0xffffffff) {
46
+ hex = value.toString(16).padStart(8, '0');
47
+ }
48
+ else if (typeof value === 'string' && /^#[0-9a-f]{6}(?:[0-9a-f]{2})?$/i.test(value)) {
49
+ hex = value.slice(1);
50
+ }
51
+ else {
52
+ throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', 'Color must be #RRGGBB, #RRGGBBAA or an unsigned RGBA integer.', { operation: 'convert-color' });
53
+ }
54
+ return `#${hex.length === 8 ? hex.slice(6, 8) + hex.slice(0, 6) : hex}`.toUpperCase();
55
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expo-harmony/config-plugins",
3
- "version": "55.0.10-harmony.2",
3
+ "version": "55.0.10-harmony.4",
4
4
  "keywords": [
5
5
  "react-native",
6
6
  "expo",
@@ -29,12 +29,17 @@
29
29
  "require": "./build/index.js",
30
30
  "default": "./build/index.js"
31
31
  },
32
- "./native-inputs": {
32
+ "./package.json": "./package.json",
33
+ "./internal": {
34
+ "types": "./build/internal.d.ts",
35
+ "require": "./build/internal.js",
36
+ "default": "./build/internal.js"
37
+ },
38
+ "./internal/native-inputs": {
33
39
  "types": "./build/nativeInputs.d.ts",
34
40
  "require": "./build/nativeInputs.js",
35
41
  "default": "./build/nativeInputs.js"
36
- },
37
- "./package.json": "./package.json"
42
+ }
38
43
  },
39
44
  "dependencies": {
40
45
  "@expo/config-plugins": "55.0.10",
@@ -44,11 +49,6 @@
44
49
  "peerDependencies": {
45
50
  "@expo/config": "55.0.17"
46
51
  },
47
- "peerDependenciesMeta": {
48
- "@expo/config": {
49
- "optional": true
50
- }
51
- },
52
52
  "devDependencies": {
53
53
  "@types/node": "18.19.0",
54
54
  "typescript": "^6.0.3"
@@ -0,0 +1,21 @@
1
+ import { compileModsAsync, type ExportedConfig, type ModPlatform } from '@expo/config-plugins';
2
+
3
+ import type { ExpoConfigWithHarmony } from './config';
4
+ import { withHarmonyBaseMods } from './mods';
5
+
6
+ export interface HarmonyCompileOptions {
7
+ projectRoot: string;
8
+ introspect?: boolean;
9
+ ignoreExistingNativeFiles?: boolean;
10
+ }
11
+
12
+ export async function compileHarmonyModsAsync<Config extends ExpoConfigWithHarmony>(
13
+ config: Config,
14
+ options: HarmonyCompileOptions
15
+ ): Promise<Config> {
16
+ return await compileModsAsync(withHarmonyBaseMods(config) as ExportedConfig, {
17
+ ...options,
18
+ platforms: ['harmony' as ModPlatform],
19
+ assertMissingModProviders: true,
20
+ }) as Config;
21
+ }
@@ -0,0 +1,45 @@
1
+ import {
2
+ createRunOncePlugin as expoCreateRunOncePlugin,
3
+ withPlugins as expoWithPlugins,
4
+ withRunOnce as expoWithRunOnce,
5
+ withStaticPlugin as expoWithStaticPlugin,
6
+ type ConfigPlugin,
7
+ } from '@expo/config-plugins';
8
+
9
+ import type { HarmonyConfigPlugin } from './pluginTypes';
10
+ import type { ExpoConfigWithHarmony } from './config';
11
+
12
+ // Expo plugins accept the narrower platform union. Method variance allows them
13
+ // alongside Harmony plugins without losing contextual types for inline callbacks.
14
+ type CompatiblePlugin<Props = void> = {
15
+ apply(config: ExpoConfigWithHarmony, props: Props): ExpoConfigWithHarmony;
16
+ }['apply'];
17
+
18
+ export type HarmonyPluginReference = string | CompatiblePlugin<never>;
19
+ export type HarmonyStaticPlugin = HarmonyPluginReference | [HarmonyPluginReference, unknown];
20
+
21
+ export const withPlugins = expoWithPlugins as unknown as HarmonyConfigPlugin<HarmonyStaticPlugin[]>;
22
+ export const withRunOnce = expoWithRunOnce as unknown as HarmonyConfigPlugin<{
23
+ plugin: CompatiblePlugin;
24
+ name: string;
25
+ version?: string;
26
+ }>;
27
+ export const withStaticPlugin = expoWithStaticPlugin as unknown as HarmonyConfigPlugin<{
28
+ plugin: HarmonyStaticPlugin;
29
+ projectRoot?: string;
30
+ fallback?: CompatiblePlugin<{ _resolverError: Error } & Record<string, unknown>>;
31
+ }>;
32
+
33
+ export function createRunOncePlugin<Props>(
34
+ plugin: HarmonyConfigPlugin<Props>, name: string, version?: string
35
+ ): HarmonyConfigPlugin<Props>;
36
+ export function createRunOncePlugin<Props>(
37
+ plugin: ConfigPlugin<Props>, name: string, version?: string
38
+ ): HarmonyConfigPlugin<Props>;
39
+ export function createRunOncePlugin(
40
+ plugin: ConfigPlugin<never> | HarmonyConfigPlugin<never>,
41
+ name: string,
42
+ version?: string
43
+ ): HarmonyConfigPlugin<never> {
44
+ return expoCreateRunOncePlugin(plugin as ConfigPlugin<never>, name, version) as HarmonyConfigPlugin<never>;
45
+ }
package/src/config.ts CHANGED
@@ -18,25 +18,14 @@ export interface HarmonyPermission {
18
18
  usedScene?: { abilities?: string[]; when?: 'inuse' | 'always' };
19
19
  }
20
20
 
21
+ export type HarmonySkillUri = Record<string, string | number | boolean>;
22
+
21
23
  export interface HarmonySkill {
22
24
  entities?: string[];
23
25
  actions?: string[];
24
- uris?: Array<Record<string, string>>;
25
- }
26
-
27
- export interface HarmonyFontDefinition {
28
- path: string;
29
- weight?: number;
30
- style?: 'normal' | 'italic';
26
+ uris?: HarmonySkillUri[];
31
27
  }
32
28
 
33
- export type HarmonyFont
34
- = | string
35
- | {
36
- fontFamily: string;
37
- fontDefinitions: HarmonyFontDefinition[];
38
- };
39
-
40
29
  export interface HarmonyConfig {
41
30
  bundleName: string;
42
31
  moduleName?: string;
@@ -46,14 +35,11 @@ export interface HarmonyConfig {
46
35
  versionCode?: number;
47
36
  versionName?: string;
48
37
  targetApiVersion?: number;
49
- /** Harmony SDK label written to build-profile.json5, e.g. 6.0.0(20). Its API must match targetApiVersion. */
50
38
  targetSdkVersion?: number | string;
51
- /** API number, or a full Harmony SDK label ending in the API number. */
52
39
  compatibleSdkVersion?: number | string;
53
40
  deviceTypes?: HarmonyDeviceType[];
54
41
  permissions?: HarmonyPermission[];
55
42
  skills?: HarmonySkill[];
56
- /** URL schemes the app may inspect with Linking.canOpenURL. http, https, and expo.scheme are added automatically. */
57
43
  querySchemes?: string[];
58
44
  icon?: string;
59
45
  label?: string;
@@ -63,8 +49,6 @@ export interface HarmonyConfig {
63
49
  jsEngine?: 'hermes';
64
50
  abiFilters?: string[];
65
51
  signingConfigFile?: string;
66
- /** Fonts bundled when the `@expo-harmony/expo-font` config plugin is registered. */
67
- fonts?: HarmonyFont[];
68
52
  }
69
53
 
70
54
  export type ExpoConfigWithHarmony = Omit<ExpoConfig, 'platforms'> & {