diff --git a/apps/bare-expo/ios/Podfile.lock b/apps/bare-expo/ios/Podfile.lock index 6f5987ec4664a9..7992007135252a 100644 --- a/apps/bare-expo/ios/Podfile.lock +++ b/apps/bare-expo/ios/Podfile.lock @@ -3854,7 +3854,7 @@ SPEC CHECKSUMS: EXUpdates: 83e4d666a085b44149f3b21d5bd057ad37b2c3e5 EXUpdatesInterface: 1436757deb0d574b84bba063bd024c315e0ec08b FBLazyVector: 3a7ea85f6009224ad89f7daeda516f189e6b5759 - hermes-engine: f631dcabc3dc2d46dc5f32c6c79d48d1d9e9aac6 + hermes-engine: 6bb3000824be2770010ae038914fa26721255c8e libavif: 84bbb62fb232c3018d6f1bab79beea87e35de7b7 libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 @@ -3872,7 +3872,7 @@ SPEC CHECKSUMS: React: 4bc1f928568ad4bcfd147260f907b4ea5873a03b React-callinvoker: 8dd44c31888a314694efd0ef5f19ab7e0e855ef8 React-Core: 0c1f3042e1204c0512b2e4263062c66550d7e6a3 - React-Core-prebuilt: baa77ffa5636202bd80ae54a374df8b194f96ed6 + React-Core-prebuilt: 7894b037a2f0fa699a44de6c88d20acd4235b255 React-CoreModules: f6a626221d52f21b5eb8df2d79780b96f20240e5 React-cxxreact: 2e3990595049d43dd1d59ccd6cb35545f0dc6f03 React-debug: 60be0767f5672afc81bfd6a50d996507430f7906 @@ -3942,7 +3942,7 @@ SPEC CHECKSUMS: ReactAppDependencyProvider: bfb12ead469222b022a2024f32aba47ce50de512 ReactCodegen: b9b0ea5c72e425fa5a89a031ada3e64dfd839771 ReactCommon: 0084791d25c4deae3d8b77efd4440fb2d5c38746 - ReactNativeDependencies: c5e58d6ef1758f36ac4ee8b97949e0fb78f31b21 + ReactNativeDependencies: 17a617edb4d5883a4c48339514ccb8b765f8af4f RNCAsyncStorage: e85a99325df9eb0191a6ee2b2a842644c7eb29f4 RNCMaskedView: 3c9d7586e2b9bbab573591dcb823918bc4668005 RNCPicker: e0149590451d5eae242cf686014a6f6d808f93c7 diff --git a/apps/expo-go/android/expoview/src/main/java/versioned/host/exp/exponent/ExperiencePackagePicker.kt b/apps/expo-go/android/expoview/src/main/java/versioned/host/exp/exponent/ExperiencePackagePicker.kt index 8ff0b2d778ae25..4fb6183399b87a 100644 --- a/apps/expo-go/android/expoview/src/main/java/versioned/host/exp/exponent/ExperiencePackagePicker.kt +++ b/apps/expo-go/android/expoview/src/main/java/versioned/host/exp/exponent/ExperiencePackagePicker.kt @@ -59,6 +59,7 @@ import expo.modules.notifications.notifications.channels.NotificationChannelMana import expo.modules.notifications.permissions.NotificationPermissionsModule import expo.modules.notifications.tokens.PushTokenModule import expo.modules.print.PrintModule +import expo.modules.router.ExpoRouterModule import expo.modules.screencapture.ScreenCaptureModule import expo.modules.screenorientation.ScreenOrientationModule import expo.modules.sensors.modules.AccelerometerModule @@ -154,6 +155,7 @@ object ExperiencePackagePicker : ModulesProvider { ExpoFetchModule::class.java to null, FontUtilsModule::class.java to null, ExpoLinkingModule::class.java to null, + ExpoRouterModule::class.java to null, FileSystemModule::class.java to null, FileSystemLegacyModule::class.java to null, FontLoaderModule::class.java to null, diff --git a/apps/expo-go/android/settings.gradle b/apps/expo-go/android/settings.gradle index 9d2a00012493c7..049d38a5b09726 100644 --- a/apps/expo-go/android/settings.gradle +++ b/apps/expo-go/android/settings.gradle @@ -43,7 +43,8 @@ expoAutolinking.exclude = [ '@expo/ui', 'expo-mesh-gradient', '@expo/app-integrity', - '@expo/home' + '@expo/home', + 'expo-widgets' ] expoAutolinking.useExpoModules() diff --git a/apps/expo-go/ios/Client/SwiftUI/HomeTabView.swift b/apps/expo-go/ios/Client/SwiftUI/HomeTabView.swift index 2463ef3b161652..d25134d8473b19 100644 --- a/apps/expo-go/ios/Client/SwiftUI/HomeTabView.swift +++ b/apps/expo-go/ios/Client/SwiftUI/HomeTabView.swift @@ -70,13 +70,9 @@ struct HomeTabView: View { } } .onAppear { - viewModel.onViewWillAppear() reviewManager.recordHomeAppear() reviewManager.updateCounts(apps: viewModel.projects.count, snacks: viewModel.snacks.count) } - .onDisappear { - viewModel.onViewDidDisappear() - } .onChange(of: viewModel.projects.count) { _ in reviewManager.updateCounts(apps: viewModel.projects.count, snacks: viewModel.snacks.count) } diff --git a/apps/expo-go/ios/Client/SwiftUI/Services/DataService.swift b/apps/expo-go/ios/Client/SwiftUI/Services/DataService.swift index d77f01733f6475..c30be9afb72f15 100644 --- a/apps/expo-go/ios/Client/SwiftUI/Services/DataService.swift +++ b/apps/expo-go/ios/Client/SwiftUI/Services/DataService.swift @@ -1,6 +1,7 @@ // Copyright 2015-present 650 Industries. All rights reserved. import Foundation + @MainActor class DataService: ObservableObject { @Published var projects: [ExpoProject] = [] diff --git a/apps/expo-go/ios/Client/SwiftUI/Services/DevelopmentServerService.swift b/apps/expo-go/ios/Client/SwiftUI/Services/DevelopmentServerService.swift index a125bcf26eeeb5..d77a88b53156e8 100644 --- a/apps/expo-go/ios/Client/SwiftUI/Services/DevelopmentServerService.swift +++ b/apps/expo-go/ios/Client/SwiftUI/Services/DevelopmentServerService.swift @@ -7,7 +7,7 @@ import UIKit class DevelopmentServerService: ObservableObject { @Published var developmentServers: [DevelopmentServer] = [] - private let discoveryInterval: TimeInterval = 2.0 + private let discoveryInterval: TimeInterval = 3.0 private let remoteRefreshInterval: TimeInterval = 10.0 private let remoteCacheKey = "expo-dev-sessions-cache" private var remoteFailureCount = 0 @@ -35,8 +35,8 @@ class DevelopmentServerService: ObservableObject { } func setSessionSecret(_ sessionSecret: String?) { + guard self.sessionSecret != sessionSecret else { return } self.sessionSecret = sessionSecret - startRemoteRefreshLoop() } func refreshRemoteSessions() async { @@ -125,36 +125,27 @@ class DevelopmentServerService: ObservableObject { } private func fetchLocalManifestInfo(url: String) async -> (name: String?, iconUrl: String?)? { - let manifestPaths = [ - "\(url)/manifest", - "\(url)/manifest?platform=ios" - ] - - for manifestPath in manifestPaths { - guard let manifestURL = URL(string: manifestPath) else { - continue - } + guard let manifestURL = URL(string: "\(url)/manifest?platform=ios") else { + return nil + } - var request = URLRequest(url: manifestURL) - request.setValue("application/expo+json,application/json", forHTTPHeaderField: "Accept") - request.setValue("ios", forHTTPHeaderField: "Expo-Platform") - request.setValue("client", forHTTPHeaderField: "Expo-Client-Environment") - request.setValue(Versions.sharedInstance.sdkVersion, forHTTPHeaderField: "Expo-SDK-Version") - - do { - let (data, response) = try await URLSession.shared.data(for: request) - guard let httpResponse = response as? HTTPURLResponse, - (200..<300).contains(httpResponse.statusCode) else { - continue - } + var request = URLRequest(url: manifestURL) + request.setValue("application/expo+json,application/json", forHTTPHeaderField: "Accept") + request.setValue("ios", forHTTPHeaderField: "Expo-Platform") + request.setValue("client", forHTTPHeaderField: "Expo-Client-Environment") + request.setValue(Versions.sharedInstance.sdkVersion, forHTTPHeaderField: "Expo-SDK-Version") - if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] { - if let parsed = parseManifestInfo(json: json, baseUrl: url) { - return parsed - } - } - } catch {} - } + do { + let (data, response) = try await URLSession.shared.data(for: request) + guard let httpResponse = response as? HTTPURLResponse, + (200..<300).contains(httpResponse.statusCode) else { + return nil + } + + if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] { + return parseManifestInfo(json: json, baseUrl: url) + } + } catch {} return nil } diff --git a/apps/expo-go/ios/Exponent/Kernel/Core/EXKernel.m b/apps/expo-go/ios/Exponent/Kernel/Core/EXKernel.m index f7d789f12df5fb..863dcc55946deb 100644 --- a/apps/expo-go/ios/Exponent/Kernel/Core/EXKernel.m +++ b/apps/expo-go/ios/Exponent/Kernel/Core/EXKernel.m @@ -207,12 +207,12 @@ - (void)viewController:(__unused EXViewController *)vc didNavigateAppToVisible:( [appStateModule setState:@"active"]; } _visibleApp = appRecord; + [self _unregisterUnusedAppRecords]; } else { _visibleApp = nil; - } - - if (_visibleApp != nil) { - [self _unregisterUnusedAppRecords]; + if (appRecordPreviouslyVisible) { + [_appRegistry unregisterAppWithRecord:appRecordPreviouslyVisible]; + } } } } diff --git a/apps/expo-go/ios/Exponent/Kernel/ReactAppManager/EXReactAppExceptionHandler.m b/apps/expo-go/ios/Exponent/Kernel/ReactAppManager/EXReactAppExceptionHandler.m index 63d8759df0e1ad..6242e80bf9004e 100644 --- a/apps/expo-go/ios/Exponent/Kernel/ReactAppManager/EXReactAppExceptionHandler.m +++ b/apps/expo-go/ios/Exponent/Kernel/ReactAppManager/EXReactAppExceptionHandler.m @@ -65,9 +65,7 @@ - (void)handleFatalJSExceptionWithMessage:(nullable NSString *)message [[EXKernel sharedInstance].serviceRegistry.errorRecoveryManager setError:error forScopeKey:_appRecord.scopeKey]; - if ([self _isProdHome]) { - RCTFatal(error); - } + [_appRecord.viewController maybeShowError:error]; } - (void)updateJSExceptionWithMessage:(nullable NSString *)message diff --git a/apps/expo-go/ios/Exponent/Kernel/Views/EXAppViewController.mm b/apps/expo-go/ios/Exponent/Kernel/Views/EXAppViewController.mm index 05b0f3312e23cd..83170f5615c404 100644 --- a/apps/expo-go/ios/Exponent/Kernel/Views/EXAppViewController.mm +++ b/apps/expo-go/ios/Exponent/Kernel/Views/EXAppViewController.mm @@ -192,10 +192,6 @@ - (void)maybeShowError:(NSError *)error dispatch_async(dispatch_get_main_queue(), ^{ [self _showErrorWithType:kEXFatalErrorTypeLoading error:error]; }); - } else if ([domain isEqualToString:@"JSServer"] && [_appRecord.appManager enablesDeveloperTools]) { - // RCTRedBox already handled this - } else if ([domain rangeOfString:RCTErrorDomain].length > 0 && [_appRecord.appManager enablesDeveloperTools]) { - // RCTRedBox already handled this } else { dispatch_async(dispatch_get_main_queue(), ^{ [self _showErrorWithType:kEXFatalErrorTypeException error:error]; diff --git a/apps/expo-go/ios/Podfile b/apps/expo-go/ios/Podfile index d12e5bcb865446..2e688cd646e867 100644 --- a/apps/expo-go/ios/Podfile +++ b/apps/expo-go/ios/Podfile @@ -52,7 +52,8 @@ target 'Expo Go' do 'expo-splash-screen', '@expo/ui', '@expo/app-integrity', - 'expo-brownfield' + 'expo-brownfield', + 'expo-widgets' ], includeTests: true, flags: { diff --git a/apps/expo-go/ios/Podfile.lock b/apps/expo-go/ios/Podfile.lock index b94849c7ad168a..fab722516f4d35 100644 --- a/apps/expo-go/ios/Podfile.lock +++ b/apps/expo-go/ios/Podfile.lock @@ -4730,7 +4730,7 @@ SPEC CHECKSUMS: GoogleAppMeasurement: 8a82b93a6400c8e6551c0bcd66a9177f2e067aed GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d - hermes-engine: 21f7021a3364f5f9dab02bdfd1fa0e21053e5dd5 + hermes-engine: 452f2dd7422b2fd7973ae9ca103898d28d7744f0 libavif: 84bbb62fb232c3018d6f1bab79beea87e35de7b7 libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 diff --git a/apps/native-component-list/metro.config.js b/apps/native-component-list/metro.config.js index 514ddb81cbc693..845b06ad6e3ad2 100644 --- a/apps/native-component-list/metro.config.js +++ b/apps/native-component-list/metro.config.js @@ -16,6 +16,7 @@ config.watchFolders = [ path.join(monorepoRoot, 'node_modules'), // Allow Metro to resolve "shared" `node_modules` of the monorepo path.join(monorepoRoot, 'apps/common'), // Allow Metro to resolve common ThemeProvider path.join(monorepoRoot, 'apps/bare-expo/modules/benchmarking'), // Allow Metro to resolve benchmarking folder + path.join(monorepoRoot, 'apps/bare-expo/modules/worklets-tester'), // Allow Metro to resolve worklets-tester folder path.join(monorepoRoot, 'apps/test-suite'), // Allow Metro to resolve test-suite app ]; diff --git a/docs/components/plugins/api/APISectionComponents.tsx b/docs/components/plugins/api/APISectionComponents.tsx index 2a0922eb8188c6..bae42bbc780e82 100644 --- a/docs/components/plugins/api/APISectionComponents.tsx +++ b/docs/components/plugins/api/APISectionComponents.tsx @@ -8,9 +8,10 @@ import { CommentData, GeneratedData, PropsDefinitionData, - TypeSignaturesData, TypeDefinitionData, + TypeSignaturesData, } from './APIDataTypes'; +import { buildCompoundNameByComponent } from './APISectionCompoundNames'; import { APISectionDeprecationNote } from './APISectionDeprecationNote'; import APISectionProps from './APISectionProps'; import { @@ -88,7 +89,8 @@ const getComponentTypeParameters = ({ const renderComponent = ( { name, comment, type, extendedTypes, children, signatures }: GeneratedData, sdkVersion: string, - componentsProps?: PropsDefinitionData[] + componentsProps?: PropsDefinitionData[], + compoundNameByComponent?: Map ) => { const resolvedSignatures = getComponentSignatures({ signatures, type }); const resolvedType = getComponentType({ signatures: resolvedSignatures }); @@ -97,7 +99,8 @@ const renderComponent = ( extendedTypes, signatures: resolvedSignatures, }); - const resolvedName = getComponentName(name, children); + const baseName = getComponentName(name, children); + const resolvedName = compoundNameByComponent?.get(baseName) ?? baseName; const extractedComment = getComponentComment(comment, resolvedSignatures); return ( @@ -128,7 +131,7 @@ const renderComponent = ( ) : null} @@ -136,8 +139,12 @@ const renderComponent = ( ); }; -const APISectionComponents = ({ data, sdkVersion, componentsProps }: APISectionComponentsProps) => - data?.length ? ( +const APISectionComponents = ({ data, sdkVersion, componentsProps }: APISectionComponentsProps) => { + if (!data?.length) { + return null; + } + const compoundNameByComponent = buildCompoundNameByComponent(data); + return ( <>

