From 0dd5dfcded408c58e7bf4d785ecaad203776dbf1 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 29 Nov 2024 21:41:27 -0800 Subject: [PATCH] always show the latest version of nvm --- .../app/[locale]/next-data/nvm-data/route.ts | 31 +++++++++++++++++++ .../Downloads/Release/ReleaseCodeBox.tsx | 21 ++++++++----- apps/site/next-data/generators/nvmData.mjs | 24 ++++++++++++++ apps/site/next-data/nvmData.ts | 17 ++++++++++ apps/site/next-data/providers/nvmData.ts | 7 +++++ apps/site/util/getNodeDownloadSnippet.ts | 9 +++--- 6 files changed, 98 insertions(+), 11 deletions(-) create mode 100644 apps/site/app/[locale]/next-data/nvm-data/route.ts create mode 100644 apps/site/next-data/generators/nvmData.mjs create mode 100644 apps/site/next-data/nvmData.ts create mode 100644 apps/site/next-data/providers/nvmData.ts diff --git a/apps/site/app/[locale]/next-data/nvm-data/route.ts b/apps/site/app/[locale]/next-data/nvm-data/route.ts new file mode 100644 index 0000000000000..1e63b8c8ecfdc --- /dev/null +++ b/apps/site/app/[locale]/next-data/nvm-data/route.ts @@ -0,0 +1,31 @@ +import provideNvmData from '@/next-data/providers/nvmData'; +import { defaultLocale } from '@/next.locales.mjs'; + +// This is the Route Handler for the `GET` method which handles the request +// for generating static data for the latest nvm version +// @see https://nextjs.org/docs/app/building-your-application/routing/router-handlers +export const GET = async () => { + const nvmData = provideNvmData(); + + return Response.json(nvmData); +}; + +// This function generates the static paths that come from the dynamic segments +// `[locale]/next-data/nvm-data/` and returns an array of all available static paths +// This is used for ISR static validation and generation +export const generateStaticParams = async () => [ + { locale: defaultLocale.code }, +]; + +// Enforces that only the paths from `generateStaticParams` are allowed, giving 404 on the contrary +// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamicparams +export const dynamicParams = false; + +// Enforces that this route is used as static rendering +// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic +export const dynamic = 'error'; + +// Ensures that this endpoint is invalidated and re-executed every X minutes +// so that when new deployments happen, the data is refreshed +// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#revalidate +export const revalidate = 300; diff --git a/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx b/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx index ca465eb90aaf0..618e288ab13bc 100644 --- a/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx +++ b/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx @@ -21,13 +21,20 @@ const ReleaseCodeBox: FC = () => { const t = useTranslations(); useEffect(() => { - const updatedCode = getNodeDownloadSnippet(release, os, t)[platform]; - // Docker and NVM support downloading tags/versions by their full release number - // but usually we should recommend users to download "major" versions - // since our Download Buttons get the latest minor of a major, it does make sense - // to request installation of a major via a package manager - memoizedShiki.then(shiki => shiki(updatedCode, 'bash')).then(setCode); - // Only react when the specific release number changed + async function getSnippet() { + const [shiki, { [platform]: updatedCode }] = await Promise.all([ + memoizedShiki, + getNodeDownloadSnippet(release, os, t), + ]); + + // Docker and nvm support downloading tags/versions by their full release number + // but usually we should recommend users to download "major" versions since + // our Download Buttons get the latest minor of a major, it does make sense + // to request installation of a major via a package manager + setCode(await shiki(updatedCode, 'bash')); + // Only react when the specific release number changed + } + getSnippet(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [release.versionWithPrefix, os, platform]); diff --git a/apps/site/next-data/generators/nvmData.mjs b/apps/site/next-data/generators/nvmData.mjs new file mode 100644 index 0000000000000..34c7b9e104a3d --- /dev/null +++ b/apps/site/next-data/generators/nvmData.mjs @@ -0,0 +1,24 @@ +'use strict'; + +const latestKnownVersion = 'v0.40.1'; + +/** + * Fetches the latest NVM version + * @returns {Promise<`v${string}`>} Latest NVM version + */ +export default async function generateNvmData() { + return fetch('https://latest.nvm.sh', { redirect: 'manual' }) + .then(({ headers }) => { + const url = headers.get('location'); + if (!url) { + throw new Error('No redirect location found'); + } + return fetch(url, { redirect: 'manual' }); + }) + .then(x => { + const url = x.headers.get('location'); + const version = url?.slice(url.lastIndexOf('/') + 1); + return version || latestKnownVersion; + }) + .catch(() => latestKnownVersion); +} diff --git a/apps/site/next-data/nvmData.ts b/apps/site/next-data/nvmData.ts new file mode 100644 index 0000000000000..0a1706eb8b49a --- /dev/null +++ b/apps/site/next-data/nvmData.ts @@ -0,0 +1,17 @@ +import { + ENABLE_STATIC_EXPORT, + IS_DEVELOPMENT, + NEXT_DATA_URL, + VERCEL_ENV, +} from '@/next.constants.mjs'; + +export default async function getNvmData(): Promise<`v${string}`> { + if (ENABLE_STATIC_EXPORT || (!IS_DEVELOPMENT && !VERCEL_ENV)) { + const { default: provideNvmData } = await import( + '@/next-data/providers/nvmData' + ); + provideNvmData(); + } + + return fetch(`${NEXT_DATA_URL}nvm-data`).then(r => r.json()); +} diff --git a/apps/site/next-data/providers/nvmData.ts b/apps/site/next-data/providers/nvmData.ts new file mode 100644 index 0000000000000..92bfee0646ae0 --- /dev/null +++ b/apps/site/next-data/providers/nvmData.ts @@ -0,0 +1,7 @@ +import { cache } from 'react'; + +import generateNvmData from '@/next-data/generators/nvmData.mjs'; + +const nvmData = await generateNvmData(); + +export default cache(() => nvmData); diff --git a/apps/site/util/getNodeDownloadSnippet.ts b/apps/site/util/getNodeDownloadSnippet.ts index b1874f6084c5b..c3cc0e16be636 100644 --- a/apps/site/util/getNodeDownloadSnippet.ts +++ b/apps/site/util/getNodeDownloadSnippet.ts @@ -1,15 +1,16 @@ import dedent from 'dedent'; import type { TranslationValues } from 'next-intl'; +import getNvmData from '@/next-data/nvmData'; import type { NodeRelease } from '@/types'; import type { PackageManager } from '@/types/release'; import type { UserOS } from '@/types/userOS'; -export const getNodeDownloadSnippet = ( +export async function getNodeDownloadSnippet( release: NodeRelease, os: UserOS, t: (key: string, values?: TranslationValues) => string -) => { +) { const snippets: Record = { NVM: '', FNM: '', @@ -39,7 +40,7 @@ export const getNodeDownloadSnippet = ( if (os === 'MAC' || os === 'LINUX') { snippets.NVM = dedent` # ${t('layouts.download.codeBox.installsNvm')} - curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/${await getNvmData()}/install.sh | bash # ${t('layouts.download.codeBox.downloadAndInstallNodejsRestartTerminal')} nvm install ${release.major} @@ -118,4 +119,4 @@ export const getNodeDownloadSnippet = ( } return snippets; -}; +}