@expo-harmony/config-plugins 55.0.10-harmony.1 → 55.0.10-harmony.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +203 -11
- package/build/compiler.d.ts +7 -0
- package/build/compiler.js +12 -0
- package/build/composition.d.ts +24 -0
- package/build/composition.js +11 -0
- package/build/config.d.ts +51 -0
- package/build/config.js +6 -0
- package/build/customFiles.d.ts +15 -0
- package/build/customFiles.js +108 -0
- package/build/errors.d.ts +11 -0
- package/build/errors.js +14 -0
- package/build/files.d.ts +6 -0
- package/build/files.js +69 -0
- package/build/generatedFiles.d.ts +15 -0
- package/build/generatedFiles.js +70 -0
- package/build/index.d.ts +27 -0
- package/build/index.js +52 -6
- package/build/internal.d.ts +3 -0
- package/build/internal.js +13 -0
- package/build/manifest.d.ts +38 -0
- package/build/manifest.js +71 -0
- package/build/mods.d.ts +57 -0
- package/build/mods.js +428 -0
- package/build/nativeInputs.d.ts +14 -0
- package/build/nativeInputs.js +81 -0
- package/build/normalizeConfig.d.ts +35 -0
- package/build/normalizeConfig.js +270 -0
- package/build/ownership.d.ts +17 -0
- package/build/ownership.js +129 -0
- package/build/paths.d.ts +64 -0
- package/build/paths.js +129 -0
- package/build/permissions.d.ts +12 -0
- package/build/permissions.js +60 -0
- package/build/pluginTypes.d.ts +15 -0
- package/build/pluginTypes.js +2 -0
- package/build/resources.d.ts +13 -0
- package/build/resources.js +55 -0
- package/package.json +9 -9
- package/src/compiler.ts +21 -0
- package/src/composition.ts +45 -0
- package/src/config.ts +3 -19
- package/src/customFiles.ts +142 -0
- package/src/files.ts +3 -4
- package/src/generatedFiles.ts +85 -0
- package/src/index.ts +19 -11
- package/src/internal.ts +3 -0
- package/src/manifest.ts +151 -0
- package/src/mods.ts +170 -129
- package/src/nativeInputs.ts +0 -1
- package/src/normalizeConfig.ts +51 -59
- package/src/ownership.ts +25 -5
- package/src/paths.ts +38 -46
- package/src/permissions.ts +68 -0
- package/src/pluginTypes.ts +24 -0
- package/src/resources.ts +65 -0
package/build/mods.js
ADDED
|
@@ -0,0 +1,428 @@
|
|
|
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.withProjectPatch = exports.withPreparation = exports.withCngManifest = exports.withHarmonyAutolinking = exports.withCMakeLists = exports.withCppPackageProvider = exports.withArkTSPackageProvider = exports.withWorker = exports.withIndexPage = exports.withEntryAbility = exports.withProfiles = exports.withMedia = exports.withColors = exports.withStrings = exports.withModuleJson = exports.withEntryHvigor = exports.withEntryOhPackage = exports.withEntryBuildProfile = exports.withHvigorConfig = exports.withNativeInputsStamp = exports.withRootHvigor = exports.withRootOhPackage = exports.withProjectBuildProfile = exports.withAppJson = exports.withReactNativeConfig = exports.HarmonyModNames = void 0;
|
|
7
|
+
exports.recordManagedFile = recordManagedFile;
|
|
8
|
+
exports.withHarmonyBaseMods = withHarmonyBaseMods;
|
|
9
|
+
exports.withHarmonyMod = withHarmonyMod;
|
|
10
|
+
exports.registerMod = registerMod;
|
|
11
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
12
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
13
|
+
const config_plugins_1 = require("@expo/config-plugins");
|
|
14
|
+
const errors_1 = require("./errors");
|
|
15
|
+
const files_1 = require("./files");
|
|
16
|
+
const paths_1 = require("./paths");
|
|
17
|
+
const JsonMods = new Set([
|
|
18
|
+
'appJson',
|
|
19
|
+
'projectBuildProfile',
|
|
20
|
+
'rootOhPackage',
|
|
21
|
+
'hvigorConfig',
|
|
22
|
+
'entryBuildProfile',
|
|
23
|
+
'entryOhPackage',
|
|
24
|
+
'moduleJson',
|
|
25
|
+
'profiles',
|
|
26
|
+
]);
|
|
27
|
+
const TextMods = new Set([
|
|
28
|
+
'reactNativeConfig',
|
|
29
|
+
'rootHvigor',
|
|
30
|
+
'nativeInputsStamp',
|
|
31
|
+
'entryHvigor',
|
|
32
|
+
'entryAbility',
|
|
33
|
+
'indexPage',
|
|
34
|
+
'worker',
|
|
35
|
+
'arktsPackageProvider',
|
|
36
|
+
'cppPackageProvider',
|
|
37
|
+
'cmakeLists',
|
|
38
|
+
]);
|
|
39
|
+
const ResourceMods = new Set(['strings', 'colors', 'media']);
|
|
40
|
+
const VirtualMods = new Set(['dangerous', 'autolinking', 'manifest', 'prepare', 'patch']);
|
|
41
|
+
const ResourceModNames = ['strings', 'colors', 'media'];
|
|
42
|
+
const AllModNames = Object.freeze([
|
|
43
|
+
'dangerous',
|
|
44
|
+
'prepare',
|
|
45
|
+
'reactNativeConfig',
|
|
46
|
+
'appJson',
|
|
47
|
+
'projectBuildProfile',
|
|
48
|
+
'rootOhPackage',
|
|
49
|
+
'rootHvigor',
|
|
50
|
+
'nativeInputsStamp',
|
|
51
|
+
'hvigorConfig',
|
|
52
|
+
'entryBuildProfile',
|
|
53
|
+
'entryOhPackage',
|
|
54
|
+
'entryHvigor',
|
|
55
|
+
'moduleJson',
|
|
56
|
+
'strings',
|
|
57
|
+
'colors',
|
|
58
|
+
'media',
|
|
59
|
+
'profiles',
|
|
60
|
+
'entryAbility',
|
|
61
|
+
'indexPage',
|
|
62
|
+
'worker',
|
|
63
|
+
'arktsPackageProvider',
|
|
64
|
+
'cppPackageProvider',
|
|
65
|
+
'cmakeLists',
|
|
66
|
+
'autolinking',
|
|
67
|
+
'patch',
|
|
68
|
+
'manifest',
|
|
69
|
+
]);
|
|
70
|
+
exports.HarmonyModNames = Object.freeze(AllModNames.filter((name) => name !== 'manifest' && name !== 'nativeInputsStamp' && name !== 'prepare' && name !== 'patch'));
|
|
71
|
+
function recordManagedFile(config, file, owner) {
|
|
72
|
+
config._internal ??= {};
|
|
73
|
+
config._internal.harmonyManagedFiles ??= [];
|
|
74
|
+
const entry = (0, paths_1.toPosixRelative)(config.modRequest.projectRoot, file);
|
|
75
|
+
const files = config._internal.harmonyManagedFiles.filter((item) => item.path !== entry);
|
|
76
|
+
files.push({ path: entry, owner });
|
|
77
|
+
files.sort((left, right) => left.path.localeCompare(right.path, 'en'));
|
|
78
|
+
config._internal.harmonyManagedFiles = files;
|
|
79
|
+
}
|
|
80
|
+
function assertModResults(name, value, kind) {
|
|
81
|
+
const valid = kind === 'text'
|
|
82
|
+
? typeof value === 'string'
|
|
83
|
+
: value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
84
|
+
if (!valid) {
|
|
85
|
+
throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_MOD_RESULTS_INVALID', `harmony.${name} must return ${kind === 'text' ? 'a string' : 'an object'} modResults.`, { operation: `harmony.${name}.write` });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function withFileProvider(config, name) {
|
|
89
|
+
return (0, config_plugins_1.withBaseMod)(config, {
|
|
90
|
+
platform: 'harmony',
|
|
91
|
+
mod: name,
|
|
92
|
+
isProvider: true,
|
|
93
|
+
isIntrospective: JsonMods.has(name),
|
|
94
|
+
saveToInternal: JsonMods.has(name),
|
|
95
|
+
async action(value) {
|
|
96
|
+
const { nextMod: next, ...request } = value.modRequest;
|
|
97
|
+
const project = name in paths_1.ProjectPaths;
|
|
98
|
+
const root = project ? request.projectRoot : request.platformProjectRoot;
|
|
99
|
+
const file = project
|
|
100
|
+
? await (0, paths_1.resolveProjectPath)(root, name)
|
|
101
|
+
: await (0, paths_1.resolveHarmonyPath)(root, paths_1.ManagedPaths[name]);
|
|
102
|
+
const exists = !request.ignoreExistingNativeFiles && node_fs_1.default.existsSync(file);
|
|
103
|
+
const template = value._internal?.harmonyTemplateDirectory;
|
|
104
|
+
const input = !exists && !project && typeof template === 'string'
|
|
105
|
+
? await (0, paths_1.resolveHarmonyPath)(template, paths_1.ManagedPaths[name])
|
|
106
|
+
: file;
|
|
107
|
+
const data = request.ignoreExistingNativeFiles && input === file
|
|
108
|
+
? (JsonMods.has(name) ? {} : '')
|
|
109
|
+
: JsonMods.has(name)
|
|
110
|
+
? await (0, files_1.readJson5)(input, {}, name)
|
|
111
|
+
: await (0, files_1.readText)(file);
|
|
112
|
+
const context = { ...request, modFile: file, modFileExists: exists };
|
|
113
|
+
const result = await next({ ...value, modRequest: context, modResults: data });
|
|
114
|
+
assertModResults(name, result.modResults, JsonMods.has(name) ? 'json' : 'text');
|
|
115
|
+
if (!result.modRequest.introspect) {
|
|
116
|
+
if (typeof result.modResults === 'string') {
|
|
117
|
+
await (0, files_1.atomicWrite)(file, result.modResults.replace(/\r\n?/g, '\n').replace(/\n?$/, '\n'));
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
await (0, files_1.writeJson5)(file, result.modResults);
|
|
121
|
+
}
|
|
122
|
+
recordManagedFile(result, file, name);
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
async function readResourceMap(root, entries, name, template, ignore = false) {
|
|
129
|
+
const resources = {};
|
|
130
|
+
for (const [scope, relative] of Object.entries(entries)) {
|
|
131
|
+
const file = await (0, paths_1.resolveHarmonyPath)(root, relative);
|
|
132
|
+
const fallback = ignore || !node_fs_1.default.existsSync(file);
|
|
133
|
+
const input = fallback && template ? await (0, paths_1.resolveHarmonyPath)(template, relative) : file;
|
|
134
|
+
resources[scope] = ignore && input === file ? {} : await (0, files_1.readJson5)(input, {}, name);
|
|
135
|
+
}
|
|
136
|
+
return resources;
|
|
137
|
+
}
|
|
138
|
+
async function readMediaMap(root) {
|
|
139
|
+
const media = {};
|
|
140
|
+
for (const [scope, relative] of Object.entries(paths_1.ResourcePaths.media)) {
|
|
141
|
+
const directory = await (0, paths_1.resolveHarmonyPath)(root, relative);
|
|
142
|
+
media[scope] = {};
|
|
143
|
+
let names = [];
|
|
144
|
+
try {
|
|
145
|
+
names = await node_fs_1.default.promises.readdir(directory);
|
|
146
|
+
}
|
|
147
|
+
catch (cause) {
|
|
148
|
+
if (cause.code !== 'ENOENT') {
|
|
149
|
+
throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_RESOURCE_READ', `Cannot read Harmony media directory ${directory}: ${cause.message}`, { cause, file: directory, operation: 'harmony.media.read' });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
for (const name of names.sort()) {
|
|
153
|
+
const file = node_path_1.default.join(directory, name);
|
|
154
|
+
if ((await node_fs_1.default.promises.lstat(file)).isFile())
|
|
155
|
+
media[scope][name] = { source: file };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return media;
|
|
159
|
+
}
|
|
160
|
+
async function writeMediaMap(root, media, config, previous = {}) {
|
|
161
|
+
const writes = [];
|
|
162
|
+
for (const [scope, files] of Object.entries(media || {})) {
|
|
163
|
+
if (!(scope in paths_1.ResourcePaths.media))
|
|
164
|
+
continue;
|
|
165
|
+
if (!files || typeof files !== 'object' || Array.isArray(files)) {
|
|
166
|
+
throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `Harmony media scope ${scope} must be an object.`, { operation: 'harmony.media.write' });
|
|
167
|
+
}
|
|
168
|
+
const directory = await (0, paths_1.resolveHarmonyPath)(root, paths_1.ResourcePaths.media[scope]);
|
|
169
|
+
for (const [name, descriptor] of Object.entries(files)) {
|
|
170
|
+
if (!/^[A-Za-z0-9_.-]+$/.test(name) || name.includes('..')) {
|
|
171
|
+
throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `Invalid Harmony media file name: ${name}`, { operation: 'harmony.media.write' });
|
|
172
|
+
}
|
|
173
|
+
if (!descriptor || typeof descriptor !== 'object' || Array.isArray(descriptor)) {
|
|
174
|
+
throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `Harmony media ${scope}/${name} must use a descriptor object.`, { operation: 'harmony.media.write' });
|
|
175
|
+
}
|
|
176
|
+
const item = descriptor;
|
|
177
|
+
if (Object.hasOwn(item, 'source') === Object.hasOwn(item, 'content')) {
|
|
178
|
+
throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `Harmony media ${scope}/${name} must define exactly one of source or content.`, { operation: 'harmony.media.write' });
|
|
179
|
+
}
|
|
180
|
+
if (item.replaceBase !== undefined && typeof item.replaceBase !== 'boolean') {
|
|
181
|
+
throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `Harmony media ${scope}/${name} replaceBase must be a boolean.`, { operation: 'harmony.media.write' });
|
|
182
|
+
}
|
|
183
|
+
let content;
|
|
184
|
+
if (Object.hasOwn(item, 'source')) {
|
|
185
|
+
if (typeof item.source !== 'string' || item.source.length === 0) {
|
|
186
|
+
throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `Harmony media ${scope}/${name} source must be a non-empty path.`, { operation: 'harmony.media.write' });
|
|
187
|
+
}
|
|
188
|
+
content = Uint8Array.from(await node_fs_1.default.promises.readFile(item.source));
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
if (typeof item.content !== 'string' && !(item.content instanceof Uint8Array)) {
|
|
192
|
+
throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `Harmony media ${scope}/${name} content must be a string or Uint8Array.`, { operation: 'harmony.media.write' });
|
|
193
|
+
}
|
|
194
|
+
content = item.content;
|
|
195
|
+
}
|
|
196
|
+
writes.push({
|
|
197
|
+
content,
|
|
198
|
+
directory,
|
|
199
|
+
name,
|
|
200
|
+
replaceBase: item.replaceBase === true,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
for (const [scope, names] of Object.entries(previous)) {
|
|
205
|
+
if (!(scope in paths_1.ResourcePaths.media))
|
|
206
|
+
continue;
|
|
207
|
+
const directory = await (0, paths_1.resolveHarmonyPath)(root, paths_1.ResourcePaths.media[scope]);
|
|
208
|
+
const next = media?.[scope];
|
|
209
|
+
for (const name of names) {
|
|
210
|
+
if (!next || typeof next !== 'object' || !Object.hasOwn(next, name)) {
|
|
211
|
+
const file = await (0, paths_1.resolveHarmonyPath)(directory, name);
|
|
212
|
+
await node_fs_1.default.promises.rm(file, { force: true });
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
for (const { content, directory, name, replaceBase } of writes) {
|
|
217
|
+
await node_fs_1.default.promises.mkdir(directory, { recursive: true });
|
|
218
|
+
if (replaceBase) {
|
|
219
|
+
const base = node_path_1.default.parse(name).name;
|
|
220
|
+
for (const entry of await node_fs_1.default.promises.readdir(directory)) {
|
|
221
|
+
if (node_path_1.default.parse(entry).name === base && entry !== name) {
|
|
222
|
+
await node_fs_1.default.promises.rm(node_path_1.default.join(directory, entry), { force: true });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const file = await (0, paths_1.resolveHarmonyPath)(directory, name);
|
|
227
|
+
await (0, files_1.atomicWrite)(file, content);
|
|
228
|
+
recordManagedFile(config, file, 'media');
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function withResourceProvider(config, name) {
|
|
232
|
+
const introspective = name !== 'media';
|
|
233
|
+
return (0, config_plugins_1.withBaseMod)(config, {
|
|
234
|
+
platform: 'harmony',
|
|
235
|
+
mod: name,
|
|
236
|
+
isProvider: true,
|
|
237
|
+
isIntrospective: introspective,
|
|
238
|
+
saveToInternal: introspective,
|
|
239
|
+
async action(value) {
|
|
240
|
+
const { nextMod: next, ...request } = value.modRequest;
|
|
241
|
+
const root = request.platformProjectRoot;
|
|
242
|
+
const data = name === 'media'
|
|
243
|
+
? (request.ignoreExistingNativeFiles
|
|
244
|
+
? Object.fromEntries(Object.keys(paths_1.ResourcePaths.media).map(scope => [scope, {}]))
|
|
245
|
+
: await readMediaMap(root))
|
|
246
|
+
: await readResourceMap(root, paths_1.ResourcePaths[name], name, value._internal?.harmonyTemplateDirectory, request.ignoreExistingNativeFiles);
|
|
247
|
+
const previous = name === 'media'
|
|
248
|
+
? Object.fromEntries(Object.entries(data).map(([scope, files]) => [scope, Object.keys(files)]))
|
|
249
|
+
: {};
|
|
250
|
+
const result = await next({ ...value, modRequest: request, modResults: data });
|
|
251
|
+
assertModResults(name, result.modResults, 'resource');
|
|
252
|
+
if (!result.modRequest.introspect) {
|
|
253
|
+
if (name === 'media') {
|
|
254
|
+
await writeMediaMap(root, result.modResults, result, previous);
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
const paths = paths_1.ResourcePaths[name];
|
|
258
|
+
for (const [scope, resource] of Object.entries(result.modResults)) {
|
|
259
|
+
if (!(scope in paths))
|
|
260
|
+
continue;
|
|
261
|
+
if (!resource || typeof resource !== 'object' || Array.isArray(resource)) {
|
|
262
|
+
throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_CONFIG_INVALID', `Harmony ${name} scope ${scope} must be an object.`, { operation: `harmony.${name}.write` });
|
|
263
|
+
}
|
|
264
|
+
const file = await (0, paths_1.resolveHarmonyPath)(root, paths[scope]);
|
|
265
|
+
// Harmony's resource compiler rejects an empty JSON root.
|
|
266
|
+
if (Object.keys(resource).length === 0) {
|
|
267
|
+
await node_fs_1.default.promises.rm(file, { force: true });
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
await (0, files_1.writeJson5)(file, resource);
|
|
271
|
+
recordManagedFile(result, file, name);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return result;
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
function withVirtualProvider(config, name) {
|
|
280
|
+
return (0, config_plugins_1.withBaseMod)(config, {
|
|
281
|
+
platform: 'harmony',
|
|
282
|
+
mod: name,
|
|
283
|
+
isProvider: true,
|
|
284
|
+
isIntrospective: name === 'prepare',
|
|
285
|
+
async action(value) {
|
|
286
|
+
const { nextMod: next, ...request } = value.modRequest;
|
|
287
|
+
return next({ ...value, modRequest: request, modResults: null });
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
function withProvider(config, name) {
|
|
292
|
+
const actions = [];
|
|
293
|
+
config = (0, config_plugins_1.withMod)(config, {
|
|
294
|
+
platform: 'harmony',
|
|
295
|
+
mod: name,
|
|
296
|
+
async action(value) {
|
|
297
|
+
let result = value;
|
|
298
|
+
// Late mods must follow Expo's last-registered-first order.
|
|
299
|
+
for (let index = actions.length - 1; index >= 0; index -= 1) {
|
|
300
|
+
result = await actions[index](result);
|
|
301
|
+
}
|
|
302
|
+
return result;
|
|
303
|
+
},
|
|
304
|
+
});
|
|
305
|
+
if (JsonMods.has(name) || TextMods.has(name)) {
|
|
306
|
+
config = withFileProvider(config, name);
|
|
307
|
+
}
|
|
308
|
+
else if (ResourceMods.has(name)) {
|
|
309
|
+
config = withResourceProvider(config, name);
|
|
310
|
+
}
|
|
311
|
+
else if (VirtualMods.has(name)) {
|
|
312
|
+
config = withVirtualProvider(config, name);
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_MOD_NOT_REGISTERED', `Unknown Harmony mod: ${name}`, { operation: 'register-base-mods' });
|
|
316
|
+
}
|
|
317
|
+
const mods = config.mods;
|
|
318
|
+
const provider = mods?.harmony?.[name];
|
|
319
|
+
if (provider?.isProvider)
|
|
320
|
+
provider.expoHarmonyLateActions = actions;
|
|
321
|
+
return config;
|
|
322
|
+
}
|
|
323
|
+
function withHarmonyBaseMods(config) {
|
|
324
|
+
for (const name of AllModNames) {
|
|
325
|
+
const mods = config.mods;
|
|
326
|
+
if (!mods?.harmony?.[name]?.isProvider)
|
|
327
|
+
config = withProvider(config, name);
|
|
328
|
+
}
|
|
329
|
+
const mods = config.mods;
|
|
330
|
+
const table = mods.harmony;
|
|
331
|
+
mods.harmony = Object.fromEntries([
|
|
332
|
+
...AllModNames.filter(name => table[name] && name !== 'manifest' && name !== 'patch').map(name => [name, table[name]]),
|
|
333
|
+
...Object.entries(table).filter(([name]) => !AllModNames.includes(name)),
|
|
334
|
+
['patch', table.patch],
|
|
335
|
+
['manifest', table.manifest],
|
|
336
|
+
]);
|
|
337
|
+
return config;
|
|
338
|
+
}
|
|
339
|
+
function withHarmonyMod(config, tuple) {
|
|
340
|
+
if (!Array.isArray(tuple) || tuple.length !== 2) {
|
|
341
|
+
throw new TypeError('withHarmonyMod expects [modName, action].');
|
|
342
|
+
}
|
|
343
|
+
const [name, action] = tuple;
|
|
344
|
+
if (!exports.HarmonyModNames.includes(name) || typeof action !== 'function') {
|
|
345
|
+
throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_MOD_NOT_REGISTERED', `Cannot register Harmony mod: ${name}`, { operation: 'register-mod' });
|
|
346
|
+
}
|
|
347
|
+
return registerMod(config, name, action);
|
|
348
|
+
}
|
|
349
|
+
function registerMod(config, name, action) {
|
|
350
|
+
const mods = config.mods;
|
|
351
|
+
const provider = mods?.harmony?.[name];
|
|
352
|
+
if (provider?.isProvider && provider.expoHarmonyLateActions) {
|
|
353
|
+
// Build an ordinary Expo interceptor on an isolated mod table, preserving
|
|
354
|
+
// its registration stack, error behavior and execution trace for late mods.
|
|
355
|
+
const carrier = (0, config_plugins_1.withMod)({ ...config, mods: {} }, {
|
|
356
|
+
platform: 'harmony',
|
|
357
|
+
mod: name,
|
|
358
|
+
action: action,
|
|
359
|
+
});
|
|
360
|
+
const interceptor = carrier.mods.harmony[name];
|
|
361
|
+
provider.expoHarmonyLateActions.push(interceptor);
|
|
362
|
+
return config;
|
|
363
|
+
}
|
|
364
|
+
return (0, config_plugins_1.withMod)(config, {
|
|
365
|
+
platform: 'harmony',
|
|
366
|
+
mod: name,
|
|
367
|
+
action: action,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
const withReactNativeConfig = (config, action) => withHarmonyMod(config, ['reactNativeConfig', action]);
|
|
371
|
+
exports.withReactNativeConfig = withReactNativeConfig;
|
|
372
|
+
const withAppJson = (config, action) => withHarmonyMod(config, ['appJson', action]);
|
|
373
|
+
exports.withAppJson = withAppJson;
|
|
374
|
+
const withProjectBuildProfile = (config, action) => withHarmonyMod(config, ['projectBuildProfile', action]);
|
|
375
|
+
exports.withProjectBuildProfile = withProjectBuildProfile;
|
|
376
|
+
const withRootOhPackage = (config, action) => withHarmonyMod(config, ['rootOhPackage', action]);
|
|
377
|
+
exports.withRootOhPackage = withRootOhPackage;
|
|
378
|
+
const withRootHvigor = (config, action) => withHarmonyMod(config, ['rootHvigor', action]);
|
|
379
|
+
exports.withRootHvigor = withRootHvigor;
|
|
380
|
+
const withNativeInputsStamp = (config, action) => registerMod(config, 'nativeInputsStamp', action);
|
|
381
|
+
exports.withNativeInputsStamp = withNativeInputsStamp;
|
|
382
|
+
const withHvigorConfig = (config, action) => withHarmonyMod(config, ['hvigorConfig', action]);
|
|
383
|
+
exports.withHvigorConfig = withHvigorConfig;
|
|
384
|
+
const withEntryBuildProfile = (config, action) => withHarmonyMod(config, ['entryBuildProfile', action]);
|
|
385
|
+
exports.withEntryBuildProfile = withEntryBuildProfile;
|
|
386
|
+
const withEntryOhPackage = (config, action) => withHarmonyMod(config, ['entryOhPackage', action]);
|
|
387
|
+
exports.withEntryOhPackage = withEntryOhPackage;
|
|
388
|
+
const withEntryHvigor = (config, action) => withHarmonyMod(config, ['entryHvigor', action]);
|
|
389
|
+
exports.withEntryHvigor = withEntryHvigor;
|
|
390
|
+
const withModuleJson = (config, action) => withHarmonyMod(config, ['moduleJson', action]);
|
|
391
|
+
exports.withModuleJson = withModuleJson;
|
|
392
|
+
const withStrings = (config, action) => withHarmonyMod(config, ['strings', action]);
|
|
393
|
+
exports.withStrings = withStrings;
|
|
394
|
+
const withColors = (config, action) => withHarmonyMod(config, ['colors', action]);
|
|
395
|
+
exports.withColors = withColors;
|
|
396
|
+
const withMedia = (config, action) => withHarmonyMod(config, ['media', action]);
|
|
397
|
+
exports.withMedia = withMedia;
|
|
398
|
+
const withProfiles = (config, action) => withHarmonyMod(config, ['profiles', action]);
|
|
399
|
+
exports.withProfiles = withProfiles;
|
|
400
|
+
const withEntryAbility = (config, action) => withHarmonyMod(config, ['entryAbility', action]);
|
|
401
|
+
exports.withEntryAbility = withEntryAbility;
|
|
402
|
+
const withIndexPage = (config, action) => withHarmonyMod(config, ['indexPage', action]);
|
|
403
|
+
exports.withIndexPage = withIndexPage;
|
|
404
|
+
const withWorker = (config, action) => withHarmonyMod(config, ['worker', action]);
|
|
405
|
+
exports.withWorker = withWorker;
|
|
406
|
+
const withArkTSPackageProvider = (config, action) => withHarmonyMod(config, ['arktsPackageProvider', action]);
|
|
407
|
+
exports.withArkTSPackageProvider = withArkTSPackageProvider;
|
|
408
|
+
const withCppPackageProvider = (config, action) => withHarmonyMod(config, ['cppPackageProvider', action]);
|
|
409
|
+
exports.withCppPackageProvider = withCppPackageProvider;
|
|
410
|
+
const withCMakeLists = (config, action) => withHarmonyMod(config, ['cmakeLists', action]);
|
|
411
|
+
exports.withCMakeLists = withCMakeLists;
|
|
412
|
+
const withHarmonyAutolinking = (config, action) => withHarmonyMod(config, ['autolinking', action]);
|
|
413
|
+
exports.withHarmonyAutolinking = withHarmonyAutolinking;
|
|
414
|
+
const withCngManifest = (config, action) => registerMod(config, 'manifest', action);
|
|
415
|
+
exports.withCngManifest = withCngManifest;
|
|
416
|
+
const withPreparation = (config, action) => registerMod(config, 'prepare', action);
|
|
417
|
+
exports.withPreparation = withPreparation;
|
|
418
|
+
const withProjectPatch = (config, action) => registerMod(config, 'patch', action);
|
|
419
|
+
exports.withProjectPatch = withProjectPatch;
|
|
420
|
+
const withHarmonyDangerousMod = (config, action) => withHarmonyMod(config, ['dangerous', action]);
|
|
421
|
+
exports.withHarmonyDangerousMod = withHarmonyDangerousMod;
|
|
422
|
+
const withHarmonyResources = (config, action) => {
|
|
423
|
+
for (const name of ResourceModNames) {
|
|
424
|
+
withHarmonyMod(config, [name, action]);
|
|
425
|
+
}
|
|
426
|
+
return config;
|
|
427
|
+
};
|
|
428
|
+
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, };
|
|
@@ -0,0 +1,81 @@
|
|
|
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.HarmonyNativeInputsFingerprintVersion = void 0;
|
|
7
|
+
exports.fingerprintHarmonyNativeInputsSync = fingerprintHarmonyNativeInputsSync;
|
|
8
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
9
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const json5_1 = __importDefault(require("json5"));
|
|
12
|
+
const HarmonyNativeInputsFingerprintVersion = 1;
|
|
13
|
+
exports.HarmonyNativeInputsFingerprintVersion = HarmonyNativeInputsFingerprintVersion;
|
|
14
|
+
function readOptionalFile(file) {
|
|
15
|
+
try {
|
|
16
|
+
return Uint8Array.from(node_fs_1.default.readFileSync(file));
|
|
17
|
+
}
|
|
18
|
+
catch (cause) {
|
|
19
|
+
if (cause && typeof cause === 'object' && 'code' in cause && cause.code === 'ENOENT')
|
|
20
|
+
return null;
|
|
21
|
+
throw new Error(cause instanceof Error ? cause.message : String(cause), { cause });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function resolveLocalHar(root, specifier) {
|
|
25
|
+
if (typeof specifier !== 'string' || !/^\.\.?[\\/]/u.test(specifier))
|
|
26
|
+
return null;
|
|
27
|
+
if (!specifier.toLowerCase().endsWith('.har'))
|
|
28
|
+
return null;
|
|
29
|
+
return node_path_1.default.resolve(root, specifier);
|
|
30
|
+
}
|
|
31
|
+
function findLocalHars(root, manifest) {
|
|
32
|
+
const specifiers = [
|
|
33
|
+
...Object.values(manifest.dependencies ?? {}),
|
|
34
|
+
...Object.values(manifest.overrides ?? {}),
|
|
35
|
+
];
|
|
36
|
+
return [...new Set(specifiers.map(specifier => resolveLocalHar(root, specifier)))]
|
|
37
|
+
.filter((value) => value !== null)
|
|
38
|
+
.sort();
|
|
39
|
+
}
|
|
40
|
+
function hashFile(hash, file) {
|
|
41
|
+
const handle = node_fs_1.default.openSync(file, 'r');
|
|
42
|
+
const buffer = new Uint8Array(1024 * 1024);
|
|
43
|
+
try {
|
|
44
|
+
while (true) {
|
|
45
|
+
const size = node_fs_1.default.readSync(handle, buffer, 0, buffer.length, null);
|
|
46
|
+
if (size === 0)
|
|
47
|
+
break;
|
|
48
|
+
hash.update(Uint8Array.from(buffer.subarray(0, size)));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
node_fs_1.default.closeSync(handle);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function fingerprintHarmonyNativeInputsSync(options) {
|
|
56
|
+
const data = node_fs_1.default.readFileSync(options.manifest);
|
|
57
|
+
const lock = readOptionalFile(options.lockfile);
|
|
58
|
+
const manifest = json5_1.default.parse(data.toString('utf8'));
|
|
59
|
+
const root = node_path_1.default.dirname(options.manifest);
|
|
60
|
+
const files = findLocalHars(root, manifest);
|
|
61
|
+
const hash = node_crypto_1.default.createHash('sha256');
|
|
62
|
+
hash.update(`expo-harmony-native-dependencies-v${HarmonyNativeInputsFingerprintVersion}\0`);
|
|
63
|
+
hash.update(Uint8Array.from(data));
|
|
64
|
+
if (lock)
|
|
65
|
+
hash.update(lock);
|
|
66
|
+
for (const file of files) {
|
|
67
|
+
if (!node_fs_1.default.statSync(file).isFile()) {
|
|
68
|
+
throw new Error(`Cannot fingerprint Harmony native artifact: ${file}`);
|
|
69
|
+
}
|
|
70
|
+
const relative = node_path_1.default.relative(options.projectRoot, file).split(node_path_1.default.sep).join('/');
|
|
71
|
+
hash.update('\0artifact\0');
|
|
72
|
+
hash.update(relative);
|
|
73
|
+
hash.update('\0');
|
|
74
|
+
hashFile(hash, file);
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
artifactCount: files.length,
|
|
78
|
+
fingerprint: hash.digest('hex'),
|
|
79
|
+
fingerprintVersion: HarmonyNativeInputsFingerprintVersion,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ExpoConfigWithHarmony, HarmonyConfig, HarmonyPermission } from './config';
|
|
2
|
+
type HarmonyExpoConfig = ExpoConfigWithHarmony;
|
|
3
|
+
interface NormalizedHarmonyConfig {
|
|
4
|
+
abiFilters: string[];
|
|
5
|
+
abilityName: string;
|
|
6
|
+
backgroundColor: string;
|
|
7
|
+
bundleName: string;
|
|
8
|
+
compatibleSdkVersionString: string;
|
|
9
|
+
deviceTypes: NonNullable<HarmonyConfig['deviceTypes']>;
|
|
10
|
+
icon?: string;
|
|
11
|
+
label: string;
|
|
12
|
+
moduleName: string;
|
|
13
|
+
nativeOrientation: Exclude<NonNullable<HarmonyConfig['orientation']>, 'default'> | 'unspecified';
|
|
14
|
+
permissions: NonNullable<HarmonyConfig['permissions']>;
|
|
15
|
+
productName: string;
|
|
16
|
+
querySchemes: string[];
|
|
17
|
+
signingConfigFile?: string;
|
|
18
|
+
skills: NonNullable<HarmonyConfig['skills']>;
|
|
19
|
+
targetApiVersion: number;
|
|
20
|
+
targetSdkVersionString: string;
|
|
21
|
+
vendor: string;
|
|
22
|
+
versionCode: number;
|
|
23
|
+
versionName: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function normalizePermission(permission: HarmonyPermission): {
|
|
26
|
+
name: string;
|
|
27
|
+
reason?: string;
|
|
28
|
+
usedScene?: {
|
|
29
|
+
abilities?: string[];
|
|
30
|
+
when: "always" | "inuse";
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
declare function normalizeHarmonyConfig(config: HarmonyExpoConfig): NormalizedHarmonyConfig;
|
|
34
|
+
export { normalizeHarmonyConfig };
|
|
35
|
+
export type { HarmonyExpoConfig, NormalizedHarmonyConfig };
|