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

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/src/paths.ts ADDED
@@ -0,0 +1,209 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ import { HarmonyConfigPluginError } from './errors';
5
+
6
+ export interface HarmonyManagedPaths {
7
+ readonly appJson: string;
8
+ readonly projectBuildProfile: string;
9
+ readonly rootOhPackage: string;
10
+ readonly rootHvigor: string;
11
+ readonly nativeInputsStamp: string;
12
+ readonly hvigorConfig: string;
13
+ readonly entryBuildProfile: string;
14
+ readonly entryOhPackage: string;
15
+ readonly entryHvigor: string;
16
+ readonly moduleJson: string;
17
+ readonly profiles: string;
18
+ readonly entryAbility: string;
19
+ readonly indexPage: string;
20
+ readonly worker: string;
21
+ readonly arktsPackageProvider: string;
22
+ readonly cppPackageProvider: string;
23
+ readonly cmakeLists: string;
24
+ }
25
+
26
+ export interface HarmonyProjectPaths {
27
+ readonly reactNativeConfig: string;
28
+ }
29
+
30
+ export interface HarmonyProjectPathCandidates {
31
+ readonly reactNativeConfig: readonly string[];
32
+ }
33
+
34
+ export interface HarmonyResourcePaths {
35
+ readonly strings: Readonly<{ app: string; entry: string }>;
36
+ readonly colors: Readonly<{ entry: string; entryDark: string }>;
37
+ readonly media: Readonly<{ app: string; entry: string; 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(platformProjectRoot: string, relativePath: string): Promise<string>;
48
+ resolveProjectPath(projectRoot: string, modName: keyof HarmonyProjectPaths): Promise<string>;
49
+ toPosixRelative(projectRoot: string, target: string): string;
50
+ }
51
+
52
+ const ManagedPaths: HarmonyManagedPaths = Object.freeze({
53
+ appJson: 'AppScope/app.json5',
54
+ projectBuildProfile: 'build-profile.json5',
55
+ rootOhPackage: 'oh-package.json5',
56
+ rootHvigor: 'hvigorfile.ts',
57
+ nativeInputsStamp: 'native-inputs-stamp.ts',
58
+ hvigorConfig: 'hvigor/hvigor-config.json5',
59
+ entryBuildProfile: 'entry/build-profile.json5',
60
+ entryOhPackage: 'entry/oh-package.json5',
61
+ entryHvigor: 'entry/hvigorfile.ts',
62
+ moduleJson: 'entry/src/main/module.json5',
63
+ profiles: 'entry/src/main/resources/base/profile/main_pages.json',
64
+ entryAbility: 'entry/src/main/ets/entryability/EntryAbility.ets',
65
+ indexPage: 'entry/src/main/ets/pages/Index.ets',
66
+ worker: 'entry/src/main/ets/workers/RNOHWorker.ets',
67
+ arktsPackageProvider: 'entry/src/main/ets/PackageProvider.ets',
68
+ cppPackageProvider: 'entry/src/main/cpp/PackageProvider.cpp',
69
+ cmakeLists: 'entry/src/main/cpp/CMakeLists.txt',
70
+ });
71
+
72
+ const ProjectPaths: HarmonyProjectPaths = Object.freeze({
73
+ reactNativeConfig: 'react-native.config.js',
74
+ });
75
+
76
+ const ProjectPathCandidates: HarmonyProjectPathCandidates = Object.freeze({
77
+ // Keep this order aligned with @react-native-community/cli-config's async
78
+ // search places. The first entry is also the file created for new projects.
79
+ reactNativeConfig: Object.freeze([
80
+ 'react-native.config.js',
81
+ 'react-native.config.cjs',
82
+ 'react-native.config.ts',
83
+ 'react-native.config.mjs',
84
+ ]),
85
+ });
86
+
87
+ const ResourcePaths: HarmonyResourcePaths = Object.freeze({
88
+ strings: Object.freeze({
89
+ app: 'AppScope/resources/base/element/string.json',
90
+ entry: 'entry/src/main/resources/base/element/string.json',
91
+ }),
92
+ colors: Object.freeze({
93
+ entry: 'entry/src/main/resources/base/element/color.json',
94
+ entryDark: 'entry/src/main/resources/dark/element/color.json',
95
+ }),
96
+ media: Object.freeze({
97
+ app: 'AppScope/resources/base/media',
98
+ entry: 'entry/src/main/resources/base/media',
99
+ entryDark: 'entry/src/main/resources/dark/media',
100
+ }),
101
+ });
102
+
103
+ export function isInside(root: string, target: string): boolean {
104
+ const relative = path.relative(root, target);
105
+
106
+ return relative === ''
107
+ || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
108
+ }
109
+
110
+ export async function assertNoExternalSymlink(root: string, target: string): Promise<void> {
111
+ const absoluteRoot = path.resolve(root);
112
+ const realRoot = await fs.promises.realpath(absoluteRoot).catch(() => absoluteRoot);
113
+ const relative = path.relative(absoluteRoot, target);
114
+ let cursor = absoluteRoot;
115
+
116
+ for (const segment of relative.split(path.sep).filter(Boolean)) {
117
+ cursor = path.join(cursor, segment);
118
+
119
+ let stat: fs.Stats;
120
+ try {
121
+ stat = await fs.promises.lstat(cursor);
122
+ } catch (cause) {
123
+ if ((cause as NodeJS.ErrnoException).code === 'ENOENT') break;
124
+
125
+ throw new HarmonyConfigPluginError(
126
+ 'ERR_HARMONY_PATH_INVALID',
127
+ `Cannot inspect managed Harmony path ${cursor}: ${(cause as Error).message}`,
128
+ { cause, file: cursor, operation: 'resolve-path' }
129
+ );
130
+ }
131
+
132
+ if (stat.isSymbolicLink()) {
133
+ const real = await fs.promises.realpath(cursor);
134
+
135
+ if (!isInside(realRoot, real)) {
136
+ throw new HarmonyConfigPluginError(
137
+ 'ERR_HARMONY_PATH_ESCAPE',
138
+ `Refusing to follow a symlink outside the Harmony project: ${cursor}`,
139
+ { file: cursor, operation: 'resolve-path' }
140
+ );
141
+ }
142
+ }
143
+ }
144
+ }
145
+
146
+ export async function resolveHarmonyPath(
147
+ platformProjectRoot: string,
148
+ relativePath: string
149
+ ): Promise<string> {
150
+ if (typeof relativePath !== 'string' || !relativePath || path.isAbsolute(relativePath)) {
151
+ throw new HarmonyConfigPluginError(
152
+ 'ERR_HARMONY_PATH_ESCAPE',
153
+ `Invalid managed path: ${relativePath}`,
154
+ { operation: 'resolve-path' }
155
+ );
156
+ }
157
+
158
+ const root = path.resolve(platformProjectRoot);
159
+ const target = path.resolve(root, relativePath);
160
+
161
+ if (!isInside(root, target)) {
162
+ throw new HarmonyConfigPluginError(
163
+ 'ERR_HARMONY_PATH_ESCAPE',
164
+ `Managed path escapes the Harmony project: ${relativePath}`,
165
+ { file: target, operation: 'resolve-path' }
166
+ );
167
+ }
168
+
169
+ await assertNoExternalSymlink(root, target);
170
+
171
+ return target;
172
+ }
173
+
174
+ export async function resolveProjectPath(
175
+ projectRoot: string,
176
+ modName: keyof HarmonyProjectPaths
177
+ ): Promise<string> {
178
+ const candidates = ProjectPathCandidates[modName] || [ProjectPaths[modName]];
179
+ const resolved = await Promise.all(candidates.map(candidate => resolveHarmonyPath(projectRoot, candidate)));
180
+ const existing = resolved.filter(file => fs.existsSync(file));
181
+
182
+ if (existing.length > 1) {
183
+ throw new HarmonyConfigPluginError(
184
+ 'ERR_HARMONY_CONFIG_INVALID',
185
+ `Multiple files provide harmony.${modName}: ${existing.map(file => path.basename(file)).join(', ')}`,
186
+ { file: existing[0], operation: `harmony.${modName}.read` }
187
+ );
188
+ }
189
+
190
+ return existing[0] || resolved[0];
191
+ }
192
+
193
+ export function toPosixRelative(projectRoot: string, target: string): string {
194
+ return path.relative(projectRoot, target).split(path.sep).join('/');
195
+ }
196
+
197
+ export const HarmonyPaths: HarmonyPathsApi = {
198
+ HARMONY_PATHS: ManagedPaths,
199
+ PROJECT_PATH_CANDIDATES: ProjectPathCandidates,
200
+ PROJECT_PATHS: ProjectPaths,
201
+ RESOURCE_PATHS: ResourcePaths,
202
+ assertNoExternalSymlink,
203
+ isInside,
204
+ resolveHarmonyPath,
205
+ resolveProjectPath,
206
+ toPosixRelative,
207
+ };
208
+
209
+ export { ManagedPaths, ProjectPathCandidates, ProjectPaths, ResourcePaths };
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "declaration": true,
4
+ "esModuleInterop": true,
5
+ "lib": ["ES2022"],
6
+ "module": "Node16",
7
+ "moduleResolution": "Node16",
8
+ "outDir": "build",
9
+ "rootDir": "src",
10
+ "skipLibCheck": true,
11
+ "strict": true,
12
+ "target": "ES2022",
13
+ "types": ["node"]
14
+ },
15
+ "include": ["src/**/*.ts"],
16
+ "exclude": ["build"]
17
+ }