@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.
Files changed (55) 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 +51 -0
  7. package/build/config.js +6 -0
  8. package/build/customFiles.d.ts +15 -0
  9. package/build/customFiles.js +108 -0
  10. package/build/errors.d.ts +11 -0
  11. package/build/errors.js +14 -0
  12. package/build/files.d.ts +6 -0
  13. package/build/files.js +69 -0
  14. package/build/generatedFiles.d.ts +15 -0
  15. package/build/generatedFiles.js +70 -0
  16. package/build/index.d.ts +27 -0
  17. package/build/index.js +52 -6
  18. package/build/internal.d.ts +3 -0
  19. package/build/internal.js +13 -0
  20. package/build/manifest.d.ts +38 -0
  21. package/build/manifest.js +71 -0
  22. package/build/mods.d.ts +57 -0
  23. package/build/mods.js +428 -0
  24. package/build/nativeInputs.d.ts +14 -0
  25. package/build/nativeInputs.js +81 -0
  26. package/build/normalizeConfig.d.ts +35 -0
  27. package/build/normalizeConfig.js +270 -0
  28. package/build/ownership.d.ts +17 -0
  29. package/build/ownership.js +129 -0
  30. package/build/paths.d.ts +64 -0
  31. package/build/paths.js +129 -0
  32. package/build/permissions.d.ts +12 -0
  33. package/build/permissions.js +60 -0
  34. package/build/pluginTypes.d.ts +15 -0
  35. package/build/pluginTypes.js +2 -0
  36. package/build/resources.d.ts +13 -0
  37. package/build/resources.js +55 -0
  38. package/package.json +9 -9
  39. package/src/compiler.ts +21 -0
  40. package/src/composition.ts +45 -0
  41. package/src/config.ts +3 -19
  42. package/src/customFiles.ts +142 -0
  43. package/src/files.ts +3 -4
  44. package/src/generatedFiles.ts +85 -0
  45. package/src/index.ts +19 -11
  46. package/src/internal.ts +3 -0
  47. package/src/manifest.ts +151 -0
  48. package/src/mods.ts +170 -129
  49. package/src/nativeInputs.ts +0 -1
  50. package/src/normalizeConfig.ts +51 -59
  51. package/src/ownership.ts +25 -5
  52. package/src/paths.ts +38 -46
  53. package/src/permissions.ts +68 -0
  54. package/src/pluginTypes.ts +24 -0
  55. package/src/resources.ts +65 -0
