1import versions from '~/public/static/constants/versions.json'; 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.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 // Redirects after creating /home route 137 '/next-steps/additional-resources/': '/additional-resources/', 138 '/get-started/create-a-new-app/': '/get-started/create-a-project', 139 '/guides/config-plugins/': '/config-plugins/introduction/', 140 '/workflow/debugging/': '/debugging/runtime-issues/', 141 '/guides/userinterface/': '/ui-programming/user-interface-libraries/', 142 '/introduction/expo/': '/core-concepts/', 143 '/introduction/why-not-expo/': '/faq/#limitations', 144 '/introduction/faq/': '/faq/', 145 '/next-steps/community/': '/', 146 '/introduction/managed-vs-bare/': '/archive/managed-vs-bare/', 147 '/workflow/expo-go/': '/get-started/expo-go/', 148 '/guides/splash-screens/': '/develop/user-interface/splash-screen/', 149 '/guides/app-icons/': '/develop/user-interface/app-icons/', 150 '/guides/color-schemes/': '/develop/user-interface/color-themes/', 151 '/development/introduction/': '/develop/development-builds/introduction/', 152 '/development/create-development-builds/': '/develop/development-builds/create-a-build/', 153 '/development/use-development-builds/': '/develop/development-builds/use-development-builds/', 154 '/development/development-workflows/': '/develop/development-builds/development-workflows/', 155 '/workflow/expo-cli/': '/more/expo-cli/', 156 '/versions/latest/workflow/expo-cli/': '/more/expo-cli/', 157 '/debugging/': '/debugging/runtime-issues/', 158 '/debugging/runtime-issue/': '/debugging/runtime-issues/', 159 '/guides/testing-with-jest/': '/develop/unit-testing/', 160 '/workflow/glossary-of-terms/': '/more/glossary-of-terms/', 161 '/development/installation/': '/develop/development-builds/installation/', 162 '/get-started/errors/': '/debugging/errors-and-warnings/', 163 '/develop/development-builds/parallel-installation': '/build-reference/variants/', 164 165 // Redirects after organizing docs in Guides 166 '/bare/hello-world/': '/bare/overview/', 167 '/guides/errors/': '/debugging/runtime-issues/', 168 '/guides/using-graphql/': '/guides/overview/', 169 '/guides/using-styled-components/': '/guides/overview/', 170 '/guides/using-bugsnag/': '/guides/overview/', 171 '/build/automating-submissions/': '/build/automate-submissions/', 172 '/workflow/run-on-device/': '/build/internal-distribution/', 173 174 // EAS Build 175 '/build-reference/eas-json/': '/eas/json/#eas-build', 176 177 // Old redirects 178 '/introduction/project-lifecycle/': '/archive/managed-vs-bare/', 179 '/versions/latest/sdk/': '/versions/latest/', 180 '/versions/latest/sdk/overview/': '/versions/latest/', 181 '/guides/building-standalone-apps/': '/archive/classic-updates/building-standalone-apps/', 182 '/distribution/building-standalone-apps/': '/archive/classic-updates/building-standalone-apps/', 183 '/guides/genymotion/': '/workflow/android-studio-emulator/', 184 '/workflow/upgrading-expo/': '/workflow/upgrading-expo-sdk-walkthrough/', 185 '/workflow/create-react-native-app/': '/more/glossary-of-terms/#create-react-native-app', 186 '/expokit/': '/archive/glossary/#expokit/', 187 188 // Development builds redirects 189 '/development/build/': '/develop/development-builds/create-a-build/', 190 '/development/getting-started/': '/develop/development-builds/create-a-build/', 191 '/development/troubleshooting/': '/develop/development-builds/introduction/', 192 '/development/upgrading/': '/develop/development-builds/introduction/', 193 '/development/extensions/': '/develop/development-builds/development-workflows/', 194 '/development/develop-your-project': '/develop/development-builds/use-development-builds/', 195 196 // Consolidate workflow page 197 '/bare/customizing/': '/workflow/customizing/', 198 199 // Lots of old links pointing to guides when they have moved elsewhere 200 '/guides/configuration/': '/workflow/configuration/', 201 '/guides/expokit/': '/archive/glossary/#expokit/', 202 '/guides/publishing/': '/archive/classic-updates/publishing/', 203 '/workflow/publishing/': '/archive/classic-updates/publishing/', 204 '/guides/linking/': '/workflow/linking/', 205 '/guides/up-and-running/': '/get-started/installation/', 206 '/guides/debugging/': '/debugging/runtime-issues/', 207 '/guides/logging/': '/workflow/logging/', 208 '/introduction/troubleshooting-proxies/': '/guides/troubleshooting-proxies/', 209 '/introduction/running-in-the-browser/': '/guides/running-in-the-browser/', 210 '/guides/using-electron/': 211 'https://dev.to/evanbacon/making-desktop-apps-with-electron-react-native-and-expo-5e36', 212 213 // Changes from redoing the getting started workflow, SDK35+ 214 '/workflow/up-and-running/': '/get-started/installation/', 215 '/introduction/additional-resources/': '/next-steps/additional-resources/', 216 '/introduction/already-used-react-native/': 217 '/faq/#what-is-the-difference-between-expo-and-react-native', 218 '/introduction/community/': '/next-steps/community/', 219 '/introduction/installation/': '/get-started/installation/', 220 '/versions/latest/overview/': '/versions/latest/', 221 '/versions/latest/introduction/installation/': '/get-started/installation/', 222 '/workflow/exploring-managed-workflow/': '/tutorial/introduction/', 223 '/introduction/walkthrough/': '/tutorial/introduction/', 224 225 // Move overview to index 226 '/versions/v37.0.0/sdk/overview/': '/versions/v37.0.0/', 227 228 // Errors and debugging is better suited for getting started than tutorial 229 '/tutorial/errors/': '/debugging/errors-and-warnings/', 230 231 // Redirects based on Next 9 upgrade (09/11/2020) 232 '/api/': '/versions/latest/', 233 234 // Redirect to expand Expo Accounts and permissions 235 '/guides/account-permissions/': '/accounts/personal/', 236 237 // Redirects based on Sentry reports 238 '/next-steps/installation/': '/get-started/installation/', 239 '/guides/release-channels/': '/archive/classic-updates/release-channels/', 240 '/guides/push-notifications/': '/push-notifications/overview/', 241 '/push-notifications/': '/push-notifications/overview/', 242 '/distribution/hosting-your-app/': '/distribution/publishing-websites/', 243 '/build-reference/how-tos/': '/build-reference/private-npm-packages/', 244 '/get-started/': '/get-started/installation/', 245 '/guides/detach/': '/archive/glossary/#detach', 246 247 // Renaming a submit section 248 '/submit/submit-ios': '/submit/ios/', 249 '/submit/submit-android': '/submit/android/', 250 251 // Fundamentals had too many things 252 '/workflow/linking/': '/guides/linking/', 253 '/workflow/how-expo-works/': '/faq/#what-is-the-difference-between-expo-and-react-native', 254 '/guides/how-expo-works/': '/faq/#what-is-the-difference-between-expo-and-react-native', 255 256 // Archive unused pages 257 '/guides/notification-channels/': '/archived/notification-channels/', 258 259 // Migrated FAQ pages 260 '/faq/image-background/': '/ui-programming/image-background/', 261 '/faq/react-native-styling-buttons/': '/ui-programming/react-native-styling-buttons/', 262 '/faq/react-native-version-mismatch/': '/troubleshooting/react-native-version-mismatch/', 263 '/faq/clear-cache-windows/': '/troubleshooting/clear-cache-windows/', 264 '/faq/clear-cache-macos-linux/': '/troubleshooting/clear-cache-macos-linux/', 265 '/faq/application-has-not-been-registered/': 266 '/troubleshooting/application-has-not-been-registered/', 267 268 // Permissions API is moved to guide 269 '/versions/v40.0.0/sdk/permissions/': '/guides/permissions/', 270 '/versions/v41.0.0/sdk/permissions/': '/guides/permissions/', 271 '/versions/v42.0.0/sdk/permissions/': '/guides/permissions/', 272 '/versions/v43.0.0/sdk/permissions/': '/guides/permissions/', 273 '/versions/latest/sdk/permissions/': '/guides/permissions/', 274 275 // Redirect Gatsby guide to index guides page 276 '/guides/using-gatsby/': '/guides/', 277 278 // Classic updates moved to archive 279 '/guides/configuring-ota-updates/': '/archive/classic-updates/getting-started/', 280 '/guides/configuring-updates/': '/archive/classic-updates/getting-started/', 281 '/distribution/release-channels/': '/archive/classic-updates/release-channels/', 282 '/distribution/advanced-release-channels/': '/archive/classic-updates/advanced-release-channels/', 283 '/distribution/optimizing-updates/': '/archive/classic-updates/optimizing-updates/', 284 '/eas-update/custom-updates-server/': '/distribution/custom-updates-server/', 285 '/guides/offline-support/': '/archive/classic-updates/offline-support/', 286 '/guides/preloading-and-caching-assets/': 287 '/archive/classic-updates/preloading-and-caching-assets/', 288 '/eas-update/bare-react-native/': '/eas-update/updating-your-app/', 289 '/worfkflow/publishing/': '/archive/classic-updates/publishing/', 290 '/classic/building-standalone-apps/': '/archive/classic-updates/building-standalone-apps/', 291 '/classic/turtle-cli/': '/archive/classic-updates/turtle-cli/', 292 '/archive/classic-updates/getting-started/': '/eas-update/getting-started/', 293 294 // Redirect bare guides to unified workflow guides 295 '/bare/using-libraries/': '/workflow/using-libraries/', 296 '/bare/exploring-bare-workflow/': '/bare/overview/', 297 '/bare/existing-apps/': '/bare/installing-expo-modules/', 298 '/bare/installing-unimodules/': '/bare/installing-expo-modules/', 299 '/bare/using-web/': '/workflow/web/', 300 '/guides/running-in-the-browser/': '/workflow/web/', 301 '/bare/unimodules-full-list/': '/bare/overview/', 302 '/bare/updating-your-app/': '/eas-update/updating-your-app/', 303 304 // Consolidate distribution 305 '/distribution/security/': '/app-signing/security/', 306 '/distribution/uploading-apps/': '/submit/introduction/', 307 '/versions/latest/distribution/uploading-apps/': '/submit/introduction/', 308 309 // Deleted or removed guides 310 '/guides/using-clojurescript/': '/guides/', 311 312 // Redirects from old to new tutorial 313 '/tutorial/planning/': '/tutorial/introduction/', 314 '/tutorial/sharing/': '/tutorial/introduction/', 315 '/tutorial/text/': '/tutorial/introduction/', 316 317 // Redirects for removed /archived pages 318 '/archived/': '/archive/', 319 '/versions/latest/expokit/eject/': '/archive/glossary/#eject', 320 '/expokit/eject/': '/archive/glossary/#eject', 321 '/expokit/expokit/': '/archive/glossary/#expokit', 322 '/submit/classic-builds/': '/submit/introduction/', 323 '/archive/adhoc-builds/': '/develop/development-builds/introduction/', 324 325 // Redirects for removed API docs based on Sentry 326 '/versions/latest/sdk/facebook/': '/guides/authentication/', 327 '/versions/latest/sdk/taskmanager/': '/versions/latest/sdk/task-manager/', 328 '/versions/latest/sdk/videothumbnails/': '/versions/latest/sdk/video-thumbnails/', 329 '/versions/latest/sdk/appearance/': '/versions/latest/react-native/appearance/', 330 '/versions/latest/sdk/app-loading/': '/versions/latest/sdk/splash-screen/', 331 '/versions/latest/sdk/app-auth/': '/guides/authentication/', 332 '/versions/latest/sdk/google-sign-in/': '/guides/authentication/', 333 '/versions/latest/sdk/branch/': 334 'https://github.com/expo/config-plugins/tree/main/packages/react-native-branch', 335 '/versions/latest/sdk/appstate/': '/versions/latest/react-native/appstate/', 336 '/versions/latest/sdk/google/': '/guides/authentication/', 337 '/versions/latest/sdk/firebase-core/': '/guides/using-firebase/', 338 '/versions/latest/sdk/firebase-analytics/': '/guides/using-firebase/', 339 '/versions/latest/sdk/firebase-recaptcha/': '/guides/using-firebase/', 340 '/versions/latest/sdk/amplitude/': '/guides/using-analytics/', 341 '/versions/latest/sdk/util/': '/versions/latest/', 342 // Push notifications 343 '/config/app/': '/workflow/configuration/', 344 '/versions/latest/sdk/settings/': '/versions/latest/', 345 '/archive/expokit/eject/': '/archive/glossary/#eject', 346 '/versions/latest/sdk/admob/': '/versions/latest/', 347 '/versions/latest/sdk/payments/': '/versions/latest/sdk/stripe/', 348 '/distribution/app-icons/': '/develop/user-interface/app-icons/', 349 '/guides/using-libraries/': '/workflow/using-libraries/', 350 '/tutorial/': '/tutorial/introduction/', 351 352 // EAS Update 353 '/eas-update/developing-with-eas-update/': '/eas-update/develop-faster/', 354 '/eas-update/eas-update-with-local-build/': '/eas-update/build-locally/', 355 '/eas-update/eas-update-and-eas-cli/': '/eas-update/eas-cli/', 356 '/eas-update/debug-updates/': '/eas-update/debug/', 357 '/eas-update/how-eas-update-works/': '/eas-update/how-it-works/', 358 '/eas-update/migrate-to-eas-update/': '/eas-update/migrate-from-classic-updates/', 359 360 // Expo Router Advanced guides 361 '/router/advance/root-layout': '/router/advanced/root-layout/', 362 '/router/advance/stack': '/router/advanced/stack/', 363 '/router/advance/tabs': '/router/advanced/tabs/', 364 '/router/advance/drawer': '/router/advanced/drawer/', 365 '/router/advance/nesting-navigators': '/router/advanced/nesting-navigators/', 366 '/router/advance/modal': '/router/advanced/modals/', 367 '/router/advance/platform-specific-modules': '/router/advanced/platform-specific-modules/', 368 '/router/advance/shared-routes': '/router/advanced/shared-routes/', 369 '/router/advance/router-settings': '/router/advanced/router-settings/', 370 371 // Note (@aman): The following redirect is temporary until Guides section has an overview 372 '/guides/': '/workflow/customizing/', 373 '/archive/workflow/customizing/': '/workflow/customizing/', 374 '/errors-and-warnings/': '/debugging/errors-and-warnings/', 375}; 376