From 891da0788593f91b2982ba4a0009ee7f7954f9a1 Mon Sep 17 00:00:00 2001 From: avivkeller Date: Sun, 8 Jun 2025 17:03:58 -0400 Subject: [PATCH 1/6] chore(site): rename and merge utilities --- .../app/[locale]/next-data/api-data/route.ts | 4 +- .../next-data/og/[category]/[title]/route.tsx | 22 ++--- .../app/[locale]/next-data/page-data/route.ts | 2 +- apps/site/client-context.ts | 2 +- .../components/Blog/BlogPostCard/index.tsx | 2 +- .../Downloads/DownloadButton/index.tsx | 4 +- .../components/Downloads/DownloadLink.tsx | 5 +- .../Downloads/MinorReleasesTable/index.tsx | 2 +- .../Downloads/Release/DownloadLink.tsx | 2 +- .../Release/InstallationMethodDropdown.tsx | 6 +- .../Release/OperatingSystemDropdown.tsx | 6 +- .../Release/PackageManagerDropdown.tsx | 6 +- .../Downloads/Release/PlatformDropdown.tsx | 4 +- .../Release/PrebuiltDownloadButtons.tsx | 4 +- .../Downloads/Release/ReleaseCodeBox.tsx | 2 +- apps/site/components/withAvatarGroup.tsx | 2 +- apps/site/components/withBadgeGroup.tsx | 2 +- apps/site/components/withBanner.tsx | 2 +- apps/site/components/withBlogCategories.tsx | 2 +- apps/site/components/withBreadcrumbs.tsx | 2 +- apps/site/components/withMetaBar.tsx | 2 +- apps/site/hooks/react-client/useDetectOS.ts | 3 +- .../hooks/react-client/useNavigationState.ts | 2 +- apps/site/i18n.tsx | 2 +- apps/site/layouts/Post.tsx | 4 +- apps/site/next.mdx.compiler.mjs | 2 +- apps/site/providers/matterProvider.tsx | 2 +- apps/site/types/userOS.ts | 2 +- .../{getNodeApiLink.test.mjs => api.test.mjs} | 2 +- .../{authorUtils.test.mjs => author.test.mjs} | 2 +- .../{blogUtils.test.mjs => blog.test.mjs} | 2 +- ...lientContext.test.mjs => context.test.mjs} | 2 +- .../{dateUtils.test.mjs => date.test.mjs} | 2 +- apps/site/util/__tests__/deepMerge.test.mjs | 20 ----- ...wnloadUtils.test.mjs => download.test.mjs} | 55 ++++++++++++- .../__tests__/getHighEntropyValues.test.mjs | 46 ----------- .../__tests__/getNodeDownloadUrl.test.mjs | 56 ------------- .../util/__tests__/getUserPlatform.test.mjs | 18 ----- .../{gitHubUtils.test.mjs => github.test.mjs} | 2 +- apps/site/util/__tests__/hexToRGBA.test.mjs | 28 ------- .../{debounce.test.mjs => misc.test.mjs} | 18 ++++- .../{stringUtils.test.mjs => string.test.mjs} | 2 +- .../{detectOS.test.mjs => ua.test.mjs} | 61 +++++++++++++- apps/site/util/{getNodeApiLink.ts => api.ts} | 8 +- apps/site/util/{authorUtils.ts => author.ts} | 4 +- apps/site/util/{blogUtils.ts => blog.ts} | 0 .../{assignClientContext.ts => context.ts} | 0 apps/site/util/{dateUtils.ts => date.ts} | 0 apps/site/util/debounce.ts | 16 ---- apps/site/util/detectOS.ts | 11 --- .../constants.json | 0 .../{downloadUtils => download}/index.tsx | 74 +++++++++++++++++ apps/site/util/getHighEntropyValues.ts | 33 -------- apps/site/util/getNodeDownloadUrl.ts | 75 ----------------- apps/site/util/getUserPlatform.ts | 15 ---- apps/site/util/{gitHubUtils.ts => github.ts} | 0 apps/site/util/hexToRGBA.ts | 10 --- apps/site/util/{deepMerge.ts => misc.ts} | 19 ++++- apps/site/util/{stringUtils.ts => string.ts} | 0 apps/site/util/ua.ts | 81 +++++++++++++++++++ 60 files changed, 361 insertions(+), 403 deletions(-) rename apps/site/util/__tests__/{getNodeApiLink.test.mjs => api.test.mjs} (96%) rename apps/site/util/__tests__/{authorUtils.test.mjs => author.test.mjs} (99%) rename apps/site/util/__tests__/{blogUtils.test.mjs => blog.test.mjs} (88%) rename apps/site/util/__tests__/{assignClientContext.test.mjs => context.test.mjs} (94%) rename apps/site/util/__tests__/{dateUtils.test.mjs => date.test.mjs} (96%) delete mode 100644 apps/site/util/__tests__/deepMerge.test.mjs rename apps/site/util/__tests__/{downloadUtils.test.mjs => download.test.mjs} (69%) delete mode 100644 apps/site/util/__tests__/getHighEntropyValues.test.mjs delete mode 100644 apps/site/util/__tests__/getNodeDownloadUrl.test.mjs delete mode 100644 apps/site/util/__tests__/getUserPlatform.test.mjs rename apps/site/util/__tests__/{gitHubUtils.test.mjs => github.test.mjs} (97%) delete mode 100644 apps/site/util/__tests__/hexToRGBA.test.mjs rename apps/site/util/__tests__/{debounce.test.mjs => misc.test.mjs} (65%) rename apps/site/util/__tests__/{stringUtils.test.mjs => string.test.mjs} (99%) rename apps/site/util/__tests__/{detectOS.test.mjs => ua.test.mjs} (53%) rename apps/site/util/{getNodeApiLink.ts => api.ts} (62%) rename apps/site/util/{authorUtils.ts => author.ts} (94%) rename apps/site/util/{blogUtils.ts => blog.ts} (100%) rename apps/site/util/{assignClientContext.ts => context.ts} (100%) rename apps/site/util/{dateUtils.ts => date.ts} (100%) delete mode 100644 apps/site/util/debounce.ts delete mode 100644 apps/site/util/detectOS.ts rename apps/site/util/{downloadUtils => download}/constants.json (100%) rename apps/site/util/{downloadUtils => download}/index.tsx (63%) delete mode 100644 apps/site/util/getHighEntropyValues.ts delete mode 100644 apps/site/util/getNodeDownloadUrl.ts delete mode 100644 apps/site/util/getUserPlatform.ts rename apps/site/util/{gitHubUtils.ts => github.ts} (100%) delete mode 100644 apps/site/util/hexToRGBA.ts rename apps/site/util/{deepMerge.ts => misc.ts} (55%) rename apps/site/util/{stringUtils.ts => string.ts} (100%) create mode 100644 apps/site/util/ua.ts diff --git a/apps/site/app/[locale]/next-data/api-data/route.ts b/apps/site/app/[locale]/next-data/api-data/route.ts index de008a59739b0..29fedbdf8393e 100644 --- a/apps/site/app/[locale]/next-data/api-data/route.ts +++ b/apps/site/app/[locale]/next-data/api-data/route.ts @@ -4,8 +4,8 @@ import provideReleaseData from '#site/next-data/providers/releaseData'; import { GITHUB_API_KEY } from '#site/next.constants.mjs'; import { defaultLocale } from '#site/next.locales.mjs'; import type { GitHubApiFile } from '#site/types'; -import { getGitHubApiDocsUrl } from '#site/util/gitHubUtils'; -import { parseRichTextIntoPlainText } from '#site/util/stringUtils'; +import { getGitHubApiDocsUrl } from '#site/util/github'; +import { parseRichTextIntoPlainText } from '#site/util/string'; // Defines if we should use the GitHub API Key for the request // based on the environment variable `GITHUB_API_KEY` diff --git a/apps/site/app/[locale]/next-data/og/[category]/[title]/route.tsx b/apps/site/app/[locale]/next-data/og/[category]/[title]/route.tsx index 05b304e5721ed..c50a666327574 100644 --- a/apps/site/app/[locale]/next-data/og/[category]/[title]/route.tsx +++ b/apps/site/app/[locale]/next-data/og/[category]/[title]/route.tsx @@ -4,19 +4,21 @@ import { ImageResponse } from 'next/og'; import { DEFAULT_CATEGORY_OG_TYPE } from '#site/next.constants.mjs'; import { defaultLocale } from '#site/next.locales.mjs'; -import { hexToRGBA } from '#site/util/hexToRGBA'; // TODO: use CSS variables instead of absolute values const CATEGORY_TO_THEME_COLOUR_MAP = { - announcement: '#1a3f1d', - release: '#0c7bb3', - vulnerability: '#ae5f00', + announcement: 'rgb(26, 63, 29)', + release: 'rgb(12, 123, 179)', + vulnerability: 'rgb(174, 95, 0)', }; -type Category = keyof typeof CATEGORY_TO_THEME_COLOUR_MAP; - -type DynamicStaticPaths = { locale: string; category: Category; title: string }; -type StaticParams = { params: Promise }; +type StaticParams = { + params: Promise<{ + locale: string; + category: keyof typeof CATEGORY_TO_THEME_COLOUR_MAP; + title: string; + }>; +}; // This is the Route Handler for the `GET` method which handles the request // for generating OpenGraph images for Blog Posts and Pages @@ -24,12 +26,12 @@ type StaticParams = { params: Promise }; export const GET = async (_: Request, props: StaticParams) => { const params = await props.params; - const categoryColour = + const categoryColor = params.category in CATEGORY_TO_THEME_COLOUR_MAP ? CATEGORY_TO_THEME_COLOUR_MAP[params.category] : CATEGORY_TO_THEME_COLOUR_MAP[DEFAULT_CATEGORY_OG_TYPE]; - const gridBackground = `radial-gradient(circle, ${hexToRGBA(categoryColour)}, transparent)`; + const gridBackground = `radial-gradient(circle, ${categoryColor}, transparent)`; return new ImageResponse( ( diff --git a/apps/site/app/[locale]/next-data/page-data/route.ts b/apps/site/app/[locale]/next-data/page-data/route.ts index d7e9fdd09efd4..35e64530caef0 100644 --- a/apps/site/app/[locale]/next-data/page-data/route.ts +++ b/apps/site/app/[locale]/next-data/page-data/route.ts @@ -4,7 +4,7 @@ import matter from 'gray-matter'; import { dynamicRouter } from '#site/next.dynamic.mjs'; import { defaultLocale } from '#site/next.locales.mjs'; -import { parseRichTextIntoPlainText } from '#site/util/stringUtils'; +import { parseRichTextIntoPlainText } from '#site/util/string'; // This is the Route Handler for the `GET` method which handles the request // for a digest and metadata of all existing pages on Node.js Website diff --git a/apps/site/client-context.ts b/apps/site/client-context.ts index 8ed30d8fb353b..3b900b02a8123 100644 --- a/apps/site/client-context.ts +++ b/apps/site/client-context.ts @@ -1,7 +1,7 @@ import { cache } from 'react'; import type { ClientSharedServerContext } from '#site/types'; -import { assignClientContext } from '#site/util/assignClientContext'; +import { assignClientContext } from '#site/util/context'; // This allows us to have Server-Side Context's of the shared "contextual" data // which includes the frontmatter, the current pathname from the dynamic segments diff --git a/apps/site/components/Blog/BlogPostCard/index.tsx b/apps/site/components/Blog/BlogPostCard/index.tsx index 4f7487b86de75..9f8c7442685b3 100644 --- a/apps/site/components/Blog/BlogPostCard/index.tsx +++ b/apps/site/components/Blog/BlogPostCard/index.tsx @@ -6,7 +6,7 @@ import FormattedTime from '#site/components/Common/FormattedTime'; import Link from '#site/components/Link'; import WithAvatarGroup from '#site/components/withAvatarGroup'; import type { BlogCategory } from '#site/types'; -import { mapBlogCategoryToPreviewType } from '#site/util/blogUtils'; +import { mapBlogCategoryToPreviewType } from '#site/util/blog'; import styles from './index.module.css'; diff --git a/apps/site/components/Downloads/DownloadButton/index.tsx b/apps/site/components/Downloads/DownloadButton/index.tsx index f238ce4546505..147d25eed6d45 100644 --- a/apps/site/components/Downloads/DownloadButton/index.tsx +++ b/apps/site/components/Downloads/DownloadButton/index.tsx @@ -7,8 +7,8 @@ import type { FC, PropsWithChildren } from 'react'; import Button from '#site/components/Common/Button'; import { useClientContext } from '#site/hooks'; import type { NodeRelease } from '#site/types'; -import { getNodeDownloadUrl } from '#site/util/getNodeDownloadUrl'; -import { getUserPlatform } from '#site/util/getUserPlatform'; +import { getNodeDownloadUrl } from '#site/util/download'; +import { getUserPlatform } from '#site/util/ua'; import styles from './index.module.css'; diff --git a/apps/site/components/Downloads/DownloadLink.tsx b/apps/site/components/Downloads/DownloadLink.tsx index e639a83d2ac7c..eda94096ce0d8 100644 --- a/apps/site/components/Downloads/DownloadLink.tsx +++ b/apps/site/components/Downloads/DownloadLink.tsx @@ -5,9 +5,8 @@ import type { FC, PropsWithChildren } from 'react'; import LinkWithArrow from '#site/components/LinkWithArrow'; import { useClientContext } from '#site/hooks'; import type { NodeRelease } from '#site/types'; -import type { DownloadKind } from '#site/util/getNodeDownloadUrl'; -import { getNodeDownloadUrl } from '#site/util/getNodeDownloadUrl'; -import { getUserPlatform } from '#site/util/getUserPlatform'; +import { getNodeDownloadUrl, type DownloadKind } from '#site/util/download'; +import { getUserPlatform } from '#site/util/ua'; type DownloadLinkProps = { release: NodeRelease; kind?: DownloadKind }; diff --git a/apps/site/components/Downloads/MinorReleasesTable/index.tsx b/apps/site/components/Downloads/MinorReleasesTable/index.tsx index c2cf17ec975a5..b812f2d150c28 100644 --- a/apps/site/components/Downloads/MinorReleasesTable/index.tsx +++ b/apps/site/components/Downloads/MinorReleasesTable/index.tsx @@ -7,7 +7,7 @@ import type { FC } from 'react'; import Link from '#site/components/Link'; import { BASE_CHANGELOG_URL } from '#site/next.constants.mjs'; import type { MinorVersion } from '#site/types'; -import { getNodeApiLink } from '#site/util/getNodeApiLink'; +import { getNodeApiLink } from '#site/util/api'; import styles from './index.module.css'; diff --git a/apps/site/components/Downloads/Release/DownloadLink.tsx b/apps/site/components/Downloads/Release/DownloadLink.tsx index dc685ccedda79..f09e37da5ade5 100644 --- a/apps/site/components/Downloads/Release/DownloadLink.tsx +++ b/apps/site/components/Downloads/Release/DownloadLink.tsx @@ -5,7 +5,7 @@ import { useContext } from 'react'; import DownloadLinkBase from '#site/components/Downloads/DownloadLink'; import { ReleaseContext } from '#site/providers/releaseProvider'; -import type { DownloadKind } from '#site/util/getNodeDownloadUrl'; +import type { DownloadKind } from '#site/util/download'; type DownloadLinkProps = { kind?: DownloadKind }; diff --git a/apps/site/components/Downloads/Release/InstallationMethodDropdown.tsx b/apps/site/components/Downloads/Release/InstallationMethodDropdown.tsx index b050b156f18c4..831b5b6e480ee 100644 --- a/apps/site/components/Downloads/Release/InstallationMethodDropdown.tsx +++ b/apps/site/components/Downloads/Release/InstallationMethodDropdown.tsx @@ -7,11 +7,7 @@ import type { FC } from 'react'; import { ReleaseContext } from '#site/providers/releaseProvider'; import type { InstallationMethod } from '#site/types/release'; -import { - nextItem, - INSTALL_METHODS, - parseCompat, -} from '#site/util/downloadUtils'; +import { nextItem, INSTALL_METHODS, parseCompat } from '#site/util/download'; const InstallationMethodDropdown: FC = () => { const release = useContext(ReleaseContext); diff --git a/apps/site/components/Downloads/Release/OperatingSystemDropdown.tsx b/apps/site/components/Downloads/Release/OperatingSystemDropdown.tsx index e5ba1e404d141..02be4fd0c5084 100644 --- a/apps/site/components/Downloads/Release/OperatingSystemDropdown.tsx +++ b/apps/site/components/Downloads/Release/OperatingSystemDropdown.tsx @@ -8,11 +8,7 @@ import type { FC } from 'react'; import { useClientContext } from '#site/hooks'; import { ReleaseContext } from '#site/providers/releaseProvider'; import type { UserOS } from '#site/types/userOS'; -import { - nextItem, - OPERATING_SYSTEMS, - parseCompat, -} from '#site/util/downloadUtils'; +import { nextItem, OPERATING_SYSTEMS, parseCompat } from '#site/util/download'; type OperatingSystemDropdownProps = { exclude?: Array }; diff --git a/apps/site/components/Downloads/Release/PackageManagerDropdown.tsx b/apps/site/components/Downloads/Release/PackageManagerDropdown.tsx index 28a48d178339d..defee5799e364 100644 --- a/apps/site/components/Downloads/Release/PackageManagerDropdown.tsx +++ b/apps/site/components/Downloads/Release/PackageManagerDropdown.tsx @@ -7,11 +7,7 @@ import type { FC } from 'react'; import { ReleaseContext } from '#site/providers/releaseProvider'; import type { PackageManager } from '#site/types/release'; -import { - nextItem, - PACKAGE_MANAGERS, - parseCompat, -} from '#site/util/downloadUtils'; +import { nextItem, PACKAGE_MANAGERS, parseCompat } from '#site/util/download'; const PackageManagerDropdown: FC = () => { const release = useContext(ReleaseContext); diff --git a/apps/site/components/Downloads/Release/PlatformDropdown.tsx b/apps/site/components/Downloads/Release/PlatformDropdown.tsx index 7e5405e612155..4d746f25dae8a 100644 --- a/apps/site/components/Downloads/Release/PlatformDropdown.tsx +++ b/apps/site/components/Downloads/Release/PlatformDropdown.tsx @@ -8,8 +8,8 @@ import { useEffect, useContext, useMemo } from 'react'; import { useClientContext } from '#site/hooks'; import { ReleaseContext } from '#site/providers/releaseProvider'; import type { UserPlatform } from '#site/types/userOS'; -import { PLATFORMS, nextItem, parseCompat } from '#site/util/downloadUtils'; -import { getUserPlatform } from '#site/util/getUserPlatform'; +import { PLATFORMS, nextItem, parseCompat } from '#site/util/download'; +import { getUserPlatform } from '#site/util/ua'; const PlatformDropdown: FC = () => { const { architecture, bitness } = useClientContext(); diff --git a/apps/site/components/Downloads/Release/PrebuiltDownloadButtons.tsx b/apps/site/components/Downloads/Release/PrebuiltDownloadButtons.tsx index ca037f6a443c2..da712e6e2870d 100644 --- a/apps/site/components/Downloads/Release/PrebuiltDownloadButtons.tsx +++ b/apps/site/components/Downloads/Release/PrebuiltDownloadButtons.tsx @@ -11,8 +11,8 @@ import { ReleaseContext } from '#site/providers/releaseProvider'; import { OS_NOT_SUPPORTING_INSTALLERS, OperatingSystemLabel, -} from '#site/util/downloadUtils'; -import { getNodeDownloadUrl } from '#site/util/getNodeDownloadUrl'; + getNodeDownloadUrl, +} from '#site/util/download'; // Retrieves the pure extension piece from the input string const getExtension = (input: string) => String(input.split('.').slice(-1)); diff --git a/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx b/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx index 7e18a7bea4b9b..b3d13a9f9df4b 100644 --- a/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx +++ b/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx @@ -16,7 +16,7 @@ import { ReleasesContext, } from '#site/providers/releaseProvider'; import type { ReleaseContextType } from '#site/types/release'; -import { INSTALL_METHODS } from '#site/util/downloadUtils'; +import { INSTALL_METHODS } from '#site/util/download'; // Creates a minimal JavaScript interpreter for parsing the JavaScript code from the snippets // Note: that the code runs inside a sandboxed environment and cannot interact with any code outside of the sandbox diff --git a/apps/site/components/withAvatarGroup.tsx b/apps/site/components/withAvatarGroup.tsx index fc0569877c50f..0e2731c72e218 100644 --- a/apps/site/components/withAvatarGroup.tsx +++ b/apps/site/components/withAvatarGroup.tsx @@ -5,7 +5,7 @@ import type { ComponentProps, FC } from 'react'; import Link from '#site/components/Link'; import type { AuthorProps } from '#site/types'; -import { getAuthors } from '#site/util/authorUtils'; +import { getAuthors } from '#site/util/author'; type WithAvatarGroupProps = Omit< ComponentProps, diff --git a/apps/site/components/withBadgeGroup.tsx b/apps/site/components/withBadgeGroup.tsx index ceda7771adff4..069e762bf48bc 100644 --- a/apps/site/components/withBadgeGroup.tsx +++ b/apps/site/components/withBadgeGroup.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react'; import Link from '#site/components/Link'; import { siteConfig } from '#site/next.json.mjs'; -import { dateIsBetween } from '#site/util/dateUtils'; +import { dateIsBetween } from '#site/util/date'; const WithBadgeGroup: FC<{ section: string }> = ({ section }) => { const badge = siteConfig.websiteBadges[section]; diff --git a/apps/site/components/withBanner.tsx b/apps/site/components/withBanner.tsx index 09f05065959d4..169ddd5598f4f 100644 --- a/apps/site/components/withBanner.tsx +++ b/apps/site/components/withBanner.tsx @@ -4,7 +4,7 @@ import type { FC } from 'react'; import Link from '#site/components/Link'; import { siteConfig } from '#site/next.json.mjs'; -import { dateIsBetween } from '#site/util/dateUtils'; +import { dateIsBetween } from '#site/util/date'; const WithBanner: FC<{ section: string }> = ({ section }) => { const banner = siteConfig.websiteBanners[section]; diff --git a/apps/site/components/withBlogCategories.tsx b/apps/site/components/withBlogCategories.tsx index c011a6b60b618..d007d1673893c 100644 --- a/apps/site/components/withBlogCategories.tsx +++ b/apps/site/components/withBlogCategories.tsx @@ -5,7 +5,7 @@ import BlogPostCard from '#site/components/Blog/BlogPostCard'; import LinkTabs from '#site/components/Common/LinkTabs'; import Pagination from '#site/components/Common/Pagination'; import type { BlogPostsRSC } from '#site/types'; -import { mapAuthorToCardAuthors } from '#site/util/authorUtils'; +import { mapAuthorToCardAuthors } from '#site/util/author'; type WithBlogCategoriesProps = { categories: ComponentProps['tabs']; diff --git a/apps/site/components/withBreadcrumbs.tsx b/apps/site/components/withBreadcrumbs.tsx index a0fcc4f28792a..2d342a2b6803a 100644 --- a/apps/site/components/withBreadcrumbs.tsx +++ b/apps/site/components/withBreadcrumbs.tsx @@ -12,7 +12,7 @@ import { useSiteNavigation, } from '#site/hooks'; import type { NavigationKeys } from '#site/types'; -import { dashToCamelCase } from '#site/util/stringUtils'; +import { dashToCamelCase } from '#site/util/string'; type WithBreadcrumbsProps = { navKeys?: Array; diff --git a/apps/site/components/withMetaBar.tsx b/apps/site/components/withMetaBar.tsx index a7dd313ad8167..b7c5348112beb 100644 --- a/apps/site/components/withMetaBar.tsx +++ b/apps/site/components/withMetaBar.tsx @@ -12,7 +12,7 @@ import useMediaQuery from '#site/hooks/react-client/useMediaQuery'; import { DEFAULT_DATE_FORMAT } from '#site/next.calendar.constants.mjs'; import { TRANSLATION_URL } from '#site/next.constants.mjs'; import { defaultLocale } from '#site/next.locales.mjs'; -import { getGitHubBlobUrl } from '#site/util/gitHubUtils'; +import { getGitHubBlobUrl } from '#site/util/github'; const WithMetaBar: FC = () => { const { headings, readingTime, frontmatter, filename } = useClientContext(); diff --git a/apps/site/hooks/react-client/useDetectOS.ts b/apps/site/hooks/react-client/useDetectOS.ts index e751738bd1c61..53910091b81e1 100644 --- a/apps/site/hooks/react-client/useDetectOS.ts +++ b/apps/site/hooks/react-client/useDetectOS.ts @@ -3,8 +3,7 @@ import { useEffect, useState } from 'react'; import type { UserArchitecture, UserBitness, UserOS } from '#site/types/userOS'; -import { detectOS } from '#site/util/detectOS'; -import { getHighEntropyValues } from '#site/util/getHighEntropyValues'; +import { getHighEntropyValues, detectOS } from '#site/util/ua'; type UserOSState = { os: UserOS | 'LOADING'; diff --git a/apps/site/hooks/react-client/useNavigationState.ts b/apps/site/hooks/react-client/useNavigationState.ts index ab3e420d6e668..d04afa17769de 100644 --- a/apps/site/hooks/react-client/useNavigationState.ts +++ b/apps/site/hooks/react-client/useNavigationState.ts @@ -4,7 +4,7 @@ import type { RefObject } from 'react'; import { useContext, useEffect } from 'react'; import { NavigationStateContext } from '#site/providers/navigationStateProvider'; -import { debounce } from '#site/util/debounce'; +import { debounce } from '#site/util/misc'; const useNavigationState = ( id: string, diff --git a/apps/site/i18n.tsx b/apps/site/i18n.tsx index 3d899b06f6755..13c8004142c31 100644 --- a/apps/site/i18n.tsx +++ b/apps/site/i18n.tsx @@ -4,7 +4,7 @@ import { getRequestConfig } from 'next-intl/server'; import { availableLocaleCodes, defaultLocale } from '#site/next.locales.mjs'; -import deepMerge from './util/deepMerge'; +import { deepMerge } from './util/misc'; // Loads the Application Locales/Translations Dynamically const loadLocaleDictionary = async (locale: string) => { diff --git a/apps/site/layouts/Post.tsx b/apps/site/layouts/Post.tsx index a614fc27a36bd..9b5234b727757 100644 --- a/apps/site/layouts/Post.tsx +++ b/apps/site/layouts/Post.tsx @@ -7,8 +7,8 @@ import WithFooter from '#site/components/withFooter'; import WithMetaBar from '#site/components/withMetaBar'; import WithNavBar from '#site/components/withNavBar'; import { useClientContext } from '#site/hooks/react-server'; -import { mapAuthorToCardAuthors } from '#site/util/authorUtils'; -import { mapBlogCategoryToPreviewType } from '#site/util/blogUtils'; +import { mapAuthorToCardAuthors } from '#site/util/author'; +import { mapBlogCategoryToPreviewType } from '#site/util/blog'; import styles from './layouts.module.css'; diff --git a/apps/site/next.mdx.compiler.mjs b/apps/site/next.mdx.compiler.mjs index 009c25f067e74..987e6c8803c7d 100644 --- a/apps/site/next.mdx.compiler.mjs +++ b/apps/site/next.mdx.compiler.mjs @@ -6,7 +6,7 @@ import { matter } from 'vfile-matter'; import { createSval } from './next.jsx.compiler.mjs'; import { REHYPE_PLUGINS, REMARK_PLUGINS } from './next.mdx.plugins.mjs'; -import { createGitHubSlugger } from './util/gitHubUtils'; +import { createGitHubSlugger } from './util/github'; // Defines a JSX Fragment and JSX Runtime for the MDX Compiler export const reactRuntime = { Fragment, jsx, jsxs }; diff --git a/apps/site/providers/matterProvider.tsx b/apps/site/providers/matterProvider.tsx index 1e683d3e9d974..38b032c2ca3a1 100644 --- a/apps/site/providers/matterProvider.tsx +++ b/apps/site/providers/matterProvider.tsx @@ -5,7 +5,7 @@ import type { FC, PropsWithChildren } from 'react'; import { useDetectOS } from '#site/hooks'; import type { ClientSharedServerContext } from '#site/types'; -import { assignClientContext } from '#site/util/assignClientContext'; +import { assignClientContext } from '#site/util/context'; export const MatterContext = createContext( assignClientContext({}) diff --git a/apps/site/types/userOS.ts b/apps/site/types/userOS.ts index 76bd462f345e5..ef882d07f90b6 100644 --- a/apps/site/types/userOS.ts +++ b/apps/site/types/userOS.ts @@ -1,4 +1,4 @@ -import type constants from '../util/downloadUtils/constants.json'; +import type constants from '../util/download/constants.json'; // Extract OS key type from the systems object export type UserOS = keyof typeof constants.systems; diff --git a/apps/site/util/__tests__/getNodeApiLink.test.mjs b/apps/site/util/__tests__/api.test.mjs similarity index 96% rename from apps/site/util/__tests__/getNodeApiLink.test.mjs rename to apps/site/util/__tests__/api.test.mjs index 1987405e325c3..378312b698e04 100644 --- a/apps/site/util/__tests__/getNodeApiLink.test.mjs +++ b/apps/site/util/__tests__/api.test.mjs @@ -1,7 +1,7 @@ import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; -import { getNodeApiLink } from '#site/util/getNodeApiLink'; +import { getNodeApiLink } from '#site/util/api'; describe('getNodeApiLink', () => { it('should return the correct API link for versions >=0.3.1 and <0.5.1', () => { diff --git a/apps/site/util/__tests__/authorUtils.test.mjs b/apps/site/util/__tests__/author.test.mjs similarity index 99% rename from apps/site/util/__tests__/authorUtils.test.mjs rename to apps/site/util/__tests__/author.test.mjs index 6a26617258a4d..04497a32ef3f2 100644 --- a/apps/site/util/__tests__/authorUtils.test.mjs +++ b/apps/site/util/__tests__/author.test.mjs @@ -6,7 +6,7 @@ import { getAuthorWithId, getAuthorWithName, getAuthors, -} from '#site/util/authorUtils'; +} from '#site/util/author'; describe('mapAuthorToCardAuthors', () => { it('maps authors to card authors with default avatar source', () => { diff --git a/apps/site/util/__tests__/blogUtils.test.mjs b/apps/site/util/__tests__/blog.test.mjs similarity index 88% rename from apps/site/util/__tests__/blogUtils.test.mjs rename to apps/site/util/__tests__/blog.test.mjs index 7142de43505fd..b68301c3ce300 100644 --- a/apps/site/util/__tests__/blogUtils.test.mjs +++ b/apps/site/util/__tests__/blog.test.mjs @@ -1,7 +1,7 @@ import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; -import { mapBlogCategoryToPreviewType } from '#site/util/blogUtils'; +import { mapBlogCategoryToPreviewType } from '#site/util/blog'; describe('mapBlogCategoryToPreviewType', () => { it('returns the correct preview type for recognized categories', () => { diff --git a/apps/site/util/__tests__/assignClientContext.test.mjs b/apps/site/util/__tests__/context.test.mjs similarity index 94% rename from apps/site/util/__tests__/assignClientContext.test.mjs rename to apps/site/util/__tests__/context.test.mjs index 5768d13cb8c3b..22b00b0cc2dd7 100644 --- a/apps/site/util/__tests__/assignClientContext.test.mjs +++ b/apps/site/util/__tests__/context.test.mjs @@ -1,7 +1,7 @@ import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; -import { assignClientContext } from '#site/util/assignClientContext'; +import { assignClientContext } from '#site/util/context'; const mockContext = { frontmatter: { title: 'Sample Title' }, diff --git a/apps/site/util/__tests__/dateUtils.test.mjs b/apps/site/util/__tests__/date.test.mjs similarity index 96% rename from apps/site/util/__tests__/dateUtils.test.mjs rename to apps/site/util/__tests__/date.test.mjs index f9dfe49d738da..804197aa76f75 100644 --- a/apps/site/util/__tests__/dateUtils.test.mjs +++ b/apps/site/util/__tests__/date.test.mjs @@ -1,7 +1,7 @@ import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; -import { dateIsBetween } from '#site/util/dateUtils'; +import { dateIsBetween } from '#site/util/date'; describe('dateIsBetween', () => { it('should return true when the current date is between start and end dates', () => { diff --git a/apps/site/util/__tests__/deepMerge.test.mjs b/apps/site/util/__tests__/deepMerge.test.mjs deleted file mode 100644 index c41a2d421e979..0000000000000 --- a/apps/site/util/__tests__/deepMerge.test.mjs +++ /dev/null @@ -1,20 +0,0 @@ -import assert from 'node:assert/strict'; -import { describe, it } from 'node:test'; - -import deepMerge from '#site/util/deepMerge'; - -describe('deepMerge', () => { - it('should merge nested objects', () => { - const obj1 = { a: { b: 1 }, c: 2 }; - const obj2 = { a: { d: 3 }, e: 4 }; - const result = deepMerge(obj1, obj2); - assert.deepEqual(result, { a: { b: 1, d: 3 }, c: 2, e: 4 }); - }); - - it('should overwrite primitive properties', () => { - const obj1 = { a: 1 }; - const obj2 = { a: 2 }; - const result = deepMerge(obj1, obj2); - assert.deepEqual(result, { a: 2 }); - }); -}); diff --git a/apps/site/util/__tests__/downloadUtils.test.mjs b/apps/site/util/__tests__/download.test.mjs similarity index 69% rename from apps/site/util/__tests__/downloadUtils.test.mjs rename to apps/site/util/__tests__/download.test.mjs index 1f1e9a8bbb502..932966838f8d5 100644 --- a/apps/site/util/__tests__/downloadUtils.test.mjs +++ b/apps/site/util/__tests__/download.test.mjs @@ -8,7 +8,10 @@ import { INSTALL_METHODS, PACKAGE_MANAGERS, PLATFORMS, -} from '#site/util/downloadUtils'; + getNodeDownloadUrl, +} from '#site/util/download'; + +const version = 'v18.16.0'; describe('parseCompat', () => { it('should handle all OS, install methods, and package managers', () => { @@ -144,3 +147,53 @@ describe('nextItem', () => { assert.equal(nextItem('valid', items), 'valid'); }); }); + +describe('getNodeDownloadUrl', () => { + it('should return 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'; + + assert.equal(getNodeDownloadUrl(version, os, bitness), expectedUrl); + }); + + it('should return the correct download URL for Windows (32-bit)', () => { + const os = 'WIN'; + const bitness = 86; + const expectedUrl = + 'https://nodejs.org/dist/v18.16.0/node-v18.16.0-x86.msi'; + + assert.equal(getNodeDownloadUrl(version, os, bitness), expectedUrl); + }); + + it('should return the correct download URL for Windows (64-bit)', () => { + const os = 'WIN'; + const bitness = 64; + const expectedUrl = + 'https://nodejs.org/dist/v18.16.0/node-v18.16.0-x64.msi'; + + assert.equal(getNodeDownloadUrl(version, os, bitness), expectedUrl); + }); + + it('should return the default download URL for other operating systems', () => { + const os = 'OTHER'; + const bitness = 86; + const expectedUrl = 'https://nodejs.org/dist/v18.16.0/node-v18.16.0.tar.gz'; + + assert.equal(getNodeDownloadUrl(version, os, bitness), expectedUrl); + }); + + describe('MAC', () => { + it('should return .pkg link for installer', () => { + const url = getNodeDownloadUrl('v18.0.0', 'MAC', 'x64', 'installer'); + assert.ok(url.includes('.pkg')); + }); + }); + + describe('WIN', () => { + it('should return an MSI link for installer', () => { + const url = getNodeDownloadUrl('v18.0.0', 'WIN', 'x64', 'installer'); + assert.ok(url.includes('.msi')); + }); + }); +}); diff --git a/apps/site/util/__tests__/getHighEntropyValues.test.mjs b/apps/site/util/__tests__/getHighEntropyValues.test.mjs deleted file mode 100644 index 6d789d3092a31..0000000000000 --- a/apps/site/util/__tests__/getHighEntropyValues.test.mjs +++ /dev/null @@ -1,46 +0,0 @@ -import assert from 'node:assert/strict'; -import { describe, it, beforeEach } from 'node:test'; - -import { getHighEntropyValues } from '#site/util/getHighEntropyValues'; - -const mock = () => Promise.resolve({ platform: 'Win32', architecture: 'x86' }); - -describe('getHighEntropyValues', () => { - beforeEach(() => { - Object.defineProperty(global, 'navigator', { - value: { - userAgentData: { - getHighEntropyValues: mock, - }, - }, - configurable: true, - }); - }); - - it('should resolve and return hint values', async () => { - const hints = ['platform']; - const result = await getHighEntropyValues(hints); - assert.equal(result.platform, 'Win32'); - }); - - it('should return an empty object on rejection', async () => { - navigator.userAgentData.getHighEntropyValues = () => Promise.resolve({}); - const hints = ['platform']; - const result = await getHighEntropyValues(hints); - assert.equal(result.platform, undefined); - navigator.userAgentData.getHighEntropyValues = mock; - }); - - it('should return multiple hint values', async () => { - const hints = ['platform', 'architecture']; - const result = await getHighEntropyValues(hints); - assert.equal(result.platform, 'Win32'); - assert.equal(result.architecture, 'x86'); - }); - - it('should return undefined for unsupported hints', async () => { - const hints = ['unsupportedHint']; - const result = await getHighEntropyValues(hints); - assert.equal(result.unsupportedHint, undefined); - }); -}); diff --git a/apps/site/util/__tests__/getNodeDownloadUrl.test.mjs b/apps/site/util/__tests__/getNodeDownloadUrl.test.mjs deleted file mode 100644 index 8ec40abda7fb2..0000000000000 --- a/apps/site/util/__tests__/getNodeDownloadUrl.test.mjs +++ /dev/null @@ -1,56 +0,0 @@ -import assert from 'node:assert/strict'; -import { describe, it } from 'node:test'; - -import { getNodeDownloadUrl } from '#site/util/getNodeDownloadUrl'; - -const version = 'v18.16.0'; - -describe('getNodeDownloadUrl', () => { - it('should return 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'; - - assert.equal(getNodeDownloadUrl(version, os, bitness), expectedUrl); - }); - - it('should return the correct download URL for Windows (32-bit)', () => { - const os = 'WIN'; - const bitness = 86; - const expectedUrl = - 'https://nodejs.org/dist/v18.16.0/node-v18.16.0-x86.msi'; - - assert.equal(getNodeDownloadUrl(version, os, bitness), expectedUrl); - }); - - it('should return the correct download URL for Windows (64-bit)', () => { - const os = 'WIN'; - const bitness = 64; - const expectedUrl = - 'https://nodejs.org/dist/v18.16.0/node-v18.16.0-x64.msi'; - - assert.equal(getNodeDownloadUrl(version, os, bitness), expectedUrl); - }); - - it('should return the default download URL for other operating systems', () => { - const os = 'OTHER'; - const bitness = 86; - const expectedUrl = 'https://nodejs.org/dist/v18.16.0/node-v18.16.0.tar.gz'; - - assert.equal(getNodeDownloadUrl(version, os, bitness), expectedUrl); - }); - - describe('MAC', () => { - it('should return .pkg link for installer', () => { - const url = getNodeDownloadUrl('v18.0.0', 'MAC', 'x64', 'installer'); - assert.ok(url.includes('.pkg')); - }); - }); - - describe('WIN', () => { - it('should return an MSI link for installer', () => { - const url = getNodeDownloadUrl('v18.0.0', 'WIN', 'x64', 'installer'); - assert.ok(url.includes('.msi')); - }); - }); -}); diff --git a/apps/site/util/__tests__/getUserPlatform.test.mjs b/apps/site/util/__tests__/getUserPlatform.test.mjs deleted file mode 100644 index 15a0ac90958d1..0000000000000 --- a/apps/site/util/__tests__/getUserPlatform.test.mjs +++ /dev/null @@ -1,18 +0,0 @@ -import assert from 'node:assert/strict'; -import { describe, it } from 'node:test'; - -import { getUserPlatform } from '#site/util/getUserPlatform'; - -describe('getUserPlatform', () => { - it('should return arm64 for arm + 64', () => { - assert.equal(getUserPlatform('arm', '64'), 'arm64'); - }); - - it('should return x64 for non-arm + 64', () => { - assert.equal(getUserPlatform('amd64', '64'), 'x64'); - }); - - it('should return x86 otherwise', () => { - assert.equal(getUserPlatform('amd64', '32'), 'x86'); - }); -}); diff --git a/apps/site/util/__tests__/gitHubUtils.test.mjs b/apps/site/util/__tests__/github.test.mjs similarity index 97% rename from apps/site/util/__tests__/gitHubUtils.test.mjs rename to apps/site/util/__tests__/github.test.mjs index 5afc3e64b8c6a..03f20de2d4372 100644 --- a/apps/site/util/__tests__/gitHubUtils.test.mjs +++ b/apps/site/util/__tests__/github.test.mjs @@ -10,7 +10,7 @@ const { createGitHubSlugger, getGitHubBlobUrl, getGitHubApiDocsUrl, -} = await import('#site/util/gitHubUtils'); +} = await import('#site/util/github'); describe('gitHubUtils', () => { it('getGitHubAvatarUrl returns the correct URL', () => { diff --git a/apps/site/util/__tests__/hexToRGBA.test.mjs b/apps/site/util/__tests__/hexToRGBA.test.mjs deleted file mode 100644 index 94bdd29fa4f83..0000000000000 --- a/apps/site/util/__tests__/hexToRGBA.test.mjs +++ /dev/null @@ -1,28 +0,0 @@ -import assert from 'node:assert/strict'; -import { describe, it } from 'node:test'; - -import { hexToRGBA } from '#site/util/hexToRGBA'; - -describe('hexToRGBA', () => { - it('should convert a hex color to an rgba color', () => { - const hexColor = '#000000'; - const rgbaColor = hexToRGBA(hexColor, 0.5); - - assert.equal(rgbaColor, 'rgba(0, 0, 0, 0.5)'); - }); - - it('should convert a hex color to an rgba color', () => { - const hexColor = '#ffffff'; - const rgbaColor = hexToRGBA(hexColor, 0.5); - - assert.equal(rgbaColor, 'rgba(255, 255, 255, 0.5)'); - }); - - it('should convert hex to RGBA correctly', () => { - assert.equal(hexToRGBA('#FFFFFF'), 'rgba(255, 255, 255, 0.9)'); - }); - - it('should handle custom alpha value', () => { - assert.equal(hexToRGBA('#000000', 0.5), 'rgba(0, 0, 0, 0.5)'); - }); -}); diff --git a/apps/site/util/__tests__/debounce.test.mjs b/apps/site/util/__tests__/misc.test.mjs similarity index 65% rename from apps/site/util/__tests__/debounce.test.mjs rename to apps/site/util/__tests__/misc.test.mjs index 0632a3c153f2b..dc5aa6e55be28 100644 --- a/apps/site/util/__tests__/debounce.test.mjs +++ b/apps/site/util/__tests__/misc.test.mjs @@ -1,7 +1,23 @@ import assert from 'node:assert/strict'; import { describe, it, beforeEach } from 'node:test'; -import { debounce } from '#site/util/debounce'; +import { deepMerge, debounce } from '#site/util/misc'; + +describe('deepMerge', () => { + it('should merge nested objects', () => { + const obj1 = { a: { b: 1 }, c: 2 }; + const obj2 = { a: { d: 3 }, e: 4 }; + const result = deepMerge(obj1, obj2); + assert.deepEqual(result, { a: { b: 1, d: 3 }, c: 2, e: 4 }); + }); + + it('should overwrite primitive properties', () => { + const obj1 = { a: 1 }; + const obj2 = { a: 2 }; + const result = deepMerge(obj1, obj2); + assert.deepEqual(result, { a: 2 }); + }); +}); describe('debounce', () => { beforeEach(t => { diff --git a/apps/site/util/__tests__/stringUtils.test.mjs b/apps/site/util/__tests__/string.test.mjs similarity index 99% rename from apps/site/util/__tests__/stringUtils.test.mjs rename to apps/site/util/__tests__/string.test.mjs index 23429d185a32a..c4d95f125a9b8 100644 --- a/apps/site/util/__tests__/stringUtils.test.mjs +++ b/apps/site/util/__tests__/string.test.mjs @@ -5,7 +5,7 @@ import { getAcronymFromString, parseRichTextIntoPlainText, dashToCamelCase, -} from '#site/util/stringUtils'; +} from '#site/util/string'; describe('String utils', () => { it('getAcronymFromString returns the correct acronym', () => { diff --git a/apps/site/util/__tests__/detectOS.test.mjs b/apps/site/util/__tests__/ua.test.mjs similarity index 53% rename from apps/site/util/__tests__/detectOS.test.mjs rename to apps/site/util/__tests__/ua.test.mjs index c056140aa714a..5d3d2e6e5f7bf 100644 --- a/apps/site/util/__tests__/detectOS.test.mjs +++ b/apps/site/util/__tests__/ua.test.mjs @@ -1,7 +1,12 @@ import assert from 'node:assert/strict'; -import { describe, it } from 'node:test'; +import { describe, it, beforeEach } from 'node:test'; -import { detectOsInUserAgent, detectOS } from '#site/util/detectOS'; +import { + detectOS, + detectOsInUserAgent, + getHighEntropyValues, + getUserPlatform, +} from '#site/util/ua'; const userAgentTestCases = [ [ @@ -29,6 +34,22 @@ const userAgentTestCases = [ [undefined, 'OTHER'], ]; +const mock = () => Promise.resolve({ platform: 'Win32', architecture: 'x86' }); + +describe('getUserPlatform', () => { + it('should return arm64 for arm + 64', () => { + assert.equal(getUserPlatform('arm', '64'), 'arm64'); + }); + + it('should return x64 for non-arm + 64', () => { + assert.equal(getUserPlatform('amd64', '64'), 'x64'); + }); + + it('should return x86 otherwise', () => { + assert.equal(getUserPlatform('amd64', '32'), 'x86'); + }); +}); + describe('detectOsInUserAgent', () => { for (const [userAgent, expected] of userAgentTestCases) { it(`should return ${expected} for userAgent ${userAgent}`, () => { @@ -69,3 +90,39 @@ describe('detectOS', () => { assert.equal(detectOS(), 'OTHER'); }); }); + +describe('getHighEntropyValues', () => { + beforeEach(() => { + Object.defineProperty(global, 'navigator', { + value: { + userAgentData: { + getHighEntropyValues: mock, + }, + }, + configurable: true, + }); + }); + + it('should resolve and return hint values', async () => { + const result = await getHighEntropyValues(['platform']); + assert.equal(result.platform, 'Win32'); + }); + + it('should return an empty object on rejection', async () => { + navigator.userAgentData.getHighEntropyValues = () => Promise.resolve({}); + const result = await getHighEntropyValues(['platform']); + assert.equal(result.platform, undefined); + navigator.userAgentData.getHighEntropyValues = mock; + }); + + it('should return multiple hint values', async () => { + const result = await getHighEntropyValues(['platform', 'architecture']); + assert.equal(result.platform, 'Win32'); + assert.equal(result.architecture, 'x86'); + }); + + it('should return undefined for unsupported hints', async () => { + const result = await getHighEntropyValues(['unsupportedHint']); + assert.equal(result.unsupportedHint, undefined); + }); +}); diff --git a/apps/site/util/getNodeApiLink.ts b/apps/site/util/api.ts similarity index 62% rename from apps/site/util/getNodeApiLink.ts rename to apps/site/util/api.ts index ec99cd89c543b..460376b73f860 100644 --- a/apps/site/util/getNodeApiLink.ts +++ b/apps/site/util/api.ts @@ -1,17 +1,17 @@ -import semVer from 'semver'; +import { satisfies } from 'semver'; import { DOCS_URL, DIST_URL } from '#site/next.constants.mjs'; export const getNodeApiLink = (version: string) => { - if (semVer.satisfies(version, '>=0.3.1 <0.5.1')) { + if (satisfies(version, '>=0.3.1 <0.5.1')) { return `${DOCS_URL}${version}/api/`; } - if (semVer.satisfies(version, '>=0.1.14 <0.3.1')) { + if (satisfies(version, '>=0.1.14 <0.3.1')) { return `${DOCS_URL}${version}/api.html`; } - return semVer.satisfies(version, '>=1.0.0 <4.0.0') + return satisfies(version, '>=1.0.0 <4.0.0') ? `https://iojs.org/dist/${version}/docs/api/` : `${DIST_URL}${version}/docs/api/`; }; diff --git a/apps/site/util/authorUtils.ts b/apps/site/util/author.ts similarity index 94% rename from apps/site/util/authorUtils.ts rename to apps/site/util/author.ts index 4a3cccc975ac5..92ab969b5d90d 100644 --- a/apps/site/util/authorUtils.ts +++ b/apps/site/util/author.ts @@ -1,7 +1,7 @@ import { authors } from '#site/next.json.mjs'; import type { AuthorProps } from '#site/types'; -import { getGitHubAvatarUrl } from '#site/util/gitHubUtils'; -import { getAcronymFromString } from '#site/util/stringUtils'; +import { getGitHubAvatarUrl } from '#site/util/github'; +import { getAcronymFromString } from '#site/util/string'; export const mapAuthorToCardAuthors = (author: string) => { // Clears text in parentheses diff --git a/apps/site/util/blogUtils.ts b/apps/site/util/blog.ts similarity index 100% rename from apps/site/util/blogUtils.ts rename to apps/site/util/blog.ts diff --git a/apps/site/util/assignClientContext.ts b/apps/site/util/context.ts similarity index 100% rename from apps/site/util/assignClientContext.ts rename to apps/site/util/context.ts diff --git a/apps/site/util/dateUtils.ts b/apps/site/util/date.ts similarity index 100% rename from apps/site/util/dateUtils.ts rename to apps/site/util/date.ts diff --git a/apps/site/util/debounce.ts b/apps/site/util/debounce.ts deleted file mode 100644 index ca569ba359e97..0000000000000 --- a/apps/site/util/debounce.ts +++ /dev/null @@ -1,16 +0,0 @@ -type DebounceFunction = (...args: Array) => void; - -let timeoutId: NodeJS.Timeout; - -export const debounce = - ( - func: T, - delay: number - ): ((...args: Parameters) => void) => - (...args: Parameters) => { - clearTimeout(timeoutId); - - timeoutId = setTimeout(() => { - func(...args); - }, delay); - }; diff --git a/apps/site/util/detectOS.ts b/apps/site/util/detectOS.ts deleted file mode 100644 index c7769c5237375..0000000000000 --- a/apps/site/util/detectOS.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { UserOS } from '#site/types/userOS'; - -export const detectOsInUserAgent = (userAgent: string | undefined): UserOS => { - // Match OS names and convert to uppercase directly if there's a match - const osMatch = userAgent?.match(/(Win|Mac|Linux|AIX)/); - return osMatch ? (osMatch[1].toUpperCase() as UserOS) : 'OTHER'; -}; - -// Since `navigator.appVersion` is deprecated, we use the `userAgent`` -// https://developer.mozilla.org/en-US/docs/Web/API/Navigator/appVersion -export const detectOS = (): UserOS => detectOsInUserAgent(navigator?.userAgent); diff --git a/apps/site/util/downloadUtils/constants.json b/apps/site/util/download/constants.json similarity index 100% rename from apps/site/util/downloadUtils/constants.json rename to apps/site/util/download/constants.json diff --git a/apps/site/util/downloadUtils/index.tsx b/apps/site/util/download/index.tsx similarity index 63% rename from apps/site/util/downloadUtils/index.tsx rename to apps/site/util/download/index.tsx index b7fa11d3dca8b..8dd0ab8b81b6e 100644 --- a/apps/site/util/downloadUtils/index.tsx +++ b/apps/site/util/download/index.tsx @@ -5,6 +5,7 @@ import * as PackageManagerIcons from '@node-core/ui-components/Icons/PackageMana import type { ElementType } from 'react'; import satisfies from 'semver/functions/satisfies'; +import { DIST_URL } from '#site/next.constants.mjs'; import type { IntlMessageKeys, NodeReleaseStatus } from '#site/types'; import type * as Types from '#site/types/release'; import type { UserOS, UserPlatform } from '#site/types/userOS'; @@ -149,3 +150,76 @@ export const PLATFORMS = Object.fromEntries( })), ]) ) as Record>>; + +export type DownloadKind = 'installer' | 'binary' | 'source'; + +export const getNodeDownloadUrl = ( + versionWithPrefix: string, + os: UserOS | 'LOADING', + platform: UserPlatform = 'x64', + kind: DownloadKind = 'installer' +) => { + const baseURL = `${DIST_URL}${versionWithPrefix}`; + + if (kind === 'source') { + return `${baseURL}/node-${versionWithPrefix}.tar.gz`; + } + + switch (os) { + case 'MAC': + // Prepares a downloadable Node.js installer link for the x64, ARM64 platforms + if (kind === 'installer') { + return `${baseURL}/node-${versionWithPrefix}.pkg`; + } + + // Prepares a downloadable Node.js link for the ARM64 platform + if (typeof platform === 'string') { + return `${baseURL}/node-${versionWithPrefix}-darwin-${platform}.tar.gz`; + } + + // Prepares a downloadable Node.js link for the x64 platform. + // Since the x86 platform is not officially supported, returns the x64 + // link as the default value. + return `${baseURL}/node-${versionWithPrefix}-darwin-x64.tar.gz`; + case 'WIN': { + if (kind === 'installer') { + // Prepares a downloadable Node.js installer link for the ARM platforms + if (typeof platform === 'string') { + return `${baseURL}/node-${versionWithPrefix}-${platform}.msi`; + } + + // Prepares a downloadable Node.js installer link for the x64 and x86 platforms + return `${baseURL}/node-${versionWithPrefix}-x${platform}.msi`; + } + + // Prepares a downloadable Node.js link for the ARM64 platform + if (typeof platform === 'string') { + return `${baseURL}/node-${versionWithPrefix}-win-${platform}.zip`; + } + + // Prepares a downloadable Node.js link for the x64 and x86 platforms + return `${baseURL}/node-${versionWithPrefix}-win-x${platform}.zip`; + } + case 'LINUX': + // Prepares a downloadable Node.js link for the ARM platforms such as + // ARMv7 and ARMv8 + if (typeof platform === 'string') { + return `${baseURL}/node-${versionWithPrefix}-linux-${platform}.tar.xz`; + } + + // Prepares a downloadable Node.js link for the x64 platform. + // Since the x86 platform is not officially supported, returns the x64 + // link as the default value. + return `${baseURL}/node-${versionWithPrefix}-linux-x64.tar.xz`; + case 'AIX': + // Prepares a downloadable Node.js link for AIX + if (typeof platform === 'string') { + return `${baseURL}/node-${versionWithPrefix}-aix-${platform}.tar.gz`; + } + + return `${baseURL}/node-${versionWithPrefix}-aix-ppc64.tar.gz`; + default: + // Prepares a downloadable Node.js source code link + return `${baseURL}/node-${versionWithPrefix}.tar.gz`; + } +}; diff --git a/apps/site/util/getHighEntropyValues.ts b/apps/site/util/getHighEntropyValues.ts deleted file mode 100644 index 8a066490bb666..0000000000000 --- a/apps/site/util/getHighEntropyValues.ts +++ /dev/null @@ -1,33 +0,0 @@ -/// - -// This method is used to retrieve a User's platform based on their architecture and bitness. -// @see https://wicg.github.io/ua-client-hints/#http-ua-hints -export const getHighEntropyValues = async >( - hints: T -) => { - let result: UADataValues = {}; - - // This is necessary to detect Windows 11 on Edge. - // [MDN](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorUAData/getHighEntropyValues) - // [MSFT](https://learn.microsoft.com/en-us/microsoft-edge/web-platform/how-to-detect-win11) - if (typeof navigator?.userAgentData?.getHighEntropyValues === 'function') { - const entropyValues = navigator.userAgentData.getHighEntropyValues(hints); - - // Apparently in some cases this is not a Promise, we can Promisify it. - result = await Promise.resolve(entropyValues).catch( - // Fallback to an empty object if the Promise fails. - () => ({}) as UADataValues - ); - } - - const mappedResult = hints.map(hint => [ - hint, - // Since the values could come as empty string in some situations - // we should check if the hint is present in the result and if it's not an empty string. - hint in result && result[hint] ? result[hint] : undefined, - ]); - - return Object.fromEntries(mappedResult) as { - [K in T[number]]: UADataValues[K]; - }; -}; diff --git a/apps/site/util/getNodeDownloadUrl.ts b/apps/site/util/getNodeDownloadUrl.ts deleted file mode 100644 index d5f2a8ef0c44d..0000000000000 --- a/apps/site/util/getNodeDownloadUrl.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { DIST_URL } from '#site/next.constants.mjs'; -import type { UserOS, UserPlatform } from '#site/types/userOS'; - -export type DownloadKind = 'installer' | 'binary' | 'source'; - -export const getNodeDownloadUrl = ( - versionWithPrefix: string, - os: UserOS | 'LOADING', - platform: UserPlatform = 'x64', - kind: DownloadKind = 'installer' -) => { - const baseURL = `${DIST_URL}${versionWithPrefix}`; - - if (kind === 'source') { - return `${baseURL}/node-${versionWithPrefix}.tar.gz`; - } - - switch (os) { - case 'MAC': - // Prepares a downloadable Node.js installer link for the x64, ARM64 platforms - if (kind === 'installer') { - return `${baseURL}/node-${versionWithPrefix}.pkg`; - } - - // Prepares a downloadable Node.js link for the ARM64 platform - if (typeof platform === 'string') { - return `${baseURL}/node-${versionWithPrefix}-darwin-${platform}.tar.gz`; - } - - // Prepares a downloadable Node.js link for the x64 platform. - // Since the x86 platform is not officially supported, returns the x64 - // link as the default value. - return `${baseURL}/node-${versionWithPrefix}-darwin-x64.tar.gz`; - case 'WIN': { - if (kind === 'installer') { - // Prepares a downloadable Node.js installer link for the ARM platforms - if (typeof platform === 'string') { - return `${baseURL}/node-${versionWithPrefix}-${platform}.msi`; - } - - // Prepares a downloadable Node.js installer link for the x64 and x86 platforms - return `${baseURL}/node-${versionWithPrefix}-x${platform}.msi`; - } - - // Prepares a downloadable Node.js link for the ARM64 platform - if (typeof platform === 'string') { - return `${baseURL}/node-${versionWithPrefix}-win-${platform}.zip`; - } - - // Prepares a downloadable Node.js link for the x64 and x86 platforms - return `${baseURL}/node-${versionWithPrefix}-win-x${platform}.zip`; - } - case 'LINUX': - // Prepares a downloadable Node.js link for the ARM platforms such as - // ARMv7 and ARMv8 - if (typeof platform === 'string') { - return `${baseURL}/node-${versionWithPrefix}-linux-${platform}.tar.xz`; - } - - // Prepares a downloadable Node.js link for the x64 platform. - // Since the x86 platform is not officially supported, returns the x64 - // link as the default value. - return `${baseURL}/node-${versionWithPrefix}-linux-x64.tar.xz`; - case 'AIX': - // Prepares a downloadable Node.js link for AIX - if (typeof platform === 'string') { - return `${baseURL}/node-${versionWithPrefix}-aix-${platform}.tar.gz`; - } - - return `${baseURL}/node-${versionWithPrefix}-aix-ppc64.tar.gz`; - default: - // Prepares a downloadable Node.js source code link - return `${baseURL}/node-${versionWithPrefix}.tar.gz`; - } -}; diff --git a/apps/site/util/getUserPlatform.ts b/apps/site/util/getUserPlatform.ts deleted file mode 100644 index 496fe4dec2f24..0000000000000 --- a/apps/site/util/getUserPlatform.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type * as Types from '#site/types/userOS'; - -// This method is used to retrieve a User's platform based on their architecture and bitness. -// Note: This is only used for automatic Platform detection for supported platforms by using `useDetectOS` -// @see https://developer.mozilla.org/en-US/docs/Web/API/NavigatorUAData/getHighEntropyValues -export const getUserPlatform = ( - userArchitecture: Types.UserArchitecture | '', - userBitness: Types.UserBitness | '' -): Types.UserPlatform => { - if (userArchitecture === 'arm' && userBitness === '64') { - return 'arm64'; - } - - return userBitness === '64' ? 'x64' : 'x86'; -}; diff --git a/apps/site/util/gitHubUtils.ts b/apps/site/util/github.ts similarity index 100% rename from apps/site/util/gitHubUtils.ts rename to apps/site/util/github.ts diff --git a/apps/site/util/hexToRGBA.ts b/apps/site/util/hexToRGBA.ts deleted file mode 100644 index fcafe312303f4..0000000000000 --- a/apps/site/util/hexToRGBA.ts +++ /dev/null @@ -1,10 +0,0 @@ -export const hexToRGBA = (hex = '', alpha = 0.9) => { - hex = hex.replace(/^#/, ''); - - const bigint = parseInt(hex, 16); - const r = (bigint >> 16) & 255; - const g = (bigint >> 8) & 255; - const b = bigint & 255; - - return `rgba(${r}, ${g}, ${b}, ${alpha})`; -}; diff --git a/apps/site/util/deepMerge.ts b/apps/site/util/misc.ts similarity index 55% rename from apps/site/util/deepMerge.ts rename to apps/site/util/misc.ts index 751c09a3afe16..1c880e5c99d80 100644 --- a/apps/site/util/deepMerge.ts +++ b/apps/site/util/misc.ts @@ -1,4 +1,21 @@ -export default function deepMerge( +type DebounceFunction = (...args: Array) => void; + +let timeoutId: NodeJS.Timeout; + +export const debounce = + ( + func: T, + delay: number + ): ((...args: Parameters) => void) => + (...args: Parameters) => { + clearTimeout(timeoutId); + + timeoutId = setTimeout(() => { + func(...args); + }, delay); + }; + +export function deepMerge( obj1: Obj1, obj2: Obj2 ): Obj1 & Obj2 { diff --git a/apps/site/util/stringUtils.ts b/apps/site/util/string.ts similarity index 100% rename from apps/site/util/stringUtils.ts rename to apps/site/util/string.ts diff --git a/apps/site/util/ua.ts b/apps/site/util/ua.ts new file mode 100644 index 0000000000000..3b0e3066c4e43 --- /dev/null +++ b/apps/site/util/ua.ts @@ -0,0 +1,81 @@ +/// + +import type * as Types from '#site/types/userOS'; +import type { UserOS } from '#site/types/userOS'; + +// Constants for better maintainability +const OS_PATTERNS = /(Win|Mac|Linux|AIX)/; +const EMPTY_UA_DATA: UADataValues = {}; + +/** + * Detects operating system from user agent string + * @param userAgent - The user agent string to parse + * @returns The detected OS or 'OTHER' if not recognized + */ +export const detectOsInUserAgent = (userAgent: string | undefined): UserOS => { + const osMatch = userAgent?.match(OS_PATTERNS); + return osMatch ? (osMatch[1].toUpperCase() as UserOS) : 'OTHER'; +}; + +/** + * Detects operating system using navigator.userAgent + * Note: navigator.appVersion is deprecated, so we use userAgent instead + * @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/appVersion + */ +export const detectOS = (): UserOS => detectOsInUserAgent(navigator?.userAgent); + +/** + * Determines user platform based on architecture and bitness + * Used for automatic platform detection with `useDetectOS` + * @see https://developer.mozilla.org/en-US/docs/Web/API/NavigatorUAData/getHighEntropyValues + */ +export const getUserPlatform = ( + userArchitecture: Types.UserArchitecture | '', + userBitness: Types.UserBitness | '' +): Types.UserPlatform => { + if (userArchitecture === 'arm' && userBitness === '64') { + return 'arm64'; + } + + return userBitness === '64' ? 'x64' : 'x86'; +}; + +/** + * Retrieves high entropy values from User-Agent Client Hints API + * This method is used to get detailed user platform information including architecture and bitness + * Necessary for detecting Windows 11 on Edge and other platform-specific features + * + * @param hints - Array of hint keys to retrieve + * @returns Promise resolving to an object with requested hint values + * + * @see https://wicg.github.io/ua-client-hints/#http-ua-hints + * @see https://developer.mozilla.org/en-US/docs/Web/API/NavigatorUAData/getHighEntropyValues + * @see https://learn.microsoft.com/en-us/microsoft-edge/web-platform/how-to-detect-win11 + */ +export const getHighEntropyValues = async >( + hints: T +): Promise<{ [K in T[number]]: UADataValues[K] }> => { + let result: UADataValues = EMPTY_UA_DATA; + + // Check if the User-Agent Client Hints API is available + if (typeof navigator?.userAgentData?.getHighEntropyValues === 'function') { + try { + const entropyValues = navigator.userAgentData.getHighEntropyValues(hints); + // Handle both Promise and non-Promise return values + result = await Promise.resolve(entropyValues); + } catch { + // Fallback to empty object if the API call fails + result = EMPTY_UA_DATA; + } + } + + // Map hints to their values, filtering out empty strings + const mappedResult = hints.map(hint => [ + hint, + hint in result && result[hint] ? result[hint] : undefined, + ]); + + return Object.fromEntries(mappedResult) as { + [K in T[number]]: UADataValues[K]; + }; +}; From da2b58a6edb34171eb4df417a4f10e40fcce23ba Mon Sep 17 00:00:00 2001 From: avivkeller Date: Mon, 9 Jun 2025 08:48:34 -0400 Subject: [PATCH 2/6] ua -> userAgent --- apps/site/components/Downloads/DownloadButton/index.tsx | 2 +- apps/site/components/Downloads/DownloadLink.tsx | 2 +- apps/site/components/Downloads/Release/PlatformDropdown.tsx | 2 +- apps/site/hooks/react-client/useDetectOS.ts | 2 +- apps/site/util/__tests__/{ua.test.mjs => userAgent.test.mjs} | 2 +- apps/site/util/{ua.ts => userAgent.ts} | 0 6 files changed, 5 insertions(+), 5 deletions(-) rename apps/site/util/__tests__/{ua.test.mjs => userAgent.test.mjs} (99%) rename apps/site/util/{ua.ts => userAgent.ts} (100%) diff --git a/apps/site/components/Downloads/DownloadButton/index.tsx b/apps/site/components/Downloads/DownloadButton/index.tsx index 147d25eed6d45..f198f29e9d2d1 100644 --- a/apps/site/components/Downloads/DownloadButton/index.tsx +++ b/apps/site/components/Downloads/DownloadButton/index.tsx @@ -8,7 +8,7 @@ import Button from '#site/components/Common/Button'; import { useClientContext } from '#site/hooks'; import type { NodeRelease } from '#site/types'; import { getNodeDownloadUrl } from '#site/util/download'; -import { getUserPlatform } from '#site/util/ua'; +import { getUserPlatform } from '#site/util/userAgent'; import styles from './index.module.css'; diff --git a/apps/site/components/Downloads/DownloadLink.tsx b/apps/site/components/Downloads/DownloadLink.tsx index eda94096ce0d8..8689f0501ad36 100644 --- a/apps/site/components/Downloads/DownloadLink.tsx +++ b/apps/site/components/Downloads/DownloadLink.tsx @@ -6,7 +6,7 @@ import LinkWithArrow from '#site/components/LinkWithArrow'; import { useClientContext } from '#site/hooks'; import type { NodeRelease } from '#site/types'; import { getNodeDownloadUrl, type DownloadKind } from '#site/util/download'; -import { getUserPlatform } from '#site/util/ua'; +import { getUserPlatform } from '#site/util/userAgent'; type DownloadLinkProps = { release: NodeRelease; kind?: DownloadKind }; diff --git a/apps/site/components/Downloads/Release/PlatformDropdown.tsx b/apps/site/components/Downloads/Release/PlatformDropdown.tsx index 4d746f25dae8a..d74ebe9f0960d 100644 --- a/apps/site/components/Downloads/Release/PlatformDropdown.tsx +++ b/apps/site/components/Downloads/Release/PlatformDropdown.tsx @@ -9,7 +9,7 @@ import { useClientContext } from '#site/hooks'; import { ReleaseContext } from '#site/providers/releaseProvider'; import type { UserPlatform } from '#site/types/userOS'; import { PLATFORMS, nextItem, parseCompat } from '#site/util/download'; -import { getUserPlatform } from '#site/util/ua'; +import { getUserPlatform } from '#site/util/userAgent'; const PlatformDropdown: FC = () => { const { architecture, bitness } = useClientContext(); diff --git a/apps/site/hooks/react-client/useDetectOS.ts b/apps/site/hooks/react-client/useDetectOS.ts index 53910091b81e1..032dab38c98a6 100644 --- a/apps/site/hooks/react-client/useDetectOS.ts +++ b/apps/site/hooks/react-client/useDetectOS.ts @@ -3,7 +3,7 @@ import { useEffect, useState } from 'react'; import type { UserArchitecture, UserBitness, UserOS } from '#site/types/userOS'; -import { getHighEntropyValues, detectOS } from '#site/util/ua'; +import { getHighEntropyValues, detectOS } from '#site/util/userAgent.js'; type UserOSState = { os: UserOS | 'LOADING'; diff --git a/apps/site/util/__tests__/ua.test.mjs b/apps/site/util/__tests__/userAgent.test.mjs similarity index 99% rename from apps/site/util/__tests__/ua.test.mjs rename to apps/site/util/__tests__/userAgent.test.mjs index 5d3d2e6e5f7bf..adfa42d05e285 100644 --- a/apps/site/util/__tests__/ua.test.mjs +++ b/apps/site/util/__tests__/userAgent.test.mjs @@ -6,7 +6,7 @@ import { detectOsInUserAgent, getHighEntropyValues, getUserPlatform, -} from '#site/util/ua'; +} from '#site/util/userAgent'; const userAgentTestCases = [ [ diff --git a/apps/site/util/ua.ts b/apps/site/util/userAgent.ts similarity index 100% rename from apps/site/util/ua.ts rename to apps/site/util/userAgent.ts From 507a474d11f2bc54fcb5ece5f9c8e46489ff9098 Mon Sep 17 00:00:00 2001 From: avivkeller Date: Mon, 9 Jun 2025 08:53:15 -0400 Subject: [PATCH 3/6] fixup! ua -> userAgent --- apps/site/hooks/react-client/useDetectOS.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/site/hooks/react-client/useDetectOS.ts b/apps/site/hooks/react-client/useDetectOS.ts index 032dab38c98a6..ba63eac151b29 100644 --- a/apps/site/hooks/react-client/useDetectOS.ts +++ b/apps/site/hooks/react-client/useDetectOS.ts @@ -3,7 +3,7 @@ import { useEffect, useState } from 'react'; import type { UserArchitecture, UserBitness, UserOS } from '#site/types/userOS'; -import { getHighEntropyValues, detectOS } from '#site/util/userAgent.js'; +import { getHighEntropyValues, detectOS } from '#site/util/userAgent'; type UserOSState = { os: UserOS | 'LOADING'; From 6f152453466c12680e72b5f96d4de2bf2bf03d35 Mon Sep 17 00:00:00 2001 From: avivkeller Date: Sat, 21 Jun 2025 11:28:14 -0400 Subject: [PATCH 4/6] code review --- apps/site/components/Downloads/DownloadLink.tsx | 4 ++-- .../components/Downloads/Release/DownloadLink.tsx | 2 +- .../Downloads/Release/OperatingSystemDropdown.tsx | 2 +- .../Downloads/Release/PlatformDropdown.tsx | 2 +- apps/site/hooks/react-client/useDetectOS.ts | 6 +++++- apps/site/hooks/react-client/useNavigationState.ts | 2 +- apps/site/i18n.tsx | 2 +- apps/site/types/{downloads.ts => download.ts} | 2 ++ apps/site/types/index.ts | 3 ++- apps/site/types/release.ts | 4 ++-- apps/site/types/{userOS.ts => userAgent.ts} | 0 .../__tests__/{misc.test.mjs => objects.test.mjs} | 2 +- apps/site/util/download/index.tsx | 11 +++++++---- apps/site/util/{misc.ts => objects.ts} | 0 apps/site/util/userAgent.ts | 14 +++++++++----- 15 files changed, 35 insertions(+), 21 deletions(-) rename apps/site/types/{downloads.ts => download.ts} (59%) rename apps/site/types/{userOS.ts => userAgent.ts} (100%) rename apps/site/util/__tests__/{misc.test.mjs => objects.test.mjs} (96%) rename apps/site/util/{misc.ts => objects.ts} (100%) diff --git a/apps/site/components/Downloads/DownloadLink.tsx b/apps/site/components/Downloads/DownloadLink.tsx index 8689f0501ad36..e6f51063e3a62 100644 --- a/apps/site/components/Downloads/DownloadLink.tsx +++ b/apps/site/components/Downloads/DownloadLink.tsx @@ -4,8 +4,8 @@ import type { FC, PropsWithChildren } from 'react'; import LinkWithArrow from '#site/components/LinkWithArrow'; import { useClientContext } from '#site/hooks'; -import type { NodeRelease } from '#site/types'; -import { getNodeDownloadUrl, type DownloadKind } from '#site/util/download'; +import type { DownloadKind, NodeRelease } from '#site/types'; +import { getNodeDownloadUrl } from '#site/util/download'; import { getUserPlatform } from '#site/util/userAgent'; type DownloadLinkProps = { release: NodeRelease; kind?: DownloadKind }; diff --git a/apps/site/components/Downloads/Release/DownloadLink.tsx b/apps/site/components/Downloads/Release/DownloadLink.tsx index f09e37da5ade5..6d9e20179f7f1 100644 --- a/apps/site/components/Downloads/Release/DownloadLink.tsx +++ b/apps/site/components/Downloads/Release/DownloadLink.tsx @@ -5,7 +5,7 @@ import { useContext } from 'react'; import DownloadLinkBase from '#site/components/Downloads/DownloadLink'; import { ReleaseContext } from '#site/providers/releaseProvider'; -import type { DownloadKind } from '#site/util/download'; +import type { DownloadKind } from '#site/types/download'; type DownloadLinkProps = { kind?: DownloadKind }; diff --git a/apps/site/components/Downloads/Release/OperatingSystemDropdown.tsx b/apps/site/components/Downloads/Release/OperatingSystemDropdown.tsx index 02be4fd0c5084..d4306b78be5df 100644 --- a/apps/site/components/Downloads/Release/OperatingSystemDropdown.tsx +++ b/apps/site/components/Downloads/Release/OperatingSystemDropdown.tsx @@ -7,7 +7,7 @@ import type { FC } from 'react'; import { useClientContext } from '#site/hooks'; import { ReleaseContext } from '#site/providers/releaseProvider'; -import type { UserOS } from '#site/types/userOS'; +import type { UserOS } from '#site/types/userAgent'; import { nextItem, OPERATING_SYSTEMS, parseCompat } from '#site/util/download'; type OperatingSystemDropdownProps = { exclude?: Array }; diff --git a/apps/site/components/Downloads/Release/PlatformDropdown.tsx b/apps/site/components/Downloads/Release/PlatformDropdown.tsx index d74ebe9f0960d..b30e40de7bf51 100644 --- a/apps/site/components/Downloads/Release/PlatformDropdown.tsx +++ b/apps/site/components/Downloads/Release/PlatformDropdown.tsx @@ -7,7 +7,7 @@ import { useEffect, useContext, useMemo } from 'react'; import { useClientContext } from '#site/hooks'; import { ReleaseContext } from '#site/providers/releaseProvider'; -import type { UserPlatform } from '#site/types/userOS'; +import type { UserPlatform } from '#site/types/userAgent'; import { PLATFORMS, nextItem, parseCompat } from '#site/util/download'; import { getUserPlatform } from '#site/util/userAgent'; diff --git a/apps/site/hooks/react-client/useDetectOS.ts b/apps/site/hooks/react-client/useDetectOS.ts index ba63eac151b29..f6ef5a9a0f8f9 100644 --- a/apps/site/hooks/react-client/useDetectOS.ts +++ b/apps/site/hooks/react-client/useDetectOS.ts @@ -2,7 +2,11 @@ import { useEffect, useState } from 'react'; -import type { UserArchitecture, UserBitness, UserOS } from '#site/types/userOS'; +import type { + UserArchitecture, + UserBitness, + UserOS, +} from '#site/types/userAgent'; import { getHighEntropyValues, detectOS } from '#site/util/userAgent'; type UserOSState = { diff --git a/apps/site/hooks/react-client/useNavigationState.ts b/apps/site/hooks/react-client/useNavigationState.ts index d04afa17769de..dbdb093ac6e25 100644 --- a/apps/site/hooks/react-client/useNavigationState.ts +++ b/apps/site/hooks/react-client/useNavigationState.ts @@ -4,7 +4,7 @@ import type { RefObject } from 'react'; import { useContext, useEffect } from 'react'; import { NavigationStateContext } from '#site/providers/navigationStateProvider'; -import { debounce } from '#site/util/misc'; +import { debounce } from '#site/util/objects'; const useNavigationState = ( id: string, diff --git a/apps/site/i18n.tsx b/apps/site/i18n.tsx index 13c8004142c31..7abd8682746a9 100644 --- a/apps/site/i18n.tsx +++ b/apps/site/i18n.tsx @@ -4,7 +4,7 @@ import { getRequestConfig } from 'next-intl/server'; import { availableLocaleCodes, defaultLocale } from '#site/next.locales.mjs'; -import { deepMerge } from './util/misc'; +import { deepMerge } from './util/objects'; // Loads the Application Locales/Translations Dynamically const loadLocaleDictionary = async (locale: string) => { diff --git a/apps/site/types/downloads.ts b/apps/site/types/download.ts similarity index 59% rename from apps/site/types/downloads.ts rename to apps/site/types/download.ts index 91f4366c26a62..3be983a9466f2 100644 --- a/apps/site/types/downloads.ts +++ b/apps/site/types/download.ts @@ -3,3 +3,5 @@ export interface DownloadSnippet { language: string; content: string; } + +export type DownloadKind = 'installer' | 'binary' | 'source'; diff --git a/apps/site/types/index.ts b/apps/site/types/index.ts index 53eb2fe7fc6fa..38f5767b732db 100644 --- a/apps/site/types/index.ts +++ b/apps/site/types/index.ts @@ -11,4 +11,5 @@ export * from './server'; export * from './github'; export * from './calendar'; export * from './author'; -export * from './downloads'; +export * from './download'; +export * from './userAgent'; diff --git a/apps/site/types/release.ts b/apps/site/types/release.ts index 28f945f6cbd3a..6d8d22ac6dc50 100644 --- a/apps/site/types/release.ts +++ b/apps/site/types/release.ts @@ -1,6 +1,6 @@ -import type { DownloadSnippet } from '#site/types/downloads'; +import type { DownloadSnippet } from '#site/types/download'; import type { NodeRelease } from '#site/types/releases'; -import type { UserOS, UserPlatform } from '#site/types/userOS'; +import type { UserOS, UserPlatform } from '#site/types/userAgent'; export type InstallationMethod = | 'NVM' diff --git a/apps/site/types/userOS.ts b/apps/site/types/userAgent.ts similarity index 100% rename from apps/site/types/userOS.ts rename to apps/site/types/userAgent.ts diff --git a/apps/site/util/__tests__/misc.test.mjs b/apps/site/util/__tests__/objects.test.mjs similarity index 96% rename from apps/site/util/__tests__/misc.test.mjs rename to apps/site/util/__tests__/objects.test.mjs index dc5aa6e55be28..d5309b40d4efd 100644 --- a/apps/site/util/__tests__/misc.test.mjs +++ b/apps/site/util/__tests__/objects.test.mjs @@ -1,7 +1,7 @@ import assert from 'node:assert/strict'; import { describe, it, beforeEach } from 'node:test'; -import { deepMerge, debounce } from '#site/util/misc'; +import { deepMerge, debounce } from '#site/util/objects'; describe('deepMerge', () => { it('should merge nested objects', () => { diff --git a/apps/site/util/download/index.tsx b/apps/site/util/download/index.tsx index 8dd0ab8b81b6e..95553e063e23b 100644 --- a/apps/site/util/download/index.tsx +++ b/apps/site/util/download/index.tsx @@ -6,9 +6,14 @@ import type { ElementType } from 'react'; import satisfies from 'semver/functions/satisfies'; import { DIST_URL } from '#site/next.constants.mjs'; -import type { IntlMessageKeys, NodeReleaseStatus } from '#site/types'; +import type { + IntlMessageKeys, + NodeReleaseStatus, + UserOS, + UserPlatform, + DownloadKind, +} from '#site/types'; import type * as Types from '#site/types/release'; -import type { UserOS, UserPlatform } from '#site/types/userOS'; import constants from './constants.json'; @@ -151,8 +156,6 @@ export const PLATFORMS = Object.fromEntries( ]) ) as Record>>; -export type DownloadKind = 'installer' | 'binary' | 'source'; - export const getNodeDownloadUrl = ( versionWithPrefix: string, os: UserOS | 'LOADING', diff --git a/apps/site/util/misc.ts b/apps/site/util/objects.ts similarity index 100% rename from apps/site/util/misc.ts rename to apps/site/util/objects.ts diff --git a/apps/site/util/userAgent.ts b/apps/site/util/userAgent.ts index 3b0e3066c4e43..89939684da39e 100644 --- a/apps/site/util/userAgent.ts +++ b/apps/site/util/userAgent.ts @@ -1,7 +1,11 @@ /// -import type * as Types from '#site/types/userOS'; -import type { UserOS } from '#site/types/userOS'; +import type { + UserOS, + UserArchitecture, + UserBitness, + UserPlatform, +} from '#site/types'; // Constants for better maintainability const OS_PATTERNS = /(Win|Mac|Linux|AIX)/; @@ -30,9 +34,9 @@ export const detectOS = (): UserOS => detectOsInUserAgent(navigator?.userAgent); * @see https://developer.mozilla.org/en-US/docs/Web/API/NavigatorUAData/getHighEntropyValues */ export const getUserPlatform = ( - userArchitecture: Types.UserArchitecture | '', - userBitness: Types.UserBitness | '' -): Types.UserPlatform => { + userArchitecture: UserArchitecture | '', + userBitness: UserBitness | '' +): UserPlatform => { if (userArchitecture === 'arm' && userBitness === '64') { return 'arm64'; } From a2319fe8b189ff97414513608a44ab3a040bfc98 Mon Sep 17 00:00:00 2001 From: avivkeller Date: Thu, 26 Jun 2025 15:48:19 -0400 Subject: [PATCH 5/6] fixup! code review --- .../next-data/og/[category]/[title]/route.tsx | 4 +- .../Downloads/DownloadButton/index.tsx | 2 +- .../components/Downloads/DownloadLink.tsx | 2 +- .../Downloads/MinorReleasesTable/index.tsx | 4 +- .../Release/PrebuiltDownloadButtons.tsx | 2 +- apps/site/util/__tests__/api.test.mjs | 52 --------- apps/site/util/__tests__/download.test.mjs | 53 --------- apps/site/util/__tests__/url.test.mjs | 104 ++++++++++++++++++ apps/site/util/api.ts | 17 --- apps/site/util/download/index.tsx | 73 ------------ apps/site/util/url.ts | 89 +++++++++++++++ 11 files changed, 200 insertions(+), 202 deletions(-) delete mode 100644 apps/site/util/__tests__/api.test.mjs create mode 100644 apps/site/util/__tests__/url.test.mjs delete mode 100644 apps/site/util/api.ts create mode 100644 apps/site/util/url.ts diff --git a/apps/site/app/[locale]/next-data/og/[category]/[title]/route.tsx b/apps/site/app/[locale]/next-data/og/[category]/[title]/route.tsx index c50a666327574..a521b6d686360 100644 --- a/apps/site/app/[locale]/next-data/og/[category]/[title]/route.tsx +++ b/apps/site/app/[locale]/next-data/og/[category]/[title]/route.tsx @@ -26,12 +26,12 @@ type StaticParams = { export const GET = async (_: Request, props: StaticParams) => { const params = await props.params; - const categoryColor = + const categoryColour = params.category in CATEGORY_TO_THEME_COLOUR_MAP ? CATEGORY_TO_THEME_COLOUR_MAP[params.category] : CATEGORY_TO_THEME_COLOUR_MAP[DEFAULT_CATEGORY_OG_TYPE]; - const gridBackground = `radial-gradient(circle, ${categoryColor}, transparent)`; + const gridBackground = `radial-gradient(circle, ${categoryColour}, transparent)`; return new ImageResponse( ( diff --git a/apps/site/components/Downloads/DownloadButton/index.tsx b/apps/site/components/Downloads/DownloadButton/index.tsx index f198f29e9d2d1..d4f3e7a76dd4c 100644 --- a/apps/site/components/Downloads/DownloadButton/index.tsx +++ b/apps/site/components/Downloads/DownloadButton/index.tsx @@ -7,7 +7,7 @@ import type { FC, PropsWithChildren } from 'react'; import Button from '#site/components/Common/Button'; import { useClientContext } from '#site/hooks'; import type { NodeRelease } from '#site/types'; -import { getNodeDownloadUrl } from '#site/util/download'; +import { getNodeDownloadUrl } from '#site/util/url'; import { getUserPlatform } from '#site/util/userAgent'; import styles from './index.module.css'; diff --git a/apps/site/components/Downloads/DownloadLink.tsx b/apps/site/components/Downloads/DownloadLink.tsx index e6f51063e3a62..905adbf352d8a 100644 --- a/apps/site/components/Downloads/DownloadLink.tsx +++ b/apps/site/components/Downloads/DownloadLink.tsx @@ -5,7 +5,7 @@ import type { FC, PropsWithChildren } from 'react'; import LinkWithArrow from '#site/components/LinkWithArrow'; import { useClientContext } from '#site/hooks'; import type { DownloadKind, NodeRelease } from '#site/types'; -import { getNodeDownloadUrl } from '#site/util/download'; +import { getNodeDownloadUrl } from '#site/util/url'; import { getUserPlatform } from '#site/util/userAgent'; type DownloadLinkProps = { release: NodeRelease; kind?: DownloadKind }; diff --git a/apps/site/components/Downloads/MinorReleasesTable/index.tsx b/apps/site/components/Downloads/MinorReleasesTable/index.tsx index b812f2d150c28..641af7fec0cb8 100644 --- a/apps/site/components/Downloads/MinorReleasesTable/index.tsx +++ b/apps/site/components/Downloads/MinorReleasesTable/index.tsx @@ -7,7 +7,7 @@ import type { FC } from 'react'; import Link from '#site/components/Link'; import { BASE_CHANGELOG_URL } from '#site/next.constants.mjs'; import type { MinorVersion } from '#site/types'; -import { getNodeApiLink } from '#site/util/api'; +import { getNodeApiUrl } from '#site/util/url'; import styles from './index.module.css'; @@ -50,7 +50,7 @@ export const MinorReleasesTable: FC = ({ {t('actions.docs')} diff --git a/apps/site/components/Downloads/Release/PrebuiltDownloadButtons.tsx b/apps/site/components/Downloads/Release/PrebuiltDownloadButtons.tsx index da712e6e2870d..fccff0462d69f 100644 --- a/apps/site/components/Downloads/Release/PrebuiltDownloadButtons.tsx +++ b/apps/site/components/Downloads/Release/PrebuiltDownloadButtons.tsx @@ -11,8 +11,8 @@ import { ReleaseContext } from '#site/providers/releaseProvider'; import { OS_NOT_SUPPORTING_INSTALLERS, OperatingSystemLabel, - getNodeDownloadUrl, } from '#site/util/download'; +import { getNodeDownloadUrl } from '#site/util/url'; // Retrieves the pure extension piece from the input string const getExtension = (input: string) => String(input.split('.').slice(-1)); diff --git a/apps/site/util/__tests__/api.test.mjs b/apps/site/util/__tests__/api.test.mjs deleted file mode 100644 index 378312b698e04..0000000000000 --- a/apps/site/util/__tests__/api.test.mjs +++ /dev/null @@ -1,52 +0,0 @@ -import assert from 'node:assert/strict'; -import { describe, it } from 'node:test'; - -import { getNodeApiLink } from '#site/util/api'; - -describe('getNodeApiLink', () => { - it('should return the correct API link for versions >=0.3.1 and <0.5.1', () => { - const version = '0.4.0'; - const expectedLink = `https://nodejs.org/docs/${version}/api/`; - - const result = getNodeApiLink(version); - - assert.equal(result, expectedLink); - }); - - it('should return the correct URL for versions >=0.3.1 and <0.5.1', () => { - const url = getNodeApiLink('v0.4.10'); - assert.ok(url.includes('/api/')); - }); - - it('should return the correct API link for versions >=0.1.14 and <0.3.1', () => { - const version = '0.2.0'; - const expectedLink = `https://nodejs.org/docs/${version}/api.html`; - - const result = getNodeApiLink(version); - - assert.equal(result, expectedLink); - }); - - it('should return the correct API link for versions >=1.0.0 and <4.0.0', () => { - const version = '2.3.0'; - const expectedLink = `https://iojs.org/dist/${version}/docs/api/`; - - const result = getNodeApiLink(version); - - assert.equal(result, expectedLink); - }); - - it('should form the correct URL for versions >=1.0.0 and <4.0.0', () => { - const url = getNodeApiLink('v1.2.3'); - assert.ok(url.includes('iojs.org/dist/v1.2.3/docs/api/')); - }); - - it('should return the correct API link for other versions', () => { - const version = '5.0.0'; - const expectedLink = `https://nodejs.org/dist/${version}/docs/api/`; - - const result = getNodeApiLink(version); - - assert.equal(result, expectedLink); - }); -}); diff --git a/apps/site/util/__tests__/download.test.mjs b/apps/site/util/__tests__/download.test.mjs index 932966838f8d5..d4bd959de1073 100644 --- a/apps/site/util/__tests__/download.test.mjs +++ b/apps/site/util/__tests__/download.test.mjs @@ -8,11 +8,8 @@ import { INSTALL_METHODS, PACKAGE_MANAGERS, PLATFORMS, - getNodeDownloadUrl, } from '#site/util/download'; -const version = 'v18.16.0'; - describe('parseCompat', () => { it('should handle all OS, install methods, and package managers', () => { OPERATING_SYSTEMS.forEach(os => { @@ -147,53 +144,3 @@ describe('nextItem', () => { assert.equal(nextItem('valid', items), 'valid'); }); }); - -describe('getNodeDownloadUrl', () => { - it('should return 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'; - - assert.equal(getNodeDownloadUrl(version, os, bitness), expectedUrl); - }); - - it('should return the correct download URL for Windows (32-bit)', () => { - const os = 'WIN'; - const bitness = 86; - const expectedUrl = - 'https://nodejs.org/dist/v18.16.0/node-v18.16.0-x86.msi'; - - assert.equal(getNodeDownloadUrl(version, os, bitness), expectedUrl); - }); - - it('should return the correct download URL for Windows (64-bit)', () => { - const os = 'WIN'; - const bitness = 64; - const expectedUrl = - 'https://nodejs.org/dist/v18.16.0/node-v18.16.0-x64.msi'; - - assert.equal(getNodeDownloadUrl(version, os, bitness), expectedUrl); - }); - - it('should return the default download URL for other operating systems', () => { - const os = 'OTHER'; - const bitness = 86; - const expectedUrl = 'https://nodejs.org/dist/v18.16.0/node-v18.16.0.tar.gz'; - - assert.equal(getNodeDownloadUrl(version, os, bitness), expectedUrl); - }); - - describe('MAC', () => { - it('should return .pkg link for installer', () => { - const url = getNodeDownloadUrl('v18.0.0', 'MAC', 'x64', 'installer'); - assert.ok(url.includes('.pkg')); - }); - }); - - describe('WIN', () => { - it('should return an MSI link for installer', () => { - const url = getNodeDownloadUrl('v18.0.0', 'WIN', 'x64', 'installer'); - assert.ok(url.includes('.msi')); - }); - }); -}); diff --git a/apps/site/util/__tests__/url.test.mjs b/apps/site/util/__tests__/url.test.mjs new file mode 100644 index 0000000000000..8f6cd5be3f302 --- /dev/null +++ b/apps/site/util/__tests__/url.test.mjs @@ -0,0 +1,104 @@ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; + +import { getNodeDownloadUrl, getNodeApiUrl } from '../url'; + +const version = 'v18.16.0'; + +describe('getNodeApiUrl', () => { + it('should return the correct API link for versions >=0.3.1 and <0.5.1', () => { + const version = '0.4.0'; + const expectedLink = `https://nodejs.org/docs/${version}/api/`; + + const result = getNodeApiUrl(version); + + assert.equal(result, expectedLink); + }); + + it('should return the correct URL for versions >=0.3.1 and <0.5.1', () => { + const url = getNodeApiUrl('v0.4.10'); + assert.ok(url.includes('/api/')); + }); + + it('should return the correct API link for versions >=0.1.14 and <0.3.1', () => { + const version = '0.2.0'; + const expectedLink = `https://nodejs.org/docs/${version}/api.html`; + + const result = getNodeApiUrl(version); + + assert.equal(result, expectedLink); + }); + + it('should return the correct API link for versions >=1.0.0 and <4.0.0', () => { + const version = '2.3.0'; + const expectedLink = `https://iojs.org/dist/${version}/docs/api/`; + + const result = getNodeApiUrl(version); + + assert.equal(result, expectedLink); + }); + + it('should form the correct URL for versions >=1.0.0 and <4.0.0', () => { + const url = getNodeApiUrl('v1.2.3'); + assert.ok(url.includes('iojs.org/dist/v1.2.3/docs/api/')); + }); + + it('should return the correct API link for other versions', () => { + const version = '5.0.0'; + const expectedLink = `https://nodejs.org/dist/${version}/docs/api/`; + + const result = getNodeApiUrl(version); + + assert.equal(result, expectedLink); + }); +}); + +describe('getNodeDownloadUrl', () => { + it('should return 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'; + + assert.equal(getNodeDownloadUrl(version, os, bitness), expectedUrl); + }); + + it('should return the correct download URL for Windows (32-bit)', () => { + const os = 'WIN'; + const bitness = 86; + const expectedUrl = + 'https://nodejs.org/dist/v18.16.0/node-v18.16.0-x86.msi'; + + assert.equal(getNodeDownloadUrl(version, os, bitness), expectedUrl); + }); + + it('should return the correct download URL for Windows (64-bit)', () => { + const os = 'WIN'; + const bitness = 64; + const expectedUrl = + 'https://nodejs.org/dist/v18.16.0/node-v18.16.0-x64.msi'; + + assert.equal(getNodeDownloadUrl(version, os, bitness), expectedUrl); + }); + + it('should return the default download URL for other operating systems', () => { + const os = 'OTHER'; + const bitness = 86; + const expectedUrl = 'https://nodejs.org/dist/v18.16.0/node-v18.16.0.tar.gz'; + + assert.equal(getNodeDownloadUrl(version, os, bitness), expectedUrl); + }); + + describe('MAC', () => { + it('should return .pkg link for installer', () => { + const url = getNodeDownloadUrl('v18.0.0', 'MAC', 'x64', 'installer'); + assert.ok(url.includes('.pkg')); + }); + }); + + describe('WIN', () => { + it('should return an MSI link for installer', () => { + const url = getNodeDownloadUrl('v18.0.0', 'WIN', 'x64', 'installer'); + assert.ok(url.includes('.msi')); + }); + }); +}); diff --git a/apps/site/util/api.ts b/apps/site/util/api.ts deleted file mode 100644 index 460376b73f860..0000000000000 --- a/apps/site/util/api.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { satisfies } from 'semver'; - -import { DOCS_URL, DIST_URL } from '#site/next.constants.mjs'; - -export const getNodeApiLink = (version: string) => { - if (satisfies(version, '>=0.3.1 <0.5.1')) { - return `${DOCS_URL}${version}/api/`; - } - - if (satisfies(version, '>=0.1.14 <0.3.1')) { - return `${DOCS_URL}${version}/api.html`; - } - - return satisfies(version, '>=1.0.0 <4.0.0') - ? `https://iojs.org/dist/${version}/docs/api/` - : `${DIST_URL}${version}/docs/api/`; -}; diff --git a/apps/site/util/download/index.tsx b/apps/site/util/download/index.tsx index 95553e063e23b..f59e9d2fdd4ef 100644 --- a/apps/site/util/download/index.tsx +++ b/apps/site/util/download/index.tsx @@ -5,13 +5,11 @@ import * as PackageManagerIcons from '@node-core/ui-components/Icons/PackageMana import type { ElementType } from 'react'; import satisfies from 'semver/functions/satisfies'; -import { DIST_URL } from '#site/next.constants.mjs'; import type { IntlMessageKeys, NodeReleaseStatus, UserOS, UserPlatform, - DownloadKind, } from '#site/types'; import type * as Types from '#site/types/release'; @@ -155,74 +153,3 @@ export const PLATFORMS = Object.fromEntries( })), ]) ) as Record>>; - -export const getNodeDownloadUrl = ( - versionWithPrefix: string, - os: UserOS | 'LOADING', - platform: UserPlatform = 'x64', - kind: DownloadKind = 'installer' -) => { - const baseURL = `${DIST_URL}${versionWithPrefix}`; - - if (kind === 'source') { - return `${baseURL}/node-${versionWithPrefix}.tar.gz`; - } - - switch (os) { - case 'MAC': - // Prepares a downloadable Node.js installer link for the x64, ARM64 platforms - if (kind === 'installer') { - return `${baseURL}/node-${versionWithPrefix}.pkg`; - } - - // Prepares a downloadable Node.js link for the ARM64 platform - if (typeof platform === 'string') { - return `${baseURL}/node-${versionWithPrefix}-darwin-${platform}.tar.gz`; - } - - // Prepares a downloadable Node.js link for the x64 platform. - // Since the x86 platform is not officially supported, returns the x64 - // link as the default value. - return `${baseURL}/node-${versionWithPrefix}-darwin-x64.tar.gz`; - case 'WIN': { - if (kind === 'installer') { - // Prepares a downloadable Node.js installer link for the ARM platforms - if (typeof platform === 'string') { - return `${baseURL}/node-${versionWithPrefix}-${platform}.msi`; - } - - // Prepares a downloadable Node.js installer link for the x64 and x86 platforms - return `${baseURL}/node-${versionWithPrefix}-x${platform}.msi`; - } - - // Prepares a downloadable Node.js link for the ARM64 platform - if (typeof platform === 'string') { - return `${baseURL}/node-${versionWithPrefix}-win-${platform}.zip`; - } - - // Prepares a downloadable Node.js link for the x64 and x86 platforms - return `${baseURL}/node-${versionWithPrefix}-win-x${platform}.zip`; - } - case 'LINUX': - // Prepares a downloadable Node.js link for the ARM platforms such as - // ARMv7 and ARMv8 - if (typeof platform === 'string') { - return `${baseURL}/node-${versionWithPrefix}-linux-${platform}.tar.xz`; - } - - // Prepares a downloadable Node.js link for the x64 platform. - // Since the x86 platform is not officially supported, returns the x64 - // link as the default value. - return `${baseURL}/node-${versionWithPrefix}-linux-x64.tar.xz`; - case 'AIX': - // Prepares a downloadable Node.js link for AIX - if (typeof platform === 'string') { - return `${baseURL}/node-${versionWithPrefix}-aix-${platform}.tar.gz`; - } - - return `${baseURL}/node-${versionWithPrefix}-aix-ppc64.tar.gz`; - default: - // Prepares a downloadable Node.js source code link - return `${baseURL}/node-${versionWithPrefix}.tar.gz`; - } -}; diff --git a/apps/site/util/url.ts b/apps/site/util/url.ts new file mode 100644 index 0000000000000..4704c28736a94 --- /dev/null +++ b/apps/site/util/url.ts @@ -0,0 +1,89 @@ +import { satisfies } from 'semver'; + +import { DOCS_URL, DIST_URL } from '#site/next.constants.mjs'; +import type { UserOS, UserPlatform, DownloadKind } from '#site/types'; + +export const getNodeApiUrl = (version: string) => { + if (satisfies(version, '>=0.3.1 <0.5.1')) { + return `${DOCS_URL}${version}/api/`; + } + + if (satisfies(version, '>=0.1.14 <0.3.1')) { + return `${DOCS_URL}${version}/api.html`; + } + + return satisfies(version, '>=1.0.0 <4.0.0') + ? `https://iojs.org/dist/${version}/docs/api/` + : `${DIST_URL}${version}/docs/api/`; +}; + +export const getNodeDownloadUrl = ( + versionWithPrefix: string, + os: UserOS | 'LOADING', + platform: UserPlatform = 'x64', + kind: DownloadKind = 'installer' +) => { + const baseURL = `${DIST_URL}${versionWithPrefix}`; + + if (kind === 'source') { + return `${baseURL}/node-${versionWithPrefix}.tar.gz`; + } + + switch (os) { + case 'MAC': + // Prepares a downloadable Node.js installer link for the x64, ARM64 platforms + if (kind === 'installer') { + return `${baseURL}/node-${versionWithPrefix}.pkg`; + } + + // Prepares a downloadable Node.js link for the ARM64 platform + if (typeof platform === 'string') { + return `${baseURL}/node-${versionWithPrefix}-darwin-${platform}.tar.gz`; + } + + // Prepares a downloadable Node.js link for the x64 platform. + // Since the x86 platform is not officially supported, returns the x64 + // link as the default value. + return `${baseURL}/node-${versionWithPrefix}-darwin-x64.tar.gz`; + case 'WIN': { + if (kind === 'installer') { + // Prepares a downloadable Node.js installer link for the ARM platforms + if (typeof platform === 'string') { + return `${baseURL}/node-${versionWithPrefix}-${platform}.msi`; + } + + // Prepares a downloadable Node.js installer link for the x64 and x86 platforms + return `${baseURL}/node-${versionWithPrefix}-x${platform}.msi`; + } + + // Prepares a downloadable Node.js link for the ARM64 platform + if (typeof platform === 'string') { + return `${baseURL}/node-${versionWithPrefix}-win-${platform}.zip`; + } + + // Prepares a downloadable Node.js link for the x64 and x86 platforms + return `${baseURL}/node-${versionWithPrefix}-win-x${platform}.zip`; + } + case 'LINUX': + // Prepares a downloadable Node.js link for the ARM platforms such as + // ARMv7 and ARMv8 + if (typeof platform === 'string') { + return `${baseURL}/node-${versionWithPrefix}-linux-${platform}.tar.xz`; + } + + // Prepares a downloadable Node.js link for the x64 platform. + // Since the x86 platform is not officially supported, returns the x64 + // link as the default value. + return `${baseURL}/node-${versionWithPrefix}-linux-x64.tar.xz`; + case 'AIX': + // Prepares a downloadable Node.js link for AIX + if (typeof platform === 'string') { + return `${baseURL}/node-${versionWithPrefix}-aix-${platform}.tar.gz`; + } + + return `${baseURL}/node-${versionWithPrefix}-aix-ppc64.tar.gz`; + default: + // Prepares a downloadable Node.js source code link + return `${baseURL}/node-${versionWithPrefix}.tar.gz`; + } +}; From cbd7e9b51b80f5482e2f94eb99e19c7c56b6c4d1 Mon Sep 17 00:00:00 2001 From: avivkeller Date: Fri, 27 Jun 2025 16:15:54 -0400 Subject: [PATCH 6/6] code review --- .../Release/OperatingSystemDropdown.tsx | 6 ++--- .../Downloads/Release/PlatformDropdown.tsx | 4 ++-- apps/site/hooks/react-client/useDetectOS.ts | 16 ++++++------- apps/site/types/release.ts | 14 +++++------ apps/site/types/userAgent.ts | 9 ++++---- apps/site/util/download/index.tsx | 16 ++++++------- apps/site/util/objects.ts | 14 +++++------ apps/site/util/url.ts | 6 ++--- apps/site/util/userAgent.ts | 23 +++++++++++-------- 9 files changed, 55 insertions(+), 53 deletions(-) diff --git a/apps/site/components/Downloads/Release/OperatingSystemDropdown.tsx b/apps/site/components/Downloads/Release/OperatingSystemDropdown.tsx index d4306b78be5df..1e096adf3ffcd 100644 --- a/apps/site/components/Downloads/Release/OperatingSystemDropdown.tsx +++ b/apps/site/components/Downloads/Release/OperatingSystemDropdown.tsx @@ -7,10 +7,10 @@ import type { FC } from 'react'; import { useClientContext } from '#site/hooks'; import { ReleaseContext } from '#site/providers/releaseProvider'; -import type { UserOS } from '#site/types/userAgent'; +import type { OperatingSystem } from '#site/types/userAgent'; import { nextItem, OPERATING_SYSTEMS, parseCompat } from '#site/util/download'; -type OperatingSystemDropdownProps = { exclude?: Array }; +type OperatingSystemDropdownProps = { exclude?: Array }; const OperatingSystemDropdown: FC = () => { const { os } = useClientContext(); @@ -49,7 +49,7 @@ const OperatingSystemDropdown: FC = () => { ); return ( - + values={parsedOperatingSystems} defaultValue={release.os !== 'LOADING' ? release.os : undefined} loading={release.os === 'LOADING'} diff --git a/apps/site/components/Downloads/Release/PlatformDropdown.tsx b/apps/site/components/Downloads/Release/PlatformDropdown.tsx index b30e40de7bf51..57a73d1d1a710 100644 --- a/apps/site/components/Downloads/Release/PlatformDropdown.tsx +++ b/apps/site/components/Downloads/Release/PlatformDropdown.tsx @@ -7,7 +7,7 @@ import { useEffect, useContext, useMemo } from 'react'; import { useClientContext } from '#site/hooks'; import { ReleaseContext } from '#site/providers/releaseProvider'; -import type { UserPlatform } from '#site/types/userAgent'; +import type { Platform } from '#site/types/userAgent'; import { PLATFORMS, nextItem, parseCompat } from '#site/util/download'; import { getUserPlatform } from '#site/util/userAgent'; @@ -57,7 +57,7 @@ const PlatformDropdown: FC = () => { ); return ( - + values={parsedPlatforms} defaultValue={release.platform !== '' ? release.platform : undefined} loading={release.os === 'LOADING' || release.platform === ''} diff --git a/apps/site/hooks/react-client/useDetectOS.ts b/apps/site/hooks/react-client/useDetectOS.ts index f6ef5a9a0f8f9..dc2d2cfcdf244 100644 --- a/apps/site/hooks/react-client/useDetectOS.ts +++ b/apps/site/hooks/react-client/useDetectOS.ts @@ -3,16 +3,16 @@ import { useEffect, useState } from 'react'; import type { - UserArchitecture, - UserBitness, - UserOS, + Architecture, + Bitness, + OperatingSystem, } from '#site/types/userAgent'; import { getHighEntropyValues, detectOS } from '#site/util/userAgent'; type UserOSState = { - os: UserOS | 'LOADING'; - bitness: UserBitness | ''; - architecture: UserArchitecture | ''; + os: OperatingSystem | 'LOADING'; + bitness: Bitness | ''; + architecture: Architecture | ''; }; const useDetectOS = () => { @@ -45,8 +45,8 @@ const useDetectOS = () => { }) => { setUserOSState(current => ({ ...current, - bitness: bitness as UserBitness, - architecture: architecture as UserArchitecture, + bitness: bitness as Bitness, + architecture: architecture as Architecture, })); } ); diff --git a/apps/site/types/release.ts b/apps/site/types/release.ts index 6d8d22ac6dc50..fc150fec22d68 100644 --- a/apps/site/types/release.ts +++ b/apps/site/types/release.ts @@ -1,6 +1,6 @@ import type { DownloadSnippet } from '#site/types/download'; import type { NodeRelease } from '#site/types/releases'; -import type { UserOS, UserPlatform } from '#site/types/userAgent'; +import type { OperatingSystem, Platform } from '#site/types/userAgent'; export type InstallationMethod = | 'NVM' @@ -17,23 +17,23 @@ export type PackageManager = 'NPM' | 'YARN' | 'PNPM'; // during runtime and do not have necessarily a consistent initial value export interface ReleaseState { version: string; - os: UserOS | 'LOADING'; - platform: UserPlatform | ''; + os: OperatingSystem | 'LOADING'; + platform: Platform | ''; installMethod: InstallationMethod | ''; packageManager: PackageManager; } export type ReleaseAction = | { type: 'SET_VERSION'; payload: string } - | { type: 'SET_OS'; payload: UserOS } - | { type: 'SET_PLATFORM'; payload: UserPlatform } + | { type: 'SET_OS'; payload: OperatingSystem } + | { type: 'SET_PLATFORM'; payload: Platform } | { type: 'SET_INSTALL_METHOD'; payload: InstallationMethod } | { type: 'SET_MANAGER'; payload: PackageManager }; export interface ReleaseDispatchActions { setVersion: (version: string) => void; - setOS: (os: UserOS) => void; - setPlatform: (bitness: UserPlatform) => void; + setOS: (os: OperatingSystem) => void; + setPlatform: (bitness: Platform) => void; setInstallMethod: (installMethod: InstallationMethod) => void; setPackageManager: (packageManager: PackageManager) => void; } diff --git a/apps/site/types/userAgent.ts b/apps/site/types/userAgent.ts index ef882d07f90b6..55d55aee03b3f 100644 --- a/apps/site/types/userAgent.ts +++ b/apps/site/types/userAgent.ts @@ -1,14 +1,13 @@ import type constants from '../util/download/constants.json'; // Extract OS key type from the systems object -export type UserOS = keyof typeof constants.systems; +export type OperatingSystem = keyof typeof constants.systems; // Derive the union type of UserPlatform from the userOptions -export type UserPlatform = (typeof constants.userOptions.platforms)[number]; +export type Platform = (typeof constants.userOptions.platforms)[number]; // Derive the union type of UserBitness from the userOptions -export type UserBitness = (typeof constants.userOptions.bitness)[number]; +export type Bitness = (typeof constants.userOptions.bitness)[number]; // Derive the union type of UserArchitecture from the userOptions -export type UserArchitecture = - (typeof constants.userOptions.architecture)[number]; +export type Architecture = (typeof constants.userOptions.architecture)[number]; diff --git a/apps/site/util/download/index.tsx b/apps/site/util/download/index.tsx index f59e9d2fdd4ef..9b31281e4c1f8 100644 --- a/apps/site/util/download/index.tsx +++ b/apps/site/util/download/index.tsx @@ -8,8 +8,8 @@ import satisfies from 'semver/functions/satisfies'; import type { IntlMessageKeys, NodeReleaseStatus, - UserOS, - UserPlatform, + OperatingSystem, + Platform, } from '#site/types'; import type * as Types from '#site/types/release'; @@ -29,9 +29,9 @@ export const OperatingSystemLabel = Object.fromEntries( // Base types for dropdown functionality type DownloadCompatibility = { - os?: Array; + os?: Array; installMethod?: Array; - platform?: Array; + platform?: Array; semver?: Array; releases?: Array; }; @@ -107,7 +107,7 @@ export const OPERATING_SYSTEMS = Object.entries(systems as ActualSystems) .filter(([key]) => key !== 'LOADING' && key !== 'OTHER') .map(([key, data]) => ({ label: data.name as IntlMessageKeys, - value: key as UserOS, + value: key as OperatingSystem, compatibility: data.compatibility, iconImage: createIcon(OSIcons, data.icon), })); @@ -123,7 +123,7 @@ export const INSTALL_METHODS = installMethods.map(method => ({ info: method.info as IntlMessageKeys, compatibility: { ...method.compatibility, - os: method.compatibility?.os?.map(os => os as UserOS), + os: method.compatibility?.os?.map(os => os as OperatingSystem), releases: method.compatibility?.releases?.map( release => release as NodeReleaseStatus ), @@ -148,8 +148,8 @@ export const PLATFORMS = Object.fromEntries( key, data.platforms.map(platform => ({ label: platform.label, - value: platform.value as UserPlatform, + value: platform.value as Platform, compatibility: platform.compatibility || {}, })), ]) -) as Record>>; +) as Record>>; diff --git a/apps/site/util/objects.ts b/apps/site/util/objects.ts index 1c880e5c99d80..b8e92400e8d81 100644 --- a/apps/site/util/objects.ts +++ b/apps/site/util/objects.ts @@ -1,19 +1,19 @@ type DebounceFunction = (...args: Array) => void; -let timeoutId: NodeJS.Timeout; +export const debounce = ( + func: T, + delay: number +): ((...args: Parameters) => void) => { + let timeoutId: NodeJS.Timeout; -export const debounce = - ( - func: T, - delay: number - ): ((...args: Parameters) => void) => - (...args: Parameters) => { + return (...args: Parameters) => { clearTimeout(timeoutId); timeoutId = setTimeout(() => { func(...args); }, delay); }; +}; export function deepMerge( obj1: Obj1, diff --git a/apps/site/util/url.ts b/apps/site/util/url.ts index 4704c28736a94..9fde24b05294e 100644 --- a/apps/site/util/url.ts +++ b/apps/site/util/url.ts @@ -1,7 +1,7 @@ import { satisfies } from 'semver'; import { DOCS_URL, DIST_URL } from '#site/next.constants.mjs'; -import type { UserOS, UserPlatform, DownloadKind } from '#site/types'; +import type { OperatingSystem, Platform, DownloadKind } from '#site/types'; export const getNodeApiUrl = (version: string) => { if (satisfies(version, '>=0.3.1 <0.5.1')) { @@ -19,8 +19,8 @@ export const getNodeApiUrl = (version: string) => { export const getNodeDownloadUrl = ( versionWithPrefix: string, - os: UserOS | 'LOADING', - platform: UserPlatform = 'x64', + os: OperatingSystem | 'LOADING', + platform: Platform = 'x64', kind: DownloadKind = 'installer' ) => { const baseURL = `${DIST_URL}${versionWithPrefix}`; diff --git a/apps/site/util/userAgent.ts b/apps/site/util/userAgent.ts index 89939684da39e..9f2417f7ee867 100644 --- a/apps/site/util/userAgent.ts +++ b/apps/site/util/userAgent.ts @@ -1,10 +1,10 @@ /// import type { - UserOS, - UserArchitecture, - UserBitness, - UserPlatform, + OperatingSystem, + Architecture, + Bitness, + Platform, } from '#site/types'; // Constants for better maintainability @@ -16,9 +16,11 @@ const EMPTY_UA_DATA: UADataValues = {}; * @param userAgent - The user agent string to parse * @returns The detected OS or 'OTHER' if not recognized */ -export const detectOsInUserAgent = (userAgent: string | undefined): UserOS => { +export const detectOsInUserAgent = ( + userAgent: string | undefined +): OperatingSystem => { const osMatch = userAgent?.match(OS_PATTERNS); - return osMatch ? (osMatch[1].toUpperCase() as UserOS) : 'OTHER'; + return osMatch ? (osMatch[1].toUpperCase() as OperatingSystem) : 'OTHER'; }; /** @@ -26,7 +28,8 @@ export const detectOsInUserAgent = (userAgent: string | undefined): UserOS => { * Note: navigator.appVersion is deprecated, so we use userAgent instead * @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/appVersion */ -export const detectOS = (): UserOS => detectOsInUserAgent(navigator?.userAgent); +export const detectOS = (): OperatingSystem => + detectOsInUserAgent(navigator?.userAgent); /** * Determines user platform based on architecture and bitness @@ -34,9 +37,9 @@ export const detectOS = (): UserOS => detectOsInUserAgent(navigator?.userAgent); * @see https://developer.mozilla.org/en-US/docs/Web/API/NavigatorUAData/getHighEntropyValues */ export const getUserPlatform = ( - userArchitecture: UserArchitecture | '', - userBitness: UserBitness | '' -): UserPlatform => { + userArchitecture: Architecture | '', + userBitness: Bitness | '' +): Platform => { if (userArchitecture === 'arm' && userBitness === '64') { return 'arm64'; }