xref: /expo/docs/common/error-utilities.ts (revision 4bf00a55)
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  '/guides/using-custom-fonts/': '/develop/user-interface/fonts/',
152  '/development/introduction/': '/develop/development-builds/introduction/',
153  '/development/create-development-builds/': '/develop/development-builds/create-a-build/',
154  '/development/use-development-builds/': '/develop/development-builds/use-development-builds/',
155  '/development/development-workflows/': '/develop/development-builds/development-workflows/',
156  '/workflow/expo-cli/': '/more/expo-cli/',
157  '/versions/latest/workflow/expo-cli/': '/more/expo-cli/',
158  '/debugging/': '/debugging/runtime-issues/',
159  '/debugging/runtime-issue/': '/debugging/runtime-issues/',
160  '/guides/testing-with-jest/': '/develop/unit-testing/',
161  '/workflow/glossary-of-terms/': '/more/glossary-of-terms/',
162  '/development/installation/': '/develop/development-builds/installation/',
163  '/get-started/errors/': '/debugging/errors-and-warnings/',
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  '/guides/routing-and-navigation/': '/routing/introduction/',
173  '/workflow/run-on-device/': '/build/internal-distribution/',
174
175  // Old redirects
176  '/introduction/project-lifecycle/': '/archive/managed-vs-bare/',
177  '/versions/latest/sdk/': '/versions/latest/',
178  '/versions/latest/sdk/overview/': '/versions/latest/',
179  '/guides/building-standalone-apps/': '/archive/classic-updates/building-standalone-apps/',
180  '/distribution/building-standalone-apps/': '/archive/classic-updates/building-standalone-apps/',
181  '/guides/genymotion/': '/workflow/android-studio-emulator/',
182  '/workflow/upgrading-expo/': '/workflow/upgrading-expo-sdk-walkthrough/',
183  '/workflow/create-react-native-app/': '/more/glossary-of-terms/#create-react-native-app',
184  '/expokit/': '/archive/glossary/#expokit/',
185
186  // Development builds redirects
187  '/development/build/': '/develop/development-builds/create-a-build/',
188  '/development/getting-started/': '/develop/development-builds/create-a-build/',
189  '/development/troubleshooting/': '/develop/development-builds/introduction/',
190  '/development/upgrading/': '/develop/development-builds/introduction/',
191  '/development/extensions/': '/develop/development-builds/development-workflows/',
192  '/development/develop-your-project': '/develop/development-builds/use-development-builds/',
193
194  // Consolidate workflow page
195  '/bare/customizing/': '/workflow/customizing/',
196
197  // Lots of old links pointing to guides when they have moved elsewhere
198  '/guides/configuration/': '/workflow/configuration/',
199  '/guides/expokit/': '/archive/glossary/#expokit/',
200  '/guides/publishing/': '/archive/classic-updates/publishing/',
201  '/workflow/publishing/': '/archive/classic-updates/publishing/',
202  '/guides/linking/': '/workflow/linking/',
203  '/guides/up-and-running/': '/get-started/installation/',
204  '/guides/debugging/': '/debugging/runtime-issues/',
205  '/guides/logging/': '/workflow/logging/',
206  '/introduction/troubleshooting-proxies/': '/guides/troubleshooting-proxies/',
207  '/introduction/running-in-the-browser/': '/guides/running-in-the-browser/',
208  '/guides/using-electron/':
209    'https://dev.to/evanbacon/making-desktop-apps-with-electron-react-native-and-expo-5e36',
210
211  // Changes from redoing the getting started workflow, SDK35+
212  '/workflow/up-and-running/': '/get-started/installation/',
213  '/introduction/additional-resources/': '/next-steps/additional-resources/',
214  '/introduction/already-used-react-native/':
215    '/faq/#what-is-the-difference-between-expo-and-react-native',
216  '/introduction/community/': '/next-steps/community/',
217  '/introduction/installation/': '/get-started/installation/',
218  '/versions/latest/overview/': '/versions/latest/',
219  '/versions/latest/introduction/installation/': '/get-started/installation/',
220  '/workflow/exploring-managed-workflow/': '/tutorial/introduction/',
221  '/introduction/walkthrough/': '/tutorial/introduction/',
222
223  // Move overview to index
224  '/versions/v37.0.0/sdk/overview/': '/versions/v37.0.0/',
225
226  // Errors and debugging is better suited for getting started than tutorial
227  '/tutorial/errors/': '/debugging/errors-and-warnings/',
228
229  // Redirects based on Next 9 upgrade (09/11/2020)
230  '/api/': '/versions/latest/',
231
232  // Redirect to expand Expo Accounts and permissions
233  '/guides/account-permissions/': '/accounts/personal/',
234
235  // Redirects based on Sentry reports
236  '/next-steps/installation/': '/get-started/installation/',
237  '/guides/release-channels/': '/archive/classic-updates/release-channels/',
238  '/guides/push-notifications/': '/push-notifications/overview/',
239  '/guides/using-fcm/': '/push-notifications/using-fcm/',
240  '/push-notifications/': '/push-notifications/overview/',
241  '/distribution/hosting-your-app/': '/distribution/publishing-websites/',
242  '/build-reference/how-tos/': '/build-reference/private-npm-packages/',
243  '/get-started/': '/get-started/installation/',
244  '/guides/detach/': '/archive/glossary/#detach',
245
246  // Renaming a submit section
247  '/submit/submit-ios': '/submit/ios/',
248  '/submit/submit-android': '/submit/android/',
249
250  // Fundamentals had too many things
251  '/workflow/linking/': '/guides/linking/',
252  '/workflow/how-expo-works/': '/faq/#what-is-the-difference-between-expo-and-react-native',
253  '/guides/how-expo-works/': '/faq/#what-is-the-difference-between-expo-and-react-native',
254
255  // Archive unused pages
256  '/guides/notification-channels/': '/archived/notification-channels/',
257
258  // Migrated FAQ pages
259  '/faq/image-background/': '/ui-programming/image-background/',
260  '/faq/react-native-styling-buttons/': '/ui-programming/react-native-styling-buttons/',
261  '/faq/react-native-version-mismatch/': '/troubleshooting/react-native-version-mismatch/',
262  '/faq/clear-cache-windows/': '/troubleshooting/clear-cache-windows/',
263  '/faq/clear-cache-macos-linux/': '/troubleshooting/clear-cache-macos-linux/',
264  '/faq/application-has-not-been-registered/':
265    '/troubleshooting/application-has-not-been-registered/',
266
267  // Permissions API is moved to guide
268  '/versions/v40.0.0/sdk/permissions/': '/guides/permissions/',
269  '/versions/v41.0.0/sdk/permissions/': '/guides/permissions/',
270  '/versions/v42.0.0/sdk/permissions/': '/guides/permissions/',
271  '/versions/v43.0.0/sdk/permissions/': '/guides/permissions/',
272  '/versions/latest/sdk/permissions/': '/guides/permissions/',
273
274  // Redirect Gatsby guide to index guides page
275  '/guides/using-gatsby/': '/guides/',
276
277  // Classic updates moved to archive
278  '/guides/configuring-ota-updates/': '/archive/classic-updates/getting-started/',
279  '/guides/configuring-updates/': '/archive/classic-updates/getting-started/',
280  '/distribution/release-channels/': '/archive/classic-updates/release-channels/',
281  '/distribution/advanced-release-channels/': '/archive/classic-updates/advanced-release-channels/',
282  '/distribution/optimizing-updates/': '/archive/classic-updates/optimizing-updates/',
283  '/eas-update/custom-updates-server/': '/distribution/custom-updates-server/',
284  '/guides/offline-support/': '/archive/classic-updates/offline-support/',
285  '/guides/preloading-and-caching-assets/':
286    '/archive/classic-updates/preloading-and-caching-assets/',
287  '/eas-update/bare-react-native/': '/eas-update/updating-your-app/',
288  '/worfkflow/publishing/': '/archive/classic-updates/publishing/',
289  '/classic/building-standalone-apps/': '/archive/classic-updates/building-standalone-apps/',
290  '/classic/turtle-cli/': '/archive/classic-updates/turtle-cli/',
291  '/archive/classic-updates/getting-started/': '/eas-update/getting-started/',
292
293  // Redirect bare guides to unified workflow guides
294  '/bare/using-libraries/': '/workflow/using-libraries/',
295  '/bare/exploring-bare-workflow/': '/bare/overview/',
296  '/bare/existing-apps/': '/bare/installing-expo-modules/',
297  '/bare/installing-unimodules/': '/bare/installing-expo-modules/',
298  '/bare/using-web/': '/workflow/web/',
299  '/guides/running-in-the-browser/': '/workflow/web/',
300  '/bare/unimodules-full-list/': '/bare/overview/',
301  '/bare/updating-your-app/': '/eas-update/updating-your-app/',
302
303  // Consolidate distribution
304  '/distribution/security/': '/app-signing/security/',
305  '/distribution/uploading-apps/': '/submit/introduction/',
306  '/versions/latest/distribution/uploading-apps/': '/submit/introduction/',
307
308  // Deleted or removed guides
309  '/guides/setup-native-firebase/': '/guides/using-firebase/',
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  '/push-notifications/using-fcm/': '/push-notifications/push-notifications-setup',
344  '/config/app/': '/workflow/configuration/',
345  '/versions/latest/sdk/settings/': '/versions/latest/',
346  '/archive/expokit/eject/': '/archive/glossary/#eject',
347  '/versions/latest/sdk/admob/': '/versions/latest/',
348  '/versions/latest/sdk/payments/': '/versions/latest/sdk/stripe/',
349  '/distribution/app-icons/': '/develop/user-interface/app-icons/',
350  '/guides/using-libraries/': '/workflow/using-libraries/',
351  '/tutorial/': '/tutorial/introduction/',
352
353  // EAS Update
354  '/eas-update/developing-with-eas-update/': '/eas-update/develop-faster/',
355  '/eas-update/eas-update-with-local-build/': '/eas-update/build-locally/',
356  '/eas-update/eas-update-and-eas-cli/': '/eas-update/eas-cli/',
357  '/eas-update/debug-updates/': '/eas-update/debug/',
358  '/eas-update/how-eas-update-works/': '/eas-update/how-it-works/',
359  '/eas-update/migrate-to-eas-update/': '/eas-update/migrate-from-classic-updates/',
360
361  // Expo Router Advanced guides
362  '/router/advance/root-layout': '/router/advanced/root-layout/',
363  '/router/advance/stack': '/router/advanced/stack/',
364  '/router/advance/tabs': '/router/advanced/tabs/',
365  '/router/advance/drawer': '/router/advanced/drawer/',
366  '/router/advance/nesting-navigators': '/router/advanced/nesting-navigators/',
367  '/router/advance/modal': '/router/advanced/modals/',
368  '/router/advance/platform-specific-modules': '/router/advanced/platform-specific-modules/',
369  '/router/advance/shared-routes': '/router/advanced/shared-routes/',
370  '/router/advance/router-settings': '/router/advanced/router-settings/',
371
372  // Note (@aman): The following redirect is temporary until Guides section has an overview
373  '/guides/': '/workflow/customizing/',
374  '/archive/workflow/customizing/': '/workflow/customizing/',
375  '/errors-and-warnings/': '/debugging/errors-and-warnings/',
376};
377