diff --git a/.husky/pre-commit b/.husky/pre-commit index 674ab67d4f451..98819fd136e06 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -3,3 +3,6 @@ # lint and format staged files npx lint-staged + +# verify typescript staged files +npx tsc --build . diff --git a/app/[locale]/next-data/api-data/route.ts b/app/[locale]/next-data/api-data/route.ts index 11d48967bbe0c..5392b97901ce1 100644 --- a/app/[locale]/next-data/api-data/route.ts +++ b/app/[locale]/next-data/api-data/route.ts @@ -55,7 +55,7 @@ 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 = 'force-static'; +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 diff --git a/app/[locale]/next-data/blog-data/[category]/[page]/route.ts b/app/[locale]/next-data/blog-data/[category]/[page]/route.ts index 71181cccef0f3..e00c2305a10c8 100644 --- a/app/[locale]/next-data/blog-data/[category]/[page]/route.ts +++ b/app/[locale]/next-data/blog-data/[category]/[page]/route.ts @@ -59,7 +59,7 @@ export const dynamicParams = false; // Enforces that this route is cached and static as much as possible // @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic -export const dynamic = 'force-static'; +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 diff --git a/app/[locale]/next-data/page-data/route.ts b/app/[locale]/next-data/page-data/route.ts index 34a715f5973de..b779e64569224 100644 --- a/app/[locale]/next-data/page-data/route.ts +++ b/app/[locale]/next-data/page-data/route.ts @@ -64,7 +64,7 @@ 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 = 'force-static'; +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 diff --git a/app/[locale]/next-data/release-data/route.ts b/app/[locale]/next-data/release-data/route.ts index b15402ccda8c9..a664ad9cf3ef6 100644 --- a/app/[locale]/next-data/release-data/route.ts +++ b/app/[locale]/next-data/release-data/route.ts @@ -24,7 +24,7 @@ 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 = 'force-static'; +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 diff --git a/components/Common/ActiveLink/index.tsx b/components/Common/ActiveLink/index.tsx index 00db4336eef31..bdf566d87dc71 100644 --- a/components/Common/ActiveLink/index.tsx +++ b/components/Common/ActiveLink/index.tsx @@ -4,7 +4,7 @@ import classNames from 'classnames'; import type { ComponentProps, FC } from 'react'; import Link from '@/components/Link'; -import { useClientContext } from '@/hooks'; +import { usePathname } from '@/navigation.mjs'; type ActiveLocalizedLinkProps = ComponentProps & { activeClassName?: string; @@ -19,7 +19,7 @@ const ActiveLink: FC = ({ href = '', ...props }) => { - const { pathname } = useClientContext(); + const pathname = usePathname(); const finalClassName = classNames(className, { [activeClassName]: allowSubPath diff --git a/components/Common/Breadcrumbs/index.tsx b/components/Common/Breadcrumbs/index.tsx index c07b9ff76681f..ce74dc88188ff 100644 --- a/components/Common/Breadcrumbs/index.tsx +++ b/components/Common/Breadcrumbs/index.tsx @@ -1,4 +1,5 @@ -import { useMemo, type FC } from 'react'; +import type { FC } from 'react'; +import { useMemo } from 'react'; import BreadcrumbHomeLink from '@/components/Common/Breadcrumbs/BreadcrumbHomeLink'; import BreadcrumbItem from '@/components/Common/Breadcrumbs/BreadcrumbItem'; diff --git a/components/Common/CodeBox/index.tsx b/components/Common/CodeBox/index.tsx index f99a99323cc3f..ccb3eeb802aae 100644 --- a/components/Common/CodeBox/index.tsx +++ b/components/Common/CodeBox/index.tsx @@ -16,7 +16,7 @@ import styles from './index.module.css'; // Transforms a code element with plain text content into a more structured // format for rendering with line numbers -const transformCode = (code: ReactNode): ReactNode => { +const transformCode = (code: ReactNode, language: string): ReactNode => { if (!isValidElement(code)) { // Early return when the `CodeBox` child is not a valid element since the // type is a ReactNode, and can assume any value @@ -35,8 +35,10 @@ const transformCode = (code: ReactNode): ReactNode => { // being an empty string, so we need to remove it const lines = content.split('\n'); + const extraStyle = language.length === 0 ? { fontFamily: 'monospace' } : {}; + return ( - + {lines .flatMap((line, lineIndex) => { const columns = line.split(' '); @@ -97,7 +99,7 @@ const CodeBox: FC> = ({ return (
-        {transformCode(children)}
+        {transformCode(children, language)}
       