{data.length === 1 ? 'Component' : 'Components'}

{data.map(component => @@ -146,10 +153,12 @@ const APISectionComponents = ({ data, sdkVersion, componentsProps }: APISectionC sdkVersion, componentsProps.filter(cp => getPossibleComponentPropsNames(component.name, component.children).includes(cp.name) - ) + ), + compoundNameByComponent ) )} - ) : null; + ); +}; export default APISectionComponents; diff --git a/docs/components/plugins/api/APISectionCompoundNames.test.ts b/docs/components/plugins/api/APISectionCompoundNames.test.ts new file mode 100644 index 00000000000000..b4d8581abc53d6 --- /dev/null +++ b/docs/components/plugins/api/APISectionCompoundNames.test.ts @@ -0,0 +1,66 @@ +import { GeneratedData, PropData, TypeDefinitionData, TypeDocKind } from './APIDataTypes'; +import { buildCompoundNameByComponent } from './APISectionCompoundNames'; + +const makeComponentType = (propsName: string): TypeDefinitionData => ({ + type: 'reference', + name: 'React.FC', + typeArguments: [{ type: 'reference', name: propsName }], +}); + +const makeProp = (name: string, type?: TypeDefinitionData, defaultValue?: string): PropData => + ({ + name, + kind: TypeDocKind.Property, + type, + defaultValue, + }) as PropData; + +const makeComponent = (name: string, propsName: string, children: PropData[] = []): GeneratedData => + ({ + name, + kind: TypeDocKind.Class, + type: makeComponentType(propsName), + children, + }) as GeneratedData; + +describe('buildCompoundNameByComponent', () => { + test('maps direct component properties to compound names', () => { + const menuItem = makeComponent('MenuItem', 'MenuItemProps'); + const menu = makeComponent('Menu', 'MenuProps', [ + makeProp('Item', makeComponentType('MenuItemProps')), + ]); + + const result = Object.fromEntries(buildCompoundNameByComponent([menu, menuItem])); + + expect(result).toEqual({ + MenuItem: 'Menu.Item', + }); + }); + + test('chains compound names when parent is itself a compound component', () => { + const menuItemIcon = makeComponent('MenuItemIcon', 'MenuItemIconProps'); + const menuItem = makeComponent('MenuItem', 'MenuItemProps', [ + makeProp('Icon', makeComponentType('MenuItemIconProps')), + ]); + const menu = makeComponent('Menu', 'MenuProps', [ + makeProp('Item', makeComponentType('MenuItemProps')), + ]); + + const result = Object.fromEntries(buildCompoundNameByComponent([menu, menuItem, menuItemIcon])); + + expect(result).toEqual({ + MenuItem: 'Menu.Item', + MenuItemIcon: 'Menu.Item.Icon', + }); + }); + + test('uses string default values as component targets', () => { + const tabs = makeComponent('Tabs', 'TabsProps', [makeProp('Tab', undefined, 'Tab')]); + + const result = Object.fromEntries(buildCompoundNameByComponent([tabs])); + + expect(result).toEqual({ + Tab: 'Tabs.Tab', + }); + }); +}); diff --git a/docs/components/plugins/api/APISectionCompoundNames.ts b/docs/components/plugins/api/APISectionCompoundNames.ts new file mode 100644 index 00000000000000..afae562c8e5059 --- /dev/null +++ b/docs/components/plugins/api/APISectionCompoundNames.ts @@ -0,0 +1,145 @@ +import { GeneratedData, PropData, TypeDefinitionData, TypeDocKind } from './APIDataTypes'; +import { getComponentName } from './APISectionUtils'; + +const componentTypeNames = new Set([ + 'React.FC', + 'FC', + 'ForwardRefExoticComponent', + 'React.ForwardRefExoticComponent', + 'ComponentType', + 'React.ComponentType', + 'NamedExoticComponent', + 'React.NamedExoticComponent', +]); + +const getComponentPropertyChildren = (entry: GeneratedData): PropData[] => { + const candidates: PropData[] = []; + const pushChildren = (children?: PropData[]) => { + if (children?.length) { + candidates.push(...children); + } + }; + + if ('children' in entry) { + pushChildren(entry.children as PropData[] | undefined); + } + pushChildren(entry.type?.declaration?.children); + entry.type?.types?.forEach(type => { + pushChildren(type.declaration?.children); + }); + + if (candidates.length === 0) { + return []; + } + + const seen = new Set(); + return candidates.filter(child => { + if (!child || child.kind !== TypeDocKind.Property) { + return false; + } + const id = `${child.name ?? ''}-${child.kind ?? ''}`; + if (seen.has(id)) { + return false; + } + seen.add(id); + return true; + }); +}; + +const getPropsTypeNameFromComponentType = (type?: TypeDefinitionData): string | undefined => { + if (!type) { + return undefined; + } + if (type.type === 'reference') { + const typeName = type.name ?? type.target?.qualifiedName; + if (!typeName || !componentTypeNames.has(typeName)) { + return undefined; + } + const propsType = type.typeArguments?.[0]; + return propsType?.type === 'reference' ? propsType.name : undefined; + } + if (type.type === 'intersection' || type.type === 'union') { + for (const nested of type.types ?? []) { + const found = getPropsTypeNameFromComponentType(nested); + if (found) { + return found; + } + } + } + return undefined; +}; + +export const buildCompoundNameByComponent = (components: GeneratedData[]) => { + const componentNameByPropsType = new Map(); + const propertiesByEntry = new Map(); + const baseNameByEntry = new Map(); + + components.forEach(entry => { + const baseName = getComponentName(entry.name, entry.children); + const propsTypeName = getPropsTypeNameFromComponentType(entry.type); + if (propsTypeName && baseName) { + componentNameByPropsType.set(propsTypeName, baseName); + } + if (baseName) { + baseNameByEntry.set(entry, baseName); + } + propertiesByEntry.set(entry, getComponentPropertyChildren(entry)); + }); + + const resolveComponentFromProperty = (prop: PropData) => { + if (typeof prop.defaultValue === 'string') { + return prop.defaultValue; + } + const propsTypeName = getPropsTypeNameFromComponentType(prop.type); + if (!propsTypeName) { + return undefined; + } + return componentNameByPropsType.get(propsTypeName); + }; + + const directMap = new Map(); + + components.forEach(entry => { + const parentName = baseNameByEntry.get(entry); + if (!parentName) { + return; + } + const properties = propertiesByEntry.get(entry) ?? []; + properties.forEach(property => { + if (!property.name) { + return; + } + const target = resolveComponentFromProperty(property); + if (!target) { + return; + } + directMap.set(target, `${parentName}.${property.name}`); + }); + }); + + const compoundMap = new Map(directMap); + + components.forEach(entry => { + const parentName = baseNameByEntry.get(entry); + if (!parentName) { + return; + } + const parentAlias = directMap.get(parentName); + if (!parentAlias) { + return; + } + const properties = propertiesByEntry.get(entry) ?? []; + properties.forEach(property => { + if (!property.name) { + return; + } + const target = resolveComponentFromProperty(property); + if (!target) { + return; + } + compoundMap.set(target, `${parentAlias}.${property.name}`); + }); + }); + + return compoundMap; +}; diff --git a/docs/constants/navigation.js b/docs/constants/navigation.js index bc327a0cafd0bb..0e51215a52c5c6 100644 --- a/docs/constants/navigation.js +++ b/docs/constants/navigation.js @@ -177,7 +177,7 @@ export const general = [ { expanded: false } ), makeGroup( - 'Compile locally', + 'Build locally', [ makePage('guides/local-app-overview.mdx'), makePage('guides/local-app-development.mdx'), @@ -408,7 +408,17 @@ export const eas = [ makePage('eas/index.mdx'), makePage('eas/json.mdx'), makePage('eas/cli.mdx'), - makePage('eas/environment-variables.mdx'), + makeGroup( + 'Environment variables', + [ + makePage('eas/environment-variables/index.mdx'), + makePage('eas/environment-variables/manage.mdx'), + makePage('eas/environment-variables/usage.mdx'), + makePage('eas/environment-variables/without-eas.mdx'), + makePage('eas/environment-variables/faq.mdx'), + ], + { expanded: false } + ), ], { expanded: true, @@ -499,7 +509,6 @@ export const eas = [ makePage('eas/hosting/introduction.mdx'), makePage('eas/hosting/get-started.mdx'), makePage('eas/hosting/deployments-and-aliases.mdx'), - makePage('eas/hosting/environment-variables.mdx'), makePage('eas/hosting/custom-domain.mdx'), makePage('eas/hosting/api-routes.mdx'), makePage('eas/hosting/workflows.mdx'), diff --git a/docs/deploy.sh b/docs/deploy.sh index 3aa7ee38242ee0..59c8241ddb6398 100755 --- a/docs/deploy.sh +++ b/docs/deploy.sh @@ -377,6 +377,9 @@ redirects[router/reference/middleware]=router/web/middleware redirects[router/reference/static-rendering]=router/web/static-rendering redirects[router/reference/async-routes]=router/web/async-routes +# After creating EAS environment variables section +redirects[eas/hosting/environment-variables]=eas/environment-variables/usage/#using-environment-variables-with-eas-hosting + echo "::group::[5/6] Add custom redirects" for i in "${!redirects[@]}" # iterate over keys do diff --git a/docs/pages/eas-update/debug.mdx b/docs/pages/eas-update/debug.mdx index cb8d9fd5c5ef75..1ba0093cb57467 100644 --- a/docs/pages/eas-update/debug.mdx +++ b/docs/pages/eas-update/debug.mdx @@ -102,7 +102,7 @@ To diagnose the error causing the update crash: - See the [Troubleshooting guide on runtime issues](/debugging/runtime-issues/) to apply a strategy to identify the error. - After identifying the error, publish a new update that fixes the crash to resolve the issue. -A common reason a new update does not work but embedded code does is due to a missing environment variable. See [how environment variables work with EAS Update](/eas-update/environment-variables/) for more information. +A common reason a new update does not work but embedded code does is due to a missing environment variable. See [how environment variables work with EAS Update](/eas/environment-variables/usage/#using-environment-variables-with-eas-update) for more information. ### Failed to load all assets diff --git a/docs/pages/eas/environment-variables.mdx b/docs/pages/eas/environment-variables.mdx deleted file mode 100644 index 30941c23d7c22b..00000000000000 --- a/docs/pages/eas/environment-variables.mdx +++ /dev/null @@ -1,453 +0,0 @@ ---- -title: Environment variables in EAS -sidebar_title: Environment variables -description: Learn how to use and manage environment variables in EAS with examples. -searchRank: 8 ---- - -import { Collapsible } from '~/ui/components/Collapsible'; -import { ContentSpotlight } from '~/ui/components/ContentSpotlight'; -import { Terminal } from '~/ui/components/Snippet'; -import { CODE } from '~/ui/components/Text'; - -[Environment variables in Expo](/guides/environment-variables) describe how to use environment variables with the Expo framework and **.env** files to set environment variables that can be inlined in your JavaScript code. Expo CLI will substitute prefixed variables in your code (for example, `process.env.EXPO_PUBLIC_VARNAME`) with the corresponding environment variable values in **.env** files on your development machine. - -Since EAS Build and Workflows jobs run on a remote server, **.env** files may not be available. For instance, if **.env** files are excluded from your project, it is because they are listed in **.gitignore** or not committed to your local version control system. - -Additionally, you may want to use environment variables outside of your project code to customize your app binary at build time, like setting a bundle identifier or a private key for an error reporting service. To accommodate for those needs we have a separate (but compatible) mechanism for managing environment variables in EAS, which is focused on storing and managing environment variables on EAS servers and synchronizing them for local development. - -This guide describes how to use and manage environment variables in EAS with key practical examples. - -## Key concepts - - - -By default, all projects have three environments for environment variables: `development`, `preview` and `production`. On **Production** and **Enterprise** plans, you can also create custom environments to better organize your environment variables for complex workflows. Environments are independent sets of environment variables that can be used to customize your app in different contexts. For example, you might want to use different API keys for development and production, or different bundle identifiers for different App Store releases. Environments allows you to do so. Every EAS Build and Workflows job runs using environment variables from one of the available environments. You can also use environments for updates, allowing you to use the same set of environment variables for your build jobs. You can do this when publishing an update by providing the `--environment` flag. - -Environment variables can be assigned to multiple environments and have the same value across all of them, or be created for a single environment, so that you can set a specific value for a single environment. - - - - - -Project-wide environment variables are specific to a single EAS project. You can view and manage them by navigating to the [Environment Variables](https://expo.dev/accounts/[account]/projects/[project]/environment-variables) page on your project. - -These environment variables are available in any jobs that run on EAS servers and updates for this project. They can also be pulled locally for development if their visibility setting allows it. - - - - - -Account-wide environment variables are available across all of your projects in your EAS account. You can view and manage them by navigating to [Environment Variables](https://expo.dev/accounts/[account]/settings/environment-variables) page on your account. - -They are available in jobs that run on EAS servers and updates, together with project-wide variables for a project. You can pull them locally or read outside of EAS servers if their visibility setting allows it. - - - - - -There are three different visibility settings: - -| Visibility | Description | -| ---------- | --------------------------------------------------------------------------------------------------------------------------------------- | -| Plain text | Visible on the website, in EAS CLI, and in logs. | -| Sensitive | Obfuscated in build and workflow jobs logs. You can use a toggle to make them visible on the website. They're also readable in EAS CLI. | -| Secret | Not readable outside of the EAS servers, including on the website and in EAS CLI. They are obfuscated in build and workflow jobs logs. | - -> **warning** Always remember that **anything that is included in your client side code should be considered public and readable to any individual that can run the application**. Secret type environment variables are intended to be used to provide values to an EAS Build or Workflows job so that they may be used to alter how a job runs. For example, a good use case is setting an `NPM_TOKEN` to install private packages from npm, or a setting a Sentry API key to create a release and upload your source maps. Secrets do not provide any additional security for values that you end up embedding in your application itself. - - - -## Creating and using environment variables - -The sections below use the following common environment variables as examples: - -- `EXPO_PUBLIC_API_URL`: a plain text [`EXPO_PUBLIC_`](/guides/environment-variables/) variable that holds the URL of the API server -- `APP_VARIANT`: a plain text variable to select an [app variant](/tutorial/eas/multiple-app-variants/) -- `GOOGLE_SERVICES_JSON`: a secret file variable to supply your git ignored **google-services.json** file to the build job -- `SENTRY_AUTH_TOKEN`: a sensitive variable that holds the authentication token for Sentry used to upload source maps after builds and updates - -### Use environment variables in your code - -The environment variables with the [`EXPO_PUBLIC_`](/guides/environment-variables) prefix are available as `process.env` variables in your app's code. This means you can use them to dynamically configure your app behavior based on the values from environment variables. - -```tsx -import { Button } from 'react-native'; - -function Post() { - const apiUrl = process.env.EXPO_PUBLIC_API_URL; - - async function onPress() { - await fetch(apiUrl, { ... }) - } - - return