@nebula-rn/cli 0.0.1 → 0.1.0

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 (115) hide show
  1. package/README.md +65 -0
  2. package/dist/create.js +12 -3
  3. package/package.json +20 -7
  4. package/templates/host/.eslintrc.js +7 -0
  5. package/templates/host/App.tsx +118 -0
  6. package/templates/host/Gemfile +16 -0
  7. package/templates/host/__tests__/App.test.tsx +13 -0
  8. package/templates/host/android/app/build.gradle +55 -0
  9. package/templates/host/android/app/debug.keystore +0 -0
  10. package/templates/host/android/app/proguard-rules.pro +10 -0
  11. package/templates/host/android/app/src/main/AndroidManifest.xml +54 -0
  12. package/templates/host/android/app/src/main/java/com/SuperApp/MainActivity.kt +28 -0
  13. package/templates/host/android/app/src/main/java/com/SuperApp/MainApplication.kt +35 -0
  14. package/templates/host/android/app/src/main/res/drawable/rn_edit_text_material.xml +37 -0
  15. package/templates/host/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  16. package/templates/host/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  17. package/templates/host/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  18. package/templates/host/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  19. package/templates/host/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  20. package/templates/host/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  21. package/templates/host/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  22. package/templates/host/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  23. package/templates/host/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  24. package/templates/host/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  25. package/templates/host/android/app/src/main/res/values/strings.xml +3 -0
  26. package/templates/host/android/app/src/main/res/values/styles.xml +16 -0
  27. package/templates/host/android/build.gradle +21 -0
  28. package/templates/host/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  29. package/templates/host/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  30. package/templates/host/android/gradle.properties +44 -0
  31. package/templates/host/android/gradlew +251 -0
  32. package/templates/host/android/gradlew.bat +99 -0
  33. package/templates/host/android/settings.gradle +6 -0
  34. package/templates/host/app.json +4 -0
  35. package/templates/host/babel.config.js +4 -0
  36. package/templates/host/index.js +9 -0
  37. package/templates/host/ios/.xcode.env +11 -0
  38. package/templates/host/ios/Podfile +38 -0
  39. package/templates/host/ios/SuperApp/AppDelegate.swift +95 -0
  40. package/templates/host/ios/SuperApp/Images.xcassets/AppIcon.appiconset/Contents.json +53 -0
  41. package/templates/host/ios/SuperApp/Images.xcassets/Contents.json +6 -0
  42. package/templates/host/ios/SuperApp/Info.plist +76 -0
  43. package/templates/host/ios/SuperApp/LaunchScreen.storyboard +47 -0
  44. package/templates/host/ios/SuperApp/PrivacyInfo.xcprivacy +48 -0
  45. package/templates/host/ios/SuperApp/SceneDelegate.swift +47 -0
  46. package/templates/host/ios/SuperApp/SuperApp-Bridging-Header.h +18 -0
  47. package/templates/host/ios/SuperApp-Bridging-Header.h +21 -0
  48. package/templates/host/ios/SuperApp.xcodeproj/project.pbxproj +500 -0
  49. package/templates/host/ios/SuperApp.xcodeproj/xcshareddata/xcschemes/RNTest.xcscheme +88 -0
  50. package/templates/host/jest.config.js +3 -0
  51. package/templates/host/metro.config.js +49 -0
  52. package/templates/host/package.json +66 -0
  53. package/templates/host/tsconfig.json +8 -0
  54. package/templates/miniapp/.watchmanconfig +1 -0
  55. package/templates/miniapp/app.json +12 -0
  56. package/templates/miniapp/babel.config.js +3 -0
  57. package/templates/miniapp/package.json +34 -0
  58. package/templates/miniapp/src/pages/home/index.tsx +122 -0
  59. package/templates/miniapp/src/pages/home/page.config.ts +10 -0
  60. package/templates/miniapp/tsconfig.json +11 -0
  61. package/templates/runner/.eslintrc.js +7 -0
  62. package/templates/runner/App.tsx +515 -0
  63. package/templates/runner/Gemfile +16 -0
  64. package/templates/runner/__tests__/App.test.tsx +13 -0
  65. package/templates/runner/android/app/build.gradle +55 -0
  66. package/templates/runner/android/app/debug.keystore +0 -0
  67. package/templates/runner/android/app/proguard-rules.pro +10 -0
  68. package/templates/runner/android/app/src/main/AndroidManifest.xml +60 -0
  69. package/templates/runner/android/app/src/main/java/com/devrunner/MainActivity.kt +14 -0
  70. package/templates/runner/android/app/src/main/java/com/devrunner/MainApplication.kt +35 -0
  71. package/templates/runner/android/app/src/main/res/drawable/rn_edit_text_material.xml +37 -0
  72. package/templates/runner/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  73. package/templates/runner/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  74. package/templates/runner/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  75. package/templates/runner/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  76. package/templates/runner/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  77. package/templates/runner/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  78. package/templates/runner/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  79. package/templates/runner/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  80. package/templates/runner/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  81. package/templates/runner/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  82. package/templates/runner/android/app/src/main/res/values/strings.xml +3 -0
  83. package/templates/runner/android/app/src/main/res/values/styles.xml +16 -0
  84. package/templates/runner/android/build.gradle +21 -0
  85. package/templates/runner/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  86. package/templates/runner/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  87. package/templates/runner/android/gradle.properties +44 -0
  88. package/templates/runner/android/gradlew +251 -0
  89. package/templates/runner/android/gradlew.bat +99 -0
  90. package/templates/runner/android/settings.gradle +6 -0
  91. package/templates/runner/app.json +4 -0
  92. package/templates/runner/babel.config.js +4 -0
  93. package/templates/runner/index.js +9 -0
  94. package/templates/runner/ios/.xcode.env +11 -0
  95. package/templates/runner/ios/DevRunner/AppDelegate.swift +53 -0
  96. package/templates/runner/ios/DevRunner/DevRunner-Bridging-Header.h +19 -0
  97. package/templates/runner/ios/DevRunner/Images.xcassets/AppIcon.appiconset/Contents.json +53 -0
  98. package/templates/runner/ios/DevRunner/Images.xcassets/Contents.json +6 -0
  99. package/templates/runner/ios/DevRunner/Info.plist +51 -0
  100. package/templates/runner/ios/DevRunner/LaunchScreen.storyboard +47 -0
  101. package/templates/runner/ios/DevRunner/PrivacyInfo.xcprivacy +48 -0
  102. package/templates/runner/ios/DevRunner/SceneDelegate.swift +21 -0
  103. package/templates/runner/ios/DevRunner-Bridging-Header.h +22 -0
  104. package/templates/runner/ios/DevRunner.xcodeproj/project.pbxproj +500 -0
  105. package/templates/runner/ios/DevRunner.xcodeproj/xcshareddata/xcschemes/DevRunner.xcscheme +88 -0
  106. package/templates/runner/ios/Podfile +38 -0
  107. package/templates/runner/jest.config.js +3 -0
  108. package/templates/runner/metro.config.js +3 -0
  109. package/templates/runner/package.json +38 -0
  110. package/templates/runner/src/RunnerHomeScreen.tsx +499 -0
  111. package/templates/runner/src/assets/icon_close.png +0 -0
  112. package/templates/runner/src/assets/icon_pic.png +0 -0
  113. package/templates/runner/src/runnerTypes.ts +31 -0
  114. package/templates/runner/tsconfig.json +8 -0
  115. package/templates/versions.json +6 -0