{language && ( diff --git a/components/Common/LinkTabs/index.module.css b/components/Common/LinkTabs/index.module.css index 7f68c4c06d85e..99e8fc02bfddf 100644 --- a/components/Common/LinkTabs/index.module.css +++ b/components/Common/LinkTabs/index.module.css @@ -28,9 +28,14 @@ } } -.tabsSelect > div { - @apply my-6 - hidden - w-full - xs:flex; +.tabsSelect { + @apply sm:visible + md:hidden; + + > span { + @apply my-6 + hidden + w-full + xs:flex; + } } diff --git a/components/Common/ProgressionSidebar/index.module.css b/components/Common/ProgressionSidebar/index.module.css index 73402918076cf..7e497da5120c4 100644 --- a/components/Common/ProgressionSidebar/index.module.css +++ b/components/Common/ProgressionSidebar/index.module.css @@ -20,7 +20,7 @@ xs:hidden; } - > div { + > span { @apply hidden w-full xs:flex; diff --git a/components/Common/ProgressionSidebar/index.tsx b/components/Common/ProgressionSidebar/index.tsx index 304697ea8608a..6f897e3dfc4f4 100644 --- a/components/Common/ProgressionSidebar/index.tsx +++ b/components/Common/ProgressionSidebar/index.tsx @@ -27,6 +27,12 @@ const ProgressionSidebar: FC = ({ groups }) => { return ( ); }; diff --git a/components/Common/Select/index.module.css b/components/Common/Select/index.module.css index 631adf99165b1..da0df2bd00840 100644 --- a/components/Common/Select/index.module.css +++ b/components/Common/Select/index.module.css @@ -1,6 +1,5 @@ .select { - @apply flex - w-fit + @apply inline-flex flex-col gap-1.5; @@ -47,6 +46,7 @@ .trigger span { @apply flex + h-5 items-center gap-2; } @@ -115,9 +115,11 @@ .text { @apply text-neutral-900 data-[highlighted]:bg-neutral-100 + data-[disabled]:text-neutral-600 data-[highlighted]:text-neutral-900 dark:text-white - dark:data-[highlighted]:bg-neutral-900; + dark:data-[highlighted]:bg-neutral-900 + dark:data-[disabled]:text-neutral-700; } &.dropdown { diff --git a/components/Common/Select/index.tsx b/components/Common/Select/index.tsx index 44943f1731983..69fd9d2e85e3c 100644 --- a/components/Common/Select/index.tsx +++ b/components/Common/Select/index.tsx @@ -14,6 +14,7 @@ type SelectValue = { label: FormattedMessage; value: string; iconImage?: React.ReactNode; + disabled?: boolean; }; type SelectGroup = { @@ -34,6 +35,7 @@ type SelectProps = { label?: string; inline?: boolean; onChange?: (value: string) => void; + className?: string; }; const Select: FC = ({ @@ -43,6 +45,7 @@ const Select: FC = ({ label, inline, onChange, + className, }) => { const id = useId(); @@ -61,12 +64,19 @@ const Select: FC = ({ }, [values]); return ( -
- {label && ( + + {label && !inline && ( )} + = ({ )} - {items.map(({ value, label, iconImage }) => ( + {items.map(({ value, label, iconImage, disabled }) => ( @@ -110,7 +121,7 @@ const Select: FC = ({ -
+ ); }; diff --git a/components/Containers/NavBar/NavItem/index.tsx b/components/Containers/NavBar/NavItem/index.tsx index e9d50bd9d3ae8..b8ebcfcc9e1f1 100644 --- a/components/Containers/NavBar/NavItem/index.tsx +++ b/components/Containers/NavBar/NavItem/index.tsx @@ -27,6 +27,7 @@ const NavItem: FC> = ({ allowSubPath={href.startsWith('/')} > {children} + {type === 'nav' && href.startsWith('http') && ( )} diff --git a/components/Containers/Sidebar/index.module.css b/components/Containers/Sidebar/index.module.css index f823a991337d1..63cf0ba063ae9 100644 --- a/components/Containers/Sidebar/index.module.css +++ b/components/Containers/Sidebar/index.module.css @@ -22,7 +22,7 @@ xs:hidden; } - > div { + > span { @apply hidden w-full xs:flex; diff --git a/components/Downloads/DownloadButton/index.tsx b/components/Downloads/DownloadButton/index.tsx index 38b1068cced1c..e5e91407017e4 100644 --- a/components/Downloads/DownloadButton/index.tsx +++ b/components/Downloads/DownloadButton/index.tsx @@ -7,7 +7,7 @@ import type { FC, PropsWithChildren } from 'react'; import Button from '@/components/Common/Button'; import { useDetectOS } from '@/hooks'; import type { NodeRelease } from '@/types'; -import { downloadUrlByOS } from '@/util/downloadUrlByOS'; +import { getNodeDownloadUrl } from '@/util/getNodeDownloadUrl'; import styles from './index.module.css'; @@ -18,7 +18,7 @@ const DownloadButton: FC> = ({ children, }) => { const { os, bitness } = useDetectOS(); - const downloadLink = downloadUrlByOS(versionWithPrefix, os, bitness); + const downloadLink = getNodeDownloadUrl(versionWithPrefix, os, bitness); return ( <> diff --git a/components/Downloads/DownloadLink.tsx b/components/Downloads/DownloadLink.tsx index 9896d91a552ec..bedab21bebf7f 100644 --- a/components/Downloads/DownloadLink.tsx +++ b/components/Downloads/DownloadLink.tsx @@ -4,7 +4,7 @@ import type { FC, PropsWithChildren } from 'react'; import { useDetectOS } from '@/hooks'; import type { NodeRelease } from '@/types'; -import { downloadUrlByOS } from '@/util/downloadUrlByOS'; +import { getNodeDownloadUrl } from '@/util/getNodeDownloadUrl'; type DownloadLinkProps = { release: NodeRelease }; @@ -13,7 +13,7 @@ const DownloadLink: FC> = ({ children, }) => { const { os, bitness } = useDetectOS(); - const downloadLink = downloadUrlByOS(versionWithPrefix, os, bitness); + const downloadLink = getNodeDownloadUrl(versionWithPrefix, os, bitness); return {children}; }; diff --git a/components/Downloads/Release/BitnessDropdown.tsx b/components/Downloads/Release/BitnessDropdown.tsx new file mode 100644 index 0000000000000..d4f1adaec3a21 --- /dev/null +++ b/components/Downloads/Release/BitnessDropdown.tsx @@ -0,0 +1,91 @@ +'use client'; + +import { useTranslations } from 'next-intl'; +import type { FC } from 'react'; +import { useEffect, useContext, useMemo } from 'react'; +import semVer from 'semver'; + +import Select from '@/components/Common/Select'; +import { useDetectOS } from '@/hooks/react-client'; +import { ReleaseContext } from '@/providers/releaseProvider'; +import { bitnessItems, formatDropdownItems } from '@/util/downloadUtils'; + +const parseNumericBitness = (bitness: string) => + /^\d+$/.test(bitness) ? Number(bitness) : bitness; + +const BitnessDropdown: FC = () => { + const { bitness: userBitness } = useDetectOS(); + const { bitness, os, release, setBitness } = useContext(ReleaseContext); + const t = useTranslations(); + + // we also reset the bitness when the OS changes, because different OSs have + // different bitnesses available + useEffect(() => setBitness(userBitness), [setBitness, userBitness]); + + // @TODO: We should have a proper utility that gives + // disabled OSs, Platforms, based on specific criteria + // this can be an optimisation for the future + // to remove this logic from this component + const disabledItems = useMemo(() => { + const disabledItems = []; + + if (os === 'WIN' && semVer.satisfies(release.version, '< 19.9.0')) { + disabledItems.push('arm64'); + } + + if (os === 'LINUX' && semVer.satisfies(release.version, '< 4.0.0')) { + disabledItems.push('arm64', 'armv7l'); + } + + if (os === 'LINUX' && semVer.satisfies(release.version, '< 4.4.0')) { + disabledItems.push('ppc64le'); + } + + if (os === 'LINUX' && semVer.satisfies(release.version, '< 6.6.0')) { + disabledItems.push('s390x'); + } + + return disabledItems; + }, [os, release.version]); + + // @TODO: We should have a proper utility that gives + // disabled OSs, Platforms, based on specific criteria + // this can be an optimisation for the future + // to remove this logic from this component + useEffect(() => { + const mappedBittnessesValues = bitnessItems[os].map(({ value }) => value); + + const currentBittnessExcluded = + // Different OSs support different Bitnessess, hence we should also check + // if besides the current bitness not being supported for a given release version + // we also should check if it is not supported by the OS + disabledItems.includes(String(bitness)) || + !mappedBittnessesValues.includes(String(bitness)); + + const nonExcludedBitness = mappedBittnessesValues.find( + bittness => !disabledItems.includes(bittness) + ); + + if (currentBittnessExcluded && nonExcludedBitness) { + setBitness(nonExcludedBitness); + } + // we shouldn't react when "actions" change + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [os, disabledItems]); + + return ( + , + MAC: , + LINUX: , + }, + })} + defaultValue={os} + onChange={value => setOS(value as UserOS)} + className="w-[8.5rem]" + inline={true} + /> + ); +}; + +export default OperatingSystemDropdown; diff --git a/components/Downloads/Release/PlatformDropdown.tsx b/components/Downloads/Release/PlatformDropdown.tsx new file mode 100644 index 0000000000000..18c53f1089ece --- /dev/null +++ b/components/Downloads/Release/PlatformDropdown.tsx @@ -0,0 +1,85 @@ +'use client'; + +import { useTranslations } from 'next-intl'; +import { useContext, useEffect, useMemo } from 'react'; +import type { FC } from 'react'; + +import Select from '@/components/Common/Select'; +import Docker from '@/components/Icons/Platform/Docker'; +import Homebrew from '@/components/Icons/Platform/Homebrew'; +import NVM from '@/components/Icons/Platform/NVM'; +import { ReleaseContext } from '@/providers/releaseProvider'; +import type { PackageManager } from '@/types/release'; +import { formatDropdownItems, platformItems } from '@/util/downloadUtils'; + +const supportedHomebrewVersions = ['Active LTS', 'Maintenance LTS', 'Current']; + +const PlatformDropdown: FC = () => { + const { release, os, platform, setPlatform } = useContext(ReleaseContext); + const t = useTranslations(); + + // @TODO: We should have a proper utility that gives + // disabled OSs, Platforms, based on specific criteria + // this can be an optimisation for the future + // to remove this logic from this component + const disabledItems = useMemo(() => { + const disabledItems = []; + + if (os === 'WIN') { + disabledItems.push('BREW'); + } + + if (os === 'LINUX') { + disabledItems.push('DOCKER'); + } + + const releaseSupportsHomebrew = supportedHomebrewVersions.includes( + release.status + ); + + if (!releaseSupportsHomebrew) { + disabledItems.push('BREW'); + } + + return disabledItems; + }, [os, release.status]); + + // @TODO: We should have a proper utility that gives + // disabled OSs, Platforms, based on specific criteria + // this can be an optimisation for the future + // to remove this logic from this component + useEffect(() => { + const currentPlatformExcluded = disabledItems.includes(platform); + + const nonExcludedPlatform = platformItems + .map(({ value }) => value) + .find(platform => !disabledItems.includes(platform)); + + if (currentPlatformExcluded && nonExcludedPlatform) { + setPlatform(nonExcludedPlatform); + } + // we shouldn't react when "actions" change + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [release.status, disabledItems, platform]); + + return ( + ({ + value: versionWithPrefix, + label: getDropDownStatus(versionWithPrefix, status), + }))} + defaultValue={release.versionWithPrefix} + onChange={setVersion} + className="w-40" + inline={true} + /> + ); +}; + +export default VersionDropdown; diff --git a/components/Home/HomeDownloadButton.tsx b/components/Home/HomeDownloadButton.tsx index dd4c7afa114e0..0bbbff4cc206a 100644 --- a/components/Home/HomeDownloadButton.tsx +++ b/components/Home/HomeDownloadButton.tsx @@ -7,7 +7,7 @@ import Link from '@/components/Link'; import { useDetectOS } from '@/hooks'; import { DIST_URL } from '@/next.constants.mjs'; import type { NodeRelease } from '@/types'; -import { downloadUrlByOS } from '@/util/downloadUrlByOS'; +import { getNodeDownloadUrl } from '@/util/getNodeDownloadUrl'; import { getNodejsChangelog } from '@/util/getNodeJsChangelog'; const HomeDownloadButton: FC = ({ @@ -19,7 +19,7 @@ const HomeDownloadButton: FC = ({ const { os, bitness } = useDetectOS(); const t = useTranslations(); - const nodeDownloadLink = downloadUrlByOS(versionWithPrefix, os, bitness); + const nodeDownloadLink = getNodeDownloadUrl(versionWithPrefix, os, bitness); const nodeApiLink = `${DIST_URL}latest-v${major}.x/docs/api/`; const nodeAllDownloadsLink = `/download${isLts ? '/' : '/current'}`; diff --git a/components/Icons/Platform/Docker.tsx b/components/Icons/Platform/Docker.tsx new file mode 100644 index 0000000000000..37d16e72b2083 --- /dev/null +++ b/components/Icons/Platform/Docker.tsx @@ -0,0 +1,20 @@ +import type { FC, SVGProps } from 'react'; + +const Docker: FC> = props => ( + + + +); + +export default Docker; diff --git a/components/Icons/Platform/Generic.tsx b/components/Icons/Platform/Generic.tsx index bdae9102b252d..e2ae454f062a1 100644 --- a/components/Icons/Platform/Generic.tsx +++ b/components/Icons/Platform/Generic.tsx @@ -6,12 +6,12 @@ const Generic: FC> = props => ( height="32" viewBox="0 0 32 32" fill="none" + stroke="#CBD4D9" xmlns="http://www.w3.org/2000/svg" {...props} > , 'href'> & { - href: string | undefined; + href?: string; }; const Link: FC = ({ children, href, ...props }) => { diff --git a/components/__design__/platform-logos.stories.tsx b/components/__design__/platform-logos.stories.tsx index 7acbecb261b2e..80440acc78f96 100644 --- a/components/__design__/platform-logos.stories.tsx +++ b/components/__design__/platform-logos.stories.tsx @@ -1,6 +1,7 @@ import type { Meta as MetaObj, StoryObj } from '@storybook/react'; import Apple from '@/components/Icons/Platform/Apple'; +import Docker from '@/components/Icons/Platform/Docker'; import Generic from '@/components/Icons/Platform/Generic'; import Homebrew from '@/components/Icons/Platform/Homebrew'; import Linux from '@/components/Icons/Platform/Linux'; @@ -11,13 +12,16 @@ export const PlatformLogos: StoryObj = { render: () => (
- - + +
- + + +
+
diff --git a/components/withDownloadCategories.tsx b/components/withDownloadCategories.tsx new file mode 100644 index 0000000000000..331a508649f16 --- /dev/null +++ b/components/withDownloadCategories.tsx @@ -0,0 +1,62 @@ +import { getTranslations } from 'next-intl/server'; +import type { FC, PropsWithChildren } from 'react'; + +import { useClientContext } from '@/hooks/react-server'; +import getReleaseData from '@/next-data/releaseData'; +import { ReleaseProvider } from '@/providers/releaseProvider'; +import type { NodeReleaseStatus } from '@/types'; +import { getDownloadCategory, mapCategoriesToTabs } from '@/util/downloadUtils'; + +import LinkTabs from './Common/LinkTabs'; +import WithNodeRelease from './withNodeRelease'; + +const WithDownloadCategories: FC = async ({ children }) => { + const t = await getTranslations(); + const releases = await getReleaseData(); + + const { pathname } = useClientContext(); + const { page, category, subCategory } = getDownloadCategory(pathname); + + const initialRelease: Array = pathname.includes('current') + ? ['Current'] + : ['Active LTS', 'Maintenance LTS']; + + return ( + + + {({ release }) => ( + + {children} + + )} + + + ); +}; + +export default WithDownloadCategories; diff --git a/components/withLayout.tsx b/components/withLayout.tsx index a22d882001453..9965d9a45c02d 100644 --- a/components/withLayout.tsx +++ b/components/withLayout.tsx @@ -11,6 +11,7 @@ import LegacyLearnLayout from '@/layouts/LearnLayout'; import AboutLayout from '@/layouts/New/About'; import BlogLayout from '@/layouts/New/Blog'; import DefaultLayout from '@/layouts/New/Default'; +import DownloadLayout from '@/layouts/New/Download'; import HomeLayout from '@/layouts/New/Home'; import LearnLayout from '@/layouts/New/Learn'; import PostLayout from '@/layouts/New/Post'; @@ -39,6 +40,7 @@ const redesignLayouts = { 'blog-post.hbs': PostLayout, 'blog-category.hbs': BlogLayout, 'search.hbs': SearchLayout, + 'download.hbs': DownloadLayout, } satisfies Record; type WithLayout = PropsWithChildren<{ layout: L }>; diff --git a/i18n/locales/en.json b/i18n/locales/en.json index 4b019083e4f15..330d16fa0e65e 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -266,6 +266,28 @@ "description": "This page has thrown a non-recoverable error." }, "backToHome": "Back to Home" + }, + "download": { + "selectCategory": "Categories", + "categories": { + "download": "Prebuilt Installer", + "prebuilt-binaries": "Prebuilt Binaries", + "package-manager": "Package Manager", + "source-code": "Source Code" + }, + "buttons": { + "prebuilt": "Download Node.js {version}", + "source": "Download Node.js {version} source" + }, + "dropdown": { + "bitness": "Bitness", + "os": "Operating System", + "version": "Version", + "platform": "Platform" + }, + "codeBox": { + "communityWarning": "Package Managers and their installation scripts are not maintained by the Node.js project.s" + } } }, "pages": { diff --git a/layouts/New/Download.tsx b/layouts/New/Download.tsx new file mode 100644 index 0000000000000..c8de36b3ff4c5 --- /dev/null +++ b/layouts/New/Download.tsx @@ -0,0 +1,34 @@ +import type { FC, PropsWithChildren } from 'react'; + +import WithDownloadCategories from '@/components/withDownloadCategories'; +import WithFooter from '@/components/withFooter'; +import WithNavBar from '@/components/withNavBar'; +import { useClientContext } from '@/hooks/react-server'; + +import styles from './layouts.module.css'; + +const DownloadLayout: FC = async ({ children }) => { + const { + frontmatter: { title, subtitle }, + } = useClientContext(); + + return ( + <> + + +
+
+

{title}

+ +

{subtitle}

+ + {children} +
+
+ + + + ); +}; + +export default DownloadLayout; diff --git a/layouts/New/layouts.module.css b/layouts/New/layouts.module.css index 470af0beb2447..5c6071756eb5f 100644 --- a/layouts/New/layouts.module.css +++ b/layouts/New/layouts.module.css @@ -138,7 +138,8 @@ } } -.blogLayout { +.blogLayout, +.downloadLayout { @apply flex w-full justify-center @@ -148,7 +149,7 @@ xs:dark:bg-none; main { - @apply max-w-8xl + @apply max-w-5xl gap-4 px-4 py-12 @@ -157,6 +158,27 @@ } } +.downloadLayout { + section:nth-last-child(1) { + @apply flex + flex-col + gap-2; + + p { + @apply text-sm; + } + } + + section:nth-last-child(2) p { + @apply flex + flex-row + flex-wrap + items-center + gap-2 + text-base; + } +} + .contentLayout { @apply mx-auto grid diff --git a/next.dynamic.constants.mjs b/next.dynamic.constants.mjs index 8aa2208faddd4..288dde4c96c5b 100644 --- a/next.dynamic.constants.mjs +++ b/next.dynamic.constants.mjs @@ -17,7 +17,10 @@ import { defaultLocale } from './next.locales.mjs'; export const IGNORED_ROUTES = [ // This is used to ignore all blog routes except for the English language ({ locale, pathname }) => - locale !== defaultLocale.code && /^blog\//.test(pathname), + locale !== defaultLocale.code && /^blog/.test(pathname), + // Do not statically build the redesign pages on the static export + // @deprecated this should be removed once we remove the legacy website + ({ pathname }) => /^new-design/.test(pathname), ]; /** diff --git a/next.mdx.shiki.mjs b/next.mdx.shiki.mjs index 8752610426c93..aeeb67254bebf 100644 --- a/next.mdx.shiki.mjs +++ b/next.mdx.shiki.mjs @@ -4,12 +4,17 @@ import classNames from 'classnames'; import { toString } from 'hast-util-to-string'; import { SKIP, visit } from 'unist-util-visit'; -import { highlightToHast } from './util/getHighlighter'; +import { getShiki, highlightToHast } from './util/getHighlighter'; // This is what Remark will use as prefix within a
 className
 // to attribute the current language of the 
 element
 const languagePrefix = 'language-';
 
+// We do a top-level await, since the Unist-tree visitor
+// is synchronous, and it makes more sense to do a top-level
+// await, rather than an await inside the visitor function
+const memoizedShiki = await getShiki();
+
 /**
  * Retrieve the value for the given meta key.
  *
@@ -169,7 +174,10 @@ export default function rehypeShikiji() {
       const languageId = codeLanguage.slice(languagePrefix.length);
 
       // Parses the 
 contents and returns a HAST tree with the highlighted code
-      const { children } = highlightToHast(preElementContents, languageId);
+      const { children } = highlightToHast(memoizedShiki)(
+        preElementContents,
+        languageId
+      );
 
       // Adds the original language back to the 
 element
       children[0].properties.class = classNames(
diff --git a/next.mdx.use.mjs b/next.mdx.use.mjs
index 68d23b56a088b..1b63905245d26 100644
--- a/next.mdx.use.mjs
+++ b/next.mdx.use.mjs
@@ -5,6 +5,19 @@ import Button from './components/Common/Button';
 import DownloadButton from './components/Downloads/DownloadButton';
 import DownloadLink from './components/Downloads/DownloadLink';
 import DownloadReleasesTable from './components/Downloads/DownloadReleasesTable';
+import BitnessDropdown from './components/Downloads/Release/BitnessDropdown';
+import BlogPostLink from './components/Downloads/Release/BlogPostLink';
+import ReleaseDownloadButton from './components/Downloads/Release/DownloadButton';
+import LinkWithArrow from './components/Downloads/Release/LinkWithArrow';
+import NpmVersion from './components/Downloads/Release/NpmVersion';
+import OperatingSystemDropdown from './components/Downloads/Release/OperatingSystemDropdown';
+import PlatformDropdown from './components/Downloads/Release/PlatformDropdown';
+import ReleaseCodeBox from './components/Downloads/Release/ReleaseCodeBox';
+import ReleaseStatus from './components/Downloads/Release/ReleaseStatus';
+import ReleaseVersion from './components/Downloads/Release/ReleaseVersion';
+import SourceButton from './components/Downloads/Release/SourceButton';
+import VerifyingBinariesLink from './components/Downloads/Release/VerifyingBinariesLink';
+import VersionDropdown from './components/Downloads/Release/VersionDropdown';
 import HomeDownloadButton from './components/Home/HomeDownloadButton';
 import Link from './components/Link';
 import UpcomingEvents from './components/MDX/Calendar/UpcomingEvents';
@@ -47,6 +60,36 @@ export const mdxComponents = {
   UpcomingSummits: UpcomingSummits,
   // Renders an container for Upcoming Node.js Events
   UpcomingEvents: UpcomingEvents,
+  // Links with External Arrow
+  LinkWithArrow: LinkWithArrow,
+  // Group of components that enable you to select versions for Node.js
+  // releases and download selected versions. Uses `releaseProvider` as a provider
+  Release: {
+    // Renders a drop-down menu from which the version can select
+    VersionDropdown: VersionDropdown,
+    // Renders a drop-down menu from which the platform can select
+    PlatformDropdown: PlatformDropdown,
+    // Renders a drop-down menu from which the bitness can select
+    BitnessDropdown: BitnessDropdown,
+    // Renders a drop-down menu from which the operating system can select
+    OperatingSystemDropdown: OperatingSystemDropdown,
+    // Renders a npm version of the selected release
+    NpmVersion: NpmVersion,
+    // Renders a release version of the selected release
+    Version: ReleaseVersion,
+    // Renders a release status of the selected release
+    Status: ReleaseStatus,
+    // Renders a Blog Post Link for the selected release
+    BlogPostLink: BlogPostLink,
+    // Renders a Verifying Binaries Link
+    VerifyingBinariesLink: VerifyingBinariesLink,
+    // Renders a Download Button for the selected release
+    DownloadButton: ReleaseDownloadButton,
+    // Renders a Source Download Button for the selected release
+    SourceButton: SourceButton,
+    // Renders a Release CodeBox
+    ReleaseCodeBox: ReleaseCodeBox,
+  },
 };
 
 /**
diff --git a/next.rewrites.mjs b/next.rewrites.mjs
index 33a68f7100558..bc8b10f348d65 100644
--- a/next.rewrites.mjs
+++ b/next.rewrites.mjs
@@ -48,10 +48,24 @@ const rewrites = async () => {
   // This allows us to remap legacy website URLs to the temporary redesign ones
   // @todo: remove this once website redesign is done
   if (ENABLE_WEBSITE_REDESIGN) {
-    mappedRewrites.push({
-      source: localesMatch,
-      destination: '/:locale/new-design',
-    });
+    mappedRewrites.push(
+      {
+        source: localesMatch,
+        destination: '/:locale/new-design',
+      },
+      {
+        source: '/:locale/download',
+        destination: '/:locale/new-design/download',
+      },
+      {
+        source: '/:locale/download/:path',
+        destination: '/:locale/new-design/download/:path',
+      },
+      {
+        source: '/:locale/download/:path/:version',
+        destination: '/:locale/new-design/download/:path/:version',
+      }
+    );
   }
 
   return { afterFiles: mappedRewrites };
diff --git a/package-lock.json b/package-lock.json
index b53bab752ad1a..d08ec4ee14144 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -29,6 +29,7 @@
         "autoprefixer": "~10.4.16",
         "classnames": "~2.5.1",
         "cross-env": "7.0.3",
+        "dedent": "1.5.1",
         "feed": "~4.2.2",
         "github-slugger": "~2.0.0",
         "glob": "~10.3.10",
@@ -11542,10 +11543,17 @@
       }
     },
     "node_modules/dedent": {
-      "version": "0.7.0",
-      "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
-      "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==",
-      "dev": true
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz",
+      "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==",
+      "peerDependencies": {
+        "babel-plugin-macros": "^3.1.0"
+      },
+      "peerDependenciesMeta": {
+        "babel-plugin-macros": {
+          "optional": true
+        }
+      }
     },
     "node_modules/deep-equal": {
       "version": "2.2.3",
@@ -12174,6 +12182,12 @@
         "objectorarray": "^1.0.5"
       }
     },
+    "node_modules/endent/node_modules/dedent": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
+      "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==",
+      "dev": true
+    },
     "node_modules/enhanced-resolve": {
       "version": "5.15.0",
       "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz",
@@ -16444,20 +16458,6 @@
         "url": "https://github.com/chalk/chalk?sponsor=1"
       }
     },
-    "node_modules/jest-circus/node_modules/dedent": {
-      "version": "1.5.1",
-      "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz",
-      "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==",
-      "dev": true,
-      "peerDependencies": {
-        "babel-plugin-macros": "^3.1.0"
-      },
-      "peerDependenciesMeta": {
-        "babel-plugin-macros": {
-          "optional": true
-        }
-      }
-    },
     "node_modules/jest-circus/node_modules/pretty-format": {
       "version": "29.7.0",
       "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
diff --git a/package.json b/package.json
index 217613f56160a..3a31ec3874af2 100644
--- a/package.json
+++ b/package.json
@@ -61,6 +61,7 @@
     "autoprefixer": "~10.4.16",
     "classnames": "~2.5.1",
     "cross-env": "7.0.3",
+    "dedent": "1.5.1",
     "feed": "~4.2.2",
     "github-slugger": "~2.0.0",
     "glob": "~10.3.10",
diff --git a/pages/en/new-design/download/current.mdx b/pages/en/new-design/download/current.mdx
new file mode 100644
index 0000000000000..4daf425991bb5
--- /dev/null
+++ b/pages/en/new-design/download/current.mdx
@@ -0,0 +1,22 @@
+---
+layout: download.hbs
+title: Download Node.js®
+subtitle: Download Node.js the way you want.
+---
+
+
+ I want the version of Node.js for running + + +
+ +
+Node.js includes npm () and corepack. + +Read the blog post for this version + +Learn how to verify signed SHASUMS + +Check out all available Node.js download options + +
diff --git a/pages/en/new-design/download/index.mdx b/pages/en/new-design/download/index.mdx new file mode 100644 index 0000000000000..4daf425991bb5 --- /dev/null +++ b/pages/en/new-design/download/index.mdx @@ -0,0 +1,22 @@ +--- +layout: download.hbs +title: Download Node.js® +subtitle: Download Node.js the way you want. +--- + +
+ I want the version of Node.js for running + + +
+ +
+Node.js includes npm () and corepack. + +Read the blog post for this version + +Learn how to verify signed SHASUMS + +Check out all available Node.js download options + +
diff --git a/pages/en/new-design/download/package-manager/all.md b/pages/en/new-design/download/package-manager/all.md new file mode 100644 index 0000000000000..71f555abdfe1d --- /dev/null +++ b/pages/en/new-design/download/package-manager/all.md @@ -0,0 +1,410 @@ +--- +layout: docs.hbs +title: Installing Node.js via package manager +--- + +# Installing Node.js via Package Managers + +> The packages on this page are maintained and supported by their respective packagers, **not** the Node.js core team. Please report any issues you encounter to the package maintainer. If it turns out your issue is a bug in Node.js itself, the maintainer will report the issue upstream. + +--- + +- [Alpine Linux](#alpine-linux) +- [Android](#android) +- [Arch Linux](#arch-linux) +- [CentOS, Fedora and Red Hat Enterprise Linux](#centos-fedora-and-red-hat-enterprise-linux) +- [Debian and Ubuntu based Linux distributions](#debian-and-ubuntu-based-linux-distributions) +- [fnm](#fnm) +- [FreeBSD](#freebsd) +- [Gentoo](#gentoo) +- [IBM i](#ibm-i) +- [macOS](#macos) +- [n](#n) +- [NetBSD](#netbsd) +- [Nodenv](#nodenv) +- [nvm](#nvm) +- [nvs](#nvs) +- [OpenBSD](#openbsd) +- [openSUSE and SLE](#opensuse-and-sle) +- [SmartOS and illumos](#smartos-and-illumos) +- [Snap](#snap) +- [Solus](#solus) +- [Void Linux](#void-linux) +- [Windows](#windows-1) +- [z/OS](#zos) + +--- + +## Alpine Linux + +Node.js LTS and npm packages are available in the Main Repository. + +```bash +apk add nodejs npm +``` + +Node.js Current can be installed from the Community Repository. + +```bash +apk add nodejs-current +``` + +## Android + +Android support is still experimental in Node.js, so precompiled binaries are not yet provided by Node.js developers. + +However, there are some third-party solutions. For example, [Termux](https://termux.com/) community provides terminal emulator and Linux environment for Android, as well as own package manager and [extensive collection](https://github.com/termux/termux-packages) of many precompiled applications. This command in Termux app will install the last available Node.js version: + +```bash +pkg install nodejs +``` + +Currently, Termux Node.js binaries are linked against `system-icu` (depending on `libicu` package). + +## Arch Linux + +Node.js and npm packages are available in the Community Repository. + +```bash +pacman -S nodejs npm +``` + +## CentOS, Fedora and Red Hat Enterprise Linux + +Node.js is available as a module called `nodejs` in CentOS/RHEL 8 and Fedora. + +```bash +dnf module install nodejs: +``` + +where `` corresponds to the major version of Node.js. +To see a list of available streams: + +```bash +dnf module list nodejs +``` + +For example, to install Node.js 18: + +```bash +dnf module install nodejs:18/common +``` + +### Alternatives + +These resources provide packages compatible with CentOS, Fedora, and RHEL. + +- [Node.js snaps](#snap) maintained and supported at https://github.com/nodejs/snap +- [Node.js binary distributions](#debian-and-ubuntu-based-linux-distributions) maintained and supported by [NodeSource](https://github.com/nodesource/distributions) + +## Debian and Ubuntu based Linux distributions + +[Node.js binary distributions](https://github.com/nodesource/distributions) are available from NodeSource. + +### Alternatives + +Packages compatible with Debian and Ubuntu based Linux distributions are available via [Node.js snaps](#snap). + +## fnm + +Fast and simple Node.js version manager built in Rust used to manage multiple released Node.js versions. It allows you to perform operations like install, uninstall, switch Node versions automatically based on the current directory, etc. +To install fnm, use this [install script](https://github.com/Schniz/fnm#using-a-script-macoslinux). + +fnm has cross-platform support (macOS, Windows, Linux) & all popular shells (Bash, Zsh, Fish, PowerShell, Windows Command Line Prompt). +fnm is built with speed in mind and compatibility support for `.node-version` and `.nvmrc` files. + +## FreeBSD + +The most recent release of Node.js is available via the [www/node](https://www.freshports.org/www/node) port. + +Install a binary package via [pkg](https://www.freebsd.org/cgi/man.cgi?pkg): + +```bash +pkg install node +``` + +Or compile it on your own using [ports](https://www.freebsd.org/cgi/man.cgi?ports): + +```bash +cd /usr/ports/www/node && make install +``` + +## Gentoo + +Node.js is available in the portage tree. + +```bash +emerge nodejs +``` + +## IBM i + +LTS versions of Node.js are available from IBM, and are available via [the 'yum' package manager](https://ibm.biz/ibmi-rpms). The package name is `nodejs` followed by the major version number (for instance, `nodejs12`, `nodejs14` etc) + +To install Node.js 14.x from the command line, run the following as a user with \*ALLOBJ special authority: + +```bash +yum install nodejs14 +``` + +Node.js can also be installed with the IBM i Access Client Solutions product. See [this support document](http://www-01.ibm.com/support/docview.wss?uid=nas8N1022619) for more details + +## macOS + +Download the [macOS Installer](/#home-downloadhead) directly from the [nodejs.org](https://nodejs.org/) web site. + +_If you want to download the package with bash:_ + +```bash +curl "https://nodejs.org/dist/latest/$(curl -s https://nodejs.org/dist/latest/ | grep "pkg" | cut -d'"' -f 2)" -o "$HOME/Downloads/node-latest.pkg" && sudo installer -store -pkg "$HOME/Downloads/node-latest.pkg" -target "/" +``` + +### Alternatives + +Using **[Homebrew](https://brew.sh/)**: + +```bash +brew install node +``` + +Using **[MacPorts](https://www.macports.org/)**: + +```bash +port install nodejs + +# Example +port install nodejs7 +``` + +Using **[pkgsrc](https://pkgsrc.joyent.com/install-on-macos/)**: + +Install the binary package: + +```bash +pkgin -y install nodejs +``` + +Or build manually from pkgsrc: + +```bash +cd pkgsrc/lang/nodejs && bmake install +``` + +## n + +`n` is a simple to use Node.js version manager for Mac and Linux. Specify the target version to install using a rich syntax, +or select from a menu of previously downloaded versions. The versions are installed system-wide or user-wide, and for more +targeted use you can run a version directly from the cached downloads. + +See the [homepage](https://github.com/tj/n) for install methods (bootstrap, npm, Homebrew, third-party), and all the usage details. + +If you already have `npm` then installing `n` and then the newest LTS `node` version is as simple as: + +``` +npm install -g n +n lts +``` + +## NetBSD + +Node.js is available in the pkgsrc tree: + +```bash +cd /usr/pkgsrc/lang/nodejs && make install +``` + +Or install a binary package (if available for your platform) using pkgin: + +```bash +pkgin -y install nodejs +``` + +## Nodenv + +`nodenv` is a lightweight node version manager, similar to `nvm`. It's simple and predictable. A rich plugin ecosystem lets you tailor it to suit your needs. Use `nodenv` to pick a Node version for your application and guarantee that your development environment matches production. + +Nodenv installation instructions are maintained [on its Github page](https://github.com/nodenv/nodenv#installation). Please visit that page to ensure you're following the latest version of the installation steps. + +## nvm + +Node Version Manager is a bash script used to manage multiple released Node.js versions. It allows +you to perform operations like install, uninstall, switch version, etc. +To install nvm, use this [install script](https://github.com/nvm-sh/nvm#install--update-script). + +On Unix / OS X systems Node.js built from source can be installed using +[nvm](https://github.com/creationix/nvm) by installing into the location that nvm expects: + +```bash +env VERSION=`python tools/getnodeversion.py` make install DESTDIR=`nvm_version_path v$VERSION` PREFIX="" +``` + +After this you can use `nvm` to switch between released versions and versions +built from source. +For example, if the version of Node.js is v8.0.0-pre: + +```bash +nvm use 8 +``` + +Once the official release is out you will want to uninstall the version built +from source: + +```bash +nvm uninstall 8 +``` + +## nvs + +#### Windows + +The `nvs` version manager is cross-platform and can be used on Windows, macOS, and Unix-like systems + +To install `nvs` on Windows go to the [release page](https://github.com/jasongin/nvs/releases) here and download the MSI installer file of the latest release. + +You can also use `chocolatey` to install it: + +```bash +choco install nvs +``` + +#### macOS,UnixLike + +You can find the documentation regarding the installation steps of `nvs` in macOS/Unix-like systems [here](https://github.com/jasongin/nvs/blob/master/doc/SETUP.md#mac-linux) + +#### Usage + +After this you can use `nvs` to switch between different versions of node. + +To add the latest version of node: + +```bash +nvs add latest +``` + +Or to add the latest LTS version of node: + +```bash +nvs add lts +``` + +Then run the `nvs use` command to add a version of node to your `PATH` for the current shell: + +```bash +$ nvs use lts +PATH -= %LOCALAPPDATA%\nvs\default +PATH += %LOCALAPPDATA%\nvs\node\14.17.0\x64 +``` + +To add it to `PATH` permanently, use `nvs link`: + +```bash +nvs link lts +``` + +## OpenBSD + +Node.js is available through the ports system. + +```bash +/usr/ports/lang/node +``` + +Using [pkg_add](https://man.openbsd.org/OpenBSD-current/man1/pkg_add.1) on OpenBSD: + +```bash +pkg_add node +``` + +## openSUSE and SLE + +Node.js is available in the main repositories under the following packages: + +- **openSUSE Leap 15.2**: `nodejs10`, `nodejs12`, `nodejs14` +- **openSUSE Tumbleweed**: `nodejs20` +- **SUSE Linux Enterprise Server (SLES) 12**: `nodejs10`, `nodejs12`, and `nodejs14` + (The "Web and Scripting Module" must be [enabled](https://www.suse.com/releasenotes/x86_64/SUSE-SLES/12-SP5/#intro-modulesExtensionsRelated).) +- **SUSE Linux Enterprise Server (SLES) 15 SP2**: `nodejs10`, `nodejs12`, and `nodejs14` + (The "Web and Scripting Module" must be [enabled](https://www.suse.com/releasenotes/x86_64/SUSE-SLES/15/#Intro.Module).) + +For example, to install Node.js 14.x on openSUSE Leap 15.2, run the following as root: + +```bash +zypper install nodejs14 +``` + +Different major versions of Node can be installed and used concurrently. + +## SmartOS and illumos + +SmartOS images come with pkgsrc pre-installed. On other illumos distributions, first install **[pkgsrc](https://pkgsrc.joyent.com/install-on-illumos/)**, then you may install the binary package as normal: + +```bash +pkgin -y install nodejs +``` + +Or build manually from pkgsrc: + +```bash +cd pkgsrc/lang/nodejs && bmake install +``` + +## Snap + +[Node.js snaps](https://github.com/nodejs/snap) are available as [`node`](https://snapcraft.io/node) on the Snap store. + +## Solus + +Solus provides Node.js in its main repository. + +```bash +sudo eopkg install nodejs +``` + +## Void Linux + +Void Linux ships Node.js stable in the main repository. + +```bash +xbps-install -Sy nodejs +``` + +## Windows + +Download the [Windows Installer](/#home-downloadhead) directly from the [nodejs.org](https://nodejs.org/) web site. + +### Alternatives + +Using **[Winget](https://aka.ms/winget-cli)**: + +```bash +winget install OpenJS.NodeJS +# or for LTS +winget install OpenJS.NodeJS.LTS +``` + +After running one of the two commands above, it may be necessary to restart the +terminal emulator before the `node` CLI command becomes available. + +Using **[Chocolatey](https://chocolatey.org/)**: + +```bash +cinst nodejs +# or for full install with npm +cinst nodejs.install +``` + +Using **[Scoop](https://scoop.sh/)**: + +```bash +scoop install nodejs +# or for LTS +scoop install nodejs-lts +``` + +## z/OS + +IBM® SDK for Node.js - z/OS® is available in two installation formats, +SMP/E and PAX. Select the installation format that applies to you: + +- [Installing and configuring SMP/E edition of Node.js on z/OS](https://www.ibm.com/docs/en/sdk-nodejs-zos/14.0?topic=configuring-installing-smpe-edition) +- [Installing and configuring PAX edition of Node.js on z/OS](https://www.ibm.com/docs/en/sdk-nodejs-zos/14.0?topic=configuring-installing-pax-edition) diff --git a/pages/en/new-design/download/package-manager/current.mdx b/pages/en/new-design/download/package-manager/current.mdx new file mode 100644 index 0000000000000..132e012a9e4a8 --- /dev/null +++ b/pages/en/new-design/download/package-manager/current.mdx @@ -0,0 +1,22 @@ +--- +layout: download.hbs +title: Download Node.js® +subtitle: Download Node.js the way you want. +--- + +
+ Install Node.js on using + + +
+ +
+Node.js includes npm () and corepack. + +Read the blog post for this version + +Learn how to verify signed SHASUMS + +Check out other community supported package managers + +
diff --git a/pages/en/new-design/download/package-manager/index.mdx b/pages/en/new-design/download/package-manager/index.mdx new file mode 100644 index 0000000000000..3989c120f46c8 --- /dev/null +++ b/pages/en/new-design/download/package-manager/index.mdx @@ -0,0 +1,22 @@ +--- +layout: download.hbs +title: Download Node.js® +subtitle: Download Node.js the way you want. +--- + +
+ Install Node.js on using + + +
+ +
+Node.js includes npm () and corepack. + +Read the blog post for this version + +Learn how to verify signed SHASUMS + +Check out other community supported package managers + +
diff --git a/pages/en/new-design/download/prebuilt-binaries/current.mdx b/pages/en/new-design/download/prebuilt-binaries/current.mdx new file mode 100644 index 0000000000000..bbf1af24c0c55 --- /dev/null +++ b/pages/en/new-design/download/prebuilt-binaries/current.mdx @@ -0,0 +1,22 @@ +--- +layout: download.hbs +title: Download Node.js® +subtitle: Download Node.js the way you want. +--- + +
+ I want the version of Node.js for running + + +
+ +
+Node.js includes npm () and corepack. + +Read the blog post for this version + +Learn how to verify signed SHASUMS + +Check out Nightly prebuilt binaries or Unofficial Builds for other platforms + +
diff --git a/pages/en/new-design/download/prebuilt-binaries/index.mdx b/pages/en/new-design/download/prebuilt-binaries/index.mdx new file mode 100644 index 0000000000000..bbf1af24c0c55 --- /dev/null +++ b/pages/en/new-design/download/prebuilt-binaries/index.mdx @@ -0,0 +1,22 @@ +--- +layout: download.hbs +title: Download Node.js® +subtitle: Download Node.js the way you want. +--- + +
+ I want the version of Node.js for running + + +
+ +
+Node.js includes npm () and corepack. + +Read the blog post for this version + +Learn how to verify signed SHASUMS + +Check out Nightly prebuilt binaries or Unofficial Builds for other platforms + +
diff --git a/pages/en/new-design/download/source-code/current.mdx b/pages/en/new-design/download/source-code/current.mdx new file mode 100644 index 0000000000000..ed9c5934b0e1b --- /dev/null +++ b/pages/en/new-design/download/source-code/current.mdx @@ -0,0 +1,22 @@ +--- +layout: download.hbs +title: Download Node.js® +subtitle: Download Node.js the way you want. +--- + +
+ I want the version of the Node.js source code. + + +
+ +
+Node.js includes npm () and corepack. + +Read the blog post for this version + +Learn how to verify signed SHASUMS + +Check out how to build Node.js from source. + +
diff --git a/pages/en/new-design/download/source-code/index.mdx b/pages/en/new-design/download/source-code/index.mdx new file mode 100644 index 0000000000000..ed9c5934b0e1b --- /dev/null +++ b/pages/en/new-design/download/source-code/index.mdx @@ -0,0 +1,22 @@ +--- +layout: download.hbs +title: Download Node.js® +subtitle: Download Node.js the way you want. +--- + +
+ I want the version of the Node.js source code. + + +
+ +
+Node.js includes npm () and corepack. + +Read the blog post for this version + +Learn how to verify signed SHASUMS + +Check out how to build Node.js from source. + +
diff --git a/pages/en/new-design/index.mdx b/pages/en/new-design/index.mdx index 3821e766e4ad7..23623fb5abeb5 100644 --- a/pages/en/new-design/index.mdx +++ b/pages/en/new-design/index.mdx @@ -22,7 +22,7 @@ layout: home.hbs Download Node.js (LTS) Downloads Node.js {release.versionWithPrefix} - 1 with long-term support. + 1 with long-term support. Node.js can also be installed via package managers. @@ -33,7 +33,7 @@ layout: home.hbs Want new features sooner? Get Node.js {release.versionWithPrefix} - 1 instead. + 1 instead. )} diff --git a/providers/releaseProvider.tsx b/providers/releaseProvider.tsx new file mode 100644 index 0000000000000..c17e8748af4cd --- /dev/null +++ b/providers/releaseProvider.tsx @@ -0,0 +1,74 @@ +'use client'; + +import type { Dispatch, PropsWithChildren, FC } from 'react'; +import { createContext, useMemo, useReducer } from 'react'; + +import type { NodeRelease } from '@/types'; +import type { + ReleaseDispatchActions, + ReleaseAction, + ReleaseContextType, + ReleaseProviderProps, + ReleaseState, +} from '@/types/release'; + +const initialState: ReleaseState = { + releases: [], + release: {} as NodeRelease, + os: 'OTHER', + bitness: '', + platform: 'NVM', +}; + +const createDispatchActions = ( + dispatch: Dispatch +): ReleaseDispatchActions => ({ + setVersion: payload => dispatch({ type: 'SET_VERSION', payload }), + setOS: payload => dispatch({ type: 'SET_OS', payload }), + setBitness: payload => dispatch({ type: 'SET_BITNESS', payload }), + setPlatform: payload => dispatch({ type: 'SET_PLATFORM', payload }), +}); + +export const ReleaseContext = createContext({ + ...initialState, + ...createDispatchActions(() => {}), +}); + +export const ReleaseProvider: FC> = ({ + children, + releases, + initialRelease, +}) => { + const getReleaseFromVersion = (version: string) => + releases.find(({ versionWithPrefix }) => versionWithPrefix === version) ?? + ({} as NodeRelease); + + const releaseReducer = (state: ReleaseState, action: ReleaseAction) => { + switch (action.type) { + case 'SET_VERSION': + return { ...state, release: getReleaseFromVersion(action.payload) }; + case 'SET_OS': + return { ...state, os: action.payload }; + case 'SET_BITNESS': + return { ...state, bitness: action.payload }; + case 'SET_PLATFORM': + return { ...state, platform: action.payload }; + default: + return state; + } + }; + + const [state, dispatch] = useReducer(releaseReducer, { + ...initialState, + releases: releases, + release: initialRelease, + }); + + const actions = useMemo(() => createDispatchActions(dispatch), [dispatch]); + + return ( + + {children} + + ); +}; diff --git a/types/layouts.ts b/types/layouts.ts index 873e7ee8f507b..077f190311066 100644 --- a/types/layouts.ts +++ b/types/layouts.ts @@ -6,7 +6,8 @@ export type Layouts = | 'page.hbs' | 'blog-category.hbs' | 'blog-post.hbs' - | 'search.hbs'; + | 'search.hbs' + | 'download.hbs'; // @TODO: These are legacy layouts that are going to be replaced with the `nodejs/nodejs.dev` Layouts in the future export type LegacyLayouts = diff --git a/types/release.ts b/types/release.ts new file mode 100644 index 0000000000000..2a0ca606e0242 --- /dev/null +++ b/types/release.ts @@ -0,0 +1,37 @@ +import type { ReactNode } from 'react'; + +import type { NodeRelease } from '@/types/releases'; +import type { UserOS } from '@/types/userOS'; + +export type PackageManager = 'NVM' | 'BREW' | 'DOCKER'; + +export interface ReleaseState { + os: UserOS; + release: NodeRelease; + releases: Array; + bitness: string | number; + platform: PackageManager; +} + +export type ReleaseAction = + | { type: 'SET_OS'; payload: UserOS } + | { type: 'SET_VERSION'; payload: string } + | { type: 'SET_BITNESS'; payload: string | number } + | { type: 'SET_PLATFORM'; payload: PackageManager }; + +export interface ReleaseDispatchActions { + setVersion: (version: string) => void; + setOS: (os: UserOS) => void; + setBitness: (bitness: string | number) => void; + setPlatform: (platform: PackageManager) => void; +} + +export interface ReleaseContextType + extends ReleaseState, + ReleaseDispatchActions {} + +export interface ReleaseProviderProps { + children: ReactNode; + releases: Array; + initialRelease: NodeRelease; +} diff --git a/util/__tests__/downloadUtils.test.mjs b/util/__tests__/downloadUtils.test.mjs new file mode 100644 index 0000000000000..fba82ecc2687c --- /dev/null +++ b/util/__tests__/downloadUtils.test.mjs @@ -0,0 +1,151 @@ +import { + getDownloadCategory, + mapCategoriesToTabs, + formatDropdownItems, +} from '@/util/downloadUtils'; + +describe('formatDropdownItems', () => { + it('should format dropdown items correctly', () => { + const items = [ + { value: 'item1', label: 'Item 1' }, + { value: 'item2', label: 'Item 2' }, + ]; + const disabledItems = ['item2']; + const icons = { item1: 'icon' }; + const defaultIcon = 'defaultIcon'; + + const result = formatDropdownItems({ + items: items, + disabledItems: disabledItems, + icons: icons, + defaultIcon: defaultIcon, + }); + + expect(result).toEqual([ + { value: 'item1', label: 'Item 1', disabled: false, iconImage: 'icon' }, + { + value: 'item2', + label: 'Item 2', + disabled: true, + iconImage: 'defaultIcon', + }, + ]); + }); + + it('should mark all items as disabled when all items are in the disabledItems list', () => { + const items = [ + { value: 'item1', label: 'Item 1' }, + { value: 'item2', label: 'Item 2' }, + ]; + const disabledItems = ['item1', 'item2']; + + const result = formatDropdownItems({ + items: items, + disabledItems: disabledItems, + }); + + expect(result).toEqual([ + { value: 'item1', label: 'Item 1', disabled: true }, + { value: 'item2', label: 'Item 2', disabled: true }, + ]); + }); + + it('should not mark any items as disabled when disabledItems list is empty', () => { + const items = [ + { value: 'item1', label: 'Item 1' }, + { value: 'item2', label: 'Item 2' }, + ]; + + const result = formatDropdownItems({ items: items }); + + expect(result).toEqual([ + { value: 'item1', label: 'Item 1', disabled: false }, + { value: 'item2', label: 'Item 2', disabled: false }, + ]); + }); +}); + +describe('getDownloadCategory', () => { + it('should return correct category information for /download/current', () => { + const result = getDownloadCategory('/download/current'); + + expect(result).toEqual({ + page: 'download', + category: 'download', + subCategory: 'current', + }); + }); + + it('should return correct category information for /download/category/subcategory', () => { + const result = getDownloadCategory('/download/category/subcategory'); + + expect(result).toEqual({ + page: 'download', + category: 'category', + subCategory: 'subcategory', + }); + }); + + it('should return correct category information for /download/category', () => { + const result = getDownloadCategory('/download/category'); + + expect(result).toEqual({ + page: 'download', + category: 'category', + subCategory: undefined, + }); + }); +}); + +describe('mapCategoriesToTabs', () => { + it('should return correct tabs for download page when subcategory current', () => { + const result = mapCategoriesToTabs({ + page: 'download', + categories: [ + { + category: 'download', + label: 'Download', + }, + { + category: 'package-manager', + label: 'Package Manager', + }, + ], + subCategory: 'current', + }); + + expect(result).toEqual([ + { key: 'download', label: 'Download', link: '/download/current' }, + { + key: 'package-manager', + label: 'Package Manager', + link: '/download/package-manager/current', + }, + ]); + }); + + it('should return correct tabs for download page when subcategory not defined', () => { + const result = mapCategoriesToTabs({ + page: 'download', + categories: [ + { + category: 'download', + label: 'Download', + }, + { + category: 'package-manager', + label: 'Package Manager', + }, + ], + }); + + expect(result).toEqual([ + { key: 'download', label: 'Download', link: '/download' }, + { + key: 'package-manager', + label: 'Package Manager', + link: '/download/package-manager', + }, + ]); + }); +}); diff --git a/util/__tests__/downloadUrlByOS.test.mjs b/util/__tests__/getNodeDownloadUrl.mjs similarity index 68% rename from util/__tests__/downloadUrlByOS.test.mjs rename to util/__tests__/getNodeDownloadUrl.mjs index a3dcbdf0ec454..e1b0450ce2008 100644 --- a/util/__tests__/downloadUrlByOS.test.mjs +++ b/util/__tests__/getNodeDownloadUrl.mjs @@ -1,14 +1,14 @@ -import { downloadUrlByOS } from '@/util/downloadUrlByOS'; +import { getNodeDownloadUrl } from '@/util/getNodeDownloadUrl'; const version = 'v18.16.0'; -describe('downloadUrlByOS', () => { +describe('getNodeDownloadUrl', () => { it('returns the correct download URL for Mac', () => { const os = 'MAC'; const bitness = 86; const expectedUrl = 'https://nodejs.org/dist/v18.16.0/node-v18.16.0.pkg'; - expect(downloadUrlByOS(version, os, bitness)).toBe(expectedUrl); + expect(getNodeDownloadUrl(version, os, bitness)).toBe(expectedUrl); }); it('returns the correct download URL for Windows (32-bit)', () => { @@ -17,7 +17,7 @@ describe('downloadUrlByOS', () => { const expectedUrl = 'https://nodejs.org/dist/v18.16.0/node-v18.16.0-x86.msi'; - expect(downloadUrlByOS(version, os, bitness)).toBe(expectedUrl); + expect(getNodeDownloadUrl(version, os, bitness)).toBe(expectedUrl); }); it('returns the correct download URL for Windows (64-bit)', () => { @@ -26,7 +26,7 @@ describe('downloadUrlByOS', () => { const expectedUrl = 'https://nodejs.org/dist/v18.16.0/node-v18.16.0-x64.msi'; - expect(downloadUrlByOS(version, os, bitness)).toBe(expectedUrl); + expect(getNodeDownloadUrl(version, os, bitness)).toBe(expectedUrl); }); it('returns the default download URL for other operating systems', () => { @@ -34,6 +34,6 @@ describe('downloadUrlByOS', () => { const bitness = 86; const expectedUrl = 'https://nodejs.org/dist/v18.16.0/node-v18.16.0.tar.xz'; - expect(downloadUrlByOS(version, os, bitness)).toBe(expectedUrl); + expect(getNodeDownloadUrl(version, os, bitness)).toBe(expectedUrl); }); }); diff --git a/util/downloadUrlByOS.ts b/util/downloadUrlByOS.ts deleted file mode 100644 index 4b943a74565c7..0000000000000 --- a/util/downloadUrlByOS.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { DIST_URL } from '@/next.constants.mjs'; -import type { UserOS } from '@/types/userOS'; - -export const downloadUrlByOS = ( - versionWithPrefix: string, - os: UserOS, - bitness: number -): string => { - const baseURL = `${DIST_URL}${versionWithPrefix}`; - - switch (os) { - case 'MAC': - return `${baseURL}/node-${versionWithPrefix}.pkg`; - case 'WIN': - return `${baseURL}/node-${versionWithPrefix}-x${bitness}.msi`; - case 'LINUX': - return `${baseURL}/node-${versionWithPrefix}-linux-x64.tar.xz`; - default: - return `${baseURL}/node-${versionWithPrefix}.tar.xz`; - } -}; diff --git a/util/downloadUtils.ts b/util/downloadUtils.ts new file mode 100644 index 0000000000000..7567bcb64d089 --- /dev/null +++ b/util/downloadUtils.ts @@ -0,0 +1,153 @@ +import { ENABLE_WEBSITE_REDESIGN } from '@/next.constants.mjs'; +import type { PackageManager } from '@/types/release'; +import type { UserOS } from '@/types/userOS'; + +// A utility enum to help convert `userOs` data type to user-readable format +export enum OperatingSystem { + WIN = 'Windows', + MAC = 'MacOS', + LINUX = 'Linux', + OTHER = 'Other', +} + +export const operatingSystemItems = [ + { + label: OperatingSystem.WIN, + value: 'WIN' as UserOS, + }, + { + label: OperatingSystem.MAC, + value: 'MAC' as UserOS, + }, + { + label: OperatingSystem.LINUX, + value: 'LINUX' as UserOS, + }, +]; + +export const platformItems = [ + { + label: 'NVM', + value: 'NVM' as PackageManager, + }, + { + label: 'Brew', + value: 'BREW' as PackageManager, + }, + { + label: 'Docker', + value: 'DOCKER' as PackageManager, + }, +]; + +export const bitnessItems = { + WIN: [ + { + label: '64-bit', + value: '64', + }, + { + label: '32-bit', + value: '86', + }, + { + label: 'ARM64', + value: 'arm64', + }, + ], + MAC: [ + { + label: '64-bit', + value: '64', + }, + { + label: 'ARM64', + value: 'arm64', + }, + ], + LINUX: [ + { + label: '64-bit', + value: '64', + }, + { + label: 'ARMv7', + value: 'armv7l', + }, + { + label: 'ARM64', + value: 'arm64', + }, + { + label: 'Power LE', + value: 'ppc64le', + }, + { + label: 'System Z', + value: 's390x', + }, + ], + OTHER: [], +}; + +type formatDropdownItemsType = { + items: Array<{ label: string; value: string }>; + disabledItems?: Array; + icons?: Record; + defaultIcon?: JSX.Element; +}; + +// Formats the dropdown items to be used in the `Select` component in the +// download page and adds the icons, and disabled status to the dropdown items. +export const formatDropdownItems = ({ + items, + disabledItems = [], + icons = {}, + defaultIcon, +}: formatDropdownItemsType) => + items.map(item => ({ + ...item, + disabled: disabledItems.includes(item.value), + iconImage: icons[item.value] || defaultIcon, + })); + +// Returns the page, category and subCategoy information to be used in the page +// from the pathname information on the download pages. +export const getDownloadCategory = (pathname: string) => { + /** @deprecated once the website redesign happens remove this code block */ + if (ENABLE_WEBSITE_REDESIGN) { + pathname = pathname.replace('/new-design', ''); + } + + const segments = pathname.split('/').filter(Boolean); + const [, c] = segments; + + if (c === 'current' || typeof c === 'undefined') { + segments.unshift('download'); + } + + const [page, category, subCategory] = segments; + + return { page, category, subCategory }; +}; + +type CategoryTabMappingParams = { + page: string; + categories: Array<{ category: string; label: string }>; + subCategory: string; +}; + +// Utility method used to create URLs and labels to be used in Tabs +export const mapCategoriesToTabs = ({ + page, + categories, + subCategory, +}: CategoryTabMappingParams) => + categories.map(({ category, label }) => ({ + key: category, + label: label, + link: + category === 'download' + ? `/${[page, subCategory].filter(Boolean).join('/')}` + : `/${[page, category, subCategory].filter(Boolean).join('/')}`, + })); diff --git a/util/getHighlighter.ts b/util/getHighlighter.ts index 1f6173771a602..19a5c456a6c68 100644 --- a/util/getHighlighter.ts +++ b/util/getHighlighter.ts @@ -1,17 +1,26 @@ import { getHighlighterCore } from 'shiki/core'; +import type { HighlighterCore } from 'shiki/core'; import getWasm from 'shiki/wasm'; import { LANGUAGES, DEFAULT_THEME } from '@/shiki.config.mjs'; // This creates a memoized minimal Shikiji Syntax Highlighter -const memoizedShikiji = await getHighlighterCore({ - themes: [DEFAULT_THEME], - langs: LANGUAGES, - loadWasm: getWasm, -}); +export const getShiki = () => + getHighlighterCore({ + themes: [DEFAULT_THEME], + langs: LANGUAGES, + loadWasm: getWasm, + }); -export const highlightToHtml = (code: string, language: string) => - memoizedShikiji.codeToHtml(code, { lang: language, theme: DEFAULT_THEME }); +export const highlightToHtml = + (shiki: HighlighterCore) => (code: string, language: string) => + // Shiki will always return the Highlighted code encapsulated in a
 and  tag
+    // since our own CodeBox component handles the  tag, we just want to extract
+    // the inner highlighted code to the CodeBox
+    shiki
+      .codeToHtml(code, { lang: language, theme: DEFAULT_THEME })
+      .match(/(.+?)<\/code>/s)![1];
 
-export const highlightToHast = (code: string, language: string) =>
-  memoizedShikiji.codeToHast(code, { lang: language, theme: DEFAULT_THEME });
+export const highlightToHast =
+  (shiki: HighlighterCore) => (code: string, language: string) =>
+    shiki.codeToHast(code, { lang: language, theme: DEFAULT_THEME });
diff --git a/util/getNodeDownloadSnippet.ts b/util/getNodeDownloadSnippet.ts
new file mode 100644
index 0000000000000..4ca8152f53263
--- /dev/null
+++ b/util/getNodeDownloadSnippet.ts
@@ -0,0 +1,87 @@
+import dedent from 'dedent';
+
+import type { UserOS } from '@/types/userOS';
+
+export const getNodeDownloadSnippet = (major: number, os: UserOS) => {
+  if (os === 'LINUX' || os === 'MAC') {
+    const platformSnippets = {
+      NVM: dedent`
+        # Installs NVM (Node Version Manager)
+        curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
+
+        # Installs Node.js
+        nvm install v${major}
+
+        # Checks that Node is installed
+        node -v
+
+        # Checks your NPM version
+        npm -v`,
+      BREW: dedent`
+        # Installs Brew (macOS/Linux Package Manager)
+        curl -o- https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh | bash
+
+        # Installs Node.js
+        brew install node@${major}
+
+        # Checks that Node is installed
+        node -v
+
+        # Checks your NPM version
+        npm -v`,
+      DOCKER: '',
+    };
+
+    if (os === 'MAC') {
+      platformSnippets.DOCKER = dedent`
+        # Installs Brew (macOS Package Manager)
+        curl -o- https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh | bash
+
+        # Installs Docker Desktop
+        brew install docker --cask
+
+        # Pull Node.js Docker Image
+        docker pull node:${major}-${major >= 4 ? 'alpine' : 'slim'}
+      `;
+    }
+
+    return platformSnippets;
+  }
+
+  if (os === 'WIN') {
+    return {
+      NVM: dedent`
+        # Installs Chocolatey (Windows Package Manager)
+        Set-ExecutionPolicy Bypass -Scope Process -Force;
+        [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072;
+        iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'));
+
+        # Installs NVM (Node Version Manager)
+        choco install nvm
+
+        # Installs Node.js
+        nvm install v${major}
+
+        # Checks that Node is installed
+        node -v
+
+        # Checks your NPM version
+        npm -v`,
+      DOCKER: dedent`
+        # Installs Chocolatey (Windows Package Manager)
+        Set-ExecutionPolicy Bypass -Scope Process -Force;
+        [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072;
+        iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'));
+
+        # Installs Docker Desktop
+        choco install docker-desktop
+
+        # Pull Node.js Docker Image
+        docker pull node:${major}-${major >= 4 ? 'alpine' : 'slim'}
+      `,
+      BREW: '',
+    };
+  }
+
+  return { NVM: '', BREW: '', DOCKER: '' };
+};
diff --git a/util/getNodeDownloadUrl.ts b/util/getNodeDownloadUrl.ts
new file mode 100644
index 0000000000000..93f3f46d52496
--- /dev/null
+++ b/util/getNodeDownloadUrl.ts
@@ -0,0 +1,51 @@
+import { DIST_URL } from '@/next.constants.mjs';
+import type { UserOS } from '@/types/userOS';
+
+export const getNodeDownloadUrl = (
+  versionWithPrefix: string,
+  os: UserOS,
+  bitness: string | number,
+  kind: 'installer' | 'binary' | 'source' = 'installer'
+): string => {
+  const baseURL = `${DIST_URL}${versionWithPrefix}`;
+
+  if (kind === 'source') {
+    return `${baseURL}/node-${versionWithPrefix}.tar.gz`;
+  }
+
+  switch (os) {
+    case 'MAC':
+      if (kind === 'installer') {
+        return `${baseURL}/node-${versionWithPrefix}.pkg`;
+      }
+
+      if (typeof bitness === 'string') {
+        return `${baseURL}/node-${versionWithPrefix}-darwin-${bitness}.tar.gz`;
+      }
+
+      return `${baseURL}/node-${versionWithPrefix}-darwin-x${bitness}.tar.gz`;
+    case 'WIN': {
+      if (kind === 'installer') {
+        if (typeof bitness === 'string') {
+          return `${baseURL}/node-${versionWithPrefix}-${bitness}.msi`;
+        }
+
+        return `${baseURL}/node-${versionWithPrefix}-x${bitness}.msi`;
+      }
+
+      if (typeof bitness === 'string') {
+        return `${baseURL}/node-${versionWithPrefix}-win-${bitness}.zip`;
+      }
+
+      return `${baseURL}/node-${versionWithPrefix}-win-x${bitness}.zip`;
+    }
+    case 'LINUX':
+      if (typeof bitness === 'string') {
+        return `${baseURL}/node-${versionWithPrefix}-linux-${bitness}.tar.xz`;
+      }
+
+      return `${baseURL}/node-${versionWithPrefix}-linux-x${bitness}.tar.xz`;
+    default:
+      return `${baseURL}/node-${versionWithPrefix}.tar.gz`;
+  }
+};