@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.
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 +52 -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 +36 -31
  20. package/build/mods.js +114 -86
  21. package/build/normalizeConfig.d.ts +10 -3
  22. package/build/normalizeConfig.js +41 -47
  23. package/build/ownership.d.ts +4 -3
  24. package/build/ownership.js +17 -0
  25. package/build/paths.d.ts +12 -10
  26. package/build/paths.js +31 -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 +19 -11
  41. package/src/internal.ts +3 -0
  42. package/src/manifest.ts +151 -0
  43. package/src/mods.ts +170 -129
  44. package/src/nativeInputs.ts +0 -1
  45. package/src/normalizeConfig.ts +51 -59
  46. package/src/ownership.ts +25 -5
  47. package/src/paths.ts +38 -46
  48. package/src/permissions.ts +68 -0
  49. package/src/pluginTypes.ts +24 -0
  50. package/src/resources.ts +65 -0
@@ -0,0 +1,85 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ import { HarmonyConfigPluginError } from './errors';
5
+ import { atomicWrite } from './files';
6
+ import { recordManagedFile, withHarmonyDangerousMod } from './mods';
7
+ import { getHarmonyConfigPlugins, registerHarmonyConfigPlugin } from './ownership';
8
+ import { resolveHarmonyPath } from './paths';
9
+ import type { HarmonyConfigPlugin } from './pluginTypes';
10
+
11
+ export type HarmonyFileDescriptor
12
+ = | { source: string; content?: never }
13
+ | { source?: never; content: string | Uint8Array };
14
+ export type HarmonyFileMap = Record<string, HarmonyFileDescriptor>;
15
+ export interface HarmonyGeneratedFilesOptions {
16
+ owner: string;
17
+ files: HarmonyFileMap;
18
+ }
19
+
20
+ export const withHarmonyGeneratedFiles: HarmonyConfigPlugin<HarmonyGeneratedFilesOptions> = (config, options) => {
21
+ const { owner, files } = options;
22
+ if (!files || typeof files !== 'object' || Array.isArray(files)) {
23
+ throw new HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', 'Generated files must be a path-to-descriptor map.');
24
+ }
25
+
26
+ const claims = getHarmonyConfigPlugins(config).find(plugin => plugin.owner === owner);
27
+ const paths = [...new Set([...(claims?.files ?? []), ...Object.keys(files)])];
28
+ config = registerHarmonyConfigPlugin(config, owner, { ...claims, files: paths });
29
+
30
+ return withHarmonyDangerousMod(config, async (mod) => {
31
+ if (mod.modRequest.introspect) return mod;
32
+
33
+ const writes: Array<{ file: string; content: string | Uint8Array }> = [];
34
+ for (const [relative, input] of Object.entries(files)) {
35
+ if (!input || typeof input !== 'object'
36
+ || Object.hasOwn(input, 'source') === Object.hasOwn(input, 'content')) {
37
+ throw new HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `${relative} must define exactly one of source or content.`);
38
+ }
39
+
40
+ const file = await resolveHarmonyPath(mod.modRequest.platformProjectRoot, relative);
41
+ let content: string | Uint8Array;
42
+ if (Object.hasOwn(input, 'source')) {
43
+ if (typeof input.source !== 'string' || !input.source) {
44
+ throw new HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `${relative} source must be a non-empty path.`);
45
+ }
46
+
47
+ const source = path.resolve(mod.modRequest.projectRoot, input.source);
48
+ content = Uint8Array.from(await fs.promises.readFile(source));
49
+ } else {
50
+ if (typeof input.content !== 'string' && !(input.content instanceof Uint8Array)) {
51
+ throw new HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `${relative} content must be a string or Uint8Array.`);
52
+ }
53
+
54
+ content = input.content;
55
+ }
56
+
57
+ writes.push({ file, content });
58
+ }
59
+
60
+ for (const { file, content } of writes) {
61
+ await atomicWrite(file, content);
62
+ recordManagedFile(mod, file, owner);
63
+ }
64
+
65
+ return mod;
66
+ });
67
+ };
68
+
69
+ export const withRawfile: HarmonyConfigPlugin<HarmonyGeneratedFilesOptions> = (config, { owner, files }) => {
70
+ if (!files || typeof files !== 'object' || Array.isArray(files)) {
71
+ throw new HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', 'Rawfiles must be a path-to-descriptor map.');
72
+ }
73
+
74
+ const prefix = 'entry/src/main/resources/rawfile/';
75
+ const output: HarmonyFileMap = {};
76
+ for (const [relative, input] of Object.entries(files)) {
77
+ if (!relative || relative.includes('\\') || relative.split('/').some(part => !part || part === '.' || part === '..')) {
78
+ throw new HarmonyConfigPluginError('ERR_HARMONY_PATH_ESCAPE', `Invalid rawfile path: ${relative}`);
79
+ }
80
+
81
+ output[prefix + relative] = input;
82
+ }
83
+
84
+ return withHarmonyGeneratedFiles(config, { owner, files: output });
85
+ };
package/src/index.ts CHANGED
@@ -1,20 +1,26 @@
1
1
  export { defineExpoHarmonyConfig } from './config';
2
+ export { createRunOncePlugin, withPlugins, withRunOnce, withStaticPlugin } from './composition';
3
+ export type { HarmonyPluginReference, HarmonyStaticPlugin } from './composition';
4
+ export { compileHarmonyModsAsync } from './compiler';
5
+ export type { HarmonyCompileOptions } from './compiler';
6
+ export { createHarmonyFileMod, withHarmonyJsonFile } from './customFiles';
7
+ export type { HarmonyFileModOptions, HarmonyJsonFileOptions } from './customFiles';
2
8
  export type {
3
9
  ExpoConfigWithHarmony,
4
10
  ExpoHarmonyPlatform,
5
11
  ExpoKnownPlatform,
6
12
  HarmonyConfig,
7
13
  HarmonyDeviceType,
8
- HarmonyFont,
9
- HarmonyFontDefinition,
10
14
  HarmonyOrientation,
11
15
  HarmonyPermission,
12
16
  HarmonyPlatform,
13
17
  HarmonySkill,
18
+ HarmonySkillUri,
14
19
  } from './config';
15
20
  export { HarmonyConfigPluginError } from './errors';
21
+ export type { HarmonyConfigPluginErrorOptions } from './errors';
16
22
  export { atomicWrite, stableJson as stableHarmonyJson } from './files';
17
- export { HarmonySdkVersions, normalizeHarmonyConfig } from './normalizeConfig';
23
+ export { normalizeHarmonyConfig } from './normalizeConfig';
18
24
  export type { HarmonyExpoConfig, NormalizedHarmonyConfig } from './normalizeConfig';
19
25
  export {
20
26
  HarmonyModNames as HARMONY_MOD_NAMES,
@@ -22,7 +28,6 @@ export {
22
28
  withAppJson,
23
29
  withArkTSPackageProvider,
24
30
  withCMakeLists,
25
- withCngManifest,
26
31
  withColors,
27
32
  withCppPackageProvider,
28
33
  withEntryAbility,
@@ -38,7 +43,6 @@ export {
38
43
  withIndexPage,
39
44
  withMedia,
40
45
  withModuleJson,
41
- withNativeInputsStamp,
42
46
  withProfiles,
43
47
  withProjectBuildProfile,
44
48
  withReactNativeConfig,
@@ -51,15 +55,10 @@ export type {
51
55
  HarmonyJson,
52
56
  HarmonyMediaDescriptor,
53
57
  HarmonyMediaMap,
54
- HarmonyModAction,
55
58
  HarmonyModName,
56
59
  HarmonyResourceMap,
57
60
  } from './mods';
58
- export {
59
- getHarmonyConfigPlugins,
60
- normalizeHarmonyConfigPlugins,
61
- registerHarmonyConfigPlugin,
62
- } from './ownership';
61
+ export { registerHarmonyConfigPlugin } from './ownership';
63
62
  export type { HarmonyConfigPluginOwnership } from './ownership';
64
63
  export { HarmonyPaths } from './paths';
65
64
  export type {
@@ -69,3 +68,12 @@ export type {
69
68
  HarmonyProjectPaths,
70
69
  HarmonyResourcePaths,
71
70
  } from './paths';
71
+
72
+ export type { HarmonyConfigPlugin, HarmonyModAction, HarmonyModConfig } from './pluginTypes';
73
+ export * as HarmonyManifest from './manifest';
74
+ export * as HarmonyResources from './resources';
75
+ export type { HarmonyAbility, HarmonyExtensionAbility, HarmonyMetadata, HarmonyModule, HarmonyModuleJson } from './manifest';
76
+ export type { HarmonyResourceFile, HarmonyResourceItem } from './resources';
77
+ export { withHarmonyGeneratedFiles, withRawfile } from './generatedFiles';
78
+ export type { HarmonyFileDescriptor, HarmonyFileMap, HarmonyGeneratedFilesOptions } from './generatedFiles';
79
+ export { HarmonyPermissions, withHarmonyPermissions } from './permissions';
@@ -0,0 +1,3 @@
1
+ export { getHarmonyConfigPlugins, normalizeHarmonyConfigPlugins } from './ownership';
2
+ export { withCngManifest, withNativeInputsStamp, withPreparation, withProjectPatch } from './mods';
3
+ export { NativeInputsStampPath } from './paths';
@@ -0,0 +1,151 @@
1
+ import type { HarmonyPermission, HarmonySkill } from './config';
2
+ import { HarmonyConfigPluginError } from './errors';
3
+ import { stableJson } from './files';
4
+
5
+ export interface HarmonyMetadata extends Record<string, unknown> {
6
+ name: string;
7
+ value?: string;
8
+ resource?: string;
9
+ }
10
+
11
+ export interface HarmonyAbility extends Record<string, unknown> {
12
+ name: string;
13
+ srcEntry?: string;
14
+ exported?: boolean;
15
+ skills?: HarmonySkill[];
16
+ backgroundModes?: string[];
17
+ metadata?: HarmonyMetadata[];
18
+ }
19
+
20
+ export interface HarmonyExtensionAbility extends HarmonyAbility {
21
+ type: string;
22
+ }
23
+
24
+ export interface HarmonyModule extends Record<string, unknown> {
25
+ name?: string;
26
+ mainElement?: string;
27
+ abilities?: HarmonyAbility[];
28
+ extensionAbilities?: HarmonyExtensionAbility[];
29
+ metadata?: HarmonyMetadata[];
30
+ requestPermissions?: HarmonyPermission[];
31
+ querySchemes?: string[];
32
+ }
33
+
34
+ export interface HarmonyModuleJson extends Record<string, unknown> {
35
+ module?: HarmonyModule;
36
+ }
37
+
38
+ export function getModuleOrThrow(manifest: HarmonyModuleJson): HarmonyModule {
39
+ const module = manifest.module;
40
+ if (!module || typeof module !== 'object' || Array.isArray(module)) {
41
+ throw new HarmonyConfigPluginError(
42
+ 'ERR_HARMONY_MANIFEST_INVALID',
43
+ 'Harmony module.json5 must contain a module object.',
44
+ { operation: 'edit-manifest' }
45
+ );
46
+ }
47
+
48
+ return module;
49
+ }
50
+
51
+ export function getMainAbilityOrThrow(manifest: HarmonyModuleJson, name?: string): HarmonyAbility {
52
+ const module = getModuleOrThrow(manifest);
53
+ const abilities = module.abilities;
54
+ if (!Array.isArray(abilities)) {
55
+ throw new HarmonyConfigPluginError(
56
+ 'ERR_HARMONY_MANIFEST_INVALID',
57
+ 'Harmony module.json5 must declare an abilities array.',
58
+ { operation: 'edit-manifest' }
59
+ );
60
+ }
61
+
62
+ const selected = name ?? module.mainElement ?? abilities[0]?.name;
63
+ const matches = abilities.filter(ability => ability && typeof ability.name === 'string' && ability.name === selected);
64
+ if (matches.length !== 1) {
65
+ throw new HarmonyConfigPluginError(
66
+ 'ERR_HARMONY_MANIFEST_INVALID',
67
+ `Expected exactly one Harmony Ability '${selected ?? ''}', found ${matches.length}.`,
68
+ { operation: 'edit-manifest' }
69
+ );
70
+ }
71
+
72
+ return matches[0];
73
+ }
74
+
75
+ export function setMetadata(target: { metadata?: HarmonyMetadata[] }, item: HarmonyMetadata): void {
76
+ if (!item || typeof item.name !== 'string' || !item.name.trim()) {
77
+ throw new HarmonyConfigPluginError(
78
+ 'ERR_HARMONY_MANIFEST_INVALID',
79
+ 'Metadata must have a non-empty name.',
80
+ { operation: 'edit-manifest' }
81
+ );
82
+ }
83
+ if (target.metadata !== undefined && !Array.isArray(target.metadata)) {
84
+ throw new HarmonyConfigPluginError(
85
+ 'ERR_HARMONY_MANIFEST_INVALID',
86
+ 'metadata must be an array.',
87
+ { operation: 'edit-manifest' }
88
+ );
89
+ }
90
+
91
+ const items = target.metadata ?? [];
92
+ const existing = items.find(value => value?.name === item.name);
93
+ target.metadata = [...items.filter(value => value?.name !== item.name), { ...existing, ...item }];
94
+ }
95
+
96
+ export function removeMetadata(target: { metadata?: HarmonyMetadata[] }, name: string): void {
97
+ if (target.metadata !== undefined && !Array.isArray(target.metadata)) {
98
+ throw new HarmonyConfigPluginError(
99
+ 'ERR_HARMONY_MANIFEST_INVALID',
100
+ 'metadata must be an array.',
101
+ { operation: 'edit-manifest' }
102
+ );
103
+ }
104
+
105
+ if (target.metadata) target.metadata = target.metadata.filter(item => item?.name !== name);
106
+ }
107
+
108
+ export function ensureExtensionAbility(manifest: HarmonyModuleJson, ability: HarmonyExtensionAbility): void {
109
+ if (!ability || typeof ability.name !== 'string' || !ability.name.trim()
110
+ || typeof ability.type !== 'string' || !ability.type.trim()) {
111
+ throw new HarmonyConfigPluginError(
112
+ 'ERR_HARMONY_MANIFEST_INVALID',
113
+ 'An extension Ability must have a non-empty name and type.',
114
+ { operation: 'edit-manifest' }
115
+ );
116
+ }
117
+
118
+ const module = getModuleOrThrow(manifest);
119
+ if (module.extensionAbilities !== undefined && !Array.isArray(module.extensionAbilities)) {
120
+ throw new HarmonyConfigPluginError(
121
+ 'ERR_HARMONY_MANIFEST_INVALID',
122
+ 'module.extensionAbilities must be an array.',
123
+ { operation: 'edit-manifest' }
124
+ );
125
+ }
126
+
127
+ const abilities = module.extensionAbilities ?? [];
128
+ const matches = abilities.filter(value => value?.name === ability.name);
129
+ if (matches.length > 1) {
130
+ throw new HarmonyConfigPluginError(
131
+ 'ERR_HARMONY_MANIFEST_INVALID',
132
+ `Harmony module declares ${ability.name} more than once.`,
133
+ { operation: 'edit-manifest' }
134
+ );
135
+ }
136
+ if (matches.length === 1) {
137
+ for (const [key, value] of Object.entries(ability)) {
138
+ if (stableJson(matches[0][key]) !== stableJson(value)) {
139
+ throw new HarmonyConfigPluginError(
140
+ 'ERR_HARMONY_MANIFEST_INVALID',
141
+ `Harmony extension Ability '${ability.name}' has an incompatible ${key}.`,
142
+ { operation: 'edit-manifest' }
143
+ );
144
+ }
145
+ }
146
+
147
+ return;
148
+ }
149
+
150
+ module.extensionAbilities = [...abilities, structuredClone(ability)];
151
+ }