@@ -0,0 +1,515 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import {
3
+ ActivityIndicator,
4
+ Alert,
5
+ Image,
6
+ Linking,
7
+ Platform,
8
+ Pressable,
9
+ StyleSheet,
10
+ Text,
11
+ } from 'react-native';
12
+ import Animated, {
13
+ Easing,
14
+ FadeInUp,
15
+ FadeOut,
16
+ FadeOutDown,
17
+ } from 'react-native-reanimated';
18
+ import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
19
+ import { defaultHostApis } from '@nebula-rn/host-apis';
20
+ import type { MiniappLoadingResolveContext } from '@nebula-rn/sdk';
21
+ import { NebulaAPI } from '@nebula-rn/sdk';
22
+ import { RunnerHomeScreen } from './src/RunnerHomeScreen';
23
+ import type { RunnerHistoryItem, RuntimeManifest } from './src/runnerTypes';
24
+
25
+ const DEFAULT_RUNNER_BASE_URL = 'http://localhost:8082';
26
+
27
+ type RunnerSessionTarget = {
28
+ baseUrl: string;
29
+ bundleUrl?: string | null;
30
+ manifestUrl?: string | null;
31
+ };
32
+
33
+ function normalizeRunnerBaseUrl(value: string): string {
34
+ const trimmed = value.trim();
35
+ if (!trimmed) {
36
+ return DEFAULT_RUNNER_BASE_URL;
37
+ }
38
+ return trimmed.replace(/\/$/, '');
39
+ }
40
+
41
+ function parseRunnerInput(value: string): RunnerSessionTarget {
42
+ const trimmed = value.trim();
43
+ if (!trimmed) {
44
+ return { baseUrl: DEFAULT_RUNNER_BASE_URL };
45
+ }
46
+
47
+ try {
48
+ const parsed = new URL(trimmed);
49
+ if (parsed.pathname.endsWith('.bundle')) {
50
+ return {
51
+ baseUrl: normalizeRunnerBaseUrl(parsed.origin),
52
+ bundleUrl: trimmed,
53
+ };
54
+ }
55
+ if (parsed.pathname.endsWith('.json')) {
56
+ return {
57
+ baseUrl: normalizeRunnerBaseUrl(parsed.origin),
58
+ manifestUrl: trimmed,
59
+ };
60
+ }
61
+ } catch {
62
+ return { baseUrl: normalizeRunnerBaseUrl(trimmed) };
63
+ }
64
+
65
+ return { baseUrl: normalizeRunnerBaseUrl(trimmed) };
66
+ }
67
+
68
+ function parseRunnerLaunchUrl(url: string): RunnerSessionTarget | null {
69
+ try {
70
+ const parsed = new URL(url);
71
+
72
+ if (parsed.protocol !== 'devrunner:' || parsed.hostname !== 'open') {
73
+ return null;
74
+ }
75
+
76
+ const manifestUrl = parsed.searchParams.get('manifestUrl');
77
+ const bundleUrl = parsed.searchParams.get('bundleUrl');
78
+ const baseUrl = parsed.searchParams.get('baseUrl');
79
+
80
+ if (manifestUrl) {
81
+ const manifest = new URL(manifestUrl);
82
+ return {
83
+ baseUrl: normalizeRunnerBaseUrl(manifest.origin),
84
+ manifestUrl,
85
+ bundleUrl,
86
+ };
87
+ }
88
+
89
+ if (bundleUrl) {
90
+ const bundle = new URL(bundleUrl);
91
+ return {
92
+ baseUrl: normalizeRunnerBaseUrl(bundle.origin),
93
+ bundleUrl,
94
+ };
95
+ }
96
+
97
+ if (baseUrl) {
98
+ return { baseUrl: normalizeRunnerBaseUrl(baseUrl) };
99
+ }
100
+
101
+ return null;
102
+ } catch {
103
+ return null;
104
+ }
105
+ }
106
+
107
+ function getMiniappBundleUrl(
108
+ sessionTarget: RunnerSessionTarget,
109
+ manifest: RuntimeManifest | null,
110
+ ): string {
111
+ if (sessionTarget.bundleUrl) {
112
+ return sessionTarget.bundleUrl;
113
+ }
114
+
115
+ const baseUrl = normalizeRunnerBaseUrl(sessionTarget.baseUrl);
116
+ const bundlePath = manifest?.bundlePath || '/index.bundle';
117
+ return `${baseUrl}${bundlePath}?platform=${Platform.OS}&dev=true&minify=false`;
118
+ }
119
+
120
+ function buildHistoryItem(
121
+ sessionTarget: RunnerSessionTarget,
122
+ manifest: RuntimeManifest,
123
+ ): RunnerHistoryItem {
124
+ return {
125
+ appId: manifest.appId,
126
+ baseUrl: normalizeRunnerBaseUrl(sessionTarget.baseUrl),
127
+ bundleUrl: sessionTarget.bundleUrl ?? null,
128
+ manifestUrl: sessionTarget.manifestUrl ?? null,
129
+ title: manifest.window?.navigationBarTitleText || manifest.appId,
130
+ version: manifest.version,
131
+ };
132
+ }
133
+
134
+ function mergeHistory(
135
+ history: RunnerHistoryItem[],
136
+ item: RunnerHistoryItem,
137
+ ): RunnerHistoryItem[] {
138
+ const deduped = history.filter(
139
+ entry =>
140
+ !(
141
+ entry.appId === item.appId &&
142
+ entry.baseUrl === item.baseUrl &&
143
+ entry.bundleUrl === item.bundleUrl &&
144
+ entry.manifestUrl === item.manifestUrl
145
+ ),
146
+ );
147
+ return [item, ...deduped].slice(0, 6);
148
+ }
149
+
150
+ function showActionError(error: unknown) {
151
+ console.error('[NebulaRunner] Action failed', error);
152
+ Alert.alert(
153
+ 'Action Failed',
154
+ error instanceof Error ? error.message : 'Unexpected runner error.',
155
+ );
156
+ }
157
+
158
+ function DefaultMiniappLoadingScreen({
159
+ appId,
160
+ title,
161
+ status,
162
+ iconUrl,
163
+ }: MiniappLoadingResolveContext) {
164
+ const [closing, setClosing] = useState(false);
165
+ const message =
166
+ status === 'installing'
167
+ ? 'Installing miniapp...'
168
+ : status === 'error'
169
+ ? 'Failed to load miniapp.'
170
+ : 'Preparing miniapp...';
171
+
172
+ const closeMiniapp = async () => {
173
+ if (closing) {
174
+ return;
175
+ }
176
+
177
+ try {
178
+ setClosing(true);
179
+ await NebulaAPI.closeMiniApp(appId);
180
+ } catch (error) {
181
+ console.error('[NebulaRunner] Failed to close miniapp', error);
182
+ Alert.alert('Close Failed', 'Unable to close the miniapp right now.');
183
+ } finally {
184
+ setClosing(false);
185
+ }
186
+ };
187
+
188
+ return (
189
+ <Animated.View
190
+ exiting={FadeOut.duration(160)}
191
+ style={styles.loadingOverlay}
192
+ >
193
+ <Animated.View
194
+ entering={FadeInUp.duration(520)
195
+ .delay(40)
196
+ .easing(Easing.out(Easing.cubic))
197
+ .withInitialValues({
198
+ transform: [{ translateY: 96 }, { scale: 0.96 }],
199
+ })}
200
+ exiting={FadeOutDown.duration(220).withInitialValues({
201
+ opacity: 1,
202
+ transform: [{ translateY: 0 }, { scale: 1 }],
203
+ })}
204
+ style={styles.loadingCard}
205
+ >
206
+ <Text style={styles.loadingEyebrow}>Nebula Runner</Text>
207
+ <ActivityIndicator
208
+ size="large"
209
+ color="#2563eb"
210
+ style={styles.loadingIndicator}
211
+ />
212
+ {iconUrl ? (
213
+ <Image source={{ uri: iconUrl }} style={styles.loadingIcon} />
214
+ ) : null}
215
+ <Text style={styles.loadingTitle}>{title || appId}</Text>
216
+ <Text style={styles.loadingMessage}>{message}</Text>
217
+ <Pressable
218
+ accessibilityRole="button"
219
+ disabled={closing}
220
+ onPress={closeMiniapp}
221
+ style={({ pressed }) => [
222
+ styles.loadingCloseButton,
223
+ pressed && !closing ? styles.loadingCloseButtonPressed : null,
224
+ closing ? styles.loadingCloseButtonDisabled : null,
225
+ ]}
226
+ >
227
+ <Text style={styles.loadingCloseButtonText}>
228
+ {closing ? 'Closing...' : 'Close miniapp'}
229
+ </Text>
230
+ </Pressable>
231
+ </Animated.View>
232
+ </Animated.View>
233
+ );
234
+ }
235
+
236
+ function RunnerContent() {
237
+ const [baseUrl, setBaseUrl] = useState(DEFAULT_RUNNER_BASE_URL);
238
+ const [sessionTarget, setSessionTarget] = useState<RunnerSessionTarget>({
239
+ baseUrl: DEFAULT_RUNNER_BASE_URL,
240
+ });
241
+ const [manifest, setManifest] = useState<RuntimeManifest | null>(null);
242
+ const [status, setStatus] = useState('Ready to connect to a miniapp.');
243
+ const [loading, setLoading] = useState(false);
244
+ const [recentHistory, setRecentHistory] = useState<RunnerHistoryItem[]>([]);
245
+ const [urlExpanded, setUrlExpanded] = useState(true);
246
+ const bundleUrl = manifest
247
+ ? getMiniappBundleUrl(sessionTarget, manifest)
248
+ : '';
249
+
250
+ const loadManifest = async (
251
+ nextSessionTarget: RunnerSessionTarget,
252
+ ): Promise<RuntimeManifest> => {
253
+ const normalizedBaseUrl = normalizeRunnerBaseUrl(nextSessionTarget.baseUrl);
254
+ const manifestUrl =
255
+ nextSessionTarget.manifestUrl ??
256
+ `${normalizedBaseUrl}/.nebula/generated/app.json`;
257
+
258
+ const manifestResponse = await fetch(manifestUrl, {
259
+ headers: { 'Cache-Control': 'no-cache' },
260
+ });
261
+
262
+ if (!manifestResponse.ok) {
263
+ throw new Error(`Failed to load manifest (${manifestResponse.status})`);
264
+ }
265
+
266
+ return (await manifestResponse.json()) as RuntimeManifest;
267
+ };
268
+
269
+ const applySession = (
270
+ nextSessionTarget: RunnerSessionTarget,
271
+ nextManifest: RuntimeManifest,
272
+ ) => {
273
+ const normalizedBaseUrl = normalizeRunnerBaseUrl(nextSessionTarget.baseUrl);
274
+ const normalizedSession = {
275
+ baseUrl: normalizedBaseUrl,
276
+ bundleUrl: nextSessionTarget.bundleUrl ?? null,
277
+ manifestUrl: nextSessionTarget.manifestUrl ?? null,
278
+ };
279
+
280
+ setBaseUrl(
281
+ normalizedSession.bundleUrl ??
282
+ normalizedSession.manifestUrl ??
283
+ normalizedSession.baseUrl,
284
+ );
285
+ setSessionTarget(normalizedSession);
286
+ setManifest(nextManifest);
287
+ setStatus(`Connected to ${nextManifest.appId} v${nextManifest.version}`);
288
+ setRecentHistory(history =>
289
+ mergeHistory(history, buildHistoryItem(normalizedSession, nextManifest)),
290
+ );
291
+ };
292
+
293
+ const openMiniapp = async (
294
+ nextManifest: RuntimeManifest | null = manifest,
295
+ nextSessionTarget: RunnerSessionTarget = sessionTarget,
296
+ ) => {
297
+ if (!nextManifest) {
298
+ Alert.alert(
299
+ 'Miniapp Not Ready',
300
+ 'Connect to a miniapp first so the runner can load its manifest.',
301
+ );
302
+ return;
303
+ }
304
+
305
+ await NebulaAPI.openMiniAppWithBundleURL(
306
+ nextManifest.appId,
307
+ getMiniappBundleUrl(nextSessionTarget, nextManifest),
308
+ {
309
+ title:
310
+ nextManifest.window?.navigationBarTitleText || nextManifest.appId,
311
+ },
312
+ true,
313
+ );
314
+ };
315
+
316
+ const activateRunnerSession = async (
317
+ nextSessionTarget: RunnerSessionTarget,
318
+ options?: { openMiniapp?: boolean },
319
+ ) => {
320
+ setLoading(true);
321
+ try {
322
+ const nextManifest = await loadManifest(nextSessionTarget);
323
+ applySession(nextSessionTarget, nextManifest);
324
+ if (options?.openMiniapp) {
325
+ await openMiniapp(nextManifest, {
326
+ baseUrl: normalizeRunnerBaseUrl(nextSessionTarget.baseUrl),
327
+ bundleUrl: nextSessionTarget.bundleUrl ?? null,
328
+ manifestUrl: nextSessionTarget.manifestUrl ?? null,
329
+ });
330
+ }
331
+ } catch (error) {
332
+ setManifest(null);
333
+ setStatus(
334
+ error instanceof Error
335
+ ? error.message
336
+ : 'Failed to activate runner session.',
337
+ );
338
+ throw error;
339
+ } finally {
340
+ setLoading(false);
341
+ }
342
+ };
343
+
344
+ const refreshRunner = async () => {
345
+ try {
346
+ await activateRunnerSession(sessionTarget);
347
+ } catch (error) {
348
+ console.error('[NebulaRunner] Failed to refresh runner', error);
349
+ }
350
+ };
351
+
352
+ useEffect(() => {
353
+ const handleUrl = (url: string) => {
354
+ const nextSessionTarget = parseRunnerLaunchUrl(url);
355
+
356
+ if (!nextSessionTarget) {
357
+ return;
358
+ }
359
+ activateRunnerSession(nextSessionTarget, { openMiniapp: true }).catch(
360
+ showActionError,
361
+ );
362
+ };
363
+
364
+ Linking.getInitialURL()
365
+ .then(initialUrl => {
366
+ if (initialUrl) {
367
+ handleUrl(initialUrl);
368
+ } else {
369
+ activateRunnerSession(parseRunnerInput(baseUrl)).catch(error => {
370
+ console.error(
371
+ '[NebulaRunner] Failed to refresh initial runner',
372
+ error,
373
+ );
374
+ });
375
+ }
376
+ })
377
+ .catch(error => {
378
+ console.error('[NebulaRunner] Failed to read initial URL', error);
379
+ });
380
+
381
+ const subscription = Linking.addEventListener('url', event => {
382
+ handleUrl(event.url);
383
+ });
384
+
385
+ return () => {
386
+ subscription.remove();
387
+ };
388
+ }, []);
389
+
390
+ return (
391
+ <SafeAreaView edges={['top', 'left', 'right']} style={styles.screen}>
392
+ <RunnerHomeScreen
393
+ baseUrl={baseUrl}
394
+ setBaseUrl={setBaseUrl}
395
+ manifest={manifest}
396
+ status={status}
397
+ serviceOnline={manifest != null}
398
+ loading={loading}
399
+ bundleUrl={bundleUrl}
400
+ recentHistory={recentHistory}
401
+ clearRecentHistory={() => setRecentHistory([])}
402
+ urlExpanded={urlExpanded}
403
+ setUrlExpanded={setUrlExpanded}
404
+ onConnect={() => {
405
+ activateRunnerSession(parseRunnerInput(baseUrl), {
406
+ openMiniapp: true,
407
+ }).catch(showActionError);
408
+ }}
409
+ onRefresh={refreshRunner}
410
+ onOpenCurrent={() => {
411
+ openMiniapp().catch(error => {
412
+ console.error('[NebulaRunner] Failed to open miniapp', error);
413
+ });
414
+ }}
415
+ onSelectHistory={item => {
416
+ activateRunnerSession(
417
+ {
418
+ baseUrl: item.baseUrl,
419
+ bundleUrl: item.bundleUrl ?? null,
420
+ manifestUrl: item.manifestUrl ?? null,
421
+ },
422
+ { openMiniapp: true },
423
+ ).catch(showActionError);
424
+ }}
425
+ />
426
+ </SafeAreaView>
427
+ );
428
+ }
429
+
430
+ function RunnerApp() {
431
+ return (
432
+ <SafeAreaProvider>
433
+ <RunnerContent />
434
+ </SafeAreaProvider>
435
+ );
436
+ }
437
+
438
+ export default NebulaAPI.wrap({
439
+ hostApis: defaultHostApis,
440
+ miniappLoading: {
441
+ component: DefaultMiniappLoadingScreen,
442
+ delayMs: 300,
443
+ enterContentDelayMs: 120,
444
+ },
445
+ })(RunnerApp);
446
+
447
+ const styles = StyleSheet.create({
448
+ screen: {
449
+ flex: 1,
450
+ backgroundColor: '#f4f2ff',
451
+ },
452
+ loadingOverlay: {
453
+ flex: 1,
454
+ backgroundColor: 'rgba(15, 23, 42, 0.14)',
455
+ alignItems: 'center',
456
+ justifyContent: 'center',
457
+ padding: 24,
458
+ },
459
+ loadingCard: {
460
+ width: '100%',
461
+ maxWidth: 420,
462
+ borderRadius: 24,
463
+ backgroundColor: '#ffffff',
464
+ padding: 24,
465
+ gap: 10,
466
+ },
467
+ loadingEyebrow: {
468
+ fontSize: 12,
469
+ fontWeight: '700',
470
+ letterSpacing: 1.2,
471
+ color: '#2563eb',
472
+ textTransform: 'uppercase',
473
+ },
474
+ loadingIndicator: {
475
+ marginTop: 4,
476
+ },
477
+ loadingIcon: {
478
+ width: 56,
479
+ height: 56,
480
+ borderRadius: 14,
481
+ },
482
+ loadingTitle: {
483
+ fontSize: 24,
484
+ fontWeight: '700',
485
+ color: '#0f172a',
486
+ },
487
+ loadingMessage: {
488
+ fontSize: 15,
489
+ lineHeight: 22,
490
+ color: '#475569',
491
+ },
492
+ loadingMeta: {
493
+ fontSize: 13,
494
+ color: '#64748b',
495
+ },
496
+ loadingCloseButton: {
497
+ marginTop: 8,
498
+ borderRadius: 999,
499
+ backgroundColor: '#0f172a',
500
+ paddingVertical: 12,
501
+ paddingHorizontal: 18,
502
+ alignItems: 'center',
503
+ },
504
+ loadingCloseButtonPressed: {
505
+ opacity: 0.88,
506
+ },
507
+ loadingCloseButtonDisabled: {
508
+ opacity: 0.6,
509
+ },
510
+ loadingCloseButtonText: {
511
+ color: '#ffffff',
512
+ fontSize: 14,
513
+ fontWeight: '600',
514
+ },
515
+ });
@@ -0,0 +1,16 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
4
+ ruby ">= 2.6.10"
5
+
6
+ # Exclude problematic versions of cocoapods and activesupport that causes build failures.
7
+ gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1'
8
+ gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0'
9
+ gem 'xcodeproj', '< 1.26.0'
10
+ gem 'concurrent-ruby', '< 1.3.4'
11
+
12
+ # Ruby 3.4.0 has removed some libraries from the standard library.
13
+ gem 'bigdecimal'
14
+ gem 'logger'
15
+ gem 'benchmark'
16
+ gem 'mutex_m'
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @format
3
+ */
4
+
5
+ import React from 'react';
6
+ import ReactTestRenderer from 'react-test-renderer';
7
+ import App from '../App';
8
+
9
+ test('renders correctly', async () => {
10
+ await ReactTestRenderer.act(() => {
11
+ ReactTestRenderer.create(<App />);
12
+ });
13
+ });
@@ -0,0 +1,55 @@
1
+ apply plugin: "com.android.application"
2
+ apply plugin: "org.jetbrains.kotlin.android"
3
+ apply plugin: "com.facebook.react"
4
+
5
+ react {
6
+ autolinkLibrariesWithApp()
7
+ }
8
+
9
+ def enableProguardInReleaseBuilds = false
10
+ def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'
11
+
12
+ android {
13
+ ndkVersion rootProject.ext.ndkVersion
14
+ buildToolsVersion rootProject.ext.buildToolsVersion
15
+ compileSdk rootProject.ext.compileSdkVersion
16
+
17
+ namespace "__BUNDLE_ID__"
18
+ defaultConfig {
19
+ applicationId "__BUNDLE_ID__"
20
+ minSdkVersion rootProject.ext.minSdkVersion
21
+ targetSdkVersion rootProject.ext.targetSdkVersion
22
+ versionCode 1
23
+ versionName "1.0"
24
+ manifestPlaceholders = [nebulaUrlScheme: "devrunner"]
25
+ }
26
+ signingConfigs {
27
+ debug {
28
+ storeFile file('debug.keystore')
29
+ storePassword 'android'
30
+ keyAlias 'androiddebugkey'
31
+ keyPassword 'android'
32
+ }
33
+ }
34
+ buildTypes {
35
+ debug {
36
+ signingConfig signingConfigs.debug
37
+ }
38
+ release {
39
+ signingConfig signingConfigs.debug
40
+ minifyEnabled enableProguardInReleaseBuilds
41
+ proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
42
+ }
43
+ }
44
+ }
45
+
46
+ dependencies {
47
+ implementation("com.facebook.react:react-android")
48
+ implementation("androidx.appcompat:appcompat:1.7.1")
49
+
50
+ if (hermesEnabled.toBoolean()) {
51
+ implementation("com.facebook.react:hermes-android")
52
+ } else {
53
+ implementation jscFlavor
54
+ }
55
+ }
@@ -0,0 +1,10 @@
1
+ # Add project specific ProGuard rules here.
2
+ # By default, the flags in this file are appended to flags specified
3
+ # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
4
+ # You can edit the include path and order by changing the proguardFiles
5
+ # directive in build.gradle.
6
+ #
7
+ # For more details, see
8
+ # http://developer.android.com/guide/developing/tools/proguard.html
9
+
10
+ # Add any project specific keep options here:
@@ -0,0 +1,60 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+
3
+ <uses-permission android:name="android.permission.INTERNET" />
4
+ <uses-permission android:name="android.permission.CAMERA" />
5
+ <uses-permission android:name="android.permission.DETECT_SCREEN_CAPTURE" />
6
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
7
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
8
+ <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
9
+ <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
10
+ <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
11
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
12
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
13
+
14
+ <application
15
+ android:name=".MainApplication"
16
+ android:label="@string/app_name"
17
+ android:icon="@mipmap/ic_launcher"
18
+ android:roundIcon="@mipmap/ic_launcher_round"
19
+ android:allowBackup="false"
20
+ android:theme="@style/AppTheme"
21
+ android:usesCleartextTraffic="${usesCleartextTraffic}"
22
+ android:supportsRtl="true">
23
+ <activity
24
+ android:name=".MainActivity"
25
+ android:label="@string/app_name"
26
+ android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
27
+ android:launchMode="singleTask"
28
+ android:windowSoftInputMode="adjustResize"
29
+ android:exported="true">
30
+ <intent-filter>
31
+ <action android:name="android.intent.action.MAIN" />
32
+ <category android:name="android.intent.category.LAUNCHER" />
33
+ </intent-filter>
34
+ <intent-filter>
35
+ <action android:name="android.intent.action.VIEW" />
36
+ <category android:name="android.intent.category.DEFAULT" />
37
+ <category android:name="android.intent.category.BROWSABLE" />
38
+ <data android:scheme="${nebulaUrlScheme}" android:host="review" android:pathPrefix="/install/" />
39
+ <data android:scheme="${nebulaUrlScheme}" android:host="miniapp" android:pathPrefix="/install/" />
40
+ </intent-filter>
41
+ <intent-filter>
42
+ <action android:name="android.intent.action.VIEW" />
43
+ <category android:name="android.intent.category.DEFAULT" />
44
+ <category android:name="android.intent.category.BROWSABLE" />
45
+ <data android:scheme="devrunner" android:host="open" />
46
+ </intent-filter>
47
+ </activity>
48
+ <activity
49
+ android:name="com.hectorzhuang.nebula.NebulaActivity"
50
+ android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
51
+ android:windowSoftInputMode="adjustResize"
52
+ android:exported="false" />
53
+ <activity
54
+ android:name="com.hectorzhuang.nebula.NebulaHostModalActivity"
55
+ android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
56
+ android:theme="@style/NebulaHostModalTheme"
57
+ android:windowSoftInputMode="adjustResize"
58
+ android:exported="false" />
59
+ </application>
60
+ </manifest>
@@ -0,0 +1,14 @@
1
+ package com.devrunner
2
+
3
+ import com.facebook.react.ReactActivity
4
+ import com.facebook.react.ReactActivityDelegate
5
+ import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
6
+ import com.facebook.react.defaults.DefaultReactActivityDelegate
7
+
8
+ class MainActivity : ReactActivity() {
9
+
10
+ override fun getMainComponentName(): String = "__COMPONENT_NAME__"
11
+
12
+ override fun createReactActivityDelegate(): ReactActivityDelegate =
13
+ DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
14
+ }