The Node.js® Website

feat: Download layout (#6353)

* feat: Download layout

* refactor: utils methods moved into the file

* refactor/docs: deprecated url path

* refactor/docs: download utils

* refactor: markdown formatting

* fix: download URL paths

* feat: exclude option for os dropdown

* refactor: separated type import

* refactor: styling updates

* refactor: type definitions moved into the own file

* test: unit tests for download utils

* fix: icons added into the download/source buttons

* fix: LinkWithArrow import path

* docs/refactor: source button

* refactor: review updates

* chore: code-review and code improvements

* feat: finished download page

* chore: minor fixes

* chore: code-review and bug fixes

* chore: prevent page reload on tab change

* chore: improve activelink matching

* feat: proper download versions and prebuilt binaries

* feat: added more support text for each respective version

* chore: minor bitness fixes for macOS

* refactor: cleanup of certain logic and added docker package manager

* chore: fix shiki interop client-server

* chore: fix unit test

* chore: minor text correction

* chore: nvm uses v prefix

* chore: rename label to ARM64 to keep it easier to understand

* refactor: reduce layout shift and improve select accessibility

* chore: reduce layout shift, simplify codebox and cleanup text

* Apply suggestions from code review

Signed-off-by: Brian Muenzenmeyer <brian.muenzenmeyer@gmail.com>

* added Docker platform logo, grouped by usage and alphabetized

* chore: minor changes and fixes

* chore: reduce build times by making build of these routes on-demand

* fix: keep same bitness if compatible on OS change, verify OS supports bitness

---------

Signed-off-by: Brian Muenzenmeyer <brian.muenzenmeyer@gmail.com>
Co-authored-by: Claudio Wunder <cwunder@gnome.org>
Co-authored-by: Brian Muenzenmeyer <brian.muenzenmeyer@gmail.com>

authored by Caner Akdas Claudio Wunder Brian Muenzenmeyer and committed by GitHub b624417a aef8eab0

+3
.husky/pre-commit
··· 3 3 4 4 # lint and format staged files 5 5 npx lint-staged 6 + 7 + # verify typescript staged files 8 + npx tsc --build .
+1 -1
app/[locale]/next-data/api-data/route.ts
··· 55 55 56 56 // Enforces that this route is used as static rendering 57 57 // @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic 58 - export const dynamic = 'force-static'; 58 + export const dynamic = 'error'; 59 59 60 60 // Ensures that this endpoint is invalidated and re-executed every X minutes 61 61 // so that when new deployments happen, the data is refreshed
+1 -1
app/[locale]/next-data/blog-data/[category]/[page]/route.ts
··· 59 59 60 60 // Enforces that this route is cached and static as much as possible 61 61 // @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic 62 - export const dynamic = 'force-static'; 62 + export const dynamic = 'error'; 63 63 64 64 // Ensures that this endpoint is invalidated and re-executed every X minutes 65 65 // so that when new deployments happen, the data is refreshed
+1 -1
app/[locale]/next-data/page-data/route.ts
··· 64 64 65 65 // Enforces that this route is used as static rendering 66 66 // @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic 67 - export const dynamic = 'force-static'; 67 + export const dynamic = 'error'; 68 68 69 69 // Ensures that this endpoint is invalidated and re-executed every X minutes 70 70 // so that when new deployments happen, the data is refreshed
+1 -1
app/[locale]/next-data/release-data/route.ts
··· 24 24 25 25 // Enforces that this route is used as static rendering 26 26 // @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic 27 - export const dynamic = 'force-static'; 27 + export const dynamic = 'error'; 28 28 29 29 // Ensures that this endpoint is invalidated and re-executed every X minutes 30 30 // so that when new deployments happen, the data is refreshed
+2 -2
components/Common/ActiveLink/index.tsx
··· 4 4 import type { ComponentProps, FC } from 'react'; 5 5 6 6 import Link from '@/components/Link'; 7 - import { useClientContext } from '@/hooks'; 7 + import { usePathname } from '@/navigation.mjs'; 8 8 9 9 type ActiveLocalizedLinkProps = ComponentProps<typeof Link> & { 10 10 activeClassName?: string; ··· 19 19 href = '', 20 20 ...props 21 21 }) => { 22 - const { pathname } = useClientContext(); 22 + const pathname = usePathname(); 23 23 24 24 const finalClassName = classNames(className, { 25 25 [activeClassName]: allowSubPath
+2 -1
components/Common/Breadcrumbs/index.tsx
··· 1 - import { useMemo, type FC } from 'react'; 1 + import type { FC } from 'react'; 2 + import { useMemo } from 'react'; 2 3 3 4 import BreadcrumbHomeLink from '@/components/Common/Breadcrumbs/BreadcrumbHomeLink'; 4 5 import BreadcrumbItem from '@/components/Common/Breadcrumbs/BreadcrumbItem';
+5 -3
components/Common/CodeBox/index.tsx
··· 16 16 17 17 // Transforms a code element with plain text content into a more structured 18 18 // format for rendering with line numbers 19 - const transformCode = (code: ReactNode): ReactNode => { 19 + const transformCode = (code: ReactNode, language: string): ReactNode => { 20 20 if (!isValidElement(code)) { 21 21 // Early return when the `CodeBox` child is not a valid element since the 22 22 // type is a ReactNode, and can assume any value ··· 35 35 // being an empty string, so we need to remove it 36 36 const lines = content.split('\n'); 37 37 38 + const extraStyle = language.length === 0 ? { fontFamily: 'monospace' } : {}; 39 + 38 40 return ( 39 - <code style={{ fontFamily: 'monospace' }}> 41 + <code style={extraStyle}> 40 42 {lines 41 43 .flatMap((line, lineIndex) => { 42 44 const columns = line.split(' '); ··· 97 99 return ( 98 100 <div className={styles.root}> 99 101 <pre ref={ref} className={styles.content} tabIndex={0}> 100 - {transformCode(children)} 102 + {transformCode(children, language)} 101 103 </pre> 102 104 103 105 {language && (
+10 -5
components/Common/LinkTabs/index.module.css
··· 28 28 } 29 29 } 30 30 31 - .tabsSelect > div { 32 - @apply my-6 33 - hidden 34 - w-full 35 - xs:flex; 31 + .tabsSelect { 32 + @apply sm:visible 33 + md:hidden; 34 + 35 + > span { 36 + @apply my-6 37 + hidden 38 + w-full 39 + xs:flex; 40 + } 36 41 }
+1 -1
components/Common/ProgressionSidebar/index.module.css
··· 20 20 xs:hidden; 21 21 } 22 22 23 - > div { 23 + > span { 24 24 @apply hidden 25 25 w-full 26 26 xs:flex;
+6 -6
components/Common/ProgressionSidebar/index.tsx
··· 27 27 28 28 return ( 29 29 <nav className={styles.wrapper}> 30 + <WithRouterSelect 31 + label={t('components.common.sidebar.title')} 32 + values={selectItems} 33 + defaultValue={currentItem?.value} 34 + /> 35 + 30 36 {groups.map(({ groupName, items }) => ( 31 37 <ProgressionSidebarGroup 32 38 key={groupName.toString()} ··· 34 40 items={items} 35 41 /> 36 42 ))} 37 - 38 - <WithRouterSelect 39 - label={t('components.common.sidebar.title')} 40 - values={selectItems} 41 - defaultValue={currentItem?.value} 42 - /> 43 43 </nav> 44 44 ); 45 45 };
+5 -3
components/Common/Select/index.module.css
··· 1 1 .select { 2 - @apply flex 3 - w-fit 2 + @apply inline-flex 4 3 flex-col 5 4 gap-1.5; 6 5 ··· 47 46 48 47 .trigger span { 49 48 @apply flex 49 + h-5 50 50 items-center 51 51 gap-2; 52 52 } ··· 115 115 .text { 116 116 @apply text-neutral-900 117 117 data-[highlighted]:bg-neutral-100 118 + data-[disabled]:text-neutral-600 118 119 data-[highlighted]:text-neutral-900 119 120 dark:text-white 120 - dark:data-[highlighted]:bg-neutral-900; 121 + dark:data-[highlighted]:bg-neutral-900 122 + dark:data-[disabled]:text-neutral-700; 121 123 } 122 124 123 125 &.dropdown {
+15 -4
components/Common/Select/index.tsx
··· 14 14 label: FormattedMessage; 15 15 value: string; 16 16 iconImage?: React.ReactNode; 17 + disabled?: boolean; 17 18 }; 18 19 19 20 type SelectGroup = { ··· 34 35 label?: string; 35 36 inline?: boolean; 36 37 onChange?: (value: string) => void; 38 + className?: string; 37 39 }; 38 40 39 41 const Select: FC<SelectProps> = ({ ··· 43 45 label, 44 46 inline, 45 47 onChange, 48 + className, 46 49 }) => { 47 50 const id = useId(); 48 51 ··· 61 64 }, [values]); 62 65 63 66 return ( 64 - <div className={classNames(styles.select, { [styles.inline]: inline })}> 65 - {label && ( 67 + <span 68 + className={classNames( 69 + styles.select, 70 + { [styles.inline]: inline }, 71 + className 72 + )} 73 + > 74 + {label && !inline && ( 66 75 <label className={styles.label} htmlFor={id}> 67 76 {label} 68 77 </label> 69 78 )} 79 + 70 80 <Primitive.Root value={defaultValue} onValueChange={onChange}> 71 81 <Primitive.Trigger 72 82 className={styles.trigger} ··· 92 102 </Primitive.Label> 93 103 )} 94 104 95 - {items.map(({ value, label, iconImage }) => ( 105 + {items.map(({ value, label, iconImage, disabled }) => ( 96 106 <Primitive.Item 97 107 key={value} 98 108 value={value} 109 + disabled={disabled} 99 110 className={classNames(styles.item, styles.text)} 100 111 > 101 112 <Primitive.ItemText> ··· 110 121 </Primitive.Content> 111 122 </Primitive.Portal> 112 123 </Primitive.Root> 113 - </div> 124 + </span> 114 125 ); 115 126 }; 116 127
+1
components/Containers/NavBar/NavItem/index.tsx
··· 27 27 allowSubPath={href.startsWith('/')} 28 28 > 29 29 <span className={styles.label}>{children}</span> 30 + 30 31 {type === 'nav' && href.startsWith('http') && ( 31 32 <ArrowUpRightIcon className={styles.icon} /> 32 33 )}
+1 -1
components/Containers/Sidebar/index.module.css
··· 22 22 xs:hidden; 23 23 } 24 24 25 - > div { 25 + > span { 26 26 @apply hidden 27 27 w-full 28 28 xs:flex;
+2 -2
components/Downloads/DownloadButton/index.tsx
··· 7 7 import Button from '@/components/Common/Button'; 8 8 import { useDetectOS } from '@/hooks'; 9 9 import type { NodeRelease } from '@/types'; 10 - import { downloadUrlByOS } from '@/util/downloadUrlByOS'; 10 + import { getNodeDownloadUrl } from '@/util/getNodeDownloadUrl'; 11 11 12 12 import styles from './index.module.css'; 13 13 ··· 18 18 children, 19 19 }) => { 20 20 const { os, bitness } = useDetectOS(); 21 - const downloadLink = downloadUrlByOS(versionWithPrefix, os, bitness); 21 + const downloadLink = getNodeDownloadUrl(versionWithPrefix, os, bitness); 22 22 23 23 return ( 24 24 <>
+2 -2
components/Downloads/DownloadLink.tsx
··· 4 4 5 5 import { useDetectOS } from '@/hooks'; 6 6 import type { NodeRelease } from '@/types'; 7 - import { downloadUrlByOS } from '@/util/downloadUrlByOS'; 7 + import { getNodeDownloadUrl } from '@/util/getNodeDownloadUrl'; 8 8 9 9 type DownloadLinkProps = { release: NodeRelease }; 10 10 ··· 13 13 children, 14 14 }) => { 15 15 const { os, bitness } = useDetectOS(); 16 - const downloadLink = downloadUrlByOS(versionWithPrefix, os, bitness); 16 + const downloadLink = getNodeDownloadUrl(versionWithPrefix, os, bitness); 17 17 18 18 return <a href={downloadLink}>{children}</a>; 19 19 };
+91
components/Downloads/Release/BitnessDropdown.tsx
··· 1 + 'use client'; 2 + 3 + import { useTranslations } from 'next-intl'; 4 + import type { FC } from 'react'; 5 + import { useEffect, useContext, useMemo } from 'react'; 6 + import semVer from 'semver'; 7 + 8 + import Select from '@/components/Common/Select'; 9 + import { useDetectOS } from '@/hooks/react-client'; 10 + import { ReleaseContext } from '@/providers/releaseProvider'; 11 + import { bitnessItems, formatDropdownItems } from '@/util/downloadUtils'; 12 + 13 + const parseNumericBitness = (bitness: string) => 14 + /^\d+$/.test(bitness) ? Number(bitness) : bitness; 15 + 16 + const BitnessDropdown: FC = () => { 17 + const { bitness: userBitness } = useDetectOS(); 18 + const { bitness, os, release, setBitness } = useContext(ReleaseContext); 19 + const t = useTranslations(); 20 + 21 + // we also reset the bitness when the OS changes, because different OSs have 22 + // different bitnesses available 23 + useEffect(() => setBitness(userBitness), [setBitness, userBitness]); 24 + 25 + // @TODO: We should have a proper utility that gives 26 + // disabled OSs, Platforms, based on specific criteria 27 + // this can be an optimisation for the future 28 + // to remove this logic from this component 29 + const disabledItems = useMemo(() => { 30 + const disabledItems = []; 31 + 32 + if (os === 'WIN' && semVer.satisfies(release.version, '< 19.9.0')) { 33 + disabledItems.push('arm64'); 34 + } 35 + 36 + if (os === 'LINUX' && semVer.satisfies(release.version, '< 4.0.0')) { 37 + disabledItems.push('arm64', 'armv7l'); 38 + } 39 + 40 + if (os === 'LINUX' && semVer.satisfies(release.version, '< 4.4.0')) { 41 + disabledItems.push('ppc64le'); 42 + } 43 + 44 + if (os === 'LINUX' && semVer.satisfies(release.version, '< 6.6.0')) { 45 + disabledItems.push('s390x'); 46 + } 47 + 48 + return disabledItems; 49 + }, [os, release.version]); 50 + 51 + // @TODO: We should have a proper utility that gives 52 + // disabled OSs, Platforms, based on specific criteria 53 + // this can be an optimisation for the future 54 + // to remove this logic from this component 55 + useEffect(() => { 56 + const mappedBittnessesValues = bitnessItems[os].map(({ value }) => value); 57 + 58 + const currentBittnessExcluded = 59 + // Different OSs support different Bitnessess, hence we should also check 60 + // if besides the current bitness not being supported for a given release version 61 + // we also should check if it is not supported by the OS 62 + disabledItems.includes(String(bitness)) || 63 + !mappedBittnessesValues.includes(String(bitness)); 64 + 65 + const nonExcludedBitness = mappedBittnessesValues.find( 66 + bittness => !disabledItems.includes(bittness) 67 + ); 68 + 69 + if (currentBittnessExcluded && nonExcludedBitness) { 70 + setBitness(nonExcludedBitness); 71 + } 72 + // we shouldn't react when "actions" change 73 + // eslint-disable-next-line react-hooks/exhaustive-deps 74 + }, [os, disabledItems]); 75 + 76 + return ( 77 + <Select 78 + label={t('layouts.download.dropdown.bitness')} 79 + values={formatDropdownItems({ 80 + items: bitnessItems[os], 81 + disabledItems, 82 + })} 83 + defaultValue={String(bitness)} 84 + onChange={bitness => setBitness(parseNumericBitness(bitness))} 85 + className="w-28" 86 + inline={true} 87 + /> 88 + ); 89 + }; 90 + 91 + export default BitnessDropdown;
+18
components/Downloads/Release/BlogPostLink.tsx
··· 1 + 'use client'; 2 + 3 + import type { FC, PropsWithChildren } from 'react'; 4 + import { useContext } from 'react'; 5 + 6 + import LinkWithArrow from '@/components/Downloads/Release/LinkWithArrow'; 7 + import { ReleaseContext } from '@/providers/releaseProvider'; 8 + 9 + const BlogPostLink: FC<PropsWithChildren> = ({ children }) => { 10 + const { release } = useContext(ReleaseContext); 11 + const version = release.versionWithPrefix; 12 + 13 + return ( 14 + <LinkWithArrow href={`/blog/release/${version}`}>{children}</LinkWithArrow> 15 + ); 16 + }; 17 + 18 + export default BlogPostLink;
+32
components/Downloads/Release/DownloadButton.tsx
··· 1 + 'use client'; 2 + 3 + import { CloudArrowDownIcon } from '@heroicons/react/24/outline'; 4 + import { useTranslations } from 'next-intl'; 5 + import { useContext } from 'react'; 6 + import type { FC } from 'react'; 7 + 8 + import Button from '@/components/Common/Button'; 9 + import { ReleaseContext } from '@/providers/releaseProvider'; 10 + import { getNodeDownloadUrl } from '@/util/getNodeDownloadUrl'; 11 + 12 + type DownloadButtonProps = { kind: 'installer' | 'binary' | 'source' }; 13 + 14 + const DownloadButton: FC<DownloadButtonProps> = ({ kind = 'installer' }) => { 15 + const t = useTranslations(); 16 + const { release, os, bitness } = useContext(ReleaseContext); 17 + 18 + const version = release.versionWithPrefix; 19 + const url = getNodeDownloadUrl(version, os, bitness, kind); 20 + 21 + return ( 22 + <div className="mb-2 mt-6"> 23 + <Button href={url} disabled={!version}> 24 + <CloudArrowDownIcon /> 25 + 26 + {t('layouts.download.buttons.prebuilt', { version })} 27 + </Button> 28 + </div> 29 + ); 30 + }; 31 + 32 + export default DownloadButton;
+17
components/Downloads/Release/LinkWithArrow.tsx
··· 1 + import { ArrowUpRightIcon } from '@heroicons/react/24/solid'; 2 + import type { FC, PropsWithChildren } from 'react'; 3 + 4 + import Link from '@/components/Link'; 5 + 6 + type AccessibleAnchorProps = { href?: string }; 7 + 8 + const LinkWithArrow: FC<PropsWithChildren<AccessibleAnchorProps>> = ({ 9 + children, 10 + ...props 11 + }) => ( 12 + <Link {...props}> 13 + {children} 14 + <ArrowUpRightIcon className="ml-1 inline w-3 fill-white" /> 15 + </Link> 16 + ); 17 + export default LinkWithArrow;
+16
components/Downloads/Release/NpmVersion.tsx
··· 1 + 'use client'; 2 + 3 + import { useContext } from 'react'; 4 + import type { FC } from 'react'; 5 + 6 + import { ReleaseContext } from '@/providers/releaseProvider'; 7 + 8 + const NpmVersion: FC = () => { 9 + const { 10 + release: { npm }, 11 + } = useContext(ReleaseContext); 12 + 13 + return <>{npm}</>; 14 + }; 15 + 16 + export default NpmVersion;
+88
components/Downloads/Release/OperatingSystemDropdown.tsx
··· 1 + 'use client'; 2 + 3 + import { useTranslations } from 'next-intl'; 4 + import { useContext, useEffect, useMemo } from 'react'; 5 + import type { FC } from 'react'; 6 + 7 + import Select from '@/components/Common/Select'; 8 + import Apple from '@/components/Icons/Platform/Apple'; 9 + import Linux from '@/components/Icons/Platform/Linux'; 10 + import Microsoft from '@/components/Icons/Platform/Microsoft'; 11 + import { useDetectOS } from '@/hooks/react-client'; 12 + import { ReleaseContext } from '@/providers/releaseProvider'; 13 + import type { UserOS } from '@/types/userOS'; 14 + import { 15 + formatDropdownItems, 16 + operatingSystemItems, 17 + } from '@/util/downloadUtils'; 18 + 19 + type OperatingSystemDropdownProps = { exclude: Array<UserOS> }; 20 + 21 + const OperatingSystemDropdown: FC<OperatingSystemDropdownProps> = ({ 22 + exclude = [], 23 + }) => { 24 + const { os: userOS } = useDetectOS(); 25 + const { platform, os, setOS } = useContext(ReleaseContext); 26 + const t = useTranslations(); 27 + 28 + // we shouldn't react when "actions" change 29 + // eslint-disable-next-line react-hooks/exhaustive-deps 30 + useEffect(() => setOS(userOS), [userOS]); 31 + 32 + // @TODO: We should have a proper utility that gives 33 + // disabled OSs, Platforms, based on specific criteria 34 + // this can be an optimisation for the future 35 + // to remove this logic from this component 36 + const disabledItems = useMemo(() => { 37 + const disabledItems = exclude; 38 + 39 + if (platform === 'BREW') { 40 + disabledItems.push('WIN'); 41 + } 42 + 43 + if (platform === 'DOCKER') { 44 + disabledItems.push('LINUX'); 45 + } 46 + 47 + return disabledItems; 48 + }, [exclude, platform]); 49 + 50 + // @TODO: We should have a proper utility that gives 51 + // disabled OSs, Platforms, based on specific criteria 52 + // this can be an optimisation for the future 53 + // to remove this logic from this component 54 + useEffect(() => { 55 + const currentOSExcluded = disabledItems.includes(os); 56 + 57 + const nonExcludedOS = operatingSystemItems 58 + .map(({ value }) => value) 59 + .find(os => !disabledItems.includes(os)); 60 + 61 + if (currentOSExcluded && nonExcludedOS) { 62 + setOS(nonExcludedOS); 63 + } 64 + // we shouldn't react when "actions" change 65 + // eslint-disable-next-line react-hooks/exhaustive-deps 66 + }, [os, disabledItems]); 67 + 68 + return ( 69 + <Select 70 + label={t('layouts.download.dropdown.os')} 71 + values={formatDropdownItems({ 72 + items: operatingSystemItems, 73 + disabledItems, 74 + icons: { 75 + WIN: <Microsoft width={16} height={16} />, 76 + MAC: <Apple width={16} height={16} />, 77 + LINUX: <Linux width={16} height={16} />, 78 + }, 79 + })} 80 + defaultValue={os} 81 + onChange={value => setOS(value as UserOS)} 82 + className="w-[8.5rem]" 83 + inline={true} 84 + /> 85 + ); 86 + }; 87 + 88 + export default OperatingSystemDropdown;
+85
components/Downloads/Release/PlatformDropdown.tsx
··· 1 + 'use client'; 2 + 3 + import { useTranslations } from 'next-intl'; 4 + import { useContext, useEffect, useMemo } from 'react'; 5 + import type { FC } from 'react'; 6 + 7 + import Select from '@/components/Common/Select'; 8 + import Docker from '@/components/Icons/Platform/Docker'; 9 + import Homebrew from '@/components/Icons/Platform/Homebrew'; 10 + import NVM from '@/components/Icons/Platform/NVM'; 11 + import { ReleaseContext } from '@/providers/releaseProvider'; 12 + import type { PackageManager } from '@/types/release'; 13 + import { formatDropdownItems, platformItems } from '@/util/downloadUtils'; 14 + 15 + const supportedHomebrewVersions = ['Active LTS', 'Maintenance LTS', 'Current']; 16 + 17 + const PlatformDropdown: FC = () => { 18 + const { release, os, platform, setPlatform } = useContext(ReleaseContext); 19 + const t = useTranslations(); 20 + 21 + // @TODO: We should have a proper utility that gives 22 + // disabled OSs, Platforms, based on specific criteria 23 + // this can be an optimisation for the future 24 + // to remove this logic from this component 25 + const disabledItems = useMemo(() => { 26 + const disabledItems = []; 27 + 28 + if (os === 'WIN') { 29 + disabledItems.push('BREW'); 30 + } 31 + 32 + if (os === 'LINUX') { 33 + disabledItems.push('DOCKER'); 34 + } 35 + 36 + const releaseSupportsHomebrew = supportedHomebrewVersions.includes( 37 + release.status 38 + ); 39 + 40 + if (!releaseSupportsHomebrew) { 41 + disabledItems.push('BREW'); 42 + } 43 + 44 + return disabledItems; 45 + }, [os, release.status]); 46 + 47 + // @TODO: We should have a proper utility that gives 48 + // disabled OSs, Platforms, based on specific criteria 49 + // this can be an optimisation for the future 50 + // to remove this logic from this component 51 + useEffect(() => { 52 + const currentPlatformExcluded = disabledItems.includes(platform); 53 + 54 + const nonExcludedPlatform = platformItems 55 + .map(({ value }) => value) 56 + .find(platform => !disabledItems.includes(platform)); 57 + 58 + if (currentPlatformExcluded && nonExcludedPlatform) { 59 + setPlatform(nonExcludedPlatform); 60 + } 61 + // we shouldn't react when "actions" change 62 + // eslint-disable-next-line react-hooks/exhaustive-deps 63 + }, [release.status, disabledItems, platform]); 64 + 65 + return ( 66 + <Select 67 + label={t('layouts.download.dropdown.platform')} 68 + values={formatDropdownItems({ 69 + items: platformItems, 70 + icons: { 71 + NVM: <NVM width={16} height={16} />, 72 + BREW: <Homebrew width={16} height={16} />, 73 + DOCKER: <Docker width={16} height={16} />, 74 + }, 75 + disabledItems, 76 + })} 77 + defaultValue={platform} 78 + onChange={platform => setPlatform(platform as PackageManager)} 79 + className="w-28" 80 + inline={true} 81 + /> 82 + ); 83 + }; 84 + 85 + export default PlatformDropdown;
+52
components/Downloads/Release/ReleaseCodeBox.tsx
··· 1 + 'use client'; 2 + 3 + import { useTranslations } from 'next-intl'; 4 + import { useContext, useEffect, useState } from 'react'; 5 + import type { FC } from 'react'; 6 + 7 + import CodeBox from '@/components/Common/CodeBox'; 8 + import { ReleaseContext } from '@/providers/releaseProvider'; 9 + import { getShiki, highlightToHtml } from '@/util/getHighlighter'; 10 + import { getNodeDownloadSnippet } from '@/util/getNodeDownloadSnippet'; 11 + 12 + // We cannot do top-level awaits on utilities or code that is imported by client-only components 13 + // hence we only declare a Promise and let it be fulfilled by the first call to the function 14 + const memoizedShiki = getShiki(); 15 + 16 + const ReleaseCodeBox: FC = () => { 17 + const { 18 + platform, 19 + os, 20 + release: { major }, 21 + } = useContext(ReleaseContext); 22 + 23 + const [code, setCode] = useState(''); 24 + const t = useTranslations(); 25 + 26 + useEffect(() => { 27 + const updatedCode = getNodeDownloadSnippet(major, os)[platform]; 28 + // Docker and NVM support downloading tags/versions by their full release number 29 + // but usually we should recommend users to download "major" versions 30 + // since our Downlooad Buttons get the latest minor of a major, it does make sense 31 + // to request installation of a major via a package manager 32 + memoizedShiki 33 + .then(shiki => highlightToHtml(shiki)(updatedCode, 'bash')) 34 + .then(setCode); 35 + }, [major, os, platform]); 36 + 37 + return ( 38 + <div className="mb-2 mt-6 flex min-h-80 flex-col gap-2"> 39 + {code && ( 40 + <CodeBox language="Bash"> 41 + <code dangerouslySetInnerHTML={{ __html: code }} /> 42 + </CodeBox> 43 + )} 44 + 45 + <span className="text-center text-xs text-neutral-800 dark:text-neutral-200"> 46 + {t('layouts.download.codeBox.communityWarning')} 47 + </span> 48 + </div> 49 + ); 50 + }; 51 + 52 + export default ReleaseCodeBox;
+16
components/Downloads/Release/ReleaseStatus.tsx
··· 1 + 'use client'; 2 + 3 + import { useContext } from 'react'; 4 + import type { FC } from 'react'; 5 + 6 + import { ReleaseContext } from '@/providers/releaseProvider'; 7 + 8 + const ReleaseStatus: FC = () => { 9 + const { 10 + release: { status }, 11 + } = useContext(ReleaseContext); 12 + 13 + return <>{status}</>; 14 + }; 15 + 16 + export default ReleaseStatus;
+16
components/Downloads/Release/ReleaseVersion.tsx
··· 1 + 'use client'; 2 + 3 + import { useContext } from 'react'; 4 + import type { FC } from 'react'; 5 + 6 + import { ReleaseContext } from '@/providers/releaseProvider'; 7 + 8 + const ReleaseVersion: FC = () => { 9 + const { 10 + release: { version }, 11 + } = useContext(ReleaseContext); 12 + 13 + return <>{version}</>; 14 + }; 15 + 16 + export default ReleaseVersion;
+30
components/Downloads/Release/SourceButton.tsx
··· 1 + 'use client'; 2 + 3 + import { CloudArrowDownIcon } from '@heroicons/react/24/outline'; 4 + import { useTranslations } from 'next-intl'; 5 + import { useContext } from 'react'; 6 + import type { FC } from 'react'; 7 + 8 + import Button from '@/components/Common/Button'; 9 + import { ReleaseContext } from '@/providers/releaseProvider'; 10 + 11 + const SourceButton: FC = () => { 12 + const t = useTranslations(); 13 + const { release } = useContext(ReleaseContext); 14 + 15 + const version = release.versionWithPrefix; 16 + const url = `https://nodejs.org/dist/${version}/node-${version}.tar.gz`; 17 + 18 + return ( 19 + <div className="mb-2 mt-6 flex items-center gap-2"> 20 + <Button href={url} disabled={!version}> 21 + <CloudArrowDownIcon /> 22 + {t('layouts.download.buttons.source', { 23 + version: version, 24 + })} 25 + </Button> 26 + </div> 27 + ); 28 + }; 29 + 30 + export default SourceButton;
+11
components/Downloads/Release/VerifyingBinariesLink.tsx
··· 1 + import type { FC, PropsWithChildren } from 'react'; 2 + 3 + import LinkWithArrow from '@/components/Downloads/Release/LinkWithArrow'; 4 + 5 + const VerifyingBinariesLink: FC<PropsWithChildren> = ({ children }) => ( 6 + <LinkWithArrow href="https://github.com/nodejs/node#verifying-binaries"> 7 + {children} 8 + </LinkWithArrow> 9 + ); 10 + 11 + export default VerifyingBinariesLink;
+41
components/Downloads/Release/VersionDropdown.tsx
··· 1 + 'use client'; 2 + 3 + import { useTranslations } from 'next-intl'; 4 + import type { FC } from 'react'; 5 + import { useContext } from 'react'; 6 + 7 + import Select from '@/components/Common/Select'; 8 + import { ReleaseContext } from '@/providers/releaseProvider'; 9 + 10 + const getDropDownStatus = (version: string, status: string) => { 11 + if (status === 'Active LTS') { 12 + return `${version} (LTS)`; 13 + } 14 + 15 + if (status === 'Current') { 16 + return `${version} (Current)`; 17 + } 18 + 19 + return version; 20 + }; 21 + 22 + const VersionDropdown: FC = () => { 23 + const { releases, release, setVersion } = useContext(ReleaseContext); 24 + const t = useTranslations(); 25 + 26 + return ( 27 + <Select 28 + label={t('layouts.download.dropdown.version')} 29 + values={releases.map(({ status, versionWithPrefix }) => ({ 30 + value: versionWithPrefix, 31 + label: getDropDownStatus(versionWithPrefix, status), 32 + }))} 33 + defaultValue={release.versionWithPrefix} 34 + onChange={setVersion} 35 + className="w-40" 36 + inline={true} 37 + /> 38 + ); 39 + }; 40 + 41 + export default VersionDropdown;
+2 -2
components/Home/HomeDownloadButton.tsx
··· 7 7 import { useDetectOS } from '@/hooks'; 8 8 import { DIST_URL } from '@/next.constants.mjs'; 9 9 import type { NodeRelease } from '@/types'; 10 - import { downloadUrlByOS } from '@/util/downloadUrlByOS'; 10 + import { getNodeDownloadUrl } from '@/util/getNodeDownloadUrl'; 11 11 import { getNodejsChangelog } from '@/util/getNodeJsChangelog'; 12 12 13 13 const HomeDownloadButton: FC<NodeRelease> = ({ ··· 19 19 const { os, bitness } = useDetectOS(); 20 20 const t = useTranslations(); 21 21 22 - const nodeDownloadLink = downloadUrlByOS(versionWithPrefix, os, bitness); 22 + const nodeDownloadLink = getNodeDownloadUrl(versionWithPrefix, os, bitness); 23 23 const nodeApiLink = `${DIST_URL}latest-v${major}.x/docs/api/`; 24 24 const nodeAllDownloadsLink = `/download${isLts ? '/' : '/current'}`; 25 25
+20
components/Icons/Platform/Docker.tsx
··· 1 + import type { FC, SVGProps } from 'react'; 2 + 3 + const Docker: FC<SVGProps<SVGSVGElement>> = props => ( 4 + <svg 5 + width="32" 6 + height="32" 7 + viewBox="0 0 756.26 596.9" 8 + fill="none" 9 + xmlns="http://www.w3.org/2000/svg" 10 + {...props} 11 + > 12 + <path 13 + fill="#1d63ed" 14 + strokeWidth="0px" 15 + d="M743.96,245.25c-18.54-12.48-67.26-17.81-102.68-8.27-1.91-35.28-20.1-65.01-53.38-90.95l-12.32-8.27-8.21,12.4c-16.14,24.5-22.94,57.14-20.53,86.81,1.9,18.28,8.26,38.83,20.53,53.74-46.1,26.74-88.59,20.67-276.77,20.67H.06c-.85,42.49,5.98,124.23,57.96,190.77,5.74,7.35,12.04,14.46,18.87,21.31,42.26,42.32,106.11,73.35,201.59,73.44,145.66.13,270.46-78.6,346.37-268.97,24.98.41,90.92,4.48,123.19-57.88.79-1.05,8.21-16.54,8.21-16.54l-12.3-8.27ZM189.67,206.39h-81.7v81.7h81.7v-81.7ZM295.22,206.39h-81.7v81.7h81.7v-81.7ZM400.77,206.39h-81.7v81.7h81.7v-81.7ZM506.32,206.39h-81.7v81.7h81.7v-81.7ZM84.12,206.39H2.42v81.7h81.7v-81.7ZM189.67,103.2h-81.7v81.7h81.7v-81.7ZM295.22,103.2h-81.7v81.7h81.7v-81.7ZM400.77,103.2h-81.7v81.7h81.7v-81.7ZM400.77,0h-81.7v81.7h81.7V0Z" 16 + /> 17 + </svg> 18 + ); 19 + 20 + export default Docker;
+1 -1
components/Icons/Platform/Generic.tsx
··· 6 6 height="32" 7 7 viewBox="0 0 32 32" 8 8 fill="none" 9 + stroke="#CBD4D9" 9 10 xmlns="http://www.w3.org/2000/svg" 10 11 {...props} 11 12 > 12 13 <path 13 14 d="M16 29.333c7.364 0 13.333-5.97 13.333-13.333 0-7.364-5.97-13.334-13.333-13.334C8.636 2.666 2.667 8.636 2.667 16S8.637 29.333 16 29.333Z" 14 - stroke="#000" 15 15 strokeWidth="2.667" 16 16 strokeLinecap="round" 17 17 strokeLinejoin="round"
+1 -1
components/Link.tsx
··· 3 3 import { Link as LocalizedLink } from '@/navigation.mjs'; 4 4 5 5 type LinkProps = Omit<ComponentProps<typeof LocalizedLink>, 'href'> & { 6 - href: string | undefined; 6 + href?: string; 7 7 }; 8 8 9 9 const Link: FC<LinkProps> = ({ children, href, ...props }) => {
+7 -3
components/__design__/platform-logos.stories.tsx
··· 1 1 import type { Meta as MetaObj, StoryObj } from '@storybook/react'; 2 2 3 3 import Apple from '@/components/Icons/Platform/Apple'; 4 + import Docker from '@/components/Icons/Platform/Docker'; 4 5 import Generic from '@/components/Icons/Platform/Generic'; 5 6 import Homebrew from '@/components/Icons/Platform/Homebrew'; 6 7 import Linux from '@/components/Icons/Platform/Linux'; ··· 11 12 render: () => ( 12 13 <div className="flex flex-row gap-4"> 13 14 <div className="flex flex-col items-center gap-4"> 14 - <Linux width={64} height={64} /> 15 15 <Apple width={64} height={64} /> 16 - <NVM width={64} height={64} /> 16 + <Linux width={64} height={64} /> 17 + <Microsoft width={64} height={64} /> 17 18 </div> 18 19 <div className="flex flex-col items-center gap-4"> 19 - <Microsoft width={64} height={64} /> 20 + <Docker width={64} height={64} /> 20 21 <Homebrew width={64} height={64} /> 22 + <NVM width={64} height={64} /> 23 + </div> 24 + <div className="flex flex-col items-center gap-4"> 21 25 <Generic width={64} height={64} /> 22 26 </div> 23 27 </div>
+62
components/withDownloadCategories.tsx
··· 1 + import { getTranslations } from 'next-intl/server'; 2 + import type { FC, PropsWithChildren } from 'react'; 3 + 4 + import { useClientContext } from '@/hooks/react-server'; 5 + import getReleaseData from '@/next-data/releaseData'; 6 + import { ReleaseProvider } from '@/providers/releaseProvider'; 7 + import type { NodeReleaseStatus } from '@/types'; 8 + import { getDownloadCategory, mapCategoriesToTabs } from '@/util/downloadUtils'; 9 + 10 + import LinkTabs from './Common/LinkTabs'; 11 + import WithNodeRelease from './withNodeRelease'; 12 + 13 + const WithDownloadCategories: FC<PropsWithChildren> = async ({ children }) => { 14 + const t = await getTranslations(); 15 + const releases = await getReleaseData(); 16 + 17 + const { pathname } = useClientContext(); 18 + const { page, category, subCategory } = getDownloadCategory(pathname); 19 + 20 + const initialRelease: Array<NodeReleaseStatus> = pathname.includes('current') 21 + ? ['Current'] 22 + : ['Active LTS', 'Maintenance LTS']; 23 + 24 + return ( 25 + <LinkTabs 26 + activeTab={category} 27 + label={t('layouts.download.selectCategory')} 28 + tabs={mapCategoriesToTabs({ 29 + page: page, 30 + categories: [ 31 + { 32 + category: 'download', 33 + label: t('layouts.download.categories.download'), 34 + }, 35 + { 36 + category: 'prebuilt-binaries', 37 + label: t('layouts.download.categories.prebuilt-binaries'), 38 + }, 39 + { 40 + category: 'package-manager', 41 + label: t('layouts.download.categories.package-manager'), 42 + }, 43 + { 44 + category: 'source-code', 45 + label: t('layouts.download.categories.source-code'), 46 + }, 47 + ], 48 + subCategory: subCategory, 49 + })} 50 + > 51 + <WithNodeRelease status={initialRelease}> 52 + {({ release }) => ( 53 + <ReleaseProvider initialRelease={release} releases={releases}> 54 + {children} 55 + </ReleaseProvider> 56 + )} 57 + </WithNodeRelease> 58 + </LinkTabs> 59 + ); 60 + }; 61 + 62 + export default WithDownloadCategories;
+2
components/withLayout.tsx
··· 11 11 import AboutLayout from '@/layouts/New/About'; 12 12 import BlogLayout from '@/layouts/New/Blog'; 13 13 import DefaultLayout from '@/layouts/New/Default'; 14 + import DownloadLayout from '@/layouts/New/Download'; 14 15 import HomeLayout from '@/layouts/New/Home'; 15 16 import LearnLayout from '@/layouts/New/Learn'; 16 17 import PostLayout from '@/layouts/New/Post'; ··· 39 40 'blog-post.hbs': PostLayout, 40 41 'blog-category.hbs': BlogLayout, 41 42 'search.hbs': SearchLayout, 43 + 'download.hbs': DownloadLayout, 42 44 } satisfies Record<Layouts, FC>; 43 45 44 46 type WithLayout<L = Layouts | LegacyLayouts> = PropsWithChildren<{ layout: L }>;
+22
i18n/locales/en.json
··· 266 266 "description": "This page has thrown a non-recoverable error." 267 267 }, 268 268 "backToHome": "Back to Home" 269 + }, 270 + "download": { 271 + "selectCategory": "Categories", 272 + "categories": { 273 + "download": "Prebuilt Installer", 274 + "prebuilt-binaries": "Prebuilt Binaries", 275 + "package-manager": "Package Manager", 276 + "source-code": "Source Code" 277 + }, 278 + "buttons": { 279 + "prebuilt": "Download Node.js {version}", 280 + "source": "Download Node.js {version} source" 281 + }, 282 + "dropdown": { 283 + "bitness": "Bitness", 284 + "os": "Operating System", 285 + "version": "Version", 286 + "platform": "Platform" 287 + }, 288 + "codeBox": { 289 + "communityWarning": "Package Managers and their installation scripts are not maintained by the Node.js project.s" 290 + } 269 291 } 270 292 }, 271 293 "pages": {
+34
layouts/New/Download.tsx
··· 1 + import type { FC, PropsWithChildren } from 'react'; 2 + 3 + import WithDownloadCategories from '@/components/withDownloadCategories'; 4 + import WithFooter from '@/components/withFooter'; 5 + import WithNavBar from '@/components/withNavBar'; 6 + import { useClientContext } from '@/hooks/react-server'; 7 + 8 + import styles from './layouts.module.css'; 9 + 10 + const DownloadLayout: FC<PropsWithChildren> = async ({ children }) => { 11 + const { 12 + frontmatter: { title, subtitle }, 13 + } = useClientContext(); 14 + 15 + return ( 16 + <> 17 + <WithNavBar /> 18 + 19 + <div className={styles.downloadLayout}> 20 + <main> 21 + <h1>{title}</h1> 22 + 23 + <p>{subtitle}</p> 24 + 25 + <WithDownloadCategories>{children}</WithDownloadCategories> 26 + </main> 27 + </div> 28 + 29 + <WithFooter /> 30 + </> 31 + ); 32 + }; 33 + 34 + export default DownloadLayout;
+24 -2
layouts/New/layouts.module.css
··· 138 138 } 139 139 } 140 140 141 - .blogLayout { 141 + .blogLayout, 142 + .downloadLayout { 142 143 @apply flex 143 144 w-full 144 145 justify-center ··· 148 149 xs:dark:bg-none; 149 150 150 151 main { 151 - @apply max-w-8xl 152 + @apply max-w-5xl 152 153 gap-4 153 154 px-4 154 155 py-12 155 156 md:px-14 156 157 lg:px-28; 158 + } 159 + } 160 + 161 + .downloadLayout { 162 + section:nth-last-child(1) { 163 + @apply flex 164 + flex-col 165 + gap-2; 166 + 167 + p { 168 + @apply text-sm; 169 + } 170 + } 171 + 172 + section:nth-last-child(2) p { 173 + @apply flex 174 + flex-row 175 + flex-wrap 176 + items-center 177 + gap-2 178 + text-base; 157 179 } 158 180 } 159 181
+4 -1
next.dynamic.constants.mjs
··· 17 17 export const IGNORED_ROUTES = [ 18 18 // This is used to ignore all blog routes except for the English language 19 19 ({ locale, pathname }) => 20 - locale !== defaultLocale.code && /^blog\//.test(pathname), 20 + locale !== defaultLocale.code && /^blog/.test(pathname), 21 + // Do not statically build the redesign pages on the static export 22 + // @deprecated this should be removed once we remove the legacy website 23 + ({ pathname }) => /^new-design/.test(pathname), 21 24 ]; 22 25 23 26 /**
+10 -2
next.mdx.shiki.mjs
··· 4 4 import { toString } from 'hast-util-to-string'; 5 5 import { SKIP, visit } from 'unist-util-visit'; 6 6 7 - import { highlightToHast } from './util/getHighlighter'; 7 + import { getShiki, highlightToHast } from './util/getHighlighter'; 8 8 9 9 // This is what Remark will use as prefix within a <pre> className 10 10 // to attribute the current language of the <pre> element 11 11 const languagePrefix = 'language-'; 12 + 13 + // We do a top-level await, since the Unist-tree visitor 14 + // is synchronous, and it makes more sense to do a top-level 15 + // await, rather than an await inside the visitor function 16 + const memoizedShiki = await getShiki(); 12 17 13 18 /** 14 19 * Retrieve the value for the given meta key. ··· 169 174 const languageId = codeLanguage.slice(languagePrefix.length); 170 175 171 176 // Parses the <pre> contents and returns a HAST tree with the highlighted code 172 - const { children } = highlightToHast(preElementContents, languageId); 177 + const { children } = highlightToHast(memoizedShiki)( 178 + preElementContents, 179 + languageId 180 + ); 173 181 174 182 // Adds the original language back to the <pre> element 175 183 children[0].properties.class = classNames(
+43
next.mdx.use.mjs
··· 5 5 import DownloadButton from './components/Downloads/DownloadButton'; 6 6 import DownloadLink from './components/Downloads/DownloadLink'; 7 7 import DownloadReleasesTable from './components/Downloads/DownloadReleasesTable'; 8 + import BitnessDropdown from './components/Downloads/Release/BitnessDropdown'; 9 + import BlogPostLink from './components/Downloads/Release/BlogPostLink'; 10 + import ReleaseDownloadButton from './components/Downloads/Release/DownloadButton'; 11 + import LinkWithArrow from './components/Downloads/Release/LinkWithArrow'; 12 + import NpmVersion from './components/Downloads/Release/NpmVersion'; 13 + import OperatingSystemDropdown from './components/Downloads/Release/OperatingSystemDropdown'; 14 + import PlatformDropdown from './components/Downloads/Release/PlatformDropdown'; 15 + import ReleaseCodeBox from './components/Downloads/Release/ReleaseCodeBox'; 16 + import ReleaseStatus from './components/Downloads/Release/ReleaseStatus'; 17 + import ReleaseVersion from './components/Downloads/Release/ReleaseVersion'; 18 + import SourceButton from './components/Downloads/Release/SourceButton'; 19 + import VerifyingBinariesLink from './components/Downloads/Release/VerifyingBinariesLink'; 20 + import VersionDropdown from './components/Downloads/Release/VersionDropdown'; 8 21 import HomeDownloadButton from './components/Home/HomeDownloadButton'; 9 22 import Link from './components/Link'; 10 23 import UpcomingEvents from './components/MDX/Calendar/UpcomingEvents'; ··· 47 60 UpcomingSummits: UpcomingSummits, 48 61 // Renders an container for Upcoming Node.js Events 49 62 UpcomingEvents: UpcomingEvents, 63 + // Links with External Arrow 64 + LinkWithArrow: LinkWithArrow, 65 + // Group of components that enable you to select versions for Node.js 66 + // releases and download selected versions. Uses `releaseProvider` as a provider 67 + Release: { 68 + // Renders a drop-down menu from which the version can select 69 + VersionDropdown: VersionDropdown, 70 + // Renders a drop-down menu from which the platform can select 71 + PlatformDropdown: PlatformDropdown, 72 + // Renders a drop-down menu from which the bitness can select 73 + BitnessDropdown: BitnessDropdown, 74 + // Renders a drop-down menu from which the operating system can select 75 + OperatingSystemDropdown: OperatingSystemDropdown, 76 + // Renders a npm version of the selected release 77 + NpmVersion: NpmVersion, 78 + // Renders a release version of the selected release 79 + Version: ReleaseVersion, 80 + // Renders a release status of the selected release 81 + Status: ReleaseStatus, 82 + // Renders a Blog Post Link for the selected release 83 + BlogPostLink: BlogPostLink, 84 + // Renders a Verifying Binaries Link 85 + VerifyingBinariesLink: VerifyingBinariesLink, 86 + // Renders a Download Button for the selected release 87 + DownloadButton: ReleaseDownloadButton, 88 + // Renders a Source Download Button for the selected release 89 + SourceButton: SourceButton, 90 + // Renders a Release CodeBox 91 + ReleaseCodeBox: ReleaseCodeBox, 92 + }, 50 93 }; 51 94 52 95 /**
+18 -4
next.rewrites.mjs
··· 48 48 // This allows us to remap legacy website URLs to the temporary redesign ones 49 49 // @todo: remove this once website redesign is done 50 50 if (ENABLE_WEBSITE_REDESIGN) { 51 - mappedRewrites.push({ 52 - source: localesMatch, 53 - destination: '/:locale/new-design', 54 - }); 51 + mappedRewrites.push( 52 + { 53 + source: localesMatch, 54 + destination: '/:locale/new-design', 55 + }, 56 + { 57 + source: '/:locale/download', 58 + destination: '/:locale/new-design/download', 59 + }, 60 + { 61 + source: '/:locale/download/:path', 62 + destination: '/:locale/new-design/download/:path', 63 + }, 64 + { 65 + source: '/:locale/download/:path/:version', 66 + destination: '/:locale/new-design/download/:path/:version', 67 + } 68 + ); 55 69 } 56 70 57 71 return { afterFiles: mappedRewrites };
+18 -18
package-lock.json
··· 29 29 "autoprefixer": "~10.4.16", 30 30 "classnames": "~2.5.1", 31 31 "cross-env": "7.0.3", 32 + "dedent": "1.5.1", 32 33 "feed": "~4.2.2", 33 34 "github-slugger": "~2.0.0", 34 35 "glob": "~10.3.10", ··· 11542 11543 } 11543 11544 }, 11544 11545 "node_modules/dedent": { 11545 - "version": "0.7.0", 11546 - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", 11547 - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", 11548 - "dev": true 11546 + "version": "1.5.1", 11547 + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", 11548 + "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", 11549 + "peerDependencies": { 11550 + "babel-plugin-macros": "^3.1.0" 11551 + }, 11552 + "peerDependenciesMeta": { 11553 + "babel-plugin-macros": { 11554 + "optional": true 11555 + } 11556 + } 11549 11557 }, 11550 11558 "node_modules/deep-equal": { 11551 11559 "version": "2.2.3", ··· 12173 12181 "fast-json-parse": "^1.0.3", 12174 12182 "objectorarray": "^1.0.5" 12175 12183 } 12184 + }, 12185 + "node_modules/endent/node_modules/dedent": { 12186 + "version": "0.7.0", 12187 + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", 12188 + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", 12189 + "dev": true 12176 12190 }, 12177 12191 "node_modules/enhanced-resolve": { 12178 12192 "version": "5.15.0", ··· 16442 16456 }, 16443 16457 "funding": { 16444 16458 "url": "https://github.com/chalk/chalk?sponsor=1" 16445 - } 16446 - }, 16447 - "node_modules/jest-circus/node_modules/dedent": { 16448 - "version": "1.5.1", 16449 - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", 16450 - "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", 16451 - "dev": true, 16452 - "peerDependencies": { 16453 - "babel-plugin-macros": "^3.1.0" 16454 - }, 16455 - "peerDependenciesMeta": { 16456 - "babel-plugin-macros": { 16457 - "optional": true 16458 - } 16459 16459 } 16460 16460 }, 16461 16461 "node_modules/jest-circus/node_modules/pretty-format": {
+1
package.json
··· 61 61 "autoprefixer": "~10.4.16", 62 62 "classnames": "~2.5.1", 63 63 "cross-env": "7.0.3", 64 + "dedent": "1.5.1", 64 65 "feed": "~4.2.2", 65 66 "github-slugger": "~2.0.0", 66 67 "glob": "~10.3.10",
+22
pages/en/new-design/download/current.mdx
··· 1 + --- 2 + layout: download.hbs 3 + title: Download Node.js® 4 + subtitle: Download Node.js the way you want. 5 + --- 6 + 7 + <section> 8 + I want the <Release.VersionDropdown /> version of Node.js for <Release.OperatingSystemDropdown exclude={["LINUX"]} /> running <Release.BitnessDropdown /> 9 + 10 + <Release.DownloadButton kind="installer" /> 11 + </section> 12 + 13 + <section> 14 + Node.js includes <LinkWithArrow href="https://npmjs.com">npm</LinkWithArrow> (<Release.NpmVersion />) and corepack. 15 + 16 + Read the blog post for <Release.BlogPostLink>this version</Release.BlogPostLink> 17 + 18 + Learn how to <Release.VerifyingBinariesLink>verify signed SHASUMS</Release.VerifyingBinariesLink> 19 + 20 + Check out all available Node.js <LinkWithArrow href="https://nodejs.org/dist/">download options</LinkWithArrow> 21 + 22 + </section>
+22
pages/en/new-design/download/index.mdx
··· 1 + --- 2 + layout: download.hbs 3 + title: Download Node.js® 4 + subtitle: Download Node.js the way you want. 5 + --- 6 + 7 + <section> 8 + I want the <Release.VersionDropdown /> version of Node.js for <Release.OperatingSystemDropdown exclude={["LINUX"]} /> running <Release.BitnessDropdown /> 9 + 10 + <Release.DownloadButton kind="installer" /> 11 + </section> 12 + 13 + <section> 14 + Node.js includes <LinkWithArrow href="https://npmjs.com">npm</LinkWithArrow> (<Release.NpmVersion />) and corepack. 15 + 16 + Read the blog post for <Release.BlogPostLink>this version</Release.BlogPostLink> 17 + 18 + Learn how to <Release.VerifyingBinariesLink>verify signed SHASUMS</Release.VerifyingBinariesLink> 19 + 20 + Check out all available Node.js <LinkWithArrow href="https://nodejs.org/dist/">download options</LinkWithArrow> 21 + 22 + </section>
+410
pages/en/new-design/download/package-manager/all.md
··· 1 + --- 2 + layout: docs.hbs 3 + title: Installing Node.js via package manager 4 + --- 5 + 6 + # Installing Node.js via Package Managers 7 + 8 + > 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. 9 + 10 + --- 11 + 12 + - [Alpine Linux](#alpine-linux) 13 + - [Android](#android) 14 + - [Arch Linux](#arch-linux) 15 + - [CentOS, Fedora and Red Hat Enterprise Linux](#centos-fedora-and-red-hat-enterprise-linux) 16 + - [Debian and Ubuntu based Linux distributions](#debian-and-ubuntu-based-linux-distributions) 17 + - [fnm](#fnm) 18 + - [FreeBSD](#freebsd) 19 + - [Gentoo](#gentoo) 20 + - [IBM i](#ibm-i) 21 + - [macOS](#macos) 22 + - [n](#n) 23 + - [NetBSD](#netbsd) 24 + - [Nodenv](#nodenv) 25 + - [nvm](#nvm) 26 + - [nvs](#nvs) 27 + - [OpenBSD](#openbsd) 28 + - [openSUSE and SLE](#opensuse-and-sle) 29 + - [SmartOS and illumos](#smartos-and-illumos) 30 + - [Snap](#snap) 31 + - [Solus](#solus) 32 + - [Void Linux](#void-linux) 33 + - [Windows](#windows-1) 34 + - [z/OS](#zos) 35 + 36 + --- 37 + 38 + ## Alpine Linux 39 + 40 + Node.js LTS and npm packages are available in the Main Repository. 41 + 42 + ```bash 43 + apk add nodejs npm 44 + ``` 45 + 46 + Node.js Current can be installed from the Community Repository. 47 + 48 + ```bash 49 + apk add nodejs-current 50 + ``` 51 + 52 + ## Android 53 + 54 + Android support is still experimental in Node.js, so precompiled binaries are not yet provided by Node.js developers. 55 + 56 + 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: 57 + 58 + ```bash 59 + pkg install nodejs 60 + ``` 61 + 62 + Currently, Termux Node.js binaries are linked against `system-icu` (depending on `libicu` package). 63 + 64 + ## Arch Linux 65 + 66 + Node.js and npm packages are available in the Community Repository. 67 + 68 + ```bash 69 + pacman -S nodejs npm 70 + ``` 71 + 72 + ## CentOS, Fedora and Red Hat Enterprise Linux 73 + 74 + Node.js is available as a module called `nodejs` in CentOS/RHEL 8 and Fedora. 75 + 76 + ```bash 77 + dnf module install nodejs:<stream> 78 + ``` 79 + 80 + where `<stream>` corresponds to the major version of Node.js. 81 + To see a list of available streams: 82 + 83 + ```bash 84 + dnf module list nodejs 85 + ``` 86 + 87 + For example, to install Node.js 18: 88 + 89 + ```bash 90 + dnf module install nodejs:18/common 91 + ``` 92 + 93 + ### Alternatives 94 + 95 + These resources provide packages compatible with CentOS, Fedora, and RHEL. 96 + 97 + - [Node.js snaps](#snap) maintained and supported at https://github.com/nodejs/snap 98 + - [Node.js binary distributions](#debian-and-ubuntu-based-linux-distributions) maintained and supported by [NodeSource](https://github.com/nodesource/distributions) 99 + 100 + ## Debian and Ubuntu based Linux distributions 101 + 102 + [Node.js binary distributions](https://github.com/nodesource/distributions) are available from NodeSource. 103 + 104 + ### Alternatives 105 + 106 + Packages compatible with Debian and Ubuntu based Linux distributions are available via [Node.js snaps](#snap). 107 + 108 + ## fnm 109 + 110 + 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. 111 + To install fnm, use this [install script](https://github.com/Schniz/fnm#using-a-script-macoslinux). 112 + 113 + fnm has cross-platform support (macOS, Windows, Linux) & all popular shells (Bash, Zsh, Fish, PowerShell, Windows Command Line Prompt). 114 + fnm is built with speed in mind and compatibility support for `.node-version` and `.nvmrc` files. 115 + 116 + ## FreeBSD 117 + 118 + The most recent release of Node.js is available via the [www/node](https://www.freshports.org/www/node) port. 119 + 120 + Install a binary package via [pkg](https://www.freebsd.org/cgi/man.cgi?pkg): 121 + 122 + ```bash 123 + pkg install node 124 + ``` 125 + 126 + Or compile it on your own using [ports](https://www.freebsd.org/cgi/man.cgi?ports): 127 + 128 + ```bash 129 + cd /usr/ports/www/node && make install 130 + ``` 131 + 132 + ## Gentoo 133 + 134 + Node.js is available in the portage tree. 135 + 136 + ```bash 137 + emerge nodejs 138 + ``` 139 + 140 + ## IBM i 141 + 142 + 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) 143 + 144 + To install Node.js 14.x from the command line, run the following as a user with \*ALLOBJ special authority: 145 + 146 + ```bash 147 + yum install nodejs14 148 + ``` 149 + 150 + 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 151 + 152 + ## macOS 153 + 154 + Download the [macOS Installer](/#home-downloadhead) directly from the [nodejs.org](https://nodejs.org/) web site. 155 + 156 + _If you want to download the package with bash:_ 157 + 158 + ```bash 159 + 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 "/" 160 + ``` 161 + 162 + ### Alternatives 163 + 164 + Using **[Homebrew](https://brew.sh/)**: 165 + 166 + ```bash 167 + brew install node 168 + ``` 169 + 170 + Using **[MacPorts](https://www.macports.org/)**: 171 + 172 + ```bash 173 + port install nodejs<major version> 174 + 175 + # Example 176 + port install nodejs7 177 + ``` 178 + 179 + Using **[pkgsrc](https://pkgsrc.joyent.com/install-on-macos/)**: 180 + 181 + Install the binary package: 182 + 183 + ```bash 184 + pkgin -y install nodejs 185 + ``` 186 + 187 + Or build manually from pkgsrc: 188 + 189 + ```bash 190 + cd pkgsrc/lang/nodejs && bmake install 191 + ``` 192 + 193 + ## n 194 + 195 + `n` is a simple to use Node.js version manager for Mac and Linux. Specify the target version to install using a rich syntax, 196 + or select from a menu of previously downloaded versions. The versions are installed system-wide or user-wide, and for more 197 + targeted use you can run a version directly from the cached downloads. 198 + 199 + See the [homepage](https://github.com/tj/n) for install methods (bootstrap, npm, Homebrew, third-party), and all the usage details. 200 + 201 + If you already have `npm` then installing `n` and then the newest LTS `node` version is as simple as: 202 + 203 + ``` 204 + npm install -g n 205 + n lts 206 + ``` 207 + 208 + ## NetBSD 209 + 210 + Node.js is available in the pkgsrc tree: 211 + 212 + ```bash 213 + cd /usr/pkgsrc/lang/nodejs && make install 214 + ``` 215 + 216 + Or install a binary package (if available for your platform) using pkgin: 217 + 218 + ```bash 219 + pkgin -y install nodejs 220 + ``` 221 + 222 + ## Nodenv 223 + 224 + `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. 225 + 226 + 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. 227 + 228 + ## nvm 229 + 230 + Node Version Manager is a bash script used to manage multiple released Node.js versions. It allows 231 + you to perform operations like install, uninstall, switch version, etc. 232 + To install nvm, use this [install script](https://github.com/nvm-sh/nvm#install--update-script). 233 + 234 + On Unix / OS X systems Node.js built from source can be installed using 235 + [nvm](https://github.com/creationix/nvm) by installing into the location that nvm expects: 236 + 237 + ```bash 238 + env VERSION=`python tools/getnodeversion.py` make install DESTDIR=`nvm_version_path v$VERSION` PREFIX="" 239 + ``` 240 + 241 + After this you can use `nvm` to switch between released versions and versions 242 + built from source. 243 + For example, if the version of Node.js is v8.0.0-pre: 244 + 245 + ```bash 246 + nvm use 8 247 + ``` 248 + 249 + Once the official release is out you will want to uninstall the version built 250 + from source: 251 + 252 + ```bash 253 + nvm uninstall 8 254 + ``` 255 + 256 + ## nvs 257 + 258 + #### Windows 259 + 260 + The `nvs` version manager is cross-platform and can be used on Windows, macOS, and Unix-like systems 261 + 262 + 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. 263 + 264 + You can also use `chocolatey` to install it: 265 + 266 + ```bash 267 + choco install nvs 268 + ``` 269 + 270 + #### macOS,UnixLike 271 + 272 + 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) 273 + 274 + #### Usage 275 + 276 + After this you can use `nvs` to switch between different versions of node. 277 + 278 + To add the latest version of node: 279 + 280 + ```bash 281 + nvs add latest 282 + ``` 283 + 284 + Or to add the latest LTS version of node: 285 + 286 + ```bash 287 + nvs add lts 288 + ``` 289 + 290 + Then run the `nvs use` command to add a version of node to your `PATH` for the current shell: 291 + 292 + ```bash 293 + $ nvs use lts 294 + PATH -= %LOCALAPPDATA%\nvs\default 295 + PATH += %LOCALAPPDATA%\nvs\node\14.17.0\x64 296 + ``` 297 + 298 + To add it to `PATH` permanently, use `nvs link`: 299 + 300 + ```bash 301 + nvs link lts 302 + ``` 303 + 304 + ## OpenBSD 305 + 306 + Node.js is available through the ports system. 307 + 308 + ```bash 309 + /usr/ports/lang/node 310 + ``` 311 + 312 + Using [pkg_add](https://man.openbsd.org/OpenBSD-current/man1/pkg_add.1) on OpenBSD: 313 + 314 + ```bash 315 + pkg_add node 316 + ``` 317 + 318 + ## openSUSE and SLE 319 + 320 + Node.js is available in the main repositories under the following packages: 321 + 322 + - **openSUSE Leap 15.2**: `nodejs10`, `nodejs12`, `nodejs14` 323 + - **openSUSE Tumbleweed**: `nodejs20` 324 + - **SUSE Linux Enterprise Server (SLES) 12**: `nodejs10`, `nodejs12`, and `nodejs14` 325 + (The "Web and Scripting Module" must be [enabled](https://www.suse.com/releasenotes/x86_64/SUSE-SLES/12-SP5/#intro-modulesExtensionsRelated).) 326 + - **SUSE Linux Enterprise Server (SLES) 15 SP2**: `nodejs10`, `nodejs12`, and `nodejs14` 327 + (The "Web and Scripting Module" must be [enabled](https://www.suse.com/releasenotes/x86_64/SUSE-SLES/15/#Intro.Module).) 328 + 329 + For example, to install Node.js 14.x on openSUSE Leap 15.2, run the following as root: 330 + 331 + ```bash 332 + zypper install nodejs14 333 + ``` 334 + 335 + Different major versions of Node can be installed and used concurrently. 336 + 337 + ## SmartOS and illumos 338 + 339 + 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: 340 + 341 + ```bash 342 + pkgin -y install nodejs 343 + ``` 344 + 345 + Or build manually from pkgsrc: 346 + 347 + ```bash 348 + cd pkgsrc/lang/nodejs && bmake install 349 + ``` 350 + 351 + ## Snap 352 + 353 + [Node.js snaps](https://github.com/nodejs/snap) are available as [`node`](https://snapcraft.io/node) on the Snap store. 354 + 355 + ## Solus 356 + 357 + Solus provides Node.js in its main repository. 358 + 359 + ```bash 360 + sudo eopkg install nodejs 361 + ``` 362 + 363 + ## Void Linux 364 + 365 + Void Linux ships Node.js stable in the main repository. 366 + 367 + ```bash 368 + xbps-install -Sy nodejs 369 + ``` 370 + 371 + ## Windows 372 + 373 + Download the [Windows Installer](/#home-downloadhead) directly from the [nodejs.org](https://nodejs.org/) web site. 374 + 375 + ### Alternatives 376 + 377 + Using **[Winget](https://aka.ms/winget-cli)**: 378 + 379 + ```bash 380 + winget install OpenJS.NodeJS 381 + # or for LTS 382 + winget install OpenJS.NodeJS.LTS 383 + ``` 384 + 385 + After running one of the two commands above, it may be necessary to restart the 386 + terminal emulator before the `node` CLI command becomes available. 387 + 388 + Using **[Chocolatey](https://chocolatey.org/)**: 389 + 390 + ```bash 391 + cinst nodejs 392 + # or for full install with npm 393 + cinst nodejs.install 394 + ``` 395 + 396 + Using **[Scoop](https://scoop.sh/)**: 397 + 398 + ```bash 399 + scoop install nodejs 400 + # or for LTS 401 + scoop install nodejs-lts 402 + ``` 403 + 404 + ## z/OS 405 + 406 + IBM&reg; SDK for Node.js - z/OS&reg; is available in two installation formats, 407 + SMP/E and PAX. Select the installation format that applies to you: 408 + 409 + - [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) 410 + - [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)
+22
pages/en/new-design/download/package-manager/current.mdx
··· 1 + --- 2 + layout: download.hbs 3 + title: Download Node.js® 4 + subtitle: Download Node.js the way you want. 5 + --- 6 + 7 + <section> 8 + Install Node.js <Release.VersionDropdown /> on <Release.OperatingSystemDropdown /> using <Release.PlatformDropdown /> 9 + 10 + <Release.ReleaseCodeBox /> 11 + </section> 12 + 13 + <section> 14 + Node.js includes <LinkWithArrow href="https://npmjs.com">npm</LinkWithArrow> (<Release.NpmVersion />) and corepack. 15 + 16 + Read the blog post for <Release.BlogPostLink>this version</Release.BlogPostLink> 17 + 18 + Learn how to <Release.VerifyingBinariesLink>verify signed SHASUMS</Release.VerifyingBinariesLink> 19 + 20 + Check out other community supported <LinkWithArrow href="/download/package-manager/all">package managers</LinkWithArrow> 21 + 22 + </section>
+22
pages/en/new-design/download/package-manager/index.mdx
··· 1 + --- 2 + layout: download.hbs 3 + title: Download Node.js® 4 + subtitle: Download Node.js the way you want. 5 + --- 6 + 7 + <section> 8 + Install Node.js <Release.VersionDropdown /> on <Release.OperatingSystemDropdown /> using <Release.PlatformDropdown /> 9 + 10 + <Release.ReleaseCodeBox /> 11 + </section> 12 + 13 + <section> 14 + Node.js includes <LinkWithArrow href="https://npmjs.com">npm</LinkWithArrow> (<Release.NpmVersion />) and corepack. 15 + 16 + Read the blog post for <Release.BlogPostLink>this version</Release.BlogPostLink> 17 + 18 + Learn how to <Release.VerifyingBinariesLink>verify signed SHASUMS</Release.VerifyingBinariesLink> 19 + 20 + Check out other community supported <LinkWithArrow href="/download/package-manager/all">package managers</LinkWithArrow> 21 + 22 + </section>
+22
pages/en/new-design/download/prebuilt-binaries/current.mdx
··· 1 + --- 2 + layout: download.hbs 3 + title: Download Node.js® 4 + subtitle: Download Node.js the way you want. 5 + --- 6 + 7 + <section> 8 + I want the <Release.VersionDropdown /> version of Node.js for <Release.OperatingSystemDropdown /> running <Release.BitnessDropdown /> 9 + 10 + <Release.DownloadButton kind="binary" /> 11 + </section> 12 + 13 + <section> 14 + Node.js includes <LinkWithArrow href="https://npmjs.com">npm</LinkWithArrow> (<Release.NpmVersion />) and corepack. 15 + 16 + Read the blog post for <Release.BlogPostLink>this version</Release.BlogPostLink> 17 + 18 + Learn how to <Release.VerifyingBinariesLink>verify signed SHASUMS</Release.VerifyingBinariesLink> 19 + 20 + Check out <LinkWithArrow href="https://nodejs.org/download/nightly/">Nightly</LinkWithArrow> prebuilt binaries or <LinkWithArrow href="https://unofficial-builds.nodejs.org/download/">Unofficial Builds</LinkWithArrow> for other platforms 21 + 22 + </section>
+22
pages/en/new-design/download/prebuilt-binaries/index.mdx
··· 1 + --- 2 + layout: download.hbs 3 + title: Download Node.js® 4 + subtitle: Download Node.js the way you want. 5 + --- 6 + 7 + <section> 8 + I want the <Release.VersionDropdown /> version of Node.js for <Release.OperatingSystemDropdown /> running <Release.BitnessDropdown /> 9 + 10 + <Release.DownloadButton kind="binary" /> 11 + </section> 12 + 13 + <section> 14 + Node.js includes <LinkWithArrow href="https://npmjs.com">npm</LinkWithArrow> (<Release.NpmVersion />) and corepack. 15 + 16 + Read the blog post for <Release.BlogPostLink>this version</Release.BlogPostLink> 17 + 18 + Learn how to <Release.VerifyingBinariesLink>verify signed SHASUMS</Release.VerifyingBinariesLink> 19 + 20 + Check out <LinkWithArrow href="https://nodejs.org/download/nightly/">Nightly</LinkWithArrow> prebuilt binaries or <LinkWithArrow href="https://unofficial-builds.nodejs.org/download/">Unofficial Builds</LinkWithArrow> for other platforms 21 + 22 + </section>
+22
pages/en/new-design/download/source-code/current.mdx
··· 1 + --- 2 + layout: download.hbs 3 + title: Download Node.js® 4 + subtitle: Download Node.js the way you want. 5 + --- 6 + 7 + <section> 8 + I want the <Release.VersionDropdown /> version of the Node.js source code. 9 + 10 + <Release.SourceButton kind="source" /> 11 + </section> 12 + 13 + <section> 14 + Node.js includes <LinkWithArrow href="https://npmjs.com">npm</LinkWithArrow> (<Release.NpmVersion />) and corepack. 15 + 16 + Read the blog post for <Release.BlogPostLink>this version</Release.BlogPostLink> 17 + 18 + Learn how to <Release.VerifyingBinariesLink>verify signed SHASUMS</Release.VerifyingBinariesLink> 19 + 20 + Check out how to <LinkWithArrow href="https://github.com/nodejs/node/blob/main/BUILDING.md#building-nodejs-on-supported-platforms">build Node.js</LinkWithArrow> from source. 21 + 22 + </section>
+22
pages/en/new-design/download/source-code/index.mdx
··· 1 + --- 2 + layout: download.hbs 3 + title: Download Node.js® 4 + subtitle: Download Node.js the way you want. 5 + --- 6 + 7 + <section> 8 + I want the <Release.VersionDropdown /> version of the Node.js source code. 9 + 10 + <Release.SourceButton kind="source" /> 11 + </section> 12 + 13 + <section> 14 + Node.js includes <LinkWithArrow href="https://npmjs.com">npm</LinkWithArrow> (<Release.NpmVersion />) and corepack. 15 + 16 + Read the blog post for <Release.BlogPostLink>this version</Release.BlogPostLink> 17 + 18 + Learn how to <Release.VerifyingBinariesLink>verify signed SHASUMS</Release.VerifyingBinariesLink> 19 + 20 + Check out how to <LinkWithArrow href="https://github.com/nodejs/node/blob/main/BUILDING.md#building-nodejs-on-supported-platforms">build Node.js</LinkWithArrow> from source. 21 + 22 + </section>
+2 -2
pages/en/new-design/index.mdx
··· 22 22 <DownloadButton release={release}>Download Node.js (LTS)</DownloadButton> 23 23 <small> 24 24 Downloads Node.js <b>{release.versionWithPrefix}</b> 25 - <sup title="Downloads Node.js binary for your current platform">1</sup> with long-term support. 25 + <sup title="Downloads a Node.js installer for your current platform">1</sup> with long-term support. 26 26 Node.js can also be installed via <a href="/download/package-manager">package managers</a>. 27 27 </small> 28 28 </> ··· 33 33 <small> 34 34 Want new features sooner? 35 35 Get <b>Node.js <DownloadLink release={release}>{release.versionWithPrefix}</DownloadLink></b> 36 - <sup title="Downloads Node.js binary for your current platform">1</sup> instead. 36 + <sup title="Downloads a Node.js installer for your current platform">1</sup> instead. 37 37 </small> 38 38 )} 39 39 </WithNodeRelease>
+74
providers/releaseProvider.tsx
··· 1 + 'use client'; 2 + 3 + import type { Dispatch, PropsWithChildren, FC } from 'react'; 4 + import { createContext, useMemo, useReducer } from 'react'; 5 + 6 + import type { NodeRelease } from '@/types'; 7 + import type { 8 + ReleaseDispatchActions, 9 + ReleaseAction, 10 + ReleaseContextType, 11 + ReleaseProviderProps, 12 + ReleaseState, 13 + } from '@/types/release'; 14 + 15 + const initialState: ReleaseState = { 16 + releases: [], 17 + release: {} as NodeRelease, 18 + os: 'OTHER', 19 + bitness: '', 20 + platform: 'NVM', 21 + }; 22 + 23 + const createDispatchActions = ( 24 + dispatch: Dispatch<ReleaseAction> 25 + ): ReleaseDispatchActions => ({ 26 + setVersion: payload => dispatch({ type: 'SET_VERSION', payload }), 27 + setOS: payload => dispatch({ type: 'SET_OS', payload }), 28 + setBitness: payload => dispatch({ type: 'SET_BITNESS', payload }), 29 + setPlatform: payload => dispatch({ type: 'SET_PLATFORM', payload }), 30 + }); 31 + 32 + export const ReleaseContext = createContext<ReleaseContextType>({ 33 + ...initialState, 34 + ...createDispatchActions(() => {}), 35 + }); 36 + 37 + export const ReleaseProvider: FC<PropsWithChildren<ReleaseProviderProps>> = ({ 38 + children, 39 + releases, 40 + initialRelease, 41 + }) => { 42 + const getReleaseFromVersion = (version: string) => 43 + releases.find(({ versionWithPrefix }) => versionWithPrefix === version) ?? 44 + ({} as NodeRelease); 45 + 46 + const releaseReducer = (state: ReleaseState, action: ReleaseAction) => { 47 + switch (action.type) { 48 + case 'SET_VERSION': 49 + return { ...state, release: getReleaseFromVersion(action.payload) }; 50 + case 'SET_OS': 51 + return { ...state, os: action.payload }; 52 + case 'SET_BITNESS': 53 + return { ...state, bitness: action.payload }; 54 + case 'SET_PLATFORM': 55 + return { ...state, platform: action.payload }; 56 + default: 57 + return state; 58 + } 59 + }; 60 + 61 + const [state, dispatch] = useReducer(releaseReducer, { 62 + ...initialState, 63 + releases: releases, 64 + release: initialRelease, 65 + }); 66 + 67 + const actions = useMemo(() => createDispatchActions(dispatch), [dispatch]); 68 + 69 + return ( 70 + <ReleaseContext.Provider value={{ ...state, ...actions }}> 71 + {children} 72 + </ReleaseContext.Provider> 73 + ); 74 + };
+2 -1
types/layouts.ts
··· 6 6 | 'page.hbs' 7 7 | 'blog-category.hbs' 8 8 | 'blog-post.hbs' 9 - | 'search.hbs'; 9 + | 'search.hbs' 10 + | 'download.hbs'; 10 11 11 12 // @TODO: These are legacy layouts that are going to be replaced with the `nodejs/nodejs.dev` Layouts in the future 12 13 export type LegacyLayouts =
+37
types/release.ts
··· 1 + import type { ReactNode } from 'react'; 2 + 3 + import type { NodeRelease } from '@/types/releases'; 4 + import type { UserOS } from '@/types/userOS'; 5 + 6 + export type PackageManager = 'NVM' | 'BREW' | 'DOCKER'; 7 + 8 + export interface ReleaseState { 9 + os: UserOS; 10 + release: NodeRelease; 11 + releases: Array<NodeRelease>; 12 + bitness: string | number; 13 + platform: PackageManager; 14 + } 15 + 16 + export type ReleaseAction = 17 + | { type: 'SET_OS'; payload: UserOS } 18 + | { type: 'SET_VERSION'; payload: string } 19 + | { type: 'SET_BITNESS'; payload: string | number } 20 + | { type: 'SET_PLATFORM'; payload: PackageManager }; 21 + 22 + export interface ReleaseDispatchActions { 23 + setVersion: (version: string) => void; 24 + setOS: (os: UserOS) => void; 25 + setBitness: (bitness: string | number) => void; 26 + setPlatform: (platform: PackageManager) => void; 27 + } 28 + 29 + export interface ReleaseContextType 30 + extends ReleaseState, 31 + ReleaseDispatchActions {} 32 + 33 + export interface ReleaseProviderProps { 34 + children: ReactNode; 35 + releases: Array<NodeRelease>; 36 + initialRelease: NodeRelease; 37 + }
+6 -6
util/__tests__/downloadUrlByOS.test.mjs util/__tests__/getNodeDownloadUrl.mjs
··· 1 - import { downloadUrlByOS } from '@/util/downloadUrlByOS'; 1 + import { getNodeDownloadUrl } from '@/util/getNodeDownloadUrl'; 2 2 3 3 const version = 'v18.16.0'; 4 4 5 - describe('downloadUrlByOS', () => { 5 + describe('getNodeDownloadUrl', () => { 6 6 it('returns the correct download URL for Mac', () => { 7 7 const os = 'MAC'; 8 8 const bitness = 86; 9 9 const expectedUrl = 'https://nodejs.org/dist/v18.16.0/node-v18.16.0.pkg'; 10 10 11 - expect(downloadUrlByOS(version, os, bitness)).toBe(expectedUrl); 11 + expect(getNodeDownloadUrl(version, os, bitness)).toBe(expectedUrl); 12 12 }); 13 13 14 14 it('returns the correct download URL for Windows (32-bit)', () => { ··· 17 17 const expectedUrl = 18 18 'https://nodejs.org/dist/v18.16.0/node-v18.16.0-x86.msi'; 19 19 20 - expect(downloadUrlByOS(version, os, bitness)).toBe(expectedUrl); 20 + expect(getNodeDownloadUrl(version, os, bitness)).toBe(expectedUrl); 21 21 }); 22 22 23 23 it('returns the correct download URL for Windows (64-bit)', () => { ··· 26 26 const expectedUrl = 27 27 'https://nodejs.org/dist/v18.16.0/node-v18.16.0-x64.msi'; 28 28 29 - expect(downloadUrlByOS(version, os, bitness)).toBe(expectedUrl); 29 + expect(getNodeDownloadUrl(version, os, bitness)).toBe(expectedUrl); 30 30 }); 31 31 32 32 it('returns the default download URL for other operating systems', () => { ··· 34 34 const bitness = 86; 35 35 const expectedUrl = 'https://nodejs.org/dist/v18.16.0/node-v18.16.0.tar.xz'; 36 36 37 - expect(downloadUrlByOS(version, os, bitness)).toBe(expectedUrl); 37 + expect(getNodeDownloadUrl(version, os, bitness)).toBe(expectedUrl); 38 38 }); 39 39 });
+151
util/__tests__/downloadUtils.test.mjs
··· 1 + import { 2 + getDownloadCategory, 3 + mapCategoriesToTabs, 4 + formatDropdownItems, 5 + } from '@/util/downloadUtils'; 6 + 7 + describe('formatDropdownItems', () => { 8 + it('should format dropdown items correctly', () => { 9 + const items = [ 10 + { value: 'item1', label: 'Item 1' }, 11 + { value: 'item2', label: 'Item 2' }, 12 + ]; 13 + const disabledItems = ['item2']; 14 + const icons = { item1: 'icon' }; 15 + const defaultIcon = 'defaultIcon'; 16 + 17 + const result = formatDropdownItems({ 18 + items: items, 19 + disabledItems: disabledItems, 20 + icons: icons, 21 + defaultIcon: defaultIcon, 22 + }); 23 + 24 + expect(result).toEqual([ 25 + { value: 'item1', label: 'Item 1', disabled: false, iconImage: 'icon' }, 26 + { 27 + value: 'item2', 28 + label: 'Item 2', 29 + disabled: true, 30 + iconImage: 'defaultIcon', 31 + }, 32 + ]); 33 + }); 34 + 35 + it('should mark all items as disabled when all items are in the disabledItems list', () => { 36 + const items = [ 37 + { value: 'item1', label: 'Item 1' }, 38 + { value: 'item2', label: 'Item 2' }, 39 + ]; 40 + const disabledItems = ['item1', 'item2']; 41 + 42 + const result = formatDropdownItems({ 43 + items: items, 44 + disabledItems: disabledItems, 45 + }); 46 + 47 + expect(result).toEqual([ 48 + { value: 'item1', label: 'Item 1', disabled: true }, 49 + { value: 'item2', label: 'Item 2', disabled: true }, 50 + ]); 51 + }); 52 + 53 + it('should not mark any items as disabled when disabledItems list is empty', () => { 54 + const items = [ 55 + { value: 'item1', label: 'Item 1' }, 56 + { value: 'item2', label: 'Item 2' }, 57 + ]; 58 + 59 + const result = formatDropdownItems({ items: items }); 60 + 61 + expect(result).toEqual([ 62 + { value: 'item1', label: 'Item 1', disabled: false }, 63 + { value: 'item2', label: 'Item 2', disabled: false }, 64 + ]); 65 + }); 66 + }); 67 + 68 + describe('getDownloadCategory', () => { 69 + it('should return correct category information for /download/current', () => { 70 + const result = getDownloadCategory('/download/current'); 71 + 72 + expect(result).toEqual({ 73 + page: 'download', 74 + category: 'download', 75 + subCategory: 'current', 76 + }); 77 + }); 78 + 79 + it('should return correct category information for /download/category/subcategory', () => { 80 + const result = getDownloadCategory('/download/category/subcategory'); 81 + 82 + expect(result).toEqual({ 83 + page: 'download', 84 + category: 'category', 85 + subCategory: 'subcategory', 86 + }); 87 + }); 88 + 89 + it('should return correct category information for /download/category', () => { 90 + const result = getDownloadCategory('/download/category'); 91 + 92 + expect(result).toEqual({ 93 + page: 'download', 94 + category: 'category', 95 + subCategory: undefined, 96 + }); 97 + }); 98 + }); 99 + 100 + describe('mapCategoriesToTabs', () => { 101 + it('should return correct tabs for download page when subcategory current', () => { 102 + const result = mapCategoriesToTabs({ 103 + page: 'download', 104 + categories: [ 105 + { 106 + category: 'download', 107 + label: 'Download', 108 + }, 109 + { 110 + category: 'package-manager', 111 + label: 'Package Manager', 112 + }, 113 + ], 114 + subCategory: 'current', 115 + }); 116 + 117 + expect(result).toEqual([ 118 + { key: 'download', label: 'Download', link: '/download/current' }, 119 + { 120 + key: 'package-manager', 121 + label: 'Package Manager', 122 + link: '/download/package-manager/current', 123 + }, 124 + ]); 125 + }); 126 + 127 + it('should return correct tabs for download page when subcategory not defined', () => { 128 + const result = mapCategoriesToTabs({ 129 + page: 'download', 130 + categories: [ 131 + { 132 + category: 'download', 133 + label: 'Download', 134 + }, 135 + { 136 + category: 'package-manager', 137 + label: 'Package Manager', 138 + }, 139 + ], 140 + }); 141 + 142 + expect(result).toEqual([ 143 + { key: 'download', label: 'Download', link: '/download' }, 144 + { 145 + key: 'package-manager', 146 + label: 'Package Manager', 147 + link: '/download/package-manager', 148 + }, 149 + ]); 150 + }); 151 + });
-21
util/downloadUrlByOS.ts
··· 1 - import { DIST_URL } from '@/next.constants.mjs'; 2 - import type { UserOS } from '@/types/userOS'; 3 - 4 - export const downloadUrlByOS = ( 5 - versionWithPrefix: string, 6 - os: UserOS, 7 - bitness: number 8 - ): string => { 9 - const baseURL = `${DIST_URL}${versionWithPrefix}`; 10 - 11 - switch (os) { 12 - case 'MAC': 13 - return `${baseURL}/node-${versionWithPrefix}.pkg`; 14 - case 'WIN': 15 - return `${baseURL}/node-${versionWithPrefix}-x${bitness}.msi`; 16 - case 'LINUX': 17 - return `${baseURL}/node-${versionWithPrefix}-linux-x64.tar.xz`; 18 - default: 19 - return `${baseURL}/node-${versionWithPrefix}.tar.xz`; 20 - } 21 - };
+153
util/downloadUtils.ts
··· 1 + import { ENABLE_WEBSITE_REDESIGN } from '@/next.constants.mjs'; 2 + import type { PackageManager } from '@/types/release'; 3 + import type { UserOS } from '@/types/userOS'; 4 + 5 + // A utility enum to help convert `userOs` data type to user-readable format 6 + export enum OperatingSystem { 7 + WIN = 'Windows', 8 + MAC = 'MacOS', 9 + LINUX = 'Linux', 10 + OTHER = 'Other', 11 + } 12 + 13 + export const operatingSystemItems = [ 14 + { 15 + label: OperatingSystem.WIN, 16 + value: 'WIN' as UserOS, 17 + }, 18 + { 19 + label: OperatingSystem.MAC, 20 + value: 'MAC' as UserOS, 21 + }, 22 + { 23 + label: OperatingSystem.LINUX, 24 + value: 'LINUX' as UserOS, 25 + }, 26 + ]; 27 + 28 + export const platformItems = [ 29 + { 30 + label: 'NVM', 31 + value: 'NVM' as PackageManager, 32 + }, 33 + { 34 + label: 'Brew', 35 + value: 'BREW' as PackageManager, 36 + }, 37 + { 38 + label: 'Docker', 39 + value: 'DOCKER' as PackageManager, 40 + }, 41 + ]; 42 + 43 + export const bitnessItems = { 44 + WIN: [ 45 + { 46 + label: '64-bit', 47 + value: '64', 48 + }, 49 + { 50 + label: '32-bit', 51 + value: '86', 52 + }, 53 + { 54 + label: 'ARM64', 55 + value: 'arm64', 56 + }, 57 + ], 58 + MAC: [ 59 + { 60 + label: '64-bit', 61 + value: '64', 62 + }, 63 + { 64 + label: 'ARM64', 65 + value: 'arm64', 66 + }, 67 + ], 68 + LINUX: [ 69 + { 70 + label: '64-bit', 71 + value: '64', 72 + }, 73 + { 74 + label: 'ARMv7', 75 + value: 'armv7l', 76 + }, 77 + { 78 + label: 'ARM64', 79 + value: 'arm64', 80 + }, 81 + { 82 + label: 'Power LE', 83 + value: 'ppc64le', 84 + }, 85 + { 86 + label: 'System Z', 87 + value: 's390x', 88 + }, 89 + ], 90 + OTHER: [], 91 + }; 92 + 93 + type formatDropdownItemsType = { 94 + items: Array<{ label: string; value: string }>; 95 + disabledItems?: Array<string>; 96 + icons?: Record<string, JSX.Element>; 97 + defaultIcon?: JSX.Element; 98 + }; 99 + 100 + // Formats the dropdown items to be used in the `Select` component in the 101 + // download page and adds the icons, and disabled status to the dropdown items. 102 + export const formatDropdownItems = ({ 103 + items, 104 + disabledItems = [], 105 + icons = {}, 106 + defaultIcon, 107 + }: formatDropdownItemsType) => 108 + items.map(item => ({ 109 + ...item, 110 + disabled: disabledItems.includes(item.value), 111 + iconImage: icons[item.value] || defaultIcon, 112 + })); 113 + 114 + // Returns the page, category and subCategoy information to be used in the page 115 + // from the pathname information on the download pages. 116 + export const getDownloadCategory = (pathname: string) => { 117 + /** @deprecated once the website redesign happens remove this code block */ 118 + if (ENABLE_WEBSITE_REDESIGN) { 119 + pathname = pathname.replace('/new-design', ''); 120 + } 121 + 122 + const segments = pathname.split('/').filter(Boolean); 123 + const [, c] = segments; 124 + 125 + if (c === 'current' || typeof c === 'undefined') { 126 + segments.unshift('download'); 127 + } 128 + 129 + const [page, category, subCategory] = segments; 130 + 131 + return { page, category, subCategory }; 132 + }; 133 + 134 + type CategoryTabMappingParams = { 135 + page: string; 136 + categories: Array<{ category: string; label: string }>; 137 + subCategory: string; 138 + }; 139 + 140 + // Utility method used to create URLs and labels to be used in Tabs 141 + export const mapCategoriesToTabs = ({ 142 + page, 143 + categories, 144 + subCategory, 145 + }: CategoryTabMappingParams) => 146 + categories.map(({ category, label }) => ({ 147 + key: category, 148 + label: label, 149 + link: 150 + category === 'download' 151 + ? `/${[page, subCategory].filter(Boolean).join('/')}` 152 + : `/${[page, category, subCategory].filter(Boolean).join('/')}`, 153 + }));
+18 -9
util/getHighlighter.ts
··· 1 1 import { getHighlighterCore } from 'shiki/core'; 2 + import type { HighlighterCore } from 'shiki/core'; 2 3 import getWasm from 'shiki/wasm'; 3 4 4 5 import { LANGUAGES, DEFAULT_THEME } from '@/shiki.config.mjs'; 5 6 6 7 // This creates a memoized minimal Shikiji Syntax Highlighter 7 - const memoizedShikiji = await getHighlighterCore({ 8 - themes: [DEFAULT_THEME], 9 - langs: LANGUAGES, 10 - loadWasm: getWasm, 11 - }); 8 + export const getShiki = () => 9 + getHighlighterCore({ 10 + themes: [DEFAULT_THEME], 11 + langs: LANGUAGES, 12 + loadWasm: getWasm, 13 + }); 12 14 13 - export const highlightToHtml = (code: string, language: string) => 14 - memoizedShikiji.codeToHtml(code, { lang: language, theme: DEFAULT_THEME }); 15 + export const highlightToHtml = 16 + (shiki: HighlighterCore) => (code: string, language: string) => 17 + // Shiki will always return the Highlighted code encapsulated in a <pre> and <code> tag 18 + // since our own CodeBox component handles the <code> tag, we just want to extract 19 + // the inner highlighted code to the CodeBox 20 + shiki 21 + .codeToHtml(code, { lang: language, theme: DEFAULT_THEME }) 22 + .match(/<code>(.+?)<\/code>/s)![1]; 15 23 16 - export const highlightToHast = (code: string, language: string) => 17 - memoizedShikiji.codeToHast(code, { lang: language, theme: DEFAULT_THEME }); 24 + export const highlightToHast = 25 + (shiki: HighlighterCore) => (code: string, language: string) => 26 + shiki.codeToHast(code, { lang: language, theme: DEFAULT_THEME });
+87
util/getNodeDownloadSnippet.ts
··· 1 + import dedent from 'dedent'; 2 + 3 + import type { UserOS } from '@/types/userOS'; 4 + 5 + export const getNodeDownloadSnippet = (major: number, os: UserOS) => { 6 + if (os === 'LINUX' || os === 'MAC') { 7 + const platformSnippets = { 8 + NVM: dedent` 9 + # Installs NVM (Node Version Manager) 10 + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash 11 + 12 + # Installs Node.js 13 + nvm install v${major} 14 + 15 + # Checks that Node is installed 16 + node -v 17 + 18 + # Checks your NPM version 19 + npm -v`, 20 + BREW: dedent` 21 + # Installs Brew (macOS/Linux Package Manager) 22 + curl -o- https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh | bash 23 + 24 + # Installs Node.js 25 + brew install node@${major} 26 + 27 + # Checks that Node is installed 28 + node -v 29 + 30 + # Checks your NPM version 31 + npm -v`, 32 + DOCKER: '', 33 + }; 34 + 35 + if (os === 'MAC') { 36 + platformSnippets.DOCKER = dedent` 37 + # Installs Brew (macOS Package Manager) 38 + curl -o- https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh | bash 39 + 40 + # Installs Docker Desktop 41 + brew install docker --cask 42 + 43 + # Pull Node.js Docker Image 44 + docker pull node:${major}-${major >= 4 ? 'alpine' : 'slim'} 45 + `; 46 + } 47 + 48 + return platformSnippets; 49 + } 50 + 51 + if (os === 'WIN') { 52 + return { 53 + NVM: dedent` 54 + # Installs Chocolatey (Windows Package Manager) 55 + Set-ExecutionPolicy Bypass -Scope Process -Force; 56 + [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; 57 + iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')); 58 + 59 + # Installs NVM (Node Version Manager) 60 + choco install nvm 61 + 62 + # Installs Node.js 63 + nvm install v${major} 64 + 65 + # Checks that Node is installed 66 + node -v 67 + 68 + # Checks your NPM version 69 + npm -v`, 70 + DOCKER: dedent` 71 + # Installs Chocolatey (Windows Package Manager) 72 + Set-ExecutionPolicy Bypass -Scope Process -Force; 73 + [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; 74 + iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')); 75 + 76 + # Installs Docker Desktop 77 + choco install docker-desktop 78 + 79 + # Pull Node.js Docker Image 80 + docker pull node:${major}-${major >= 4 ? 'alpine' : 'slim'} 81 + `, 82 + BREW: '', 83 + }; 84 + } 85 + 86 + return { NVM: '', BREW: '', DOCKER: '' }; 87 + };
+51
util/getNodeDownloadUrl.ts
··· 1 + import { DIST_URL } from '@/next.constants.mjs'; 2 + import type { UserOS } from '@/types/userOS'; 3 + 4 + export const getNodeDownloadUrl = ( 5 + versionWithPrefix: string, 6 + os: UserOS, 7 + bitness: string | number, 8 + kind: 'installer' | 'binary' | 'source' = 'installer' 9 + ): string => { 10 + const baseURL = `${DIST_URL}${versionWithPrefix}`; 11 + 12 + if (kind === 'source') { 13 + return `${baseURL}/node-${versionWithPrefix}.tar.gz`; 14 + } 15 + 16 + switch (os) { 17 + case 'MAC': 18 + if (kind === 'installer') { 19 + return `${baseURL}/node-${versionWithPrefix}.pkg`; 20 + } 21 + 22 + if (typeof bitness === 'string') { 23 + return `${baseURL}/node-${versionWithPrefix}-darwin-${bitness}.tar.gz`; 24 + } 25 + 26 + return `${baseURL}/node-${versionWithPrefix}-darwin-x${bitness}.tar.gz`; 27 + case 'WIN': { 28 + if (kind === 'installer') { 29 + if (typeof bitness === 'string') { 30 + return `${baseURL}/node-${versionWithPrefix}-${bitness}.msi`; 31 + } 32 + 33 + return `${baseURL}/node-${versionWithPrefix}-x${bitness}.msi`; 34 + } 35 + 36 + if (typeof bitness === 'string') { 37 + return `${baseURL}/node-${versionWithPrefix}-win-${bitness}.zip`; 38 + } 39 + 40 + return `${baseURL}/node-${versionWithPrefix}-win-x${bitness}.zip`; 41 + } 42 + case 'LINUX': 43 + if (typeof bitness === 'string') { 44 + return `${baseURL}/node-${versionWithPrefix}-linux-${bitness}.tar.xz`; 45 + } 46 + 47 + return `${baseURL}/node-${versionWithPrefix}-linux-x${bitness}.tar.xz`; 48 + default: 49 + return `${baseURL}/node-${versionWithPrefix}.tar.gz`; 50 + } 51 + };