@@ -0,0 +1,270 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizePermission = normalizePermission;
4
+ exports.normalizeHarmonyConfig = normalizeHarmonyConfig;
5
+ const errors_1 = require("./errors");
6
+ const resources_1 = require("./resources");
7
+ const BundlePattern = /^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*){2,}$/;
8
+ const IdentifierPattern = /^[A-Za-z][A-Za-z0-9_]*$/;
9
+ const ColorPattern = /^(#[0-9A-Fa-f]{6}|#[0-9A-Fa-f]{8})$/;
10
+ const Orientations = new Set([
11
+ 'default',
12
+ 'portrait',
13
+ 'landscape',
14
+ 'portrait_inverted',
15
+ 'landscape_inverted',
16
+ 'auto_rotation',
17
+ ]);
18
+ const DeviceTypes = new Set(['phone', 'tablet', '2in1']);
19
+ const MinimumApi = 13;
20
+ const TargetApi = 24;
21
+ const SdkVersions = new Map([
22
+ [13, '5.0.1(13)'],
23
+ [14, '5.0.2(14)'],
24
+ [20, '6.0.0(20)'],
25
+ [21, '6.0.1(21)'],
26
+ [23, '6.1.0(23)'],
27
+ [24, '6.1.1(24)'],
28
+ ]);
29
+ class HarmonyConfigError extends errors_1.HarmonyConfigPluginError {
30
+ constructor(message) {
31
+ super('ERR_HARMONY_CONFIG_INVALID', message, { operation: 'normalize-config' });
32
+ this.name = 'HarmonyConfigError';
33
+ }
34
+ }
35
+ function readPositiveInteger(value, field, fallback) {
36
+ const result = value === undefined ? fallback : value;
37
+ if (typeof result !== 'number' || !Number.isSafeInteger(result) || result <= 0) {
38
+ throw new HarmonyConfigError(`harmony.${field} must be a positive integer.`);
39
+ }
40
+ return result;
41
+ }
42
+ function readString(value, field, fallback) {
43
+ const result = value === undefined ? fallback : value;
44
+ if (typeof result !== 'string' || !result.trim()) {
45
+ throw new HarmonyConfigError(`${field} must be a non-empty string.`);
46
+ }
47
+ if (/\0|[\r\n]/.test(result)) {
48
+ throw new HarmonyConfigError(`${field} must not contain control characters.`);
49
+ }
50
+ return result.trim();
51
+ }
52
+ function normalizeColor(value, field, fallback) {
53
+ const color = value === undefined ? fallback : value;
54
+ if (typeof color !== 'string' || !ColorPattern.test(color)) {
55
+ throw new HarmonyConfigError(`${field} must be #RRGGBB or #RRGGBBAA.`);
56
+ }
57
+ return (0, resources_1.toArgb)(color);
58
+ }
59
+ function parseSdkApi(value, field) {
60
+ if (value === undefined)
61
+ return null;
62
+ if (typeof value === 'number' && Number.isSafeInteger(value) && value > 0)
63
+ return value;
64
+ if (typeof value !== 'string') {
65
+ throw new HarmonyConfigError(`harmony.${field} must be a positive API number or an SDK label.`);
66
+ }
67
+ const match = /\((\d+)\)$/u.exec(value.trim());
68
+ const api = Number(match?.[1]);
69
+ if (!match || !Number.isSafeInteger(api) || api <= 0) {
70
+ throw new HarmonyConfigError(`harmony.${field} must end with its API level in parentheses, for example "6.0.0(20)".`);
71
+ }
72
+ return api;
73
+ }
74
+ function resolveSdkVersion(api, field, value) {
75
+ if (value !== undefined) {
76
+ if (typeof value === 'number' && Number.isSafeInteger(value) && value > 0) {
77
+ if (value !== api) {
78
+ throw new HarmonyConfigError(`harmony.${field} is API ${value}, but the configured API level is ${api}.`);
79
+ }
80
+ }
81
+ else {
82
+ const version = readString(value, `harmony.${field}`);
83
+ const parsed = parseSdkApi(version, field);
84
+ if (parsed !== api) {
85
+ throw new HarmonyConfigError(`harmony.${field} describes API ${parsed}, but the configured API level is ${api}.`);
86
+ }
87
+ return version;
88
+ }
89
+ }
90
+ const version = SdkVersions.get(api);
91
+ if (!version) {
92
+ throw new HarmonyConfigError(`Harmony API ${api} has no built-in SDK label. Set harmony.${field} explicitly so new SDK releases do not require a package update.`);
93
+ }
94
+ return version;
95
+ }
96
+ function normalizeStringArray(value, field, allowed) {
97
+ if (!Array.isArray(value) || value.length === 0) {
98
+ throw new HarmonyConfigError(`${field} must be a non-empty array.`);
99
+ }
100
+ const result = [];
101
+ for (const item of value) {
102
+ if (typeof item !== 'string' || !item || (allowed && !allowed.has(item))) {
103
+ throw new HarmonyConfigError(`Invalid ${field} entry: ${item}`);
104
+ }
105
+ if (!result.includes(item))
106
+ result.push(item);
107
+ }
108
+ return result;
109
+ }
110
+ function normalizePermission(permission) {
111
+ if (!permission || typeof permission !== 'object') {
112
+ throw new HarmonyConfigError('harmony.permissions entries must be objects.');
113
+ }
114
+ const name = readString(permission.name, 'harmony.permissions[].name');
115
+ if (!/^ohos\.permission\.[A-Z0-9_]+$/.test(name)) {
116
+ throw new HarmonyConfigError(`Invalid Harmony permission: ${name}`);
117
+ }
118
+ const result = { name };
119
+ if (permission.reason !== undefined) {
120
+ result.reason = readString(permission.reason, 'harmony.permissions[].reason');
121
+ }
122
+ if (permission.usedScene !== undefined) {
123
+ if (!permission.usedScene || typeof permission.usedScene !== 'object') {
124
+ throw new HarmonyConfigError('permission.usedScene must be an object.');
125
+ }
126
+ const when = permission.usedScene.when === undefined
127
+ ? 'inuse'
128
+ : permission.usedScene.when;
129
+ if (!['inuse', 'always'].includes(when)) {
130
+ throw new HarmonyConfigError('permission.usedScene.when must be inuse or always.');
131
+ }
132
+ const abilities = permission.usedScene.abilities === undefined
133
+ ? undefined
134
+ : normalizeStringArray(permission.usedScene.abilities, 'permission.usedScene.abilities');
135
+ result.usedScene = { ...(abilities ? { abilities } : {}), when };
136
+ }
137
+ return result;
138
+ }
139
+ function normalizeSkill(skill) {
140
+ if (!skill || typeof skill !== 'object') {
141
+ throw new HarmonyConfigError('harmony.skills entries must be objects.');
142
+ }
143
+ const result = {};
144
+ for (const field of ['entities', 'actions']) {
145
+ if (skill[field] !== undefined)
146
+ result[field] = normalizeStringArray(skill[field], `harmony.skills[].${field}`);
147
+ }
148
+ if (skill.uris !== undefined) {
149
+ if (!Array.isArray(skill.uris)) {
150
+ throw new HarmonyConfigError('harmony.skills[].uris must be an array.');
151
+ }
152
+ result.uris = skill.uris.map((uri) => {
153
+ if (!uri || typeof uri !== 'object' || Array.isArray(uri)) {
154
+ throw new HarmonyConfigError('Harmony skill URI must be an object.');
155
+ }
156
+ const result = {};
157
+ for (const [key, value] of Object.entries(uri)) {
158
+ result[key] = typeof value === 'boolean' || (typeof value === 'number' && Number.isFinite(value))
159
+ ? value
160
+ : readString(value, `harmony.skills[].uris[].${key}`);
161
+ }
162
+ return result;
163
+ });
164
+ }
165
+ if (Object.keys(result).length === 0) {
166
+ throw new HarmonyConfigError('Harmony skill must contain entities, actions, or uris.');
167
+ }
168
+ return result;
169
+ }
170
+ function readExpoSchemes(config) {
171
+ const values = Array.isArray(config.scheme) ? config.scheme : config.scheme ? [config.scheme] : [];
172
+ return values.map(value => readString(value, 'scheme'));
173
+ }
174
+ function normalizeHarmonyConfig(config) {
175
+ const harmony = config.harmony;
176
+ if (!harmony || typeof harmony !== 'object' || Array.isArray(harmony)) {
177
+ throw new HarmonyConfigError('Expo config must contain a harmony object.');
178
+ }
179
+ const bundle = readString(harmony.bundleName, 'harmony.bundleName');
180
+ if (!BundlePattern.test(bundle)) {
181
+ throw new HarmonyConfigError('harmony.bundleName must contain at least three valid dot-separated segments.');
182
+ }
183
+ const module = readString(harmony.moduleName, 'harmony.moduleName', 'entry');
184
+ const ability = readString(harmony.abilityName, 'harmony.abilityName', 'EntryAbility');
185
+ if (!IdentifierPattern.test(module)) {
186
+ throw new HarmonyConfigError('harmony.moduleName must be a valid Harmony identifier.');
187
+ }
188
+ if (!IdentifierPattern.test(ability)) {
189
+ throw new HarmonyConfigError('harmony.abilityName must be a valid Harmony identifier.');
190
+ }
191
+ const version = readString(harmony.versionName, 'harmony.versionName', config.version || '1.0.0');
192
+ const code = readPositiveInteger(harmony.versionCode, 'versionCode', 1);
193
+ const target = readPositiveInteger(harmony.targetApiVersion, 'targetApiVersion', parseSdkApi(harmony.targetSdkVersion, 'targetSdkVersion') ?? TargetApi);
194
+ const compatible = readPositiveInteger(parseSdkApi(harmony.compatibleSdkVersion, 'compatibleSdkVersion') ?? undefined, 'compatibleSdkVersion', MinimumApi);
195
+ if (compatible < MinimumApi) {
196
+ throw new HarmonyConfigError(`Harmony compatible API must be ${MinimumApi} or newer.`);
197
+ }
198
+ if (compatible > target) {
199
+ throw new HarmonyConfigError('Harmony compatibleSdkVersion cannot exceed targetApiVersion.');
200
+ }
201
+ const orientation = harmony.orientation || config.orientation || 'default';
202
+ if (!Orientations.has(orientation)) {
203
+ throw new HarmonyConfigError(`Unsupported Harmony orientation: ${orientation}`);
204
+ }
205
+ const background = normalizeColor(harmony.backgroundColor || config.backgroundColor, 'harmony.backgroundColor', '#FFFFFF');
206
+ const engine = harmony.jsEngine || 'hermes';
207
+ if (engine !== 'hermes') {
208
+ throw new HarmonyConfigError('Expo Harmony currently supports only the Hermes JavaScript engine.');
209
+ }
210
+ const abis = harmony.abiFilters === undefined
211
+ ? ['arm64-v8a', 'x86_64']
212
+ : normalizeStringArray(harmony.abiFilters, 'harmony.abiFilters');
213
+ for (const abi of abis) {
214
+ if (!/^[A-Za-z0-9_-]+$/.test(abi)) {
215
+ throw new HarmonyConfigError(`Invalid Harmony ABI filter: ${abi}`);
216
+ }
217
+ }
218
+ const permissions = (harmony.permissions || []).map(normalizePermission);
219
+ if (!permissions.some(permission => permission.name === 'ohos.permission.INTERNET')) {
220
+ permissions.unshift({ name: 'ohos.permission.INTERNET' });
221
+ }
222
+ const schemes = readExpoSchemes(config);
223
+ let skills = (harmony.skills || []).map(normalizeSkill);
224
+ for (const scheme of schemes) {
225
+ skills.push({
226
+ actions: ['ohos.want.action.viewData'],
227
+ entities: ['entity.system.browsable'],
228
+ uris: [{ scheme }],
229
+ });
230
+ }
231
+ skills = [...new Map(skills.map(skill => [JSON.stringify(skill), skill])).values()];
232
+ const queries = [...new Set([
233
+ 'http',
234
+ 'https',
235
+ 'tel',
236
+ 'sms',
237
+ ...schemes,
238
+ ...(harmony.querySchemes === undefined
239
+ ? []
240
+ : normalizeStringArray(harmony.querySchemes, 'harmony.querySchemes')),
241
+ ])];
242
+ const signing = harmony.signingConfigFile === undefined
243
+ ? undefined
244
+ : readString(harmony.signingConfigFile, 'harmony.signingConfigFile');
245
+ const devices = harmony.deviceTypes === undefined
246
+ ? ['phone', 'tablet']
247
+ : normalizeStringArray(harmony.deviceTypes, 'harmony.deviceTypes', DeviceTypes);
248
+ return Object.freeze({
249
+ abiFilters: abis,
250
+ abilityName: ability,
251
+ backgroundColor: background,
252
+ bundleName: bundle,
253
+ compatibleSdkVersionString: resolveSdkVersion(compatible, 'compatibleSdkVersion', harmony.compatibleSdkVersion),
254
+ deviceTypes: devices,
255
+ icon: harmony.icon || config.icon,
256
+ label: readString(harmony.label, 'harmony.label', config.name),
257
+ moduleName: module,
258
+ nativeOrientation: orientation === 'default' ? 'unspecified' : orientation,
259
+ permissions,
260
+ productName: readString(harmony.productName, 'harmony.productName', 'default'),
261
+ querySchemes: queries,
262
+ signingConfigFile: signing,
263
+ skills,
264
+ targetApiVersion: target,
265
+ targetSdkVersionString: resolveSdkVersion(target, 'targetSdkVersion', harmony.targetSdkVersion),
266
+ vendor: readString(harmony.vendor, 'harmony.vendor', 'expo-harmony'),
267
+ versionCode: code,
268
+ versionName: version,
269
+ });
270
+ }
@@ -0,0 +1,17 @@
1
+ import type { ExpoConfigWithHarmony } from './config';
2
+ export interface HarmonyConfigPluginOwnership {
3
+ readonly owner: string;
4
+ readonly files?: readonly string[];
5
+ readonly ability?: Readonly<Partial<{
6
+ startWindowBackground: string;
7
+ startWindowIcon: string;
8
+ }>>;
9
+ readonly resources?: Readonly<Partial<{
10
+ colors: Readonly<Partial<Record<'entry' | 'entryDark', readonly string[]>>>;
11
+ media: Readonly<Partial<Record<'app' | 'entry' | 'entryDark', readonly string[]>>>;
12
+ strings: Readonly<Partial<Record<'app' | 'entry', readonly string[]>>>;
13
+ }>>;
14
+ }
15
+ export declare function normalizeHarmonyConfigPlugins(value: unknown): readonly HarmonyConfigPluginOwnership[];
16
+ export declare function registerHarmonyConfigPlugin<Config extends ExpoConfigWithHarmony>(config: Config, owner: string, claims?: Omit<HarmonyConfigPluginOwnership, 'owner'>): Config;
17
+ export declare function getHarmonyConfigPlugins(config: ExpoConfigWithHarmony): readonly HarmonyConfigPluginOwnership[];
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeHarmonyConfigPlugins = normalizeHarmonyConfigPlugins;
4
+ exports.registerHarmonyConfigPlugin = registerHarmonyConfigPlugin;
5
+ exports.getHarmonyConfigPlugins = getHarmonyConfigPlugins;
6
+ const ResourceScopes = {
7
+ colors: new Set(['entry', 'entryDark']),
8
+ media: new Set(['app', 'entry', 'entryDark']),
9
+ strings: new Set(['app', 'entry']),
10
+ };
11
+ const AbilityFields = new Set(['startWindowBackground', 'startWindowIcon']);
12
+ const ResourceName = /^[A-Za-z0-9_.-]+$/u;
13
+ function isRecord(value) {
14
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
15
+ }
16
+ function normalizeStringList(value, field) {
17
+ if (!Array.isArray(value)) {
18
+ throw new TypeError(`Invalid Harmony config-plugin ownership: ${field} must be an array.`);
19
+ }
20
+ const items = [];
21
+ for (const item of value) {
22
+ if (typeof item !== 'string' || !item || !ResourceName.test(item) || item.includes('..')) {
23
+ throw new TypeError(`Invalid Harmony config-plugin ownership: ${field} contains an invalid resource name.`);
24
+ }
25
+ if (!items.includes(item))
26
+ items.push(item);
27
+ }
28
+ return items.sort((left, right) => left.localeCompare(right, 'en'));
29
+ }
30
+ function normalizeResources(value) {
31
+ if (value === undefined)
32
+ return {};
33
+ if (!isRecord(value)) {
34
+ throw new TypeError('Invalid Harmony config-plugin ownership: resources must be an object.');
35
+ }
36
+ const resources = {};
37
+ for (const kind of Object.keys(value).sort()) {
38
+ if (!Object.hasOwn(ResourceScopes, kind)) {
39
+ throw new TypeError(`Invalid Harmony config-plugin ownership: resources.${kind} is not supported.`);
40
+ }
41
+ const scopes = value[kind];
42
+ if (!isRecord(scopes)) {
43
+ throw new TypeError(`Invalid Harmony config-plugin ownership: resources.${kind} must be an object.`);
44
+ }
45
+ const allowed = ResourceScopes[kind];
46
+ resources[kind] = {};
47
+ for (const scope of Object.keys(scopes).sort()) {
48
+ if (!allowed.has(scope)) {
49
+ throw new TypeError(`Invalid Harmony config-plugin ownership: resources.${kind}.${scope} is not a supported scope.`);
50
+ }
51
+ resources[kind][scope] = normalizeStringList(scopes[scope], `resources.${kind}.${scope}`);
52
+ }
53
+ }
54
+ return resources;
55
+ }
56
+ function normalizeAbility(value) {
57
+ if (value === undefined)
58
+ return {};
59
+ if (!isRecord(value)) {
60
+ throw new TypeError('Invalid Harmony config-plugin ownership: ability must be an object.');
61
+ }
62
+ const ability = {};
63
+ for (const field of Object.keys(value).sort()) {
64
+ if (!AbilityFields.has(field)) {
65
+ throw new TypeError(`Invalid Harmony config-plugin ownership: ability.${field} is not supported.`);
66
+ }
67
+ if (typeof value[field] !== 'string' || !value[field]) {
68
+ throw new TypeError(`Invalid Harmony config-plugin ownership: ability.${field} must be a non-empty string.`);
69
+ }
70
+ ability[field] = value[field];
71
+ }
72
+ return ability;
73
+ }
74
+ function normalizeHarmonyConfigPlugins(value) {
75
+ if (value === undefined)
76
+ return [];
77
+ if (!Array.isArray(value)) {
78
+ throw new TypeError('Invalid Harmony config-plugin ownership: the plugin list must be an array.');
79
+ }
80
+ const owners = new Set();
81
+ const plugins = value.map((plugin, index) => {
82
+ if (!isRecord(plugin)) {
83
+ throw new TypeError(`Invalid Harmony config-plugin ownership: plugin ${index} must be an object.`);
84
+ }
85
+ if (typeof plugin.owner !== 'string' || !plugin.owner.trim()) {
86
+ throw new TypeError(`Invalid Harmony config-plugin ownership: plugin ${index}.owner must be a non-empty string.`);
87
+ }
88
+ if (owners.has(plugin.owner)) {
89
+ throw new TypeError(`Invalid Harmony config-plugin ownership: owner ${plugin.owner} is duplicated.`);
90
+ }
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
+ }
101
+ return {
102
+ ...(files === undefined ? {} : { files }),
103
+ ability: normalizeAbility(plugin.ability),
104
+ owner: plugin.owner,
105
+ resources: normalizeResources(plugin.resources),
106
+ };
107
+ });
108
+ return plugins.sort((left, right) => left.owner.localeCompare(right.owner, 'en'));
109
+ }
110
+ function registerHarmonyConfigPlugin(config, owner, claims = {}) {
111
+ const plugin = normalizeHarmonyConfigPlugins([{ ...claims, owner }])[0];
112
+ config._internal ??= {};
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
+ }
121
+ config._internal.harmonyConfigPlugins = normalizeHarmonyConfigPlugins([
122
+ ...plugins.filter(item => item.owner !== owner),
123
+ plugin,
124
+ ]);
125
+ return config;
126
+ }
127
+ function getHarmonyConfigPlugins(config) {
128
+ return normalizeHarmonyConfigPlugins(config?._internal?.harmonyConfigPlugins);
129
+ }
@@ -0,0 +1,64 @@
1
+ export interface HarmonyManagedPaths {
2
+ readonly appJson: string;
3
+ readonly projectBuildProfile: string;
4
+ readonly rootOhPackage: string;
5
+ readonly rootHvigor: string;
6
+ readonly hvigorConfig: string;
7
+ readonly entryBuildProfile: string;
8
+ readonly entryOhPackage: string;
9
+ readonly entryHvigor: string;
10
+ readonly moduleJson: string;
11
+ readonly profiles: string;
12
+ readonly entryAbility: string;
13
+ readonly indexPage: string;
14
+ readonly worker: string;
15
+ readonly arktsPackageProvider: string;
16
+ readonly cppPackageProvider: string;
17
+ readonly cmakeLists: string;
18
+ }
19
+ export interface HarmonyProjectPaths {
20
+ readonly reactNativeConfig: string;
21
+ }
22
+ export interface HarmonyProjectPathCandidates {
23
+ readonly reactNativeConfig: readonly string[];
24
+ }
25
+ export interface HarmonyResourcePaths {
26
+ readonly strings: Readonly<{
27
+ app: string;
28
+ entry: string;
29
+ }>;
30
+ readonly colors: Readonly<{
31
+ entry: string;
32
+ entryDark: string;
33
+ }>;
34
+ readonly media: Readonly<{
35
+ app: string;
36
+ entry: string;
37
+ entryDark: string;
38
+ }>;
39
+ }
40
+ export interface HarmonyPathsApi {
41
+ readonly HARMONY_PATHS: HarmonyManagedPaths;
42
+ readonly PROJECT_PATH_CANDIDATES: HarmonyProjectPathCandidates;
43
+ readonly PROJECT_PATHS: HarmonyProjectPaths;
44
+ readonly RESOURCE_PATHS: HarmonyResourcePaths;
45
+ assertNoExternalSymlink(root: string, target: string): Promise<void>;
46
+ isInside(root: string, target: string): boolean;
47
+ resolveHarmonyPath(directory: string, relative: string): Promise<string>;
48
+ resolveProjectPath(root: string, name: keyof HarmonyProjectPaths): Promise<string>;
49
+ toPosixRelative(root: string, target: string): string;
50
+ }
51
+ declare const ManagedPaths: HarmonyManagedPaths & {
52
+ readonly nativeInputsStamp: string;
53
+ };
54
+ declare const ProjectPaths: HarmonyProjectPaths;
55
+ declare const ProjectPathCandidates: HarmonyProjectPathCandidates;
56
+ declare const ResourcePaths: HarmonyResourcePaths;
57
+ export declare function isInside(root: string, target: string): boolean;
58
+ export declare function assertNoExternalSymlink(root: string, target: string): Promise<void>;
59
+ export declare function resolveHarmonyPath(directory: string, relative: string): Promise<string>;
60
+ export declare function resolveProjectPath(root: string, name: keyof HarmonyProjectPaths): Promise<string>;
61
+ export declare function toPosixRelative(root: string, target: string): string;
62
+ declare const NativeInputsStampPath: string;
63
+ export declare const HarmonyPaths: Readonly<HarmonyPathsApi>;
64
+ export { ManagedPaths, NativeInputsStampPath, ProjectPathCandidates, ProjectPaths, ResourcePaths };
package/build/paths.js ADDED
@@ -0,0 +1,129 @@
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.ResourcePaths = exports.ProjectPaths = exports.ProjectPathCandidates = exports.NativeInputsStampPath = exports.ManagedPaths = exports.HarmonyPaths = void 0;
7
+ exports.isInside = isInside;
8
+ exports.assertNoExternalSymlink = assertNoExternalSymlink;
9
+ exports.resolveHarmonyPath = resolveHarmonyPath;
10
+ exports.resolveProjectPath = resolveProjectPath;
11
+ exports.toPosixRelative = toPosixRelative;
12
+ const node_fs_1 = __importDefault(require("node:fs"));
13
+ const node_path_1 = __importDefault(require("node:path"));
14
+ const errors_1 = require("./errors");
15
+ const ManagedPaths = Object.freeze({
16
+ appJson: 'AppScope/app.json5',
17
+ projectBuildProfile: 'build-profile.json5',
18
+ rootOhPackage: 'oh-package.json5',
19
+ rootHvigor: 'hvigorfile.ts',
20
+ nativeInputsStamp: 'native-inputs-stamp.ts',
21
+ hvigorConfig: 'hvigor/hvigor-config.json5',
22
+ entryBuildProfile: 'entry/build-profile.json5',
23
+ entryOhPackage: 'entry/oh-package.json5',
24
+ entryHvigor: 'entry/hvigorfile.ts',
25
+ moduleJson: 'entry/src/main/module.json5',
26
+ profiles: 'entry/src/main/resources/base/profile/main_pages.json',
27
+ entryAbility: 'entry/src/main/ets/entryability/EntryAbility.ets',
28
+ indexPage: 'entry/src/main/ets/pages/Index.ets',
29
+ worker: 'entry/src/main/ets/workers/RNOHWorker.ets',
30
+ arktsPackageProvider: 'entry/src/main/ets/PackageProvider.ets',
31
+ cppPackageProvider: 'entry/src/main/cpp/PackageProvider.cpp',
32
+ cmakeLists: 'entry/src/main/cpp/CMakeLists.txt',
33
+ });
34
+ exports.ManagedPaths = ManagedPaths;
35
+ const ProjectPaths = Object.freeze({
36
+ reactNativeConfig: 'react-native.config.js',
37
+ });
38
+ exports.ProjectPaths = ProjectPaths;
39
+ const ProjectPathCandidates = Object.freeze({
40
+ // Match React Native CLI lookup precedence; new projects use the first path.
41
+ reactNativeConfig: Object.freeze([
42
+ 'react-native.config.js',
43
+ 'react-native.config.cjs',
44
+ 'react-native.config.ts',
45
+ 'react-native.config.mjs',
46
+ ]),
47
+ });
48
+ exports.ProjectPathCandidates = ProjectPathCandidates;
49
+ const ResourcePaths = Object.freeze({
50
+ strings: Object.freeze({
51
+ app: 'AppScope/resources/base/element/string.json',
52
+ entry: 'entry/src/main/resources/base/element/string.json',
53
+ }),
54
+ colors: Object.freeze({
55
+ entry: 'entry/src/main/resources/base/element/color.json',
56
+ entryDark: 'entry/src/main/resources/dark/element/color.json',
57
+ }),
58
+ media: Object.freeze({
59
+ app: 'AppScope/resources/base/media',
60
+ entry: 'entry/src/main/resources/base/media',
61
+ entryDark: 'entry/src/main/resources/dark/media',
62
+ }),
63
+ });
64
+ exports.ResourcePaths = ResourcePaths;
65
+ function isInside(root, target) {
66
+ const relative = node_path_1.default.relative(root, target);
67
+ return relative === ''
68
+ || (!relative.startsWith(`..${node_path_1.default.sep}`) && relative !== '..' && !node_path_1.default.isAbsolute(relative));
69
+ }
70
+ async function assertNoExternalSymlink(root, target) {
71
+ const absolute = node_path_1.default.resolve(root);
72
+ const canonical = await node_fs_1.default.promises.realpath(absolute).catch(() => absolute);
73
+ const relative = node_path_1.default.relative(absolute, target);
74
+ let current = absolute;
75
+ for (const segment of relative.split(node_path_1.default.sep).filter(Boolean)) {
76
+ current = node_path_1.default.join(current, segment);
77
+ let stat;
78
+ try {
79
+ stat = await node_fs_1.default.promises.lstat(current);
80
+ }
81
+ catch (cause) {
82
+ if (cause.code === 'ENOENT')
83
+ break;
84
+ throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_PATH_INVALID', `Cannot inspect managed Harmony path ${current}: ${cause.message}`, { cause, file: current, operation: 'resolve-path' });
85
+ }
86
+ if (stat.isSymbolicLink()) {
87
+ const real = await node_fs_1.default.promises.realpath(current);
88
+ if (!isInside(canonical, real)) {
89
+ throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_PATH_ESCAPE', `Refusing to follow a symlink outside the Harmony project: ${current}`, { file: current, operation: 'resolve-path' });
90
+ }
91
+ }
92
+ }
93
+ }
94
+ async function resolveHarmonyPath(directory, relative) {
95
+ if (typeof relative !== 'string' || !relative || node_path_1.default.isAbsolute(relative)) {
96
+ throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_PATH_ESCAPE', `Invalid managed path: ${relative}`, { operation: 'resolve-path' });
97
+ }
98
+ const root = node_path_1.default.resolve(directory);
99
+ const target = node_path_1.default.resolve(root, relative);
100
+ if (!isInside(root, target)) {
101
+ throw new errors_1.HarmonyConfigPluginError('ERR_HARMONY_PATH_ESCAPE', `Managed path escapes the Harmony project: ${relative}`, { file: target, operation: 'resolve-path' });
102
+ }
103
+ await assertNoExternalSymlink(root, target);
104
+ return target;
105
+ }
106
+ async function resolveProjectPath(root, name) {
107
+ const candidates = ProjectPathCandidates[name] || [ProjectPaths[name]];
108
+ for (const candidate of candidates) {
109
+ if (node_fs_1.default.existsSync(node_path_1.default.join(root, candidate)))
110
+ return resolveHarmonyPath(root, candidate);
111
+ }
112
+ return resolveHarmonyPath(root, candidates[0]);
113
+ }
114
+ function toPosixRelative(root, target) {
115
+ return node_path_1.default.relative(root, target).split(node_path_1.default.sep).join('/');
116
+ }
117
+ const { nativeInputsStamp: NativeInputsStampPath, ...PublicManagedPaths } = ManagedPaths;
118
+ exports.NativeInputsStampPath = NativeInputsStampPath;
119
+ exports.HarmonyPaths = Object.freeze({
120
+ HARMONY_PATHS: Object.freeze(PublicManagedPaths),
121
+ PROJECT_PATH_CANDIDATES: ProjectPathCandidates,
122
+ PROJECT_PATHS: ProjectPaths,
123
+ RESOURCE_PATHS: ResourcePaths,
124
+ assertNoExternalSymlink,
125
+ isInside,
126
+ resolveHarmonyPath,
127
+ resolveProjectPath,
128
+ toPosixRelative,
129
+ });
@@ -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
+ }>;