1import { VERSIONS } from '~/constants/versions'; 2 3export function getRedirectPath(redirectPath: string): string { 4 // index.html is no longer a thing in our docs 5 if (pathIncludesIndexHtml(redirectPath)) { 6 redirectPath = redirectPath.replace('index.html', ''); 7 } 8 9 // Remove the .html extension if it is included in the path 10 if (pathIncludesHtmlExtension(redirectPath)) { 11 redirectPath = redirectPath.replace('.html', ''); 12 } 13 14 // Unsure why this is happening, but sometimes URLs end up with /null in 15 // the last path part 16 // https://docs.expo.dev/versions/latest/sdk/overview/null 17 if (endsInNull(redirectPath)) { 18 redirectPath = redirectPath.replace(/null$/, ''); 19 } 20 21 // Add a trailing slash if there is not one 22 if (redirectPath[redirectPath.length - 1] !== '/') { 23 redirectPath = `${redirectPath}/`; 24 } 25 26 // A list of pages we know are renamed and can redirect 27 if (RENAMED_PAGES[redirectPath]) { 28 redirectPath = RENAMED_PAGES[redirectPath]; 29 } 30 31 // Catch any unversioned paths which are also renamed 32 if (isVersionedPath(redirectPath)) { 33 const unversionedPath = removeVersionFromPath(redirectPath); 34 if (RENAMED_PAGES[unversionedPath]) { 35 redirectPath = RENAMED_PAGES[unversionedPath]; 36 } 37 } 38 39 // Check if the version is documented, replace it with latest if not 40 if (!isVersionDocumented(redirectPath)) { 41 redirectPath = replaceVersionWithLatest(redirectPath); 42 } 43 44 // Remove versioning from path if this section is no longer versioned 45 if (isVersionedPath(redirectPath) && !pathRequiresVersioning(redirectPath)) { 46 redirectPath = removeVersionFromPath(redirectPath); 47 } 48 49 // Catch any redirects to sdk paths without versions and send to the latest version 50 if (redirectPath.startsWith('/sdk/')) { 51 redirectPath = `/versions/latest${redirectPath}`; 52 } 53 54 // If a page is missing for react-native paths we redirect to react-native docs 55 if (redirectPath.match(/\/versions\/.*\/react-native\//)) { 56 const pathParts = redirectPath.split('/'); 57 const page = pathParts[pathParts.length - 2]; 58 redirectPath = `https://reactnative.dev/docs/${page}`; 59 } 60 61 // Remove version from path if the version is still supported, to redirect to the root 62 if (isVersionedPath(redirectPath) && isVersionDocumented(redirectPath)) { 63 redirectPath = `/versions/${getVersionFromPath(redirectPath)}/`; 64 } 65 66 return redirectPath; 67} 68 69function getVersionFromPath(path: string) { 70 const pathParts = path.split(/\//); 71 // eg: ["", "versions", "v32.0.0", ""] 72 return pathParts[2]; 73} 74 75// Filter unversioned and latest out, so we end up with v34, etc. 76const supportedVersions = VERSIONS.filter(v => v.match(/^v/)); 77 78// Return true if the version is still included in documentation 79function isVersionDocumented(path: string) { 80 return supportedVersions.includes(getVersionFromPath(path)); 81} 82 83function pathIncludesHtmlExtension(path: string) { 84 return !!path.match(/\.html$/); 85} 86 87function pathIncludesIndexHtml(path: string) { 88 return !!path.match(/index\.html$/); 89} 90 91const VERSION_PART_PATTERN = `(latest|unversioned|v\\d+\\.\\d+.\\d+)`; 92const VERSIONED_PATH_PATTERN = `^\\/versions\\/${VERSION_PART_PATTERN}`; 93const SDK_PATH_PATTERN = `${VERSIONED_PATH_PATTERN}/sdk`; 94const REACT_NATIVE_PATH_PATTERN = `${VERSIONED_PATH_PATTERN}/react-native`; 95 96// Check if path is valid (matches /versions/some-valid-version-here/) 97function isVersionedPath(path: string) { 98 return !!path.match(new RegExp(VERSIONED_PATH_PATTERN)); 99} 100 101// Replace an unsupported SDK version with latest 102function replaceVersionWithLatest(path: string) { 103 return path.replace(new RegExp(VERSION_PART_PATTERN), 'latest'); 104} 105 106/** 107 * Determine if the path requires versioning, if not we can remove the versioned prefix from the path. 108 * The following paths require versioning: 109 * - `/versions/<version>/sdk/**`, pages within the Expo SDK docs. 110 * - `/versions/<version>/react-native/**`, pages within the React Native API docs. 111 * - `/versions/<version>/`, the index of a specific Expo SDK version. 112 * All other paths shouldn't require versioning, some of them are: 113 * - `/versions/<version>/workflow/expo-cli/, moved outside versioned folders. 114 * - `/versions/<version>/guides/assets/, moved outside versioned folders. 115 */ 116function pathRequiresVersioning(path: string) { 117 const isExpoSdkPage = path.match(new RegExp(SDK_PATH_PATTERN)); 118 const isExpoSdkIndexPage = path.match(new RegExp(VERSIONED_PATH_PATTERN + '/$')); 119 const isReactNativeApiPage = path.match(new RegExp(REACT_NATIVE_PATH_PATTERN)); 120 121 return isExpoSdkIndexPage || isExpoSdkPage || isReactNativeApiPage; 122} 123 124function removeVersionFromPath(path: string) { 125 return path.replace(new RegExp(VERSIONED_PATH_PATTERN), ''); 126} 127 128// Not sure why this happens but sometimes the URL ends in /null 129function endsInNull(path: string) { 130 return !!path.match(/\/null$/); 131} 132 133// Simple remapping of renamed pages, similar to in deploy.sh but in some cases, 134// for reasons I'm not totally clear on, those redirects do not work 135const RENAMED_PAGES: Record<string, string> = { 136 '/introduction/project-lifecycle/': '/introduction/managed-vs-bare/', 137 '/guides/': '/workflow/exploring-managed-workflow/', 138 '/versions/latest/sdk/': '/versions/latest/', 139 '/versions/latest/sdk/overview/': '/versions/latest/', 140 '/guides/building-standalone-apps/': '/distribution/building-standalone-apps/', 141 '/guides/genymotion/': '/workflow/android-studio-emulator/', 142 '/workflow/upgrading-expo/': '/workflow/upgrading-expo-sdk-walkthrough/', 143 '/workflow/create-react-native-app/': '/workflow/glossary-of-terms/#create-react-native-app', 144 '/expokit/': '/expokit/overview/', 145 '/guides/detach/': '/expokit/eject/', 146 '/expokit/detach/': '/expokit/eject/', 147 148 // Consolidate workflow page 149 '/bare/customizing/': '/workflow/customizing/', 150 151 // Lots of old links pointing to guides when they have moved elsewhere 152 '/guides/configuration/': '/workflow/configuration/', 153 '/guides/expokit/': '/expokit/overview/', 154 '/guides/publishing/': '/workflow/publishing/', 155 '/guides/linking/': '/workflow/linking/', 156 '/guides/up-and-running/': '/get-started/installation/', 157 '/guides/debugging/': '/workflow/debugging/', 158 '/guides/logging/': '/workflow/logging/', 159 '/introduction/troubleshooting-proxies/': '/guides/troubleshooting-proxies/', 160 '/introduction/running-in-the-browser/': '/guides/running-in-the-browser/', 161 162 // Changes from redoing the getting started workflow, SDK35+ 163 '/workflow/up-and-running/': '/get-started/installation/', 164 '/introduction/additional-resources/': '/next-steps/additional-resources/', 165 '/introduction/already-used-react-native/': '/workflow/already-used-react-native/', 166 '/introduction/community/': '/next-steps/community/', 167 '/introduction/installation/': '/get-started/installation/', 168 '/versions/latest/overview/': '/versions/latest/', 169 '/versions/latest/introduction/installation/': '/get-started/installation/', 170 '/workflow/exploring-managed-workflow/': '/introduction/walkthrough/', 171 172 // Move overview to index 173 '/versions/v37.0.0/sdk/overview/': '/versions/v37.0.0/', 174 175 // Errors and debugging is better suited for getting started than tutorial 176 '/tutorial/errors/': '/get-started/errors/', 177 178 // Additional redirects based on Sentry (04/28/2020) 179 '/next-steps/installation/': '/get-started/installation/', 180 '/guides/release-channels/': '/distribution/release-channels/', 181 182 // Redirects based on Next 9 upgrade (09/11/2020) 183 '/api/': '/versions/latest/', 184 185 // Redirect to expand Expo Accounts and permissions 186 '/guides/account-permissions/': '/accounts/personal/', 187 188 // Redirects based on Sentry (11/26/2020) 189 '/guides/push-notifications/': '/push-notifications/overview/', 190 '/guides/using-fcm/': '/push-notifications/using-fcm/', 191 192 // Renaming a submit section 193 '/submit/submit-ios': '/submit/ios/', 194 '/submit/submit-android': '/submit/android/', 195 196 // Fundamentals had too many things 197 '/workflow/linking/': '/guides/linking/', 198 '/workflow/how-expo-works/': '/guides/how-expo-works/', 199 200 // Archive unused pages 201 '/guides/notification-channels/': '/archived/notification-channels/', 202 203 // Migrated FAQ pages 204 '/faq/image-background/': '/ui-programming/image-background/', 205 '/faq/react-native-styling-buttons/': '/ui-programming/react-native-styling-buttons/', 206 '/faq/react-native-version-mismatch/': '/troubleshooting/react-native-version-mismatch/', 207 '/faq/clear-cache-windows/': '/troubleshooting/clear-cache-windows/', 208 '/faq/clear-cache-macos-linux/': '/troubleshooting/clear-cache-macos-linux/', 209 '/faq/application-has-not-been-registered/': 210 '/troubleshooting/application-has-not-been-registered/', 211 212 // Permissions API is moved to guide 213 '/versions/v40.0.0/sdk/permissions/': '/guides/permissions/', 214 '/versions/v41.0.0/sdk/permissions/': '/guides/permissions/', 215 '/versions/v42.0.0/sdk/permissions/': '/guides/permissions/', 216}; 217