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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,64 @@
1
+ import type { ExpoConfig } from '@expo/config-types';
2
+ export type HarmonyPlatform = 'harmony';
3
+ export type ExpoKnownPlatform = NonNullable<ExpoConfig['platforms']>[number];
4
+ export type ExpoHarmonyPlatform = ExpoKnownPlatform | HarmonyPlatform;
5
+ export type HarmonyDeviceType = 'phone' | 'tablet' | '2in1';
6
+ export type HarmonyOrientation = 'default' | 'portrait' | 'landscape' | 'portrait_inverted' | 'landscape_inverted' | 'auto_rotation';
7
+ export interface HarmonyPermission {
8
+ name: string;
9
+ reason?: string;
10
+ usedScene?: {
11
+ abilities?: string[];
12
+ when?: 'inuse' | 'always';
13
+ };
14
+ }
15
+ export interface HarmonySkill {
16
+ entities?: string[];
17
+ actions?: string[];
18
+ uris?: Array<Record<string, string>>;
19
+ }
20
+ export interface HarmonyFontDefinition {
21
+ path: string;
22
+ weight?: number;
23
+ style?: 'normal' | 'italic';
24
+ }
25
+ export type HarmonyFont = string | {
26
+ fontFamily: string;
27
+ fontDefinitions: HarmonyFontDefinition[];
28
+ };
29
+ export interface HarmonyConfig {
30
+ bundleName: string;
31
+ moduleName?: string;
32
+ abilityName?: string;
33
+ productName?: string;
34
+ vendor?: string;
35
+ versionCode?: number;
36
+ versionName?: string;
37
+ targetApiVersion?: number;
38
+ /** Harmony SDK label written to build-profile.json5, e.g. 6.0.0(20). Its API must match targetApiVersion. */
39
+ targetSdkVersion?: number | string;
40
+ /** API number, or a full Harmony SDK label ending in the API number. */
41
+ compatibleSdkVersion?: number | string;
42
+ deviceTypes?: HarmonyDeviceType[];
43
+ permissions?: HarmonyPermission[];
44
+ skills?: HarmonySkill[];
45
+ /** URL schemes the app may inspect with Linking.canOpenURL. http, https, and expo.scheme are added automatically. */
46
+ querySchemes?: string[];
47
+ icon?: string;
48
+ label?: string;
49
+ backgroundColor?: string;
50
+ orientation?: HarmonyOrientation;
51
+ userInterfaceStyle?: 'light' | 'dark' | 'automatic';
52
+ jsEngine?: 'hermes';
53
+ abiFilters?: string[];
54
+ signingConfigFile?: string;
55
+ /** Fonts bundled when the `@expo-harmony/expo-font` config plugin is registered. */
56
+ fonts?: HarmonyFont[];
57
+ }
58
+ export type ExpoConfigWithHarmony = Omit<ExpoConfig, 'platforms'> & {
59
+ platforms?: ExpoHarmonyPlatform[];
60
+ harmony?: HarmonyConfig;
61
+ };
62
+ export declare function defineExpoHarmonyConfig<T extends {
63
+ expo: ExpoConfigWithHarmony;
64
+ } | ExpoConfigWithHarmony>(config: T): T;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defineExpoHarmonyConfig = defineExpoHarmonyConfig;
4
+ function defineExpoHarmonyConfig(config) {
5
+ return config;
6
+ }
@@ -0,0 +1,11 @@
1
+ export interface HarmonyConfigPluginErrorOptions {
2
+ cause?: unknown;
3
+ file?: string;
4
+ operation?: string;
5
+ }
6
+ export declare class HarmonyConfigPluginError extends Error {
7
+ readonly code: string;
8
+ readonly operation: string;
9
+ readonly file?: string;
10
+ constructor(code: string, message: string, options?: HarmonyConfigPluginErrorOptions);
11
+ }
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HarmonyConfigPluginError = void 0;
4
+ class HarmonyConfigPluginError extends Error {
5
+ constructor(code, message, options = {}) {
6
+ super(message, options.cause === undefined ? undefined : { cause: options.cause });
7
+ this.name = 'HarmonyConfigPluginError';
8
+ this.code = code;
9
+ this.operation = options.operation || 'config-plugin';
10
+ if (options.file)
11
+ this.file = options.file;
12
+ }
13
+ }
14
+ exports.HarmonyConfigPluginError = HarmonyConfigPluginError;
@@ -0,0 +1,6 @@
1
+ export declare function stableJson(value: unknown): string;
2
+ export declare function readJson5<T>(file: string, fallback: T, modName: string): Promise<T>;
3
+ export declare function readText(file: string, fallback?: string): Promise<string>;
4
+ export declare function atomicWrite(file: string, content: string | Uint8Array): Promise<void>;
5
+ export declare function writeJson5(file: string, value: unknown): Promise<void>;
6
+ export declare function sha256File(file: string): Promise<string>;
package/build/files.js ADDED
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.stableJson = stableJson;
7
+ exports.readJson5 = readJson5;
8
+ exports.readText = readText;
9
+ exports.atomicWrite = atomicWrite;
10
+ exports.writeJson5 = writeJson5;
11
+ exports.sha256File = sha256File;
12
+ const node_crypto_1 = __importDefault(require("node:crypto"));
13
+ const node_fs_1 = __importDefault(require("node:fs"));
14
+ const node_path_1 = __importDefault(require("node:path"));
15
+ const json5_1 = __importDefault(require("json5"));
16
+ const errors_1 = require("./errors");
17
+ function sortValue(value) {
18
+ if (Array.isArray(value))
19
+ return value.map(sortValue);
20
+ if (value && typeof value === 'object' && !Buffer.isBuffer(value)) {
21
+ const record = value;
22
+ const entries = Object.keys(record).sort().map(key => [key, sortValue(record[key])]);
23
+ return Object.fromEntries(entries);
24
+ }
25
+ return value;
26
+ }
27
+ function stableJson(value) {
28
+ return `${JSON.stringify(sortValue(value), null, 2)}\n`;
29
+ }
30
+ async function readJson5(file, fallback, modName) {
31
+ try {
32
+ const source = await node_fs_1.default.promises.readFile(file, 'utf8');
33
+ return json5_1.default.parse(source);
34
+ }
35
+ catch (cause) {
36
+ if (cause.code === 'ENOENT')
37
+ return structuredClone(fallback);
38
+ throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_JSON5_INVALID', `Cannot parse ${file} for harmony.${modName}: ${cause.message}`, { cause, file, operation: `harmony.${modName}.read` });
39
+ }
40
+ }
41
+ async function readText(file, fallback = '') {
42
+ try {
43
+ return await node_fs_1.default.promises.readFile(file, 'utf8');
44
+ }
45
+ catch (cause) {
46
+ if (cause.code === 'ENOENT')
47
+ return fallback;
48
+ throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_TEXT_INVALID', `Cannot read ${file}: ${cause.message}`, { cause, file, operation: 'harmony.mod.read' });
49
+ }
50
+ }
51
+ async function atomicWrite(file, content) {
52
+ const directory = node_path_1.default.dirname(file);
53
+ const temporary = node_path_1.default.join(directory, `.${node_path_1.default.basename(file)}.${process.pid}.${node_crypto_1.default.randomBytes(6).toString('hex')}.tmp`);
54
+ await node_fs_1.default.promises.mkdir(directory, { recursive: true });
55
+ try {
56
+ await node_fs_1.default.promises.writeFile(temporary, content);
57
+ await node_fs_1.default.promises.rename(temporary, file);
58
+ }
59
+ finally {
60
+ await node_fs_1.default.promises.rm(temporary, { force: true }).catch(() => { });
61
+ }
62
+ }
63
+ async function writeJson5(file, value) {
64
+ await atomicWrite(file, stableJson(value));
65
+ }
66
+ async function sha256File(file) {
67
+ const data = Uint8Array.from(await node_fs_1.default.promises.readFile(file));
68
+ return node_crypto_1.default.createHash('sha256').update(data).digest('hex');
69
+ }
@@ -0,0 +1,12 @@
1
+ export { defineExpoHarmonyConfig } from './config';
2
+ export type { ExpoConfigWithHarmony, ExpoHarmonyPlatform, ExpoKnownPlatform, HarmonyConfig, HarmonyDeviceType, HarmonyFont, HarmonyFontDefinition, HarmonyOrientation, HarmonyPermission, HarmonyPlatform, HarmonySkill, } from './config';
3
+ export { HarmonyConfigPluginError } from './errors';
4
+ export { atomicWrite, stableJson as stableHarmonyJson } from './files';
5
+ export { HarmonySdkVersions, normalizeHarmonyConfig } from './normalizeConfig';
6
+ export type { HarmonyExpoConfig, NormalizedHarmonyConfig } from './normalizeConfig';
7
+ export { HarmonyModNames as HARMONY_MOD_NAMES, recordManagedFile, withAppJson, withArkTSPackageProvider, withCMakeLists, withCngManifest, withColors, withCppPackageProvider, withEntryAbility, withEntryBuildProfile, withEntryHvigor, withEntryOhPackage, withHarmonyAutolinking, withHarmonyBaseMods, withHarmonyDangerousMod, withHarmonyMod, withHarmonyResources, withHvigorConfig, withIndexPage, withMedia, withModuleJson, withNativeInputsStamp, withProfiles, withProjectBuildProfile, withReactNativeConfig, withRootHvigor, withRootOhPackage, withStrings, withWorker, } from './mods';
8
+ export type { HarmonyJson, HarmonyMediaDescriptor, HarmonyMediaMap, HarmonyModAction, HarmonyModName, HarmonyResourceMap, } from './mods';
9
+ export { getHarmonyConfigPlugins, normalizeHarmonyConfigPlugins, registerHarmonyConfigPlugin, } from './ownership';
10
+ export type { HarmonyConfigPluginOwnership } from './ownership';
11
+ export { HarmonyPaths } from './paths';
12
+ export type { HarmonyManagedPaths, HarmonyPathsApi, HarmonyProjectPathCandidates, HarmonyProjectPaths, HarmonyResourcePaths, } from './paths';
@@ -0,0 +1,52 @@
1
+ import { type ConfigPlugin, type ExportedConfigWithProps } from '@expo/config-plugins';
2
+ import type { ExpoConfig } from '@expo/config-types';
3
+ export type HarmonyModName = 'dangerous' | 'reactNativeConfig' | 'appJson' | 'projectBuildProfile' | 'rootOhPackage' | 'rootHvigor' | 'nativeInputsStamp' | 'hvigorConfig' | 'entryBuildProfile' | 'entryOhPackage' | 'entryHvigor' | 'moduleJson' | 'strings' | 'colors' | 'media' | 'profiles' | 'entryAbility' | 'indexPage' | 'worker' | 'arktsPackageProvider' | 'cppPackageProvider' | 'cmakeLists' | 'autolinking' | 'manifest';
4
+ export type HarmonyModAction<T = unknown> = (config: ExportedConfigWithProps<T>) => ExportedConfigWithProps<T> | Promise<ExportedConfigWithProps<T>>;
5
+ export type HarmonyJson = Record<string, unknown>;
6
+ export type HarmonyResourceMap = Record<string, HarmonyJson>;
7
+ export type HarmonyMediaDescriptor = {
8
+ source: string;
9
+ content?: never;
10
+ replaceBase?: boolean;
11
+ } | {
12
+ source?: never;
13
+ content: string | Uint8Array;
14
+ replaceBase?: boolean;
15
+ };
16
+ export type HarmonyMediaMap = Record<string, Record<string, HarmonyMediaDescriptor>>;
17
+ interface ManagedConfig {
18
+ _internal?: ExpoConfig['_internal'];
19
+ modRequest: {
20
+ projectRoot: string;
21
+ };
22
+ }
23
+ export declare const HarmonyModNames: readonly HarmonyModName[];
24
+ export declare function recordManagedFile(config: ManagedConfig, file: string, owner: string): void;
25
+ export declare function withHarmonyBaseMods(config: ExpoConfig): ExpoConfig;
26
+ export declare function withHarmonyMod<T = unknown>(config: ExpoConfig, tuple: [HarmonyModName, HarmonyModAction<T>]): ExpoConfig;
27
+ export declare const withReactNativeConfig: ConfigPlugin<HarmonyModAction<string>>;
28
+ export declare const withAppJson: ConfigPlugin<HarmonyModAction<HarmonyJson>>;
29
+ export declare const withProjectBuildProfile: ConfigPlugin<HarmonyModAction<HarmonyJson>>;
30
+ export declare const withRootOhPackage: ConfigPlugin<HarmonyModAction<HarmonyJson>>;
31
+ export declare const withRootHvigor: ConfigPlugin<HarmonyModAction<string>>;
32
+ export declare const withNativeInputsStamp: ConfigPlugin<HarmonyModAction<string>>;
33
+ export declare const withHvigorConfig: ConfigPlugin<HarmonyModAction<HarmonyJson>>;
34
+ export declare const withEntryBuildProfile: ConfigPlugin<HarmonyModAction<HarmonyJson>>;
35
+ export declare const withEntryOhPackage: ConfigPlugin<HarmonyModAction<HarmonyJson>>;
36
+ export declare const withEntryHvigor: ConfigPlugin<HarmonyModAction<string>>;
37
+ export declare const withModuleJson: ConfigPlugin<HarmonyModAction<HarmonyJson>>;
38
+ export declare const withStrings: ConfigPlugin<HarmonyModAction<HarmonyResourceMap>>;
39
+ export declare const withColors: ConfigPlugin<HarmonyModAction<HarmonyResourceMap>>;
40
+ export declare const withMedia: ConfigPlugin<HarmonyModAction<HarmonyMediaMap>>;
41
+ export declare const withProfiles: ConfigPlugin<HarmonyModAction<HarmonyJson>>;
42
+ export declare const withEntryAbility: ConfigPlugin<HarmonyModAction<string>>;
43
+ export declare const withIndexPage: ConfigPlugin<HarmonyModAction<string>>;
44
+ export declare const withWorker: ConfigPlugin<HarmonyModAction<string>>;
45
+ export declare const withArkTSPackageProvider: ConfigPlugin<HarmonyModAction<string>>;
46
+ export declare const withCppPackageProvider: ConfigPlugin<HarmonyModAction<string>>;
47
+ export declare const withCMakeLists: ConfigPlugin<HarmonyModAction<string>>;
48
+ export declare const withHarmonyAutolinking: ConfigPlugin<HarmonyModAction<null>>;
49
+ export declare const withCngManifest: ConfigPlugin<HarmonyModAction<null>>;
50
+ export declare const withHarmonyDangerousMod: ConfigPlugin<HarmonyModAction<null>>;
51
+ export declare const withHarmonyResources: ConfigPlugin<HarmonyModAction<unknown>>;
52
+ export {};
package/build/mods.js ADDED
@@ -0,0 +1,400 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.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;
7
+ exports.recordManagedFile = recordManagedFile;
8
+ exports.withHarmonyBaseMods = withHarmonyBaseMods;
9
+ exports.withHarmonyMod = withHarmonyMod;
10
+ const node_fs_1 = __importDefault(require("node:fs"));
11
+ const node_path_1 = __importDefault(require("node:path"));
12
+ const config_plugins_1 = require("@expo/config-plugins");
13
+ const errors_1 = require("./errors");
14
+ const files_1 = require("./files");
15
+ const paths_1 = require("./paths");
16
+ const JsonMods = new Set([
17
+ 'appJson',
18
+ 'projectBuildProfile',
19
+ 'rootOhPackage',
20
+ 'hvigorConfig',
21
+ 'entryBuildProfile',
22
+ 'entryOhPackage',
23
+ 'moduleJson',
24
+ 'profiles',
25
+ ]);
26
+ const TextMods = new Set([
27
+ 'reactNativeConfig',
28
+ 'rootHvigor',
29
+ 'nativeInputsStamp',
30
+ 'entryHvigor',
31
+ 'entryAbility',
32
+ 'indexPage',
33
+ 'worker',
34
+ 'arktsPackageProvider',
35
+ 'cppPackageProvider',
36
+ 'cmakeLists',
37
+ ]);
38
+ const ResourceMods = new Set(['strings', 'colors', 'media']);
39
+ const VirtualMods = new Set(['dangerous', 'autolinking', 'manifest']);
40
+ const ResourceModNames = ['strings', 'colors', 'media'];
41
+ exports.HarmonyModNames = Object.freeze([
42
+ 'dangerous',
43
+ 'reactNativeConfig',
44
+ 'appJson',
45
+ 'projectBuildProfile',
46
+ 'rootOhPackage',
47
+ 'rootHvigor',
48
+ 'nativeInputsStamp',
49
+ 'hvigorConfig',
50
+ 'entryBuildProfile',
51
+ 'entryOhPackage',
52
+ 'entryHvigor',
53
+ 'moduleJson',
54
+ 'strings',
55
+ 'colors',
56
+ 'media',
57
+ 'profiles',
58
+ 'entryAbility',
59
+ 'indexPage',
60
+ 'worker',
61
+ 'arktsPackageProvider',
62
+ 'cppPackageProvider',
63
+ 'cmakeLists',
64
+ 'autolinking',
65
+ 'manifest',
66
+ ]);
67
+ function recordManagedFile(config, file, owner) {
68
+ config._internal ??= {};
69
+ config._internal.harmonyManagedFiles ??= [];
70
+ const entry = (0, paths_1.toPosixRelative)(config.modRequest.projectRoot, file);
71
+ const files = config._internal.harmonyManagedFiles.filter((item) => item.path !== entry);
72
+ files.push({ path: entry, owner });
73
+ files.sort((left, right) => left.path.localeCompare(right.path, 'en'));
74
+ config._internal.harmonyManagedFiles = files;
75
+ }
76
+ function assertModResults(modName, value, kind) {
77
+ const valid = kind === 'text'
78
+ ? typeof value === 'string'
79
+ : value !== null && typeof value === 'object' && !Array.isArray(value);
80
+ 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` });
82
+ }
83
+ }
84
+ function withFileProvider(config, modName) {
85
+ return (0, config_plugins_1.withBaseMod)(config, {
86
+ platform: 'harmony',
87
+ mod: modName,
88
+ isProvider: true,
89
+ isIntrospective: JsonMods.has(modName),
90
+ saveToInternal: JsonMods.has(modName),
91
+ 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');
105
+ if (!result.modRequest.introspect) {
106
+ if (typeof result.modResults === 'string') {
107
+ await (0, files_1.atomicWrite)(file, result.modResults.replace(/\r\n?/g, '\n').replace(/\n?$/, '\n'));
108
+ }
109
+ else {
110
+ await (0, files_1.writeJson5)(file, result.modResults);
111
+ }
112
+ recordManagedFile(result, file, modName);
113
+ }
114
+ return result;
115
+ },
116
+ });
117
+ }
118
+ async function readResourceMap(root, entries, modName) {
119
+ const resources = {};
120
+ for (const [scope, relative] of Object.entries(entries)) {
121
+ const file = await (0, paths_1.resolveHarmonyPath)(root, relative);
122
+ resources[scope] = await (0, files_1.readJson5)(file, {}, modName);
123
+ }
124
+ return resources;
125
+ }
126
+ async function readMediaMap(root) {
127
+ const media = {};
128
+ for (const [scope, relative] of Object.entries(paths_1.ResourcePaths.media)) {
129
+ const directory = await (0, paths_1.resolveHarmonyPath)(root, relative);
130
+ media[scope] = {};
131
+ let names = [];
132
+ try {
133
+ names = await node_fs_1.default.promises.readdir(directory);
134
+ }
135
+ catch (cause) {
136
+ if (cause.code !== 'ENOENT') {
137
+ throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_RESOURCE_READ', `Cannot read Harmony media directory ${directory}: ${cause.message}`, { cause, file: directory, operation: 'harmony.media.read' });
138
+ }
139
+ }
140
+ for (const name of names.sort()) {
141
+ const file = node_path_1.default.join(directory, name);
142
+ if ((await node_fs_1.default.promises.lstat(file)).isFile())
143
+ media[scope][name] = { source: file };
144
+ }
145
+ }
146
+ return media;
147
+ }
148
+ async function writeMediaMap(root, media, config, previous = {}) {
149
+ const writes = [];
150
+ for (const [scope, files] of Object.entries(media || {})) {
151
+ if (!(scope in paths_1.ResourcePaths.media))
152
+ continue;
153
+ if (!files || typeof files !== 'object' || Array.isArray(files)) {
154
+ throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `Harmony media scope ${scope} must be an object.`, { operation: 'harmony.media.write' });
155
+ }
156
+ const directory = await (0, paths_1.resolveHarmonyPath)(root, paths_1.ResourcePaths.media[scope]);
157
+ for (const [name, descriptor] of Object.entries(files)) {
158
+ if (!/^[A-Za-z0-9_.-]+$/.test(name) || name.includes('..')) {
159
+ throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `Invalid Harmony media file name: ${name}`, { operation: 'harmony.media.write' });
160
+ }
161
+ if (!descriptor || typeof descriptor !== 'object' || Array.isArray(descriptor)) {
162
+ throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `Harmony media ${scope}/${name} must use a descriptor object.`, { operation: 'harmony.media.write' });
163
+ }
164
+ const item = descriptor;
165
+ const hasSource = Object.hasOwn(item, 'source');
166
+ const hasContent = Object.hasOwn(item, 'content');
167
+ if (hasSource === hasContent) {
168
+ 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
+ }
170
+ if (item.replaceBase !== undefined && typeof item.replaceBase !== 'boolean') {
171
+ throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `Harmony media ${scope}/${name} replaceBase must be a boolean.`, { operation: 'harmony.media.write' });
172
+ }
173
+ let content;
174
+ if (hasSource) {
175
+ if (typeof item.source !== 'string' || item.source.length === 0) {
176
+ 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
+ }
178
+ content = Uint8Array.from(await node_fs_1.default.promises.readFile(item.source));
179
+ }
180
+ else {
181
+ if (typeof item.content !== 'string' && !(item.content instanceof Uint8Array)) {
182
+ throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `Harmony media ${scope}/${name} content must be a string or Uint8Array.`, { operation: 'harmony.media.write' });
183
+ }
184
+ content = item.content;
185
+ }
186
+ writes.push({
187
+ content,
188
+ directory,
189
+ name,
190
+ replaceBase: item.replaceBase === true,
191
+ });
192
+ }
193
+ }
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
+ for (const [scope, names] of Object.entries(previous)) {
198
+ if (!(scope in paths_1.ResourcePaths.media))
199
+ continue;
200
+ const directory = await (0, paths_1.resolveHarmonyPath)(root, paths_1.ResourcePaths.media[scope]);
201
+ const next = media?.[scope];
202
+ for (const name of names) {
203
+ if (!next || typeof next !== 'object' || !Object.hasOwn(next, name)) {
204
+ const file = await (0, paths_1.resolveHarmonyPath)(directory, name);
205
+ await node_fs_1.default.promises.rm(file, { force: true });
206
+ }
207
+ }
208
+ }
209
+ for (const { content, directory, name, replaceBase } of writes) {
210
+ await node_fs_1.default.promises.mkdir(directory, { recursive: true });
211
+ if (replaceBase) {
212
+ 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 });
216
+ }
217
+ }
218
+ }
219
+ const file = await (0, paths_1.resolveHarmonyPath)(directory, name);
220
+ await (0, files_1.atomicWrite)(file, content);
221
+ recordManagedFile(config, file, 'media');
222
+ }
223
+ }
224
+ function withResourceProvider(config, modName) {
225
+ const isIntrospective = modName !== 'media';
226
+ return (0, config_plugins_1.withBaseMod)(config, {
227
+ platform: 'harmony',
228
+ mod: modName,
229
+ isProvider: true,
230
+ isIntrospective,
231
+ saveToInternal: isIntrospective,
232
+ 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'
239
+ ? Object.fromEntries(Object.entries(data).map(([scope, files]) => [scope, Object.keys(files)]))
240
+ : {};
241
+ const result = await nextMod({ ...value, modRequest, modResults: data });
242
+ assertModResults(modName, result.modResults, 'resource');
243
+ if (!result.modRequest.introspect) {
244
+ if (modName === 'media') {
245
+ await writeMediaMap(root, result.modResults, result, previous);
246
+ }
247
+ else {
248
+ const paths = paths_1.ResourcePaths[modName];
249
+ for (const [scope, resource] of Object.entries(result.modResults)) {
250
+ if (!(scope in paths))
251
+ 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
+ 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` });
257
+ }
258
+ const file = await (0, paths_1.resolveHarmonyPath)(root, paths[scope]);
259
+ if (Object.keys(resource).length === 0) {
260
+ await node_fs_1.default.promises.rm(file, { force: true });
261
+ continue;
262
+ }
263
+ await (0, files_1.writeJson5)(file, resource);
264
+ recordManagedFile(result, file, modName);
265
+ }
266
+ }
267
+ }
268
+ return result;
269
+ },
270
+ });
271
+ }
272
+ function withVirtualProvider(config, modName) {
273
+ return (0, config_plugins_1.withBaseMod)(config, {
274
+ platform: 'harmony',
275
+ mod: modName,
276
+ isProvider: true,
277
+ async action(value) {
278
+ const { nextMod, ...modRequest } = value.modRequest;
279
+ return nextMod({ ...value, modRequest, modResults: null });
280
+ },
281
+ });
282
+ }
283
+ function withProvider(config, modName) {
284
+ const actions = [];
285
+ config = (0, config_plugins_1.withMod)(config, {
286
+ platform: 'harmony',
287
+ mod: modName,
288
+ async action(value) {
289
+ 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.
292
+ for (let index = actions.length - 1; index >= 0; index -= 1) {
293
+ result = await actions[index](result);
294
+ }
295
+ return result;
296
+ },
297
+ });
298
+ if (JsonMods.has(modName) || TextMods.has(modName)) {
299
+ config = withFileProvider(config, modName);
300
+ }
301
+ else if (ResourceMods.has(modName)) {
302
+ config = withResourceProvider(config, modName);
303
+ }
304
+ else if (VirtualMods.has(modName)) {
305
+ config = withVirtualProvider(config, modName);
306
+ }
307
+ else {
308
+ throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_MOD_NOT_REGISTERED', `Unknown Harmony mod: ${modName}`, { operation: 'register-base-mods' });
309
+ }
310
+ const mods = config.mods;
311
+ const provider = mods?.harmony?.[modName];
312
+ if (provider?.isProvider)
313
+ provider.expoHarmonyLateActions = actions;
314
+ return config;
315
+ }
316
+ function withHarmonyBaseMods(config) {
317
+ for (const modName of exports.HarmonyModNames) {
318
+ const mods = config.mods;
319
+ if (!mods?.harmony?.[modName]?.isProvider)
320
+ config = withProvider(config, modName);
321
+ }
322
+ const mods = config.mods;
323
+ const table = mods.harmony;
324
+ 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)),
327
+ ]);
328
+ return config;
329
+ }
330
+ function withHarmonyMod(config, tuple) {
331
+ if (!Array.isArray(tuple) || tuple.length !== 2) {
332
+ throw new TypeError('withHarmonyMod expects [modName, action].');
333
+ }
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' });
337
+ }
338
+ const mods = config.mods;
339
+ const provider = mods?.harmony?.[modName];
340
+ if (provider?.isProvider && provider.expoHarmonyLateActions) {
341
+ provider.expoHarmonyLateActions.push(action);
342
+ return config;
343
+ }
344
+ return (0, config_plugins_1.withMod)(config, { platform: 'harmony', mod: modName, action });
345
+ }
346
+ const withReactNativeConfig = (config, action) => withHarmonyMod(config, ['reactNativeConfig', action]);
347
+ exports.withReactNativeConfig = withReactNativeConfig;
348
+ const withAppJson = (config, action) => withHarmonyMod(config, ['appJson', action]);
349
+ exports.withAppJson = withAppJson;
350
+ const withProjectBuildProfile = (config, action) => withHarmonyMod(config, ['projectBuildProfile', action]);
351
+ exports.withProjectBuildProfile = withProjectBuildProfile;
352
+ const withRootOhPackage = (config, action) => withHarmonyMod(config, ['rootOhPackage', action]);
353
+ exports.withRootOhPackage = withRootOhPackage;
354
+ const withRootHvigor = (config, action) => withHarmonyMod(config, ['rootHvigor', action]);
355
+ exports.withRootHvigor = withRootHvigor;
356
+ const withNativeInputsStamp = (config, action) => withHarmonyMod(config, ['nativeInputsStamp', action]);
357
+ exports.withNativeInputsStamp = withNativeInputsStamp;
358
+ const withHvigorConfig = (config, action) => withHarmonyMod(config, ['hvigorConfig', action]);
359
+ exports.withHvigorConfig = withHvigorConfig;
360
+ const withEntryBuildProfile = (config, action) => withHarmonyMod(config, ['entryBuildProfile', action]);
361
+ exports.withEntryBuildProfile = withEntryBuildProfile;
362
+ const withEntryOhPackage = (config, action) => withHarmonyMod(config, ['entryOhPackage', action]);
363
+ exports.withEntryOhPackage = withEntryOhPackage;
364
+ const withEntryHvigor = (config, action) => withHarmonyMod(config, ['entryHvigor', action]);
365
+ exports.withEntryHvigor = withEntryHvigor;
366
+ const withModuleJson = (config, action) => withHarmonyMod(config, ['moduleJson', action]);
367
+ exports.withModuleJson = withModuleJson;
368
+ const withStrings = (config, action) => withHarmonyMod(config, ['strings', action]);
369
+ exports.withStrings = withStrings;
370
+ const withColors = (config, action) => withHarmonyMod(config, ['colors', action]);
371
+ exports.withColors = withColors;
372
+ const withMedia = (config, action) => withHarmonyMod(config, ['media', action]);
373
+ exports.withMedia = withMedia;
374
+ const withProfiles = (config, action) => withHarmonyMod(config, ['profiles', action]);
375
+ exports.withProfiles = withProfiles;
376
+ const withEntryAbility = (config, action) => withHarmonyMod(config, ['entryAbility', action]);
377
+ exports.withEntryAbility = withEntryAbility;
378
+ const withIndexPage = (config, action) => withHarmonyMod(config, ['indexPage', action]);
379
+ exports.withIndexPage = withIndexPage;
380
+ const withWorker = (config, action) => withHarmonyMod(config, ['worker', action]);
381
+ exports.withWorker = withWorker;
382
+ const withArkTSPackageProvider = (config, action) => withHarmonyMod(config, ['arktsPackageProvider', action]);
383
+ exports.withArkTSPackageProvider = withArkTSPackageProvider;
384
+ const withCppPackageProvider = (config, action) => withHarmonyMod(config, ['cppPackageProvider', action]);
385
+ exports.withCppPackageProvider = withCppPackageProvider;
386
+ const withCMakeLists = (config, action) => withHarmonyMod(config, ['cmakeLists', action]);
387
+ exports.withCMakeLists = withCMakeLists;
388
+ const withHarmonyAutolinking = (config, action) => withHarmonyMod(config, ['autolinking', action]);
389
+ exports.withHarmonyAutolinking = withHarmonyAutolinking;
390
+ const withCngManifest = (config, action) => withHarmonyMod(config, ['manifest', action]);
391
+ exports.withCngManifest = withCngManifest;
392
+ const withHarmonyDangerousMod = (config, action) => withHarmonyMod(config, ['dangerous', action]);
393
+ exports.withHarmonyDangerousMod = withHarmonyDangerousMod;
394
+ const withHarmonyResources = (config, action) => {
395
+ for (const modName of ResourceModNames) {
396
+ withHarmonyMod(config, [modName, action]);
397
+ }
398
+ return config;
399
+ };
400
+ exports.withHarmonyResources = withHarmonyResources;
@@ -0,0 +1,14 @@
1
+ declare const HarmonyNativeInputsFingerprintVersion = 1;
2
+ interface HarmonyNativeInputsFingerprint {
3
+ artifactCount: number;
4
+ fingerprint: string;
5
+ fingerprintVersion: typeof HarmonyNativeInputsFingerprintVersion;
6
+ }
7
+ interface HarmonyNativeInputsFingerprintOptions {
8
+ lockfile: string;
9
+ manifest: string;
10
+ projectRoot: string;
11
+ }
12
+ declare function fingerprintHarmonyNativeInputsSync(options: HarmonyNativeInputsFingerprintOptions): HarmonyNativeInputsFingerprint;
13
+ export { HarmonyNativeInputsFingerprintVersion, fingerprintHarmonyNativeInputsSync, };
14
+ export type { HarmonyNativeInputsFingerprint, HarmonyNativeInputsFingerprintOptions, };