From 3937420801c2fd0fd0624fe4689f75016b6c924f Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Tue, 20 May 2025 23:03:14 +0300 Subject: [PATCH 01/38] feat: simplified download page --- .../SimplifiedDownloadTable/index.module.css | 8 ++ .../SimplifiedDownloadTable/index.tsx | 45 +++++++ apps/site/components/withLayout.tsx | 2 + .../components/withProgressionSidebar.tsx | 44 ++++--- .../components/withSimplifiedDownload.tsx | 43 +++++++ apps/site/layouts/DownloadSimple.tsx | 112 ++++++++++++++++++ apps/site/next.dynamic.constants.mjs | 11 ++ apps/site/next.mdx.use.mjs | 4 + apps/site/pages/en/download/simplified.mdx | 86 ++++++++++++++ apps/site/types/layouts.ts | 1 + apps/site/util/downloadUtils.tsx | 96 ++++++++++++++- packages/ui-components/styles/markdown.css | 41 +++++++ 12 files changed, 475 insertions(+), 18 deletions(-) create mode 100644 apps/site/components/Downloads/SimplifiedDownloadTable/index.module.css create mode 100644 apps/site/components/Downloads/SimplifiedDownloadTable/index.tsx create mode 100644 apps/site/components/withSimplifiedDownload.tsx create mode 100644 apps/site/layouts/DownloadSimple.tsx create mode 100644 apps/site/pages/en/download/simplified.mdx diff --git a/apps/site/components/Downloads/SimplifiedDownloadTable/index.module.css b/apps/site/components/Downloads/SimplifiedDownloadTable/index.module.css new file mode 100644 index 0000000000000..5740edadd7de7 --- /dev/null +++ b/apps/site/components/Downloads/SimplifiedDownloadTable/index.module.css @@ -0,0 +1,8 @@ +@reference "../../../styles/index.css"; + +.links { + @apply flex + h-4 + items-center + gap-2; +} diff --git a/apps/site/components/Downloads/SimplifiedDownloadTable/index.tsx b/apps/site/components/Downloads/SimplifiedDownloadTable/index.tsx new file mode 100644 index 0000000000000..1b71b94be0b17 --- /dev/null +++ b/apps/site/components/Downloads/SimplifiedDownloadTable/index.tsx @@ -0,0 +1,45 @@ +'use client'; + +import type { FC } from 'react'; + +import Link from '@/components/Link'; +import type { UserOS } from '@/types/userOS'; +import { OperatingSystemLabel } from '@/util/downloadUtils'; + +type DownloadTable = { + file: string; + os: string; + architecture: string; + url: string; +}; + +type SimplifiedDownloadTableProps = { + source: Array; +}; + +const SimplifiedDownloadTable: FC = ({ + source, +}) => ( + + + + + + + + + + {source.map(release => ( + + + + + + ))} + +
File NameOSArchitecture
+ {release.file} + {OperatingSystemLabel[release.os as UserOS]}{release.architecture}
+); + +export default SimplifiedDownloadTable; diff --git a/apps/site/components/withLayout.tsx b/apps/site/components/withLayout.tsx index 0c4a1638ee5b3..0cd300265fb48 100644 --- a/apps/site/components/withLayout.tsx +++ b/apps/site/components/withLayout.tsx @@ -5,6 +5,7 @@ import ArticlePageLayout from '@/layouts/ArticlePage'; import BlogLayout from '@/layouts/Blog'; import DefaultLayout from '@/layouts/Default'; import DownloadLayout from '@/layouts/Download'; +import DownloadSimple from '@/layouts/DownloadSimple'; import GlowingBackdropLayout from '@/layouts/GlowingBackdrop'; import LearnLayout from '@/layouts/Learn'; import PostLayout from '@/layouts/Post'; @@ -18,6 +19,7 @@ const layouts = { 'blog-post': PostLayout, 'blog-category': BlogLayout, download: DownloadLayout, + 'download-simple': DownloadSimple, article: ArticlePageLayout, } satisfies Record; diff --git a/apps/site/components/withProgressionSidebar.tsx b/apps/site/components/withProgressionSidebar.tsx index 9ee005206bf46..185b34c18d6b5 100644 --- a/apps/site/components/withProgressionSidebar.tsx +++ b/apps/site/components/withProgressionSidebar.tsx @@ -4,39 +4,53 @@ import ProgressionSidebar from '@node-core/ui-components/Common/ProgressionSideb import { usePathname } from 'next/navigation'; import { useLocale, useTranslations } from 'next-intl'; import type { RichTranslationValues } from 'next-intl'; -import type { FC } from 'react'; +import type { ComponentProps, FC } from 'react'; import Link from '@/components/Link'; import { useSiteNavigation } from '@/hooks/server'; import { useRouter } from '@/navigation.mjs'; import type { NavigationKeys } from '@/types'; -type WithProgressionSidebarProps = { - navKey: NavigationKeys; - context?: Record; -}; +type Group = ComponentProps['groups'][number]; + +type WithProgressionSidebarProps = + | { + navKey: NavigationKeys; + context?: Record; + groups?: never; + } + | { + groups: Array; + navKey?: never; + context?: never; + }; -const WithProgressionSidebar: FC = ({ - navKey, - context, -}) => { +const WithProgressionSidebar: FC = props => { const { getSideNavigation } = useSiteNavigation(); const pathname = usePathname(); const locale = useLocale(); const t = useTranslations(); const { push } = useRouter(); - const [[, sidebarNavigation]] = getSideNavigation([navKey], context); - const mappedProgressionSidebarItems = sidebarNavigation.items.map( - ([, { label, items }]) => ({ + let groups: Array = []; + + if ('navKey' in props && props.navKey) { + const [[, sidebarNavigation]] = getSideNavigation( + [props.navKey], + props.context + ); + + groups = sidebarNavigation.items.map(([, { label, items }]) => ({ groupName: label, items: items.map(([, item]) => item), - }) - ); + })); + } else if ('groups' in props) { + groups = props.groups; + } return ( ; + +type WithSimplifiedDownloadProps = { + children: FC<{ + version: string; + binaries: DownloadTable['binaries']; + installers: DownloadTable['installers']; + minors: DownloadTable['minors']; + }>; +}; + +const WithSimplifiedDownload: FC = async ({ + children: Component, +}) => { + const releaseData = await getReleaseData(); + + const { pathname } = getClientContext(); + const major = pathname.split('/').pop(); + const release = releaseData.find( + release => + release.major === Number(major) || + (release.isLts === true && major === 'simplified') + ); + + const { binaries, installers, minors } = getDownloadTable(release!); + + return ( + + ); +}; + +export default WithSimplifiedDownload; diff --git a/apps/site/layouts/DownloadSimple.tsx b/apps/site/layouts/DownloadSimple.tsx new file mode 100644 index 0000000000000..e80b925332fde --- /dev/null +++ b/apps/site/layouts/DownloadSimple.tsx @@ -0,0 +1,112 @@ +import { getLocale } from 'next-intl/server'; +import type { FC, PropsWithChildren } from 'react'; + +import { getClientContext, setClientContext } from '@/client-context'; +import WithFooter from '@/components/withFooter'; +import WithMetaBar from '@/components/withMetaBar'; +import WithNavBar from '@/components/withNavBar'; +import WithProgressionSidebar from '@/components/withProgressionSidebar'; +import getReleaseData from '@/next-data/releaseData'; +import { dynamicRouter } from '@/next.dynamic.mjs'; + +import ArticleLayout from './Article'; + +const mapSidebarItems = ( + releaseData: Awaited> +) => + Object.values( + releaseData.reduce< + Record< + string, + { groupName: string; items: Array<{ label: string; link: string }> } + > + >((acc, release) => { + const key = release.status; + if (!acc[key]) { + acc[key] = { + groupName: key, + items: [], + }; + } + + const label = [`v${release.major}`]; + + if (release.codename) { + label.push(release.codename); + } + + acc[key].items.push({ + label: label.join(' '), + link: `/download/${release.major}`, + }); + + return acc; + }, {}) + ); + +const getMarkdownContent = async ( + locale: string, + pathname: string, + file: Array +) => { + const filePathname = dynamicRouter.getPathname(file); + + // We retrieve the source of the Markdown file by doing an educated guess + // of what possible files could be the source of the page, since the extension + // context is lost from `getStaticProps` as a limitation of Next.js itself + const { source, filename } = await dynamicRouter.getMarkdownFile( + locale, + filePathname + ); + + // This parses the source Markdown content and returns a React Component and + // relevant context from the Markdown File + const { content, frontmatter, headings, readingTime } = + await dynamicRouter.getMDXContent(source, filename); + + // Metadata and shared Context to be available through the lifecycle of the page + const sharedContext = { + frontmatter, + headings, + readingTime, + filename, + pathname: `/${pathname}`, + }; + + // Defines a shared Server Context for the Client-Side + // That is shared for all pages under the dynamic router + setClientContext(sharedContext); + + return { content }; +}; + +const DownloadLayout: FC = async ({ children }) => { + const releaseData = await getReleaseData(); + const sidebarGroups = mapSidebarItems(releaseData); + + const locale = await getLocale(); + const { pathname } = getClientContext(); + const { content } = await getMarkdownContent(locale, pathname, [ + 'download', + 'simplified', + ]); + + return ( + <> + + + + + +
+
{children || content}
+ +
+
+ + + + ); +}; + +export default DownloadLayout; diff --git a/apps/site/next.dynamic.constants.mjs b/apps/site/next.dynamic.constants.mjs index 783b1758d102a..3b393db2533dc 100644 --- a/apps/site/next.dynamic.constants.mjs +++ b/apps/site/next.dynamic.constants.mjs @@ -4,6 +4,7 @@ import { provideBlogCategories, provideBlogPosts, } from './next-data/providers/blogData'; +import provideReleaseData from './next-data/providers/releaseData'; import { BASE_PATH, BASE_URL } from './next.constants.mjs'; import { siteConfig } from './next.json.mjs'; import { defaultLocale } from './next.locales.mjs'; @@ -20,6 +21,12 @@ export const IGNORED_ROUTES = [ locale !== defaultLocale.code && /^blog/.test(pathname), // This is used to ignore all pathnames that are empty ({ locale, pathname }) => locale.length && !pathname.length, + // This is used to ignore all simplified download routes except for the English language + ({ locale, pathname }) => + locale !== defaultLocale.code && /^download/.test(pathname), + // This is used to ignore all simplified download routes except for the English language + ({ locale, pathname }) => + locale !== defaultLocale.code && /^simplified/.test(pathname), ]; /** @@ -32,6 +39,10 @@ export const IGNORED_ROUTES = [ export const DYNAMIC_ROUTES = new Map([ // Provides Routes for all Blog Categories ...provideBlogCategories().map(c => [`blog/${c}`, 'blog-category']), + ...provideReleaseData().map(({ major }) => [ + `download/${major}`, + 'download-simple', + ]), // Provides Routes for all Blog Categories w/ Pagination ...provideBlogCategories() // retrieves the amount of pages for each blog category diff --git a/apps/site/next.mdx.use.mjs b/apps/site/next.mdx.use.mjs index 537872c020e64..7f5e597369a06 100644 --- a/apps/site/next.mdx.use.mjs +++ b/apps/site/next.mdx.use.mjs @@ -1,10 +1,12 @@ 'use strict'; import DownloadReleasesTable from './components/Downloads/DownloadReleasesTable'; +import SimplifiedDownloadTable from './components/Downloads/SimplifiedDownloadTable'; import UpcomingMeetings from './components/MDX/Calendar/UpcomingMeetings'; import WithBadgeGroup from './components/withBadgeGroup'; import WithBanner from './components/withBanner'; import WithNodeRelease from './components/withNodeRelease'; +import WithSimplifiedDownload from './components/withSimplifiedDownload'; /** * A full list of React Components that we want to pass through to MDX @@ -13,6 +15,8 @@ import WithNodeRelease from './components/withNodeRelease'; */ export const mdxComponents = { DownloadReleasesTable: DownloadReleasesTable, + WithSimplifiedDownload: WithSimplifiedDownload, + SimplifiedDownloadTable: SimplifiedDownloadTable, // HOC for getting Node.js Release Metadata WithNodeRelease: WithNodeRelease, // HOC for providing Banner Data diff --git a/apps/site/pages/en/download/simplified.mdx b/apps/site/pages/en/download/simplified.mdx new file mode 100644 index 0000000000000..5bc21ca415ef0 --- /dev/null +++ b/apps/site/pages/en/download/simplified.mdx @@ -0,0 +1,86 @@ +--- +title: Download Node.js® +layout: download-simple +--- + +# Download Node.js® + + + {({ binaries, version }) => ( + <> +

Node.js® {version} – Binary Downloads

+ + + )} +
+ + + {({ installers, version }) => ( + <> +

Node.js® {version} – Installer Packages

+ + + )} +
+ +Learn more about [Node.js releases](/about/previous-releases), including the release schedule and LTS status. + + + {({ version }) => ( +

+ + Signed SHASUMS + {' '} + for release files. How to{' '} + + verify + {' '} + signed SHASUMS. +

+ )} +
+ + + {({ version }) => ( +

+ Download a signed{' '} + + Node.js {version} source + {' '} + tarball. +

+ )} +
+ +## Other versions + + + {({ minors }) => + minors.map(([binaries, installers,version]) => ( +
+

Node.js® {version}

+
+

Binary Downloads

+ + +

Installer Packages

+ + +

+ Download a signed{' '} + + Node.js {version} source + {' '} + tarball. +

+
+
+ ))} + +
diff --git a/apps/site/types/layouts.ts b/apps/site/types/layouts.ts index a3e81f69132f7..fabc10237e979 100644 --- a/apps/site/types/layouts.ts +++ b/apps/site/types/layouts.ts @@ -6,4 +6,5 @@ export type Layouts = | 'blog-category' | 'blog-post' | 'download' + | 'download-simple' | 'article'; diff --git a/apps/site/util/downloadUtils.tsx b/apps/site/util/downloadUtils.tsx index f95e2a626e210..21811fd4e06a7 100644 --- a/apps/site/util/downloadUtils.tsx +++ b/apps/site/util/downloadUtils.tsx @@ -4,10 +4,14 @@ import * as OSIcons from '@node-core/ui-components/Icons/OperatingSystem'; import * as PackageManagerIcons from '@node-core/ui-components/Icons/PackageManager'; import satisfies from 'semver/functions/satisfies'; -import type { NodeReleaseStatus } from '@/types'; +import { DIST_URL } from '@/next.constants.mjs'; +import type { NodeRelease, NodeReleaseStatus } from '@/types'; import type * as Types from '@/types/release'; import type { UserOS, UserPlatform } from '@/types/userOS'; +import type { DownloadKind } from './getNodeDownloadUrl'; +import { getNodeDownloadUrl } from './getNodeDownloadUrl'; + // This is a manual list of OS's that do not support/have a way of being installed // with an executable installer. This is used to disable the installer button. // Note: Windows has one tiny exception for x64 on Node.js versions < 4.0.0 @@ -125,6 +129,88 @@ export const parseCompat = < })); }; +type ParsedArtifact = { + file: string; + kind: DownloadKind; + os: UserOS; + architecture: string; + url: string; + version: string; +}; + +export function generateCompatibleDownloads({ + platforms = PLATFORMS, + exclude = [], + version, + kind = 'binary', +}: { + platforms?: Record>>; + exclude?: Array; + version: string; + kind?: DownloadKind; +}): Array { + return Object.entries(platforms).reduce>( + (acc, [os, items]) => { + if (exclude.includes(os as UserOS)) return acc; + + const operatingSystem = os as UserOS; + items.forEach(({ compatibility, value, label }) => { + const { + os: operatingSystems, + platform: platforms, + semver: versions, + } = compatibility; + + if ( + (operatingSystems?.includes(operatingSystem) ?? true) && + (platforms?.includes(value) ?? true) && + (versions?.every(r => satisfies(version, r)) ?? true) + ) { + const url = getNodeDownloadUrl(version, operatingSystem, value, kind); + + acc.push({ + file: url.replace(`${DIST_URL}${version}/`, ''), + kind: kind, + os: operatingSystem, + architecture: label, + url: url, + version: version, + }); + } + }); + + return acc; + }, + [] + ); +} + +export const getDownloadTable = (release: NodeRelease) => ({ + binaries: generateCompatibleDownloads({ + version: release.versionWithPrefix, + kind: 'binary' as DownloadKind, + }), + installers: generateCompatibleDownloads({ + exclude: OS_NOT_SUPPORTING_INSTALLERS, + version: release.versionWithPrefix, + kind: 'installer' as DownloadKind, + }), + minors: release.minorVersions + .filter(minor => `v${minor.version}` !== release.versionWithPrefix) + .map(minor => [ + generateCompatibleDownloads({ + version: `v${minor.version}`, + kind: 'binary' as DownloadKind, + }), + generateCompatibleDownloads({ + exclude: OS_NOT_SUPPORTING_INSTALLERS, + version: `v${minor.version}`, + kind: 'installer' as DownloadKind, + }), + `v${minor.version}`, + ]), +}); + export const OPERATING_SYSTEMS: Array> = [ { label: OperatingSystemLabel.WIN, @@ -260,7 +346,9 @@ export const PLATFORMS: Record< { label: 'x64', value: 'x64', - compatibility: {}, + compatibility: { + semver: ['>= 4.0.0'], + }, }, { label: 'x86', @@ -282,7 +370,9 @@ export const PLATFORMS: Record< { label: 'ARM64', value: 'arm64', - compatibility: {}, + compatibility: { + semver: ['>= 16.0.0'], + }, }, ], LINUX: [ diff --git a/packages/ui-components/styles/markdown.css b/packages/ui-components/styles/markdown.css index f1c91f056fc84..4874e53fc8c14 100644 --- a/packages/ui-components/styles/markdown.css +++ b/packages/ui-components/styles/markdown.css @@ -175,3 +175,44 @@ main { } } } + +details { + @apply border-b + border-neutral-200 + px-0 + pb-4 + text-neutral-900 + dark:border-neutral-800 + dark:text-white; + + summary::marker { + @apply hidden + content-['']; + } + + summary { + @apply cursor-default + select-none + after:float-right + after:pr-4 + after:content-['›']; + + > * { + @apply inline; + } + } + + &[open] summary { + @apply pb-4 + after:rotate-90 + after:transform + after:pt-4; + } + + > *:not(summary) { + @apply flex + select-none + flex-col + gap-4; + } +} From 3e9cbeba6a35ee9da44d221a8dad5e68940114d2 Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Tue, 20 May 2025 23:25:41 +0300 Subject: [PATCH 02/38] chore: fix type errors --- apps/site/layouts/DownloadSimple.tsx | 4 ++-- apps/site/next.dynamic.constants.mjs | 6 +++--- apps/site/util/downloadUtils.tsx | 7 ++++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/apps/site/layouts/DownloadSimple.tsx b/apps/site/layouts/DownloadSimple.tsx index a9711a194d0fe..ab2578265f64b 100644 --- a/apps/site/layouts/DownloadSimple.tsx +++ b/apps/site/layouts/DownloadSimple.tsx @@ -1,8 +1,6 @@ import { getLocale } from 'next-intl/server'; import type { FC, PropsWithChildren } from 'react'; -import ArticleLayout from './Article'; - import { getClientContext, setClientContext } from '#site/client-context'; import WithFooter from '#site/components/withFooter'; import WithMetaBar from '#site/components/withMetaBar'; @@ -11,6 +9,8 @@ import WithProgressionSidebar from '#site/components/withProgressionSidebar'; import getReleaseData from '#site/next-data/releaseData'; import { dynamicRouter } from '#site/next.dynamic.mjs'; +import ArticleLayout from './Article'; + const mapSidebarItems = ( releaseData: Awaited> ) => diff --git a/apps/site/next.dynamic.constants.mjs b/apps/site/next.dynamic.constants.mjs index 242768bd12374..b8814d5246a70 100644 --- a/apps/site/next.dynamic.constants.mjs +++ b/apps/site/next.dynamic.constants.mjs @@ -1,13 +1,13 @@ 'use strict'; +import provideReleaseData from '#site/next-data/providers/releaseData'; +import { blogData } from '#site/next.json.mjs'; + import { provideBlogPosts } from './next-data/providers/blogData'; import { BASE_PATH, BASE_URL } from './next.constants.mjs'; import { siteConfig } from './next.json.mjs'; import { defaultLocale } from './next.locales.mjs'; -import provideReleaseData from '#site/next-data/providers/releaseData'; -import { blogData } from '#site/next.json.mjs'; - /** * This is a list of all static routes or pages from the Website that we do not * want to allow to be statically built on our Static Export Build. diff --git a/apps/site/util/downloadUtils.tsx b/apps/site/util/downloadUtils.tsx index f3120a6bf1951..0ec7bedce751b 100644 --- a/apps/site/util/downloadUtils.tsx +++ b/apps/site/util/downloadUtils.tsx @@ -4,14 +4,14 @@ import * as OSIcons from '@node-core/ui-components/Icons/OperatingSystem'; import * as PackageManagerIcons from '@node-core/ui-components/Icons/PackageManager'; import satisfies from 'semver/functions/satisfies'; -import type { DownloadKind } from './getNodeDownloadUrl'; -import { getNodeDownloadUrl } from './getNodeDownloadUrl'; - import { DIST_URL } from '#site/next.constants.mjs'; import type { NodeRelease, NodeReleaseStatus } from '#site/types'; import type * as Types from '#site/types/release'; import type { UserOS, UserPlatform } from '#site/types/userOS'; +import { getNodeDownloadUrl } from './getNodeDownloadUrl'; +import type { DownloadKind } from './getNodeDownloadUrl'; + // This is a manual list of OS's that do not support/have a way of being installed // with an executable installer. This is used to disable the installer button. // Note: Windows has one tiny exception for x64 on Node.js versions < 4.0.0 @@ -410,4 +410,5 @@ export const PLATFORMS: Record< }, ], OTHER: [], + LOADING: [], }; From 8abad4e612b31ab91518a1c34749bb482d974152 Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Tue, 20 May 2025 23:29:05 +0300 Subject: [PATCH 03/38] chore: remove unused css file --- .../Downloads/SimplifiedDownloadTable/index.module.css | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 apps/site/components/Downloads/SimplifiedDownloadTable/index.module.css diff --git a/apps/site/components/Downloads/SimplifiedDownloadTable/index.module.css b/apps/site/components/Downloads/SimplifiedDownloadTable/index.module.css deleted file mode 100644 index 5740edadd7de7..0000000000000 --- a/apps/site/components/Downloads/SimplifiedDownloadTable/index.module.css +++ /dev/null @@ -1,8 +0,0 @@ -@reference "../../../styles/index.css"; - -.links { - @apply flex - h-4 - items-center - gap-2; -} From 924c640632ca8403fdc884ef92b5185dc258d169 Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Tue, 20 May 2025 23:57:03 +0300 Subject: [PATCH 04/38] refactor: release check and version without minor --- .../components/withSimplifiedDownload.tsx | 35 +++++++------ apps/site/pages/en/download/simplified.mdx | 49 ++++++++++--------- 2 files changed, 47 insertions(+), 37 deletions(-) diff --git a/apps/site/components/withSimplifiedDownload.tsx b/apps/site/components/withSimplifiedDownload.tsx index b26856e922cfa..c5de437d4eec8 100644 --- a/apps/site/components/withSimplifiedDownload.tsx +++ b/apps/site/components/withSimplifiedDownload.tsx @@ -18,26 +18,31 @@ type WithSimplifiedDownloadProps = { const WithSimplifiedDownload: FC = async ({ children: Component, }) => { + const { pathname } = getClientContext(); const releaseData = await getReleaseData(); - const { pathname } = getClientContext(); - const major = pathname.split('/').pop(); - const release = releaseData.find( - release => - release.major === Number(major) || - (release.isLts === true && major === 'simplified') + const version = pathname.split('/').pop(); + const matchingRelease = releaseData.find( + ({ major, codename, isLts }) => + major === Number(version) || + codename === version || + (isLts === true && version === 'simplified') ); - const { binaries, installers, minors } = getDownloadTable(release!); + if (matchingRelease !== undefined) { + const { binaries, installers, minors } = getDownloadTable(matchingRelease); - return ( - - ); + return ( + + ); + } + + return null; }; export default WithSimplifiedDownload; diff --git a/apps/site/pages/en/download/simplified.mdx b/apps/site/pages/en/download/simplified.mdx index 5bc21ca415ef0..a08fde515ec0d 100644 --- a/apps/site/pages/en/download/simplified.mdx +++ b/apps/site/pages/en/download/simplified.mdx @@ -56,31 +56,36 @@ Learn more about [Node.js releases](/about/previous-releases), including the rel )} -## Other versions - {({ minors }) => - minors.map(([binaries, installers,version]) => ( -
-

Node.js® {version}

-
-

Binary Downloads

- + minors.length > 0 && ( + <> +

Other versions

+ {minors.map(([binaries, installers, version]) => ( +
+ +

Node.js® {version}

+
+
+

Binary Downloads

+ -

Installer Packages

- +

Installer Packages

+ -

- Download a signed{' '} - - Node.js {version} source - {' '} - tarball. -

-
-
- ))} +

+ Download a signed{' '} + + Node.js {version} source + {' '} + tarball. +

+
+
+ ))} + + )}
From 9c375d53356b2bd39c3605bbc69ea5b5a7135d85 Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Sun, 25 May 2025 16:52:07 +0300 Subject: [PATCH 05/38] refactor: better localization --- .../SimplifiedDownloadTable/index.tsx | 67 ++++++++++------- .../components/withSimplifiedDownload.tsx | 26 ++----- apps/site/pages/en/download/simplified.mdx | 75 ++++++++----------- apps/site/util/downloadUtils.tsx | 70 ++++++++++++----- apps/site/util/getNodeDownloadUrl.ts | 6 +- packages/i18n/locales/en.json | 5 ++ 6 files changed, 138 insertions(+), 111 deletions(-) diff --git a/apps/site/components/Downloads/SimplifiedDownloadTable/index.tsx b/apps/site/components/Downloads/SimplifiedDownloadTable/index.tsx index 0751fbd02201d..46448a503c3f9 100644 --- a/apps/site/components/Downloads/SimplifiedDownloadTable/index.tsx +++ b/apps/site/components/Downloads/SimplifiedDownloadTable/index.tsx @@ -1,45 +1,54 @@ 'use client'; +import { useTranslations } from 'next-intl'; import type { FC } from 'react'; import Link from '#site/components/Link'; import type { UserOS } from '#site/types/userOS'; +import type { ParsedArtifact } from '#site/util/downloadUtils'; import { OperatingSystemLabel } from '#site/util/downloadUtils'; -type DownloadTable = { - file: string; - os: string; - architecture: string; - url: string; -}; - type SimplifiedDownloadTableProps = { - source: Array; + source: Array; }; const SimplifiedDownloadTable: FC = ({ source, -}) => ( - - - - - - - - - - {source.map(release => ( - - - - +}) => { + const t = useTranslations(); + + return ( +
File NameOSArchitecture
- {release.file} - {OperatingSystemLabel[release.os as UserOS]}{release.architecture}
+ + + + + - ))} - -
{t('components.simpleDownloadTable.fileName')} + {t('components.simpleDownloadTable.operatingSystem')} + + {t('components.simpleDownloadTable.architecture')} +
-); + + + {source.map(release => ( + + + {release.file} + + + {OperatingSystemLabel[release.os as UserOS]} + + + {release.architecture} + + + ))} + + + ); +}; export default SimplifiedDownloadTable; diff --git a/apps/site/components/withSimplifiedDownload.tsx b/apps/site/components/withSimplifiedDownload.tsx index c5de437d4eec8..367b574cc7e05 100644 --- a/apps/site/components/withSimplifiedDownload.tsx +++ b/apps/site/components/withSimplifiedDownload.tsx @@ -7,12 +7,7 @@ import { getDownloadTable } from '#site/util/downloadUtils'; type DownloadTable = ReturnType; type WithSimplifiedDownloadProps = { - children: FC<{ - version: string; - binaries: DownloadTable['binaries']; - installers: DownloadTable['installers']; - minors: DownloadTable['minors']; - }>; + children: FC; }; const WithSimplifiedDownload: FC = async ({ @@ -23,23 +18,14 @@ const WithSimplifiedDownload: FC = async ({ const version = pathname.split('/').pop(); const matchingRelease = releaseData.find( - ({ major, codename, isLts }) => - major === Number(version) || - codename === version || - (isLts === true && version === 'simplified') + ({ major, isLts }) => + major === Number(version) || (isLts === true && version === 'simplified') ); if (matchingRelease !== undefined) { - const { binaries, installers, minors } = getDownloadTable(matchingRelease); - - return ( - - ); + const table = getDownloadTable(matchingRelease); + + return ; } return null; diff --git a/apps/site/pages/en/download/simplified.mdx b/apps/site/pages/en/download/simplified.mdx index a08fde515ec0d..c3dc215815d13 100644 --- a/apps/site/pages/en/download/simplified.mdx +++ b/apps/site/pages/en/download/simplified.mdx @@ -6,17 +6,10 @@ layout: download-simple # Download Node.js® - {({ binaries, version }) => ( + {({ binaries, installers, version }) => ( <>

Node.js® {version} – Binary Downloads

- - )} -
- - - {({ installers, version }) => ( - <>

Node.js® {version} – Installer Packages

@@ -26,33 +19,28 @@ layout: download-simple Learn more about [Node.js releases](/about/previous-releases), including the release schedule and LTS status. - {({ version }) => ( -

- - Signed SHASUMS - {' '} - for release files. How to{' '} - - verify - {' '} - signed SHASUMS. -

- )} -
- - - {({ version }) => ( -

- Download a signed{' '} - - Node.js {version} source - {' '} - tarball. -

+ {({ version, urls }) => ( + <> +

+ Read the changelog{' '} + or blog post for this version. +

+

+ Signed SHASUMS for + release files. How to{' '} + + verify + {' '} + signed SHASUMS. +

+

+ Download a signed{' '} + + Node.js {version} source + {' '} + tarball. +

+ )}
@@ -61,7 +49,7 @@ Learn more about [Node.js releases](/about/previous-releases), including the rel minors.length > 0 && ( <>

Other versions

- {minors.map(([binaries, installers, version]) => ( + {minors.map(({ binaries, installers, version, urls }) => (

Node.js® {version}

@@ -69,15 +57,16 @@ Learn more about [Node.js releases](/about/previous-releases), including the rel

Binary Downloads

-

Installer Packages

- +

+ Read the{' '} + changelog{' '} + or blog post for this version. +

Download a signed{' '} - + Node.js {version} source {' '} tarball. @@ -86,6 +75,6 @@ Learn more about [Node.js releases](/about/previous-releases), including the rel

))} - )} - + ) + }
diff --git a/apps/site/util/downloadUtils.tsx b/apps/site/util/downloadUtils.tsx index 0ec7bedce751b..ec7760d0d8644 100644 --- a/apps/site/util/downloadUtils.tsx +++ b/apps/site/util/downloadUtils.tsx @@ -129,7 +129,7 @@ export const parseCompat = < })); }; -type ParsedArtifact = { +export type ParsedArtifact = { file: string; kind: DownloadKind; os: UserOS; @@ -185,30 +185,64 @@ export function generateCompatibleDownloads({ ); } -export const getDownloadTable = (release: NodeRelease) => ({ +export const getDownloadTable = ({ + versionWithPrefix, + minorVersions, +}: NodeRelease) => ({ binaries: generateCompatibleDownloads({ - version: release.versionWithPrefix, + version: versionWithPrefix, kind: 'binary' as DownloadKind, }), + urls: { + shasum: getNodeDownloadUrl( + versionWithPrefix, + 'LOADING', + 'x64', + 'shasum' as DownloadKind + ), + source: getNodeDownloadUrl( + versionWithPrefix, + 'LOADING', + 'x64', + 'source' as DownloadKind + ), + changelog: `https://github.com/nodejs/node/releases/tag/${versionWithPrefix}`, + blogPost: `/blog/release/${versionWithPrefix}`, + }, installers: generateCompatibleDownloads({ exclude: OS_NOT_SUPPORTING_INSTALLERS, - version: release.versionWithPrefix, + version: versionWithPrefix, kind: 'installer' as DownloadKind, }), - minors: release.minorVersions - .filter(minor => `v${minor.version}` !== release.versionWithPrefix) - .map(minor => [ - generateCompatibleDownloads({ - version: `v${minor.version}`, - kind: 'binary' as DownloadKind, - }), - generateCompatibleDownloads({ - exclude: OS_NOT_SUPPORTING_INSTALLERS, - version: `v${minor.version}`, - kind: 'installer' as DownloadKind, - }), - `v${minor.version}`, - ]), + version: versionWithPrefix, + minors: minorVersions + .filter(minor => `v${minor.version}` !== versionWithPrefix) + .map(minor => { + const versionWithPrefix = `v${minor.version}`; + + return { + binaries: generateCompatibleDownloads({ + version: versionWithPrefix, + kind: 'binary' as DownloadKind, + }), + installers: generateCompatibleDownloads({ + exclude: OS_NOT_SUPPORTING_INSTALLERS, + version: versionWithPrefix, + kind: 'installer' as DownloadKind, + }), + version: versionWithPrefix, + urls: { + source: getNodeDownloadUrl( + versionWithPrefix, + 'LOADING', + 'x64', + 'source' as DownloadKind + ), + changelog: `https://github.com/nodejs/node/releases/tag/${versionWithPrefix}`, + blogPost: `/blog/release/${versionWithPrefix}`, + }, + }; + }), }); export const OPERATING_SYSTEMS: Array> = [ diff --git a/apps/site/util/getNodeDownloadUrl.ts b/apps/site/util/getNodeDownloadUrl.ts index d5f2a8ef0c44d..cfe972bd45240 100644 --- a/apps/site/util/getNodeDownloadUrl.ts +++ b/apps/site/util/getNodeDownloadUrl.ts @@ -1,7 +1,7 @@ import { DIST_URL } from '#site/next.constants.mjs'; import type { UserOS, UserPlatform } from '#site/types/userOS'; -export type DownloadKind = 'installer' | 'binary' | 'source'; +export type DownloadKind = 'installer' | 'binary' | 'source' | 'shasum'; export const getNodeDownloadUrl = ( versionWithPrefix: string, @@ -15,6 +15,10 @@ export const getNodeDownloadUrl = ( return `${baseURL}/node-${versionWithPrefix}.tar.gz`; } + if (kind === 'shasum') { + return `${baseURL}/SHASUMS256.txt.asc`; + } + switch (os) { case 'MAC': // Prepares a downloadable Node.js installer link for the x64, ARM64 platforms diff --git a/packages/i18n/locales/en.json b/packages/i18n/locales/en.json index 156804a86cf2e..b7d386295fec0 100644 --- a/packages/i18n/locales/en.json +++ b/packages/i18n/locales/en.json @@ -156,6 +156,11 @@ "status": "Status", "details": "Details" }, + "simpleDownloadTable": { + "fileName": "File Name", + "operatingSystem": "OS", + "architecture": "Architecture" + }, "releaseModal": { "title": "Node.js {version} ({codename})", "titleWithoutCodename": "Node.js {version}", From e586292ae7b323fe7180b8b85840e50f82119708 Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Sun, 25 May 2025 17:21:44 +0300 Subject: [PATCH 06/38] refactor: separate layout, markdown and sidebar logic --- apps/site/components/withMarkdownContent.tsx | 59 ++++++++ .../components/withSimplifiedDownload.tsx | 39 +++++- apps/site/layouts/DownloadSimple.tsx | 129 ++++-------------- 3 files changed, 124 insertions(+), 103 deletions(-) create mode 100644 apps/site/components/withMarkdownContent.tsx diff --git a/apps/site/components/withMarkdownContent.tsx b/apps/site/components/withMarkdownContent.tsx new file mode 100644 index 0000000000000..33dc546eab124 --- /dev/null +++ b/apps/site/components/withMarkdownContent.tsx @@ -0,0 +1,59 @@ +import { getLocale } from 'next-intl/server'; +import type { FC, PropsWithChildren } from 'react'; + +import { getClientContext, setClientContext } from '#site/client-context'; + +import { dynamicRouter } from '#site/next.dynamic'; + +const getMarkdownContent = async ( + locale: string, + pathname: string, + file: Array +) => { + const filePathname = dynamicRouter.getPathname(file); + + // We retrieve the source of the Markdown file by doing an educated guess + // of what possible files could be the source of the page, since the extension + // context is lost from `getStaticProps` as a limitation of Next.js itself + const { source, filename } = await dynamicRouter.getMarkdownFile( + locale, + filePathname + ); + + // This parses the source Markdown content and returns a React Component and + // relevant context from the Markdown File + const { content, frontmatter, headings, readingTime } = + await dynamicRouter.getMDXContent(source, filename); + + // Metadata and shared Context to be available through the lifecycle of the page + const sharedContext = { + frontmatter, + headings, + readingTime, + filename, + pathname: `/${pathname}`, + }; + + // Defines a shared Server Context for the Client-Side + // That is shared for all pages under the dynamic router + setClientContext(sharedContext); + + return { content }; +}; + +type WithMarkdownContentProps = { + file: Array; + fallback?: React.ReactNode; +}; + +const WithMarkdownContent: FC< + PropsWithChildren +> = async ({ file, fallback: children }) => { + const locale = await getLocale(); + const { pathname } = getClientContext(); + const { content } = await getMarkdownContent(locale, pathname, file); + + return content || children || null; +}; + +export default WithMarkdownContent; diff --git a/apps/site/components/withSimplifiedDownload.tsx b/apps/site/components/withSimplifiedDownload.tsx index 367b574cc7e05..c004a5d824634 100644 --- a/apps/site/components/withSimplifiedDownload.tsx +++ b/apps/site/components/withSimplifiedDownload.tsx @@ -4,10 +4,44 @@ import { getClientContext } from '#site/client-context'; import getReleaseData from '#site/next-data/releaseData'; import { getDownloadTable } from '#site/util/downloadUtils'; +const mapSidebarItems = ( + releaseData: Awaited> +) => + Object.values( + releaseData.reduce< + Record< + string, + { groupName: string; items: Array<{ label: string; link: string }> } + > + >((acc, release) => { + const key = release.status; + if (!acc[key]) { + acc[key] = { + groupName: key, + items: [], + }; + } + + const label = [`v${release.major}`]; + + if (release.codename) { + label.push(release.codename); + } + + acc[key].items.push({ + label: label.join(' '), + link: `/download/${release.major}`, + }); + + return acc; + }, {}) + ); + type DownloadTable = ReturnType; +type Sidebar = ReturnType; type WithSimplifiedDownloadProps = { - children: FC; + children: FC; }; const WithSimplifiedDownload: FC = async ({ @@ -15,6 +49,7 @@ const WithSimplifiedDownload: FC = async ({ }) => { const { pathname } = getClientContext(); const releaseData = await getReleaseData(); + const sidebarGroups = mapSidebarItems(releaseData); const version = pathname.split('/').pop(); const matchingRelease = releaseData.find( @@ -25,7 +60,7 @@ const WithSimplifiedDownload: FC = async ({ if (matchingRelease !== undefined) { const table = getDownloadTable(matchingRelease); - return ; + return ; } return null; diff --git a/apps/site/layouts/DownloadSimple.tsx b/apps/site/layouts/DownloadSimple.tsx index ab2578265f64b..11b7ec63e79fa 100644 --- a/apps/site/layouts/DownloadSimple.tsx +++ b/apps/site/layouts/DownloadSimple.tsx @@ -1,112 +1,39 @@ -import { getLocale } from 'next-intl/server'; import type { FC, PropsWithChildren } from 'react'; -import { getClientContext, setClientContext } from '#site/client-context'; import WithFooter from '#site/components/withFooter'; +import WithMarkdownContent from '#site/components/withMarkdownContent'; import WithMetaBar from '#site/components/withMetaBar'; import WithNavBar from '#site/components/withNavBar'; import WithProgressionSidebar from '#site/components/withProgressionSidebar'; -import getReleaseData from '#site/next-data/releaseData'; -import { dynamicRouter } from '#site/next.dynamic.mjs'; +import WithSimplifiedDownload from '#site/components/withSimplifiedDownload'; import ArticleLayout from './Article'; -const mapSidebarItems = ( - releaseData: Awaited> -) => - Object.values( - releaseData.reduce< - Record< - string, - { groupName: string; items: Array<{ label: string; link: string }> } - > - >((acc, release) => { - const key = release.status; - if (!acc[key]) { - acc[key] = { - groupName: key, - items: [], - }; - } - - const label = [`v${release.major}`]; - - if (release.codename) { - label.push(release.codename); - } - - acc[key].items.push({ - label: label.join(' '), - link: `/download/${release.major}`, - }); - - return acc; - }, {}) - ); - -const getMarkdownContent = async ( - locale: string, - pathname: string, - file: Array -) => { - const filePathname = dynamicRouter.getPathname(file); - - // We retrieve the source of the Markdown file by doing an educated guess - // of what possible files could be the source of the page, since the extension - // context is lost from `getStaticProps` as a limitation of Next.js itself - const { source, filename } = await dynamicRouter.getMarkdownFile( - locale, - filePathname - ); - - // This parses the source Markdown content and returns a React Component and - // relevant context from the Markdown File - const { content, frontmatter, headings, readingTime } = - await dynamicRouter.getMDXContent(source, filename); - - // Metadata and shared Context to be available through the lifecycle of the page - const sharedContext = { - frontmatter, - headings, - readingTime, - filename, - pathname: `/${pathname}`, - }; - - // Defines a shared Server Context for the Client-Side - // That is shared for all pages under the dynamic router - setClientContext(sharedContext); - - return { content }; -}; - -const DownloadLayout: FC = async ({ children }) => { - const releaseData = await getReleaseData(); - const sidebarGroups = mapSidebarItems(releaseData); - - const locale = await getLocale(); - const { pathname } = getClientContext(); - const { content } = await getMarkdownContent(locale, pathname, [ - 'download', - 'simplified', - ]); - - return ( - <> - - - - - -
-
{children || content}
- -
-
- - - - ); -}; +const DownloadLayout: FC = async ({ children }) => ( + <> + + + + + {({ mappedReleases }) => ( + <> + +
+
+ +
+ +
+ + )} +
+
+ + + +); export default DownloadLayout; From 644882fdc62850a72058f4e375b3fc4c6e7c95ce Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Sun, 25 May 2025 17:54:41 +0300 Subject: [PATCH 07/38] refactor: simple download utils --- .../SimplifiedDownloadTable/index.tsx | 2 +- .../components/withSimplifiedDownload.tsx | 40 +- apps/site/util/downloadUtils.tsx | 448 ------------------ apps/site/util/downloadUtils/index.tsx | 2 +- apps/site/util/downloadUtils/simple.ts | 163 +++++++ 5 files changed, 170 insertions(+), 485 deletions(-) delete mode 100644 apps/site/util/downloadUtils.tsx create mode 100644 apps/site/util/downloadUtils/simple.ts diff --git a/apps/site/components/Downloads/SimplifiedDownloadTable/index.tsx b/apps/site/components/Downloads/SimplifiedDownloadTable/index.tsx index 46448a503c3f9..f9a9e46de499b 100644 --- a/apps/site/components/Downloads/SimplifiedDownloadTable/index.tsx +++ b/apps/site/components/Downloads/SimplifiedDownloadTable/index.tsx @@ -5,8 +5,8 @@ import type { FC } from 'react'; import Link from '#site/components/Link'; import type { UserOS } from '#site/types/userOS'; -import type { ParsedArtifact } from '#site/util/downloadUtils'; import { OperatingSystemLabel } from '#site/util/downloadUtils'; +import type { ParsedArtifact } from '#site/util/downloadUtils/simple'; type SimplifiedDownloadTableProps = { source: Array; diff --git a/apps/site/components/withSimplifiedDownload.tsx b/apps/site/components/withSimplifiedDownload.tsx index c004a5d824634..0347245e5c3e5 100644 --- a/apps/site/components/withSimplifiedDownload.tsx +++ b/apps/site/components/withSimplifiedDownload.tsx @@ -2,40 +2,10 @@ import type { FC } from 'react'; import { getClientContext } from '#site/client-context'; import getReleaseData from '#site/next-data/releaseData'; -import { getDownloadTable } from '#site/util/downloadUtils'; - -const mapSidebarItems = ( - releaseData: Awaited> -) => - Object.values( - releaseData.reduce< - Record< - string, - { groupName: string; items: Array<{ label: string; link: string }> } - > - >((acc, release) => { - const key = release.status; - if (!acc[key]) { - acc[key] = { - groupName: key, - items: [], - }; - } - - const label = [`v${release.major}`]; - - if (release.codename) { - label.push(release.codename); - } - - acc[key].items.push({ - label: label.join(' '), - link: `/download/${release.major}`, - }); - - return acc; - }, {}) - ); +import { + getDownloadTable, + mapSidebarItems, +} from '#site/util/downloadUtils/simple'; type DownloadTable = ReturnType; type Sidebar = ReturnType; @@ -47,8 +17,8 @@ type WithSimplifiedDownloadProps = { const WithSimplifiedDownload: FC = async ({ children: Component, }) => { - const { pathname } = getClientContext(); const releaseData = await getReleaseData(); + const { pathname } = getClientContext(); const sidebarGroups = mapSidebarItems(releaseData); const version = pathname.split('/').pop(); diff --git a/apps/site/util/downloadUtils.tsx b/apps/site/util/downloadUtils.tsx deleted file mode 100644 index ec7760d0d8644..0000000000000 --- a/apps/site/util/downloadUtils.tsx +++ /dev/null @@ -1,448 +0,0 @@ -import type { SelectValue } from '@node-core/ui-components/Common/Select'; -import * as InstallMethodIcons from '@node-core/ui-components/Icons/InstallationMethod'; -import * as OSIcons from '@node-core/ui-components/Icons/OperatingSystem'; -import * as PackageManagerIcons from '@node-core/ui-components/Icons/PackageManager'; -import satisfies from 'semver/functions/satisfies'; - -import { DIST_URL } from '#site/next.constants.mjs'; -import type { NodeRelease, NodeReleaseStatus } from '#site/types'; -import type * as Types from '#site/types/release'; -import type { UserOS, UserPlatform } from '#site/types/userOS'; - -import { getNodeDownloadUrl } from './getNodeDownloadUrl'; -import type { DownloadKind } from './getNodeDownloadUrl'; - -// This is a manual list of OS's that do not support/have a way of being installed -// with an executable installer. This is used to disable the installer button. -// Note: Windows has one tiny exception for x64 on Node.js versions < 4.0.0 -export const OS_NOT_SUPPORTING_INSTALLERS: Array = [ - 'LINUX', - 'AIX', - 'OTHER', - 'LOADING', -]; - -export enum OperatingSystemLabel { - WIN = 'Windows', - MAC = 'macOS', - LINUX = 'Linux', - AIX = 'AIX', - OTHER = 'Other', - LOADING = 'N/A', -} - -export enum InstallationMethodLabel { - NVM = 'nvm', - FNM = 'fnm', - BREW = 'Brew', - CHOCO = 'Chocolatey', - DEVBOX = 'Devbox', - DOCKER = 'Docker', - N = 'n', - VOLTA = 'Volta', -} - -export enum PackageManagerLabel { - NPM = 'npm', - YARN = 'Yarn', - PNPM = 'pnpm', -} - -type DownloadCompatibility = { - os: Array; - installMethod: Array; - platform: Array; - semver: Array; - releases: Array; -}; - -// Defines the Type definition for a Release Dropdown Item -type DownloadDropdownItem = { - // A label to be used within the Dropdown message - label: string; - // A flag that indicates if the item is recommended or not (official or community based) - recommended?: boolean; - // A URL pointing to the docs or support page for the item - url?: string; - // A bottom info that provides additional information about the item - info?: IntlMessageKeys; - // A compatibility object that defines the compatibility of the item with the current Release Context - compatibility: Partial; -} & Omit, 'label'>; - -// This function is used to get the next item in the dropdown -// when the current item is disabeld/excluded/not valid -// And this is useful when a parent release value (i.e. OS) is changed -// and requires the current dropdown (i.e. Platform) to be updated -export const nextItem = ( - current: T, - items: Array> -): T => { - const item = items.find(({ value }) => String(value) === String(current)); - - const isDisabledOrExcluded = !item || item.disabled; - - if (isDisabledOrExcluded) { - const nextItem = items.find(({ disabled }) => !disabled); - - if (nextItem) { - return nextItem.value; - } - } - - return current; -}; - -// This function is used to parse the compatibility of the dropdown items -// In a nice and static way that allows a lot of abstraction and flexibility -export const parseCompat = < - K extends string, - T extends DownloadDropdownItem, ->( - items: Array, - { os, installMethod, platform, version, release }: Types.ReleaseContextType -): Array => { - const satisfiesSemver = (semver: string) => satisfies(version, semver); - - const supportsOS = (i: T['compatibility']) => i.os?.includes(os) ?? true; - - const supportsInstallMethod = (i: T['compatibility']) => - i.installMethod?.includes(installMethod) ?? true; - - const supportsPlatform = (i: T['compatibility']) => - i.platform?.includes(platform) ?? true; - - const supportsVersion = (i: T['compatibility']) => - i.semver?.some(satisfiesSemver) ?? true; - - const supportsRelease = (i: T['compatibility']) => - i.releases?.includes(release.status) ?? true; - - return items.map(item => ({ - ...item, - disabled: - !supportsOS(item.compatibility) || - !supportsInstallMethod(item.compatibility) || - !supportsPlatform(item.compatibility) || - !supportsVersion(item.compatibility) || - !supportsRelease(item.compatibility), - })); -}; - -export type ParsedArtifact = { - file: string; - kind: DownloadKind; - os: UserOS; - architecture: string; - url: string; - version: string; -}; - -export function generateCompatibleDownloads({ - platforms = PLATFORMS, - exclude = [], - version, - kind = 'binary', -}: { - platforms?: Record>>; - exclude?: Array; - version: string; - kind?: DownloadKind; -}): Array { - return Object.entries(platforms).reduce>( - (acc, [os, items]) => { - if (exclude.includes(os as UserOS)) return acc; - - const operatingSystem = os as UserOS; - items.forEach(({ compatibility, value, label }) => { - const { - os: operatingSystems, - platform: platforms, - semver: versions, - } = compatibility; - - if ( - (operatingSystems?.includes(operatingSystem) ?? true) && - (platforms?.includes(value) ?? true) && - (versions?.every(r => satisfies(version, r)) ?? true) - ) { - const url = getNodeDownloadUrl(version, operatingSystem, value, kind); - - acc.push({ - file: url.replace(`${DIST_URL}${version}/`, ''), - kind: kind, - os: operatingSystem, - architecture: label, - url: url, - version: version, - }); - } - }); - - return acc; - }, - [] - ); -} - -export const getDownloadTable = ({ - versionWithPrefix, - minorVersions, -}: NodeRelease) => ({ - binaries: generateCompatibleDownloads({ - version: versionWithPrefix, - kind: 'binary' as DownloadKind, - }), - urls: { - shasum: getNodeDownloadUrl( - versionWithPrefix, - 'LOADING', - 'x64', - 'shasum' as DownloadKind - ), - source: getNodeDownloadUrl( - versionWithPrefix, - 'LOADING', - 'x64', - 'source' as DownloadKind - ), - changelog: `https://github.com/nodejs/node/releases/tag/${versionWithPrefix}`, - blogPost: `/blog/release/${versionWithPrefix}`, - }, - installers: generateCompatibleDownloads({ - exclude: OS_NOT_SUPPORTING_INSTALLERS, - version: versionWithPrefix, - kind: 'installer' as DownloadKind, - }), - version: versionWithPrefix, - minors: minorVersions - .filter(minor => `v${minor.version}` !== versionWithPrefix) - .map(minor => { - const versionWithPrefix = `v${minor.version}`; - - return { - binaries: generateCompatibleDownloads({ - version: versionWithPrefix, - kind: 'binary' as DownloadKind, - }), - installers: generateCompatibleDownloads({ - exclude: OS_NOT_SUPPORTING_INSTALLERS, - version: versionWithPrefix, - kind: 'installer' as DownloadKind, - }), - version: versionWithPrefix, - urls: { - source: getNodeDownloadUrl( - versionWithPrefix, - 'LOADING', - 'x64', - 'source' as DownloadKind - ), - changelog: `https://github.com/nodejs/node/releases/tag/${versionWithPrefix}`, - blogPost: `/blog/release/${versionWithPrefix}`, - }, - }; - }), -}); - -export const OPERATING_SYSTEMS: Array> = [ - { - label: OperatingSystemLabel.WIN, - value: 'WIN', - compatibility: {}, - iconImage: , - }, - { - label: OperatingSystemLabel.MAC, - value: 'MAC', - compatibility: {}, - iconImage: , - }, - { - label: OperatingSystemLabel.LINUX, - value: 'LINUX', - compatibility: {}, - iconImage: , - }, - { - label: OperatingSystemLabel.AIX, - value: 'AIX', - compatibility: { installMethod: [''], semver: ['>= 6.7.0'] }, - iconImage: , - }, -]; - -export const INSTALL_METHODS: Array< - DownloadDropdownItem & - // Since the ReleaseCodeBox requires an info key to be provided, we force this - // to be mandatory for install methods - Required< - Pick, 'info' | 'url'> - > -> = [ - { - label: InstallationMethodLabel.NVM, - value: 'NVM', - compatibility: { os: ['MAC', 'LINUX', 'OTHER'] }, - iconImage: , - recommended: true, - url: 'https://github.com/nvm-sh/nvm', - info: 'layouts.download.codeBox.platformInfo.nvm', - }, - { - label: InstallationMethodLabel.FNM, - value: 'FNM', - compatibility: { os: ['MAC', 'LINUX', 'WIN'] }, - iconImage: , - recommended: true, - url: 'https://github.com/Schniz/fnm', - info: 'layouts.download.codeBox.platformInfo.fnm', - }, - { - label: InstallationMethodLabel.BREW, - value: 'BREW', - compatibility: { os: ['MAC', 'LINUX'], releases: ['Current', 'LTS'] }, - iconImage: , - url: 'https://brew.sh/', - info: 'layouts.download.codeBox.platformInfo.brew', - }, - { - label: InstallationMethodLabel.DEVBOX, - value: 'DEVBOX', - compatibility: { os: ['MAC', 'LINUX'] }, - iconImage: , - url: 'https://jetify.com/devbox/', - info: 'layouts.download.codeBox.platformInfo.devbox', - }, - { - label: InstallationMethodLabel.CHOCO, - value: 'CHOCO', - compatibility: { os: ['WIN'] }, - iconImage: , - url: 'https://chocolatey.org/', - info: 'layouts.download.codeBox.platformInfo.choco', - }, - { - label: InstallationMethodLabel.DOCKER, - value: 'DOCKER', - compatibility: { os: ['WIN', 'MAC', 'LINUX'] }, - iconImage: , - recommended: true, - url: 'https://docs.docker.com/get-started/get-docker/', - info: 'layouts.download.codeBox.platformInfo.docker', - }, - { - label: InstallationMethodLabel.N, - value: 'N', - compatibility: { os: ['MAC', 'LINUX'] }, - iconImage: , - url: 'https://github.com/tj/n', - info: 'layouts.download.codeBox.platformInfo.n', - }, - { - label: InstallationMethodLabel.VOLTA, - value: 'VOLTA', - compatibility: { os: ['WIN', 'MAC', 'LINUX'] }, - iconImage: , - url: 'https://docs.volta.sh/guide/getting-started', - info: 'layouts.download.codeBox.platformInfo.volta', - }, -]; - -export const PACKAGE_MANAGERS: Array< - DownloadDropdownItem -> = [ - { - label: PackageManagerLabel.NPM, - value: 'NPM', - compatibility: {}, - iconImage: , - }, - { - label: PackageManagerLabel.YARN, - value: 'YARN', - compatibility: { semver: ['>= v14.19.0', '>= v16.9.0'] }, - iconImage: , - }, - { - label: PackageManagerLabel.PNPM, - value: 'PNPM', - compatibility: { semver: ['>= v14.19.0', '>= v16.9.0'] }, - iconImage: , - }, -]; - -export const PLATFORMS: Record< - UserOS, - Array> -> = { - WIN: [ - { - label: 'x64', - value: 'x64', - compatibility: { - semver: ['>= 4.0.0'], - }, - }, - { - label: 'x86', - value: 'x86', - compatibility: { semver: ['< 23.0.0'] }, - }, - { - label: 'ARM64', - value: 'arm64', - compatibility: { semver: ['>= 19.9.0'] }, - }, - ], - MAC: [ - { - label: 'x64', - value: 'x64', - compatibility: {}, - }, - { - label: 'ARM64', - value: 'arm64', - compatibility: { - semver: ['>= 16.0.0'], - }, - }, - ], - LINUX: [ - { - label: 'x64', - value: 'x64', - compatibility: {}, - }, - { - label: 'ARMv7', - value: 'armv7l', - compatibility: { semver: ['>= 4.0.0'] }, - }, - { - label: 'ARM64', - value: 'arm64', - compatibility: { semver: ['>= 4.0.0'] }, - }, - { - label: 'Power LE', - value: 'ppc64le', - compatibility: { semver: ['>= 4.4.0'] }, - }, - { - label: 'System Z', - value: 's390x', - compatibility: { semver: ['>= 6.6.0'] }, - }, - ], - AIX: [ - { - label: 'Power', - value: 'ppc64', - compatibility: { semver: ['>= 6.7.0'] }, - }, - ], - OTHER: [], - LOADING: [], -}; diff --git a/apps/site/util/downloadUtils/index.tsx b/apps/site/util/downloadUtils/index.tsx index 2ecfeb77bdea0..6d5ff71d3075c 100644 --- a/apps/site/util/downloadUtils/index.tsx +++ b/apps/site/util/downloadUtils/index.tsx @@ -32,7 +32,7 @@ type DownloadCompatibility = { releases?: Array; }; -type DownloadDropdownItem = { +export type DownloadDropdownItem = { label: string; recommended?: boolean; url?: string; diff --git a/apps/site/util/downloadUtils/simple.ts b/apps/site/util/downloadUtils/simple.ts new file mode 100644 index 0000000000000..fbe64011ffb57 --- /dev/null +++ b/apps/site/util/downloadUtils/simple.ts @@ -0,0 +1,163 @@ +import { satisfies } from 'semver'; + +import type getReleaseData from '#site/next-data/releaseData'; +import type { NodeRelease } from '#site/types/releases'; +import type { UserOS, UserPlatform } from '#site/types/userOS'; + +import type { DownloadKind } from '../getNodeDownloadUrl'; +import { getNodeDownloadUrl } from '../getNodeDownloadUrl'; + +import type { DownloadDropdownItem } from '.'; +import { PLATFORMS, OS_NOT_SUPPORTING_INSTALLERS } from '.'; + +import { DIST_URL, BASE_CHANGELOG_URL } from '#site/next.constants'; + +export type ParsedArtifact = { + file: string; + kind: DownloadKind; + os: UserOS; + architecture: string; + url: string; + version: string; +}; + +function generateCompatibleDownloads({ + platforms = PLATFORMS, + exclude = [], + version, + kind = 'binary', +}: { + platforms?: Record>>; + exclude?: Array; + version: string; + kind?: DownloadKind; +}): Array { + return Object.entries(platforms).reduce>( + (acc, [os, items]) => { + if (exclude.includes(os as UserOS)) return acc; + + const operatingSystem = os as UserOS; + items.forEach(({ compatibility, value, label }) => { + const { + os: operatingSystems, + platform: platforms, + semver: versions, + } = compatibility; + + if ( + (operatingSystems?.includes(operatingSystem) ?? true) && + (platforms?.includes(value) ?? true) && + (versions?.every(r => satisfies(version, r)) ?? true) + ) { + const url = getNodeDownloadUrl(version, operatingSystem, value, kind); + + acc.push({ + file: url.replace(`${DIST_URL}${version}/`, ''), + kind: kind, + os: operatingSystem, + architecture: label, + url: url, + version: version, + }); + } + }); + + return acc; + }, + [] + ); +} + +export const getDownloadTable = ({ + versionWithPrefix, + version, + minorVersions, +}: NodeRelease) => ({ + binaries: generateCompatibleDownloads({ + version: versionWithPrefix, + kind: 'binary' as DownloadKind, + }), + urls: { + shasum: getNodeDownloadUrl( + versionWithPrefix, + 'LOADING', + 'x64', + 'shasum' as DownloadKind + ), + source: getNodeDownloadUrl( + versionWithPrefix, + 'LOADING', + 'x64', + 'source' as DownloadKind + ), + changelog: `${BASE_CHANGELOG_URL}${version}`, + blogPost: `/blog/release/${versionWithPrefix}`, + }, + installers: generateCompatibleDownloads({ + exclude: OS_NOT_SUPPORTING_INSTALLERS, + version: versionWithPrefix, + kind: 'installer' as DownloadKind, + }), + version: versionWithPrefix, + minors: minorVersions + .filter(minor => `v${minor.version}` !== versionWithPrefix) + .map(minor => { + const versionWithPrefix = `v${minor.version}`; + + return { + binaries: generateCompatibleDownloads({ + version: versionWithPrefix, + kind: 'binary' as DownloadKind, + }), + installers: generateCompatibleDownloads({ + exclude: OS_NOT_SUPPORTING_INSTALLERS, + version: versionWithPrefix, + kind: 'installer' as DownloadKind, + }), + version: versionWithPrefix, + urls: { + source: getNodeDownloadUrl( + versionWithPrefix, + 'LOADING', + 'x64', + 'source' as DownloadKind + ), + changelog: `${BASE_CHANGELOG_URL}${minor.version}`, + blogPost: `/blog/release/${versionWithPrefix}`, + }, + }; + }), +}); + +export const mapSidebarItems = ( + releaseData: Awaited> +) => + Object.values( + releaseData.reduce< + Record< + string, + { groupName: string; items: Array<{ label: string; link: string }> } + > + >((acc, release) => { + const key = release.status; + if (!acc[key]) { + acc[key] = { + groupName: key, + items: [], + }; + } + + const label = [`v${release.major}`]; + + if (release.codename) { + label.push(release.codename); + } + + acc[key].items.push({ + label: label.join(' '), + link: `/download/${release.major}`, + }); + + return acc; + }, {}) + ); From 4a0fd1cce569a9bac57295ee3ac25928ebbc56db Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Sun, 25 May 2025 18:10:41 +0300 Subject: [PATCH 08/38] refactor: getNodeDownloadUrl args destructuring --- .../Downloads/DownloadButton/index.tsx | 6 ++- .../components/Downloads/DownloadLink.tsx | 12 ++--- .../Release/PrebuiltDownloadButtons.tsx | 14 +++++- .../__tests__/getNodeDownloadUrl.test.mjs | 34 +++++++++++--- apps/site/util/downloadUtils/simple.ts | 39 +++++++-------- apps/site/util/getNodeDownloadUrl.ts | 47 +++++++++++-------- 6 files changed, 94 insertions(+), 58 deletions(-) diff --git a/apps/site/components/Downloads/DownloadButton/index.tsx b/apps/site/components/Downloads/DownloadButton/index.tsx index f238ce4546505..d835af5ac0d0d 100644 --- a/apps/site/components/Downloads/DownloadButton/index.tsx +++ b/apps/site/components/Downloads/DownloadButton/index.tsx @@ -21,7 +21,11 @@ const DownloadButton: FC> = ({ const { os, bitness, architecture } = useClientContext(); const platform = getUserPlatform(architecture, bitness); - const downloadLink = getNodeDownloadUrl(versionWithPrefix, os, platform); + const downloadLink = getNodeDownloadUrl({ + version: versionWithPrefix, + os: os, + platform: platform, + }); return ( <> diff --git a/apps/site/components/Downloads/DownloadLink.tsx b/apps/site/components/Downloads/DownloadLink.tsx index e639a83d2ac7c..6bb0dea587fe0 100644 --- a/apps/site/components/Downloads/DownloadLink.tsx +++ b/apps/site/components/Downloads/DownloadLink.tsx @@ -20,12 +20,12 @@ const DownloadLink: FC> = ({ const platform = getUserPlatform(architecture, bitness); - const downloadLink = getNodeDownloadUrl( - versionWithPrefix, - os, - platform, - kind - ); + const downloadLink = getNodeDownloadUrl({ + version: versionWithPrefix, + os: os, + platform: platform, + kind: kind, + }); return {children}; }; diff --git a/apps/site/components/Downloads/Release/PrebuiltDownloadButtons.tsx b/apps/site/components/Downloads/Release/PrebuiltDownloadButtons.tsx index ca037f6a443c2..d015294397775 100644 --- a/apps/site/components/Downloads/Release/PrebuiltDownloadButtons.tsx +++ b/apps/site/components/Downloads/Release/PrebuiltDownloadButtons.tsx @@ -22,11 +22,21 @@ const PrebuiltDownloadButtons: FC = () => { const { release, os, platform } = useContext(ReleaseContext); const installerUrl = platform - ? getNodeDownloadUrl(release.versionWithPrefix, os, platform, 'installer') + ? getNodeDownloadUrl({ + version: release.versionWithPrefix, + os: os, + platform: platform, + kind: 'installer', + }) : ''; const binaryUrl = platform - ? getNodeDownloadUrl(release.versionWithPrefix, os, platform, 'binary') + ? getNodeDownloadUrl({ + version: release.versionWithPrefix, + os: os, + platform: platform, + kind: 'binary', + }) : ''; return ( diff --git a/apps/site/util/__tests__/getNodeDownloadUrl.test.mjs b/apps/site/util/__tests__/getNodeDownloadUrl.test.mjs index 8ec40abda7fb2..ae8bbad354e0b 100644 --- a/apps/site/util/__tests__/getNodeDownloadUrl.test.mjs +++ b/apps/site/util/__tests__/getNodeDownloadUrl.test.mjs @@ -11,7 +11,10 @@ describe('getNodeDownloadUrl', () => { const bitness = 86; const expectedUrl = 'https://nodejs.org/dist/v18.16.0/node-v18.16.0.pkg'; - assert.equal(getNodeDownloadUrl(version, os, bitness), expectedUrl); + assert.equal( + getNodeDownloadUrl({ version: version, os: os, platform: bitness }), + expectedUrl + ); }); it('should return the correct download URL for Windows (32-bit)', () => { @@ -20,7 +23,10 @@ describe('getNodeDownloadUrl', () => { const expectedUrl = 'https://nodejs.org/dist/v18.16.0/node-v18.16.0-x86.msi'; - assert.equal(getNodeDownloadUrl(version, os, bitness), expectedUrl); + assert.equal( + getNodeDownloadUrl({ version: version, os: os, platform: bitness }), + expectedUrl + ); }); it('should return the correct download URL for Windows (64-bit)', () => { @@ -29,7 +35,10 @@ describe('getNodeDownloadUrl', () => { const expectedUrl = 'https://nodejs.org/dist/v18.16.0/node-v18.16.0-x64.msi'; - assert.equal(getNodeDownloadUrl(version, os, bitness), expectedUrl); + assert.equal( + getNodeDownloadUrl({ version: version, os: os, platform: bitness }), + expectedUrl + ); }); it('should return the default download URL for other operating systems', () => { @@ -37,19 +46,32 @@ describe('getNodeDownloadUrl', () => { 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); + assert.equal( + getNodeDownloadUrl({ version: version, os: os, platform: bitness }), + expectedUrl + ); }); describe('MAC', () => { it('should return .pkg link for installer', () => { - const url = getNodeDownloadUrl('v18.0.0', 'MAC', 'x64', 'installer'); + const url = getNodeDownloadUrl({ + version: 'v18.0.0', + os: 'MAC', + platform: 'x64', + kind: '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'); + const url = getNodeDownloadUrl({ + version: 'v18.0.0', + os: 'WIN', + platform: 'x64', + kind: 'installer', + }); assert.ok(url.includes('.msi')); }); }); diff --git a/apps/site/util/downloadUtils/simple.ts b/apps/site/util/downloadUtils/simple.ts index fbe64011ffb57..1faaebc0288aa 100644 --- a/apps/site/util/downloadUtils/simple.ts +++ b/apps/site/util/downloadUtils/simple.ts @@ -49,7 +49,12 @@ function generateCompatibleDownloads({ (platforms?.includes(value) ?? true) && (versions?.every(r => satisfies(version, r)) ?? true) ) { - const url = getNodeDownloadUrl(version, operatingSystem, value, kind); + const url = getNodeDownloadUrl({ + version: version, + os: operatingSystem, + platform: value, + kind: kind, + }); acc.push({ file: url.replace(`${DIST_URL}${version}/`, ''), @@ -75,28 +80,18 @@ export const getDownloadTable = ({ }: NodeRelease) => ({ binaries: generateCompatibleDownloads({ version: versionWithPrefix, - kind: 'binary' as DownloadKind, + kind: 'binary', }), urls: { - shasum: getNodeDownloadUrl( - versionWithPrefix, - 'LOADING', - 'x64', - 'shasum' as DownloadKind - ), - source: getNodeDownloadUrl( - versionWithPrefix, - 'LOADING', - 'x64', - 'source' as DownloadKind - ), + shasum: getNodeDownloadUrl({ version: versionWithPrefix, kind: 'shasum' }), + source: getNodeDownloadUrl({ version: versionWithPrefix, kind: 'source' }), changelog: `${BASE_CHANGELOG_URL}${version}`, blogPost: `/blog/release/${versionWithPrefix}`, }, installers: generateCompatibleDownloads({ exclude: OS_NOT_SUPPORTING_INSTALLERS, version: versionWithPrefix, - kind: 'installer' as DownloadKind, + kind: 'installer', }), version: versionWithPrefix, minors: minorVersions @@ -107,21 +102,19 @@ export const getDownloadTable = ({ return { binaries: generateCompatibleDownloads({ version: versionWithPrefix, - kind: 'binary' as DownloadKind, + kind: 'binary', }), installers: generateCompatibleDownloads({ exclude: OS_NOT_SUPPORTING_INSTALLERS, version: versionWithPrefix, - kind: 'installer' as DownloadKind, + kind: 'installer', }), version: versionWithPrefix, urls: { - source: getNodeDownloadUrl( - versionWithPrefix, - 'LOADING', - 'x64', - 'source' as DownloadKind - ), + source: getNodeDownloadUrl({ + version: versionWithPrefix, + kind: 'source', + }), changelog: `${BASE_CHANGELOG_URL}${minor.version}`, blogPost: `/blog/release/${versionWithPrefix}`, }, diff --git a/apps/site/util/getNodeDownloadUrl.ts b/apps/site/util/getNodeDownloadUrl.ts index cfe972bd45240..00dcced12275e 100644 --- a/apps/site/util/getNodeDownloadUrl.ts +++ b/apps/site/util/getNodeDownloadUrl.ts @@ -3,16 +3,23 @@ import type { UserOS, UserPlatform } from '#site/types/userOS'; export type DownloadKind = 'installer' | 'binary' | 'source' | 'shasum'; -export const getNodeDownloadUrl = ( - versionWithPrefix: string, - os: UserOS | 'LOADING', - platform: UserPlatform = 'x64', - kind: DownloadKind = 'installer' -) => { - const baseURL = `${DIST_URL}${versionWithPrefix}`; +type DownloadOptions = { + version: string; + os?: UserOS | 'LOADING'; + platform?: UserPlatform; + kind?: DownloadKind; +}; + +export const getNodeDownloadUrl = ({ + version, + os = 'LOADING', + platform = 'x64', + kind = 'installer', +}: DownloadOptions) => { + const baseURL = `${DIST_URL}${version}`; if (kind === 'source') { - return `${baseURL}/node-${versionWithPrefix}.tar.gz`; + return `${baseURL}/node-${version}.tar.gz`; } if (kind === 'shasum') { @@ -23,57 +30,57 @@ export const getNodeDownloadUrl = ( case 'MAC': // Prepares a downloadable Node.js installer link for the x64, ARM64 platforms if (kind === 'installer') { - return `${baseURL}/node-${versionWithPrefix}.pkg`; + return `${baseURL}/node-${version}.pkg`; } // Prepares a downloadable Node.js link for the ARM64 platform if (typeof platform === 'string') { - return `${baseURL}/node-${versionWithPrefix}-darwin-${platform}.tar.gz`; + return `${baseURL}/node-${version}-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`; + return `${baseURL}/node-${version}-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`; + return `${baseURL}/node-${version}-${platform}.msi`; } // Prepares a downloadable Node.js installer link for the x64 and x86 platforms - return `${baseURL}/node-${versionWithPrefix}-x${platform}.msi`; + return `${baseURL}/node-${version}-x${platform}.msi`; } // Prepares a downloadable Node.js link for the ARM64 platform if (typeof platform === 'string') { - return `${baseURL}/node-${versionWithPrefix}-win-${platform}.zip`; + return `${baseURL}/node-${version}-win-${platform}.zip`; } // Prepares a downloadable Node.js link for the x64 and x86 platforms - return `${baseURL}/node-${versionWithPrefix}-win-x${platform}.zip`; + return `${baseURL}/node-${version}-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`; + return `${baseURL}/node-${version}-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`; + return `${baseURL}/node-${version}-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-${version}-aix-${platform}.tar.gz`; } - return `${baseURL}/node-${versionWithPrefix}-aix-ppc64.tar.gz`; + return `${baseURL}/node-${version}-aix-ppc64.tar.gz`; default: // Prepares a downloadable Node.js source code link - return `${baseURL}/node-${versionWithPrefix}.tar.gz`; + return `${baseURL}/node-${version}.tar.gz`; } }; From 426f4ed4ff2d75727357280caefdb0216203639c Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Sun, 25 May 2025 18:30:59 +0300 Subject: [PATCH 09/38] refactor: localize and improve sidebar getter --- .../components/withSimplifiedDownload.tsx | 17 ++-- apps/site/layouts/DownloadSimple.tsx | 4 +- apps/site/util/downloadUtils/simple.ts | 77 +++++++++++-------- packages/i18n/locales/en.json | 8 ++ 4 files changed, 66 insertions(+), 40 deletions(-) diff --git a/apps/site/components/withSimplifiedDownload.tsx b/apps/site/components/withSimplifiedDownload.tsx index 0347245e5c3e5..352d36003bd0b 100644 --- a/apps/site/components/withSimplifiedDownload.tsx +++ b/apps/site/components/withSimplifiedDownload.tsx @@ -1,17 +1,18 @@ +import { getTranslations } from 'next-intl/server'; import type { FC } from 'react'; import { getClientContext } from '#site/client-context'; import getReleaseData from '#site/next-data/releaseData'; import { getDownloadTable, - mapSidebarItems, + groupReleasesForSidebar, } from '#site/util/downloadUtils/simple'; type DownloadTable = ReturnType; -type Sidebar = ReturnType; +type Sidebar = ReturnType; type WithSimplifiedDownloadProps = { - children: FC; + children: FC; }; const WithSimplifiedDownload: FC = async ({ @@ -19,7 +20,13 @@ const WithSimplifiedDownload: FC = async ({ }) => { const releaseData = await getReleaseData(); const { pathname } = getClientContext(); - const sidebarGroups = mapSidebarItems(releaseData); + const t = await getTranslations(); + + const mappedSidebarItems = groupReleasesForSidebar(releaseData); + const localizedSidebarItems = mappedSidebarItems.map(item => ({ + ...item, + groupName: t(`layouts.simpleDownload.sidebar.${item.groupName}`), + })); const version = pathname.split('/').pop(); const matchingRelease = releaseData.find( @@ -30,7 +37,7 @@ const WithSimplifiedDownload: FC = async ({ if (matchingRelease !== undefined) { const table = getDownloadTable(matchingRelease); - return ; + return ; } return null; diff --git a/apps/site/layouts/DownloadSimple.tsx b/apps/site/layouts/DownloadSimple.tsx index 11b7ec63e79fa..f8fd936aa0dff 100644 --- a/apps/site/layouts/DownloadSimple.tsx +++ b/apps/site/layouts/DownloadSimple.tsx @@ -15,9 +15,9 @@ const DownloadLayout: FC = async ({ children }) => ( - {({ mappedReleases }) => ( + {({ mappedSidebarItems }) => ( <> - +
`v${minor.version}` !== versionWithPrefix) + .filter(minor => `v${minor.version}` !== versionWithPrefix) // Exclude the current version .map(minor => { const versionWithPrefix = `v${minor.version}`; @@ -116,41 +118,50 @@ export const getDownloadTable = ({ kind: 'source', }), changelog: `${BASE_CHANGELOG_URL}${minor.version}`, - blogPost: `/blog/release/${versionWithPrefix}`, + blogPost: `${RELEASE_POST_URL}${versionWithPrefix}`, }, }; }), }); -export const mapSidebarItems = ( +export const groupReleasesForSidebar = ( releaseData: Awaited> -) => - Object.values( - releaseData.reduce< - Record< - string, - { groupName: string; items: Array<{ label: string; link: string }> } - > - >((acc, release) => { - const key = release.status; - if (!acc[key]) { - acc[key] = { - groupName: key, - items: [], - }; - } - - const label = [`v${release.major}`]; - - if (release.codename) { - label.push(release.codename); - } - - acc[key].items.push({ - label: label.join(' '), - link: `/download/${release.major}`, - }); +): Array<{ + groupName: string; + items: Array<{ label: string; link: string }>; +}> => { + // Reduce the release data into a record grouped by release status (e.g., 'LTS', 'Current') + const grouped = releaseData.reduce< + Record< + string, + { groupName: string; items: Array<{ label: string; link: string }> } + > + >((acc, release) => { + const statusKey = release.status; + + // Initialize the group if it doesn't exist yet + if (!acc[statusKey]) { + acc[statusKey] = { + groupName: statusKey, + items: [], + }; + } - return acc; - }, {}) - ); + // Build the label: always include major version, optionally codename + const labelParts = [`v${release.major}`]; + if (release.codename) { + labelParts.push(release.codename); + } + + // Add the release to the group's items + acc[statusKey].items.push({ + label: labelParts.join(' '), + link: `/download/${release.major}`, + }); + + return acc; + }, {}); + + // Return the grouped items as an array for sidebar consumption + return Object.values(grouped); +}; diff --git a/packages/i18n/locales/en.json b/packages/i18n/locales/en.json index b7d386295fec0..2442b40122e5f 100644 --- a/packages/i18n/locales/en.json +++ b/packages/i18n/locales/en.json @@ -323,6 +323,14 @@ } } }, + "simpleDownload": { + "sidebar": { + "Current": "Current", + "LTS": "LTS", + "Maintenance": "Maintenance", + "End-of-life": "End of Life" + } + }, "logo": "Node.js logo" } } From 1f6e3dbead1a8ccfe7687b8ae1f1cd6e3b669efe Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Sun, 25 May 2025 18:58:11 +0300 Subject: [PATCH 10/38] refactor: better naming, docs and alias import --- .../SimplifiedDownloadTable/index.tsx | 3 +- .../components/withSimplifiedDownload.tsx | 21 ++- apps/site/util/downloadUtils/simple.ts | 144 ++++++++++-------- 3 files changed, 91 insertions(+), 77 deletions(-) diff --git a/apps/site/components/Downloads/SimplifiedDownloadTable/index.tsx b/apps/site/components/Downloads/SimplifiedDownloadTable/index.tsx index f9a9e46de499b..cbfa826460133 100644 --- a/apps/site/components/Downloads/SimplifiedDownloadTable/index.tsx +++ b/apps/site/components/Downloads/SimplifiedDownloadTable/index.tsx @@ -4,7 +4,6 @@ import { useTranslations } from 'next-intl'; import type { FC } from 'react'; import Link from '#site/components/Link'; -import type { UserOS } from '#site/types/userOS'; import { OperatingSystemLabel } from '#site/util/downloadUtils'; import type { ParsedArtifact } from '#site/util/downloadUtils/simple'; @@ -39,7 +38,7 @@ const SimplifiedDownloadTable: FC = ({ - {OperatingSystemLabel[release.os as UserOS]} + {OperatingSystemLabel[release.os]} {release.architecture} diff --git a/apps/site/components/withSimplifiedDownload.tsx b/apps/site/components/withSimplifiedDownload.tsx index 352d36003bd0b..2241de413e8cd 100644 --- a/apps/site/components/withSimplifiedDownload.tsx +++ b/apps/site/components/withSimplifiedDownload.tsx @@ -4,12 +4,12 @@ import type { FC } from 'react'; import { getClientContext } from '#site/client-context'; import getReleaseData from '#site/next-data/releaseData'; import { - getDownloadTable, - groupReleasesForSidebar, + buildReleaseArtifacts, + groupReleasesByStatus, } from '#site/util/downloadUtils/simple'; -type DownloadTable = ReturnType; -type Sidebar = ReturnType; +type DownloadTable = ReturnType; +type Sidebar = ReturnType; type WithSimplifiedDownloadProps = { children: FC; @@ -22,7 +22,7 @@ const WithSimplifiedDownload: FC = async ({ const { pathname } = getClientContext(); const t = await getTranslations(); - const mappedSidebarItems = groupReleasesForSidebar(releaseData); + const mappedSidebarItems = groupReleasesByStatus(releaseData); const localizedSidebarItems = mappedSidebarItems.map(item => ({ ...item, groupName: t(`layouts.simpleDownload.sidebar.${item.groupName}`), @@ -35,9 +35,14 @@ const WithSimplifiedDownload: FC = async ({ ); if (matchingRelease !== undefined) { - const table = getDownloadTable(matchingRelease); - - return ; + const releaseArtifacts = buildReleaseArtifacts(matchingRelease); + + return ( + + ); } return null; diff --git a/apps/site/util/downloadUtils/simple.ts b/apps/site/util/downloadUtils/simple.ts index ad5a3c336aae9..c0aa750b2bdb7 100644 --- a/apps/site/util/downloadUtils/simple.ts +++ b/apps/site/util/downloadUtils/simple.ts @@ -1,14 +1,17 @@ +import type ProgressionSidebarGroup from '@node-core/ui-components/Common/ProgressionSidebar/ProgressionSidebarGroup'; +import type { ComponentProps } from 'react'; import { satisfies } from 'semver'; import type getReleaseData from '#site/next-data/releaseData'; import type { NodeRelease } from '#site/types/releases'; import type { UserOS, UserPlatform } from '#site/types/userOS'; - -import type { DownloadKind } from '../getNodeDownloadUrl'; -import { getNodeDownloadUrl } from '../getNodeDownloadUrl'; - -import type { DownloadDropdownItem } from '.'; -import { PLATFORMS, OS_NOT_SUPPORTING_INSTALLERS } from '.'; +import type { DownloadDropdownItem } from '#site/util/downloadUtils'; +import { + PLATFORMS, + OS_NOT_SUPPORTING_INSTALLERS, +} from '#site/util/downloadUtils'; +import type { DownloadKind } from '#site/util/getNodeDownloadUrl'; +import { getNodeDownloadUrl } from '#site/util/getNodeDownloadUrl'; import { DIST_URL, BASE_CHANGELOG_URL } from '#site/next.constants'; @@ -23,64 +26,77 @@ export type ParsedArtifact = { version: string; }; -function generateCompatibleDownloads({ - platforms = PLATFORMS, - exclude = [], - version, - kind = 'binary', -}: { +/** + * Checks if a download item is compatible with the given OS, platform, and version. + */ +function isCompatible( + compatibility: DownloadDropdownItem['compatibility'], + os: UserOS, + platform: UserPlatform, + version: string +): boolean { + const { + os: osList, + platform: platformList, + semver: versions, + } = compatibility; + + return ( + (osList?.includes(os) ?? true) && + (platformList?.includes(platform) ?? true) && + (versions?.every(r => satisfies(version, r)) ?? true) + ); +} + +type CompatibleArtifactOptions = { platforms?: Record>>; exclude?: Array; version: string; kind?: DownloadKind; -}): Array { - return Object.entries(platforms).reduce>( - (acc, [os, items]) => { - if (exclude.includes(os as UserOS)) return acc; - - const operatingSystem = os as UserOS; - items.forEach(({ compatibility, value, label }) => { - const { - os: operatingSystems, - platform: platforms, - semver: versions, - } = compatibility; - - if ( - (operatingSystems?.includes(operatingSystem) ?? true) && - (platforms?.includes(value) ?? true) && - (versions?.every(r => satisfies(version, r)) ?? true) - ) { - const url = getNodeDownloadUrl({ - version: version, - os: operatingSystem, - platform: value, - kind: kind, - }); - - acc.push({ - file: url.replace(`${DIST_URL}${version}/`, ''), - kind: kind, - os: operatingSystem, - architecture: label, - url: url, - version: version, - }); - } - }); +}; - return acc; - }, - [] - ); +/** + * Returns a list of compatible artifacts for the given options. + */ +function getCompatibleArtifacts({ + platforms = PLATFORMS, + exclude = [], + version, + kind = 'binary', +}: CompatibleArtifactOptions): Array { + return Object.entries(platforms).flatMap(([os, items]) => { + if (exclude.includes(os as UserOS)) return []; + const operatingSystem = os as UserOS; + return items + .filter(({ compatibility, value }) => + isCompatible(compatibility, operatingSystem, value, version) + ) + .map(({ value, label }) => { + const url = getNodeDownloadUrl({ + version: version, + os: operatingSystem, + platform: value, + kind: kind, + }); + + return { + file: url.replace(`${DIST_URL}${version}/`, ''), + kind: kind, + os: operatingSystem, + architecture: label, + url: url, + version: version, + }; + }); + }); } -export const getDownloadTable = ({ +export const buildReleaseArtifacts = ({ versionWithPrefix, version, minorVersions, }: NodeRelease) => ({ - binaries: generateCompatibleDownloads({ + binaries: getCompatibleArtifacts({ version: versionWithPrefix, kind: 'binary', }), @@ -90,7 +106,7 @@ export const getDownloadTable = ({ changelog: `${BASE_CHANGELOG_URL}${version}`, blogPost: `${RELEASE_POST_URL}${versionWithPrefix}`, }, - installers: generateCompatibleDownloads({ + installers: getCompatibleArtifacts({ exclude: OS_NOT_SUPPORTING_INSTALLERS, version: versionWithPrefix, kind: 'installer', @@ -102,11 +118,11 @@ export const getDownloadTable = ({ const versionWithPrefix = `v${minor.version}`; return { - binaries: generateCompatibleDownloads({ + binaries: getCompatibleArtifacts({ version: versionWithPrefix, kind: 'binary', }), - installers: generateCompatibleDownloads({ + installers: getCompatibleArtifacts({ exclude: OS_NOT_SUPPORTING_INSTALLERS, version: versionWithPrefix, kind: 'installer', @@ -124,19 +140,13 @@ export const getDownloadTable = ({ }), }); -export const groupReleasesForSidebar = ( +type k = ComponentProps; + +export const groupReleasesByStatus = ( releaseData: Awaited> -): Array<{ - groupName: string; - items: Array<{ label: string; link: string }>; -}> => { +): Array => { // Reduce the release data into a record grouped by release status (e.g., 'LTS', 'Current') - const grouped = releaseData.reduce< - Record< - string, - { groupName: string; items: Array<{ label: string; link: string }> } - > - >((acc, release) => { + const grouped = releaseData.reduce>((acc, release) => { const statusKey = release.status; // Initialize the group if it doesn't exist yet From dfd47a0b8f17b69a62418a2f2c25e6eafbaf20f1 Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Sun, 25 May 2025 18:59:24 +0300 Subject: [PATCH 11/38] chore: sidebargroup naming --- apps/site/util/downloadUtils/simple.ts | 57 ++++++++++++++------------ 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/apps/site/util/downloadUtils/simple.ts b/apps/site/util/downloadUtils/simple.ts index c0aa750b2bdb7..71d091a8ced93 100644 --- a/apps/site/util/downloadUtils/simple.ts +++ b/apps/site/util/downloadUtils/simple.ts @@ -140,37 +140,40 @@ export const buildReleaseArtifacts = ({ }), }); -type k = ComponentProps; +type SidebarGroup = ComponentProps; export const groupReleasesByStatus = ( releaseData: Awaited> -): Array => { +): Array => { // Reduce the release data into a record grouped by release status (e.g., 'LTS', 'Current') - const grouped = releaseData.reduce>((acc, release) => { - const statusKey = release.status; - - // Initialize the group if it doesn't exist yet - if (!acc[statusKey]) { - acc[statusKey] = { - groupName: statusKey, - items: [], - }; - } - - // Build the label: always include major version, optionally codename - const labelParts = [`v${release.major}`]; - if (release.codename) { - labelParts.push(release.codename); - } - - // Add the release to the group's items - acc[statusKey].items.push({ - label: labelParts.join(' '), - link: `/download/${release.major}`, - }); - - return acc; - }, {}); + const grouped = releaseData.reduce>( + (acc, release) => { + const statusKey = release.status; + + // Initialize the group if it doesn't exist yet + if (!acc[statusKey]) { + acc[statusKey] = { + groupName: statusKey, + items: [], + }; + } + + // Build the label: always include major version, optionally codename + const labelParts = [`v${release.major}`]; + if (release.codename) { + labelParts.push(release.codename); + } + + // Add the release to the group's items + acc[statusKey].items.push({ + label: labelParts.join(' '), + link: `/download/${release.major}`, + }); + + return acc; + }, + {} + ); // Return the grouped items as an array for sidebar consumption return Object.values(grouped); From fd09988c3b67a280e54b083c7655893e2254e420 Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Sun, 25 May 2025 22:46:31 +0300 Subject: [PATCH 12/38] fix: active simplified sidebar and more docs --- .../index.tsx | 22 ++++------ .../Downloads/Release/ReleaseCodeBox.tsx | 2 +- .../components/withSimplifiedDownload.tsx | 43 +++++++++++++++---- apps/site/next.mdx.use.mjs | 4 +- apps/site/pages/en/download/simplified.mdx | 8 ++-- apps/site/util/downloadUtils/simple.ts | 27 +++++++++--- apps/site/util/getNodeDownloadUrl.ts | 14 ++++++ packages/i18n/locales/en.json | 4 +- 8 files changed, 89 insertions(+), 35 deletions(-) rename apps/site/components/Downloads/{SimplifiedDownloadTable => DownloadsTable}/index.tsx (59%) diff --git a/apps/site/components/Downloads/SimplifiedDownloadTable/index.tsx b/apps/site/components/Downloads/DownloadsTable/index.tsx similarity index 59% rename from apps/site/components/Downloads/SimplifiedDownloadTable/index.tsx rename to apps/site/components/Downloads/DownloadsTable/index.tsx index cbfa826460133..b6433bf7f0db6 100644 --- a/apps/site/components/Downloads/SimplifiedDownloadTable/index.tsx +++ b/apps/site/components/Downloads/DownloadsTable/index.tsx @@ -7,40 +7,36 @@ import Link from '#site/components/Link'; import { OperatingSystemLabel } from '#site/util/downloadUtils'; import type { ParsedArtifact } from '#site/util/downloadUtils/simple'; -type SimplifiedDownloadTableProps = { +type DownloadsTableProps = { source: Array; }; -const SimplifiedDownloadTable: FC = ({ - source, -}) => { +const DownloadsTable: FC = ({ source }) => { const t = useTranslations(); return ( - + {source.map(release => ( - - - @@ -50,4 +46,4 @@ const SimplifiedDownloadTable: FC = ({ ); }; -export default SimplifiedDownloadTable; +export default DownloadsTable; diff --git a/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx b/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx index 1ee5e6303d0c0..7a9d1c8cb4c97 100644 --- a/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx +++ b/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx @@ -106,7 +106,7 @@ const ReleaseCodeBox: FC = () => { > {t.rich('layouts.download.codeBox.noScriptDetected', { link: text => ( - + {text} ), diff --git a/apps/site/components/withSimplifiedDownload.tsx b/apps/site/components/withSimplifiedDownload.tsx index 2241de413e8cd..f406a0d028cc7 100644 --- a/apps/site/components/withSimplifiedDownload.tsx +++ b/apps/site/components/withSimplifiedDownload.tsx @@ -1,3 +1,19 @@ +/** + * @fileoverview + * Higher-order component to provide simplified download data and sidebar items + * to its child component, based on the current route and release data. + * + * - Fetches release data and translations + * - Determines the current version from the URL + * - Finds the matching release and builds download artifacts + * - Localizes sidebar group names + * + * Usage: + * + * {(props) => } + * + */ + import { getTranslations } from 'next-intl/server'; import type { FC } from 'react'; @@ -8,33 +24,44 @@ import { groupReleasesByStatus, } from '#site/util/downloadUtils/simple'; -type DownloadTable = ReturnType; -type Sidebar = ReturnType; +type SimplifiedDownload = ReturnType & { + mappedSidebarItems: ReturnType; +}; type WithSimplifiedDownloadProps = { - children: FC; + children: FC; }; +/** + * Provides download artifacts and sidebar items to its child component + */ const WithSimplifiedDownload: FC = async ({ children: Component, }) => { - const releaseData = await getReleaseData(); + const [releaseData, t] = await Promise.all([ + getReleaseData(), + getTranslations(), + ]); const { pathname } = getClientContext(); - const t = await getTranslations(); - const mappedSidebarItems = groupReleasesByStatus(releaseData); + // Group and localize sidebar items + const mappedSidebarItems = groupReleasesByStatus(releaseData, pathname); const localizedSidebarItems = mappedSidebarItems.map(item => ({ ...item, groupName: t(`layouts.simpleDownload.sidebar.${item.groupName}`), })); - const version = pathname.split('/').pop(); + // Extract version from pathname + const version = pathname?.split('/').pop(); + if (!version) return null; + + // Find the matching release const matchingRelease = releaseData.find( ({ major, isLts }) => major === Number(version) || (isLts === true && version === 'simplified') ); - if (matchingRelease !== undefined) { + if (matchingRelease) { const releaseArtifacts = buildReleaseArtifacts(matchingRelease); return ( diff --git a/apps/site/next.mdx.use.mjs b/apps/site/next.mdx.use.mjs index 7f5e597369a06..40ab027792b7a 100644 --- a/apps/site/next.mdx.use.mjs +++ b/apps/site/next.mdx.use.mjs @@ -1,7 +1,7 @@ 'use strict'; import DownloadReleasesTable from './components/Downloads/DownloadReleasesTable'; -import SimplifiedDownloadTable from './components/Downloads/SimplifiedDownloadTable'; +import DownloadsTable from './components/Downloads/DownloadsTable'; import UpcomingMeetings from './components/MDX/Calendar/UpcomingMeetings'; import WithBadgeGroup from './components/withBadgeGroup'; import WithBanner from './components/withBanner'; @@ -16,7 +16,7 @@ import WithSimplifiedDownload from './components/withSimplifiedDownload'; export const mdxComponents = { DownloadReleasesTable: DownloadReleasesTable, WithSimplifiedDownload: WithSimplifiedDownload, - SimplifiedDownloadTable: SimplifiedDownloadTable, + DownloadsTable: DownloadsTable, // HOC for getting Node.js Release Metadata WithNodeRelease: WithNodeRelease, // HOC for providing Banner Data diff --git a/apps/site/pages/en/download/simplified.mdx b/apps/site/pages/en/download/simplified.mdx index c3dc215815d13..32fd5c278cbf2 100644 --- a/apps/site/pages/en/download/simplified.mdx +++ b/apps/site/pages/en/download/simplified.mdx @@ -9,9 +9,9 @@ layout: download-simple {({ binaries, installers, version }) => ( <>

Node.js® {version} – Binary Downloads

- +

Node.js® {version} – Installer Packages

- + )} @@ -56,9 +56,9 @@ Learn more about [Node.js releases](/about/previous-releases), including the rel

Binary Downloads

- +

Installer Packages

- +

Read the{' '} changelog{' '} diff --git a/apps/site/util/downloadUtils/simple.ts b/apps/site/util/downloadUtils/simple.ts index 71d091a8ced93..c4a245d8885a4 100644 --- a/apps/site/util/downloadUtils/simple.ts +++ b/apps/site/util/downloadUtils/simple.ts @@ -143,8 +143,11 @@ export const buildReleaseArtifacts = ({ type SidebarGroup = ComponentProps; export const groupReleasesByStatus = ( - releaseData: Awaited> + releaseData: Awaited>, + pathname: string ): Array => { + let simplified = false; + // Reduce the release data into a record grouped by release status (e.g., 'LTS', 'Current') const grouped = releaseData.reduce>( (acc, release) => { @@ -156,6 +159,11 @@ export const groupReleasesByStatus = ( groupName: statusKey, items: [], }; + + // Check if the current pathname indicates a simplified download page + if (statusKey === 'LTS' && pathname.endsWith('simplified')) { + simplified = true; + } } // Build the label: always include major version, optionally codename @@ -165,10 +173,19 @@ export const groupReleasesByStatus = ( } // Add the release to the group's items - acc[statusKey].items.push({ - label: labelParts.join(' '), - link: `/download/${release.major}`, - }); + if (simplified) { + acc[statusKey].items.push({ + label: labelParts.join(' '), + link: `/download/simplified`, + }); + + simplified = false; + } else { + acc[statusKey].items.push({ + label: labelParts.join(' '), + link: `/download/${release.major}`, + }); + } return acc; }, diff --git a/apps/site/util/getNodeDownloadUrl.ts b/apps/site/util/getNodeDownloadUrl.ts index 00dcced12275e..f1ff8c2b0e1a4 100644 --- a/apps/site/util/getNodeDownloadUrl.ts +++ b/apps/site/util/getNodeDownloadUrl.ts @@ -10,6 +10,20 @@ type DownloadOptions = { kind?: DownloadKind; }; +/** + * Generates a Node.js download URL for the given options. + * + * @param options - The download options. + * @param options.version - The Node.js version string, must include the 'v' prefix (e.g., 'v20.12.2'). + * @param options.os - The target operating system. Defaults to 'LOADING'. + * @param options.platform - The target platform/architecture (e.g., 'x64', 'arm64'). Defaults to 'x64'. + * @param options.kind - The type of download artifact. Can be 'installer', 'binary', 'source', or 'shasum'. Defaults to 'installer'. + * @returns The fully qualified URL to the requested Node.js artifact. + * + * @example + * getNodeDownloadUrl({ version: 'v20.12.2', os: 'MAC', platform: 'arm64', kind: 'binary' }); + * // => 'https://nodejs.org/dist/v20.12.2/node-v20.12.2-darwin-arm64.tar.gz' + */ export const getNodeDownloadUrl = ({ version, os = 'LOADING', diff --git a/packages/i18n/locales/en.json b/packages/i18n/locales/en.json index 2442b40122e5f..fcfbe6577c1d5 100644 --- a/packages/i18n/locales/en.json +++ b/packages/i18n/locales/en.json @@ -156,7 +156,7 @@ "status": "Status", "details": "Details" }, - "simpleDownloadTable": { + "downloadsTable": { "fileName": "File Name", "operatingSystem": "OS", "architecture": "Architecture" @@ -309,7 +309,7 @@ "unsupportedVersionWarning": "This version is out of maintenance. Please use a currently supported version. Understand EOL support.", "communityPlatformInfo": "Installation methods that involve community software are supported by the teams maintaining that software.", "externalSupportInfo": "If you encounter any issues please visit {platform}'s website", - "noScriptDetected": "This page requires JavaScript. You can download Node.js without JavaScript by visiting the releases page directly.", + "noScriptDetected": "This page requires JavaScript. You can download Node.js without JavaScript by visiting the simplified download page directly.", "platformInfo": { "default": "{platform} and their installation scripts are not maintained by the Node.js project.", "nvm": "\"nvm\" is a cross-platform Node.js version manager.", From 0f1ae0a09a5c0efffef815c6095ab0236c36839c Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Wed, 28 May 2025 17:48:42 +0300 Subject: [PATCH 13/38] feat: simple download meta bar added --- apps/site/components/withMarkdownContent.tsx | 44 +++++-------------- apps/site/components/withMetaBar.tsx | 13 ++++-- .../components/withSimplifiedDownload.tsx | 21 +++++---- apps/site/layouts/DownloadSimple.tsx | 15 +++---- apps/site/pages/en/download/simplified.mdx | 22 ++++++---- .../downloadUtils/{simple.ts => simple.tsx} | 40 +++++++++++++++-- packages/i18n/locales/en.json | 14 ++++-- packages/ui-components/styles/markdown.css | 5 ++- 8 files changed, 104 insertions(+), 70 deletions(-) rename apps/site/util/downloadUtils/{simple.ts => simple.tsx} (85%) diff --git a/apps/site/components/withMarkdownContent.tsx b/apps/site/components/withMarkdownContent.tsx index 33dc546eab124..3177c8d281f39 100644 --- a/apps/site/components/withMarkdownContent.tsx +++ b/apps/site/components/withMarkdownContent.tsx @@ -1,15 +1,9 @@ import { getLocale } from 'next-intl/server'; -import type { FC, PropsWithChildren } from 'react'; - -import { getClientContext, setClientContext } from '#site/client-context'; +import type { FC } from 'react'; import { dynamicRouter } from '#site/next.dynamic'; -const getMarkdownContent = async ( - locale: string, - pathname: string, - file: Array -) => { +const getMarkdownContent = async (locale: string, file: Array) => { const filePathname = dynamicRouter.getPathname(file); // We retrieve the source of the Markdown file by doing an educated guess @@ -20,40 +14,22 @@ const getMarkdownContent = async ( filePathname ); - // This parses the source Markdown content and returns a React Component and - // relevant context from the Markdown File - const { content, frontmatter, headings, readingTime } = - await dynamicRouter.getMDXContent(source, filename); - - // Metadata and shared Context to be available through the lifecycle of the page - const sharedContext = { - frontmatter, - headings, - readingTime, - filename, - pathname: `/${pathname}`, - }; - - // Defines a shared Server Context for the Client-Side - // That is shared for all pages under the dynamic router - setClientContext(sharedContext); - - return { content }; + // This parses the source Markdown content and returns a React Component + // from the Markdown File + const { content } = await dynamicRouter.getMDXContent(source, filename); + + return content; }; type WithMarkdownContentProps = { file: Array; - fallback?: React.ReactNode; }; -const WithMarkdownContent: FC< - PropsWithChildren -> = async ({ file, fallback: children }) => { +const WithMarkdownContent: FC = async ({ file }) => { const locale = await getLocale(); - const { pathname } = getClientContext(); - const { content } = await getMarkdownContent(locale, pathname, file); + const content = await getMarkdownContent(locale, file); - return content || children || null; + return content || null; }; export default WithMarkdownContent; diff --git a/apps/site/components/withMetaBar.tsx b/apps/site/components/withMetaBar.tsx index a7dd313ad8167..cc7b36508f693 100644 --- a/apps/site/components/withMetaBar.tsx +++ b/apps/site/components/withMetaBar.tsx @@ -3,7 +3,7 @@ import MetaBar from '@node-core/ui-components/Containers/MetaBar'; import GitHubIcon from '@node-core/ui-components/Icons/Social/GitHub'; import { useFormatter, useLocale, useTranslations } from 'next-intl'; -import type { FC } from 'react'; +import type { ComponentProps, FC } from 'react'; import Link from '#site/components/Link'; import WithAvatarGroup from '#site/components/withAvatarGroup'; @@ -14,7 +14,11 @@ import { TRANSLATION_URL } from '#site/next.constants.mjs'; import { defaultLocale } from '#site/next.locales.mjs'; import { getGitHubBlobUrl } from '#site/util/gitHubUtils'; -const WithMetaBar: FC = () => { +type WithMetaBarProps = { + items?: ComponentProps['items']; +}; + +const WithMetaBar: FC = ({ items }) => { const { headings, readingTime, frontmatter, filename } = useClientContext(); const formatter = useFormatter(); const lastUpdated = frontmatter.date @@ -45,8 +49,11 @@ const WithMetaBar: FC = () => { heading={t('components.metabar.tableOfContents')} as={Link} items={{ + ...items, [t('components.metabar.lastUpdated')]: lastUpdated, - [t('components.metabar.readingTime')]: readingTimeText, + ...(readingTime.minutes >= 1 && { + [t('components.metabar.readingTime')]: readingTimeText, + }), ...(usernames.length && { [t( `components.metabar.${usernames.length > 1 ? 'authors' : 'author'}` diff --git a/apps/site/components/withSimplifiedDownload.tsx b/apps/site/components/withSimplifiedDownload.tsx index f406a0d028cc7..68d19e5f86100 100644 --- a/apps/site/components/withSimplifiedDownload.tsx +++ b/apps/site/components/withSimplifiedDownload.tsx @@ -14,18 +14,21 @@ * */ +import type MetaBar from '@node-core/ui-components/Containers/MetaBar/index.jsx'; import { getTranslations } from 'next-intl/server'; -import type { FC } from 'react'; +import type { ComponentProps, FC } from 'react'; import { getClientContext } from '#site/client-context'; import getReleaseData from '#site/next-data/releaseData'; import { + buildMetaBarItems, buildReleaseArtifacts, groupReleasesByStatus, } from '#site/util/downloadUtils/simple'; type SimplifiedDownload = ReturnType & { - mappedSidebarItems: ReturnType; + sidebarItems: ReturnType; + metabarItems: ComponentProps['items']; }; type WithSimplifiedDownloadProps = { @@ -38,17 +41,17 @@ type WithSimplifiedDownloadProps = { const WithSimplifiedDownload: FC = async ({ children: Component, }) => { + const { pathname } = getClientContext(); const [releaseData, t] = await Promise.all([ getReleaseData(), getTranslations(), ]); - const { pathname } = getClientContext(); // Group and localize sidebar items const mappedSidebarItems = groupReleasesByStatus(releaseData, pathname); const localizedSidebarItems = mappedSidebarItems.map(item => ({ ...item, - groupName: t(`layouts.simpleDownload.sidebar.${item.groupName}`), + groupName: t(`layouts.simpleDownload.statusNames.${item.groupName}`), })); // Extract version from pathname @@ -56,18 +59,20 @@ const WithSimplifiedDownload: FC = async ({ if (!version) return null; // Find the matching release - const matchingRelease = releaseData.find( + const release = releaseData.find( ({ major, isLts }) => major === Number(version) || (isLts === true && version === 'simplified') ); - if (matchingRelease) { - const releaseArtifacts = buildReleaseArtifacts(matchingRelease); + if (release) { + const releaseArtifacts = buildReleaseArtifacts(release); + const metabarItems = buildMetaBarItems(release, t); return ( ); } diff --git a/apps/site/layouts/DownloadSimple.tsx b/apps/site/layouts/DownloadSimple.tsx index f8fd936aa0dff..8d126655f6b79 100644 --- a/apps/site/layouts/DownloadSimple.tsx +++ b/apps/site/layouts/DownloadSimple.tsx @@ -1,4 +1,4 @@ -import type { FC, PropsWithChildren } from 'react'; +import type { FC } from 'react'; import WithFooter from '#site/components/withFooter'; import WithMarkdownContent from '#site/components/withMarkdownContent'; @@ -9,23 +9,20 @@ import WithSimplifiedDownload from '#site/components/withSimplifiedDownload'; import ArticleLayout from './Article'; -const DownloadLayout: FC = async ({ children }) => ( +const DownloadLayout: FC = async () => ( <> - {({ mappedSidebarItems }) => ( + {({ sidebarItems, metabarItems }) => ( <> - +

- +
- +
)} diff --git a/apps/site/pages/en/download/simplified.mdx b/apps/site/pages/en/download/simplified.mdx index 32fd5c278cbf2..990d969764fc8 100644 --- a/apps/site/pages/en/download/simplified.mdx +++ b/apps/site/pages/en/download/simplified.mdx @@ -19,14 +19,15 @@ layout: download-simple Learn more about [Node.js releases](/about/previous-releases), including the release schedule and LTS status. - {({ version, urls }) => ( + {({ version, release }) => ( <>

- Read the changelog{' '} - or blog post for this version. + Read the{' '} + changelog or{' '} + blog post for this version.

- Signed SHASUMS for + Signed SHASUMS for release files. How to{' '} verify @@ -35,7 +36,7 @@ Learn more about [Node.js releases](/about/previous-releases), including the rel

Download a signed{' '} - + Node.js {version} source {' '} tarball. @@ -49,7 +50,7 @@ Learn more about [Node.js releases](/about/previous-releases), including the rel minors.length > 0 && ( <>

Other versions

- {minors.map(({ binaries, installers, version, urls }) => ( + {minors.map(({ binaries, installers, version, release }) => (

Node.js® {version}

@@ -61,12 +62,15 @@ Learn more about [Node.js releases](/about/previous-releases), including the rel

Read the{' '} - changelog{' '} - or blog post for this version. + + changelog + {' '} + or blog post for this + version.

Download a signed{' '} - + Node.js {version} source {' '} tarball. diff --git a/apps/site/util/downloadUtils/simple.ts b/apps/site/util/downloadUtils/simple.tsx similarity index 85% rename from apps/site/util/downloadUtils/simple.ts rename to apps/site/util/downloadUtils/simple.tsx index c4a245d8885a4..fbc8080259e15 100644 --- a/apps/site/util/downloadUtils/simple.ts +++ b/apps/site/util/downloadUtils/simple.tsx @@ -2,6 +2,7 @@ import type ProgressionSidebarGroup from '@node-core/ui-components/Common/Progre import type { ComponentProps } from 'react'; import { satisfies } from 'semver'; +import FormattedTime from '#site/components/Common/FormattedTime'; import type getReleaseData from '#site/next-data/releaseData'; import type { NodeRelease } from '#site/types/releases'; import type { UserOS, UserPlatform } from '#site/types/userOS'; @@ -65,8 +66,10 @@ function getCompatibleArtifacts({ kind = 'binary', }: CompatibleArtifactOptions): Array { return Object.entries(platforms).flatMap(([os, items]) => { - if (exclude.includes(os as UserOS)) return []; + if (exclude.includes(os)) return []; + const operatingSystem = os as UserOS; + return items .filter(({ compatibility, value }) => isCompatible(compatibility, operatingSystem, value, version) @@ -100,7 +103,7 @@ export const buildReleaseArtifacts = ({ version: versionWithPrefix, kind: 'binary', }), - urls: { + release: { shasum: getNodeDownloadUrl({ version: versionWithPrefix, kind: 'shasum' }), source: getNodeDownloadUrl({ version: versionWithPrefix, kind: 'source' }), changelog: `${BASE_CHANGELOG_URL}${version}`, @@ -128,7 +131,7 @@ export const buildReleaseArtifacts = ({ kind: 'installer', }), version: versionWithPrefix, - urls: { + release: { source: getNodeDownloadUrl({ version: versionWithPrefix, kind: 'source', @@ -140,6 +143,37 @@ export const buildReleaseArtifacts = ({ }), }); +export const buildMetaBarItems = ( + { + codename, + status, + currentStart, + releaseDate, + minorVersions, + modules, + npm, + v8, + }: NodeRelease, + t: (key: string) => string +) => ({ + ...(codename && { + [t('layouts.simpleDownload.codename')]: codename, + }), + [t('layouts.simpleDownload.status')]: t( + `layouts.simpleDownload.statusNames.${status}` + ), + [t('layouts.simpleDownload.firstReleased')]: ( + + ), + [t('layouts.simpleDownload.lastUpdated')]: ( + + ), + [t('layouts.simpleDownload.minorVersions')]: minorVersions.length, + [t('layouts.simpleDownload.nApiVersion')]: modules, + [t('layouts.simpleDownload.npmVersion')]: npm, + [t('layouts.simpleDownload.v8Version')]: v8, +}); + type SidebarGroup = ComponentProps; export const groupReleasesByStatus = ( diff --git a/packages/i18n/locales/en.json b/packages/i18n/locales/en.json index fcfbe6577c1d5..f3a8d657bae45 100644 --- a/packages/i18n/locales/en.json +++ b/packages/i18n/locales/en.json @@ -324,13 +324,21 @@ } }, "simpleDownload": { - "sidebar": { + "statusNames": { "Current": "Current", "LTS": "LTS", "Maintenance": "Maintenance", "End-of-life": "End of Life" - } + }, + "codename": "Codename", + "status": "Status", + "firstReleased": "First Released", + "lastUpdated": "Last Updated", + "minorVersions": "Minor Versions", + "nApiVersion": "N-API Version", + "npmVersion": "npm Version", + "v8Version": "V8 Version" }, "logo": "Node.js logo" } -} +} \ No newline at end of file diff --git a/packages/ui-components/styles/markdown.css b/packages/ui-components/styles/markdown.css index 644194f6d3458..b651d65edac81 100644 --- a/packages/ui-components/styles/markdown.css +++ b/packages/ui-components/styles/markdown.css @@ -191,7 +191,10 @@ details { select-none after:float-right after:pr-4 - after:content-['›']; + after:text-2xl + after:text-neutral-400 + after:content-['›'] + dark:after:text-neutral-600; > * { @apply inline; From 9c966396c3519fc3d0b6c807d4e69dc7e00b54c1 Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Fri, 30 May 2025 00:20:37 +0300 Subject: [PATCH 14/38] chore: details styles moved into the site app --- packages/ui-components/styles/markdown.css | 44 ---------------------- 1 file changed, 44 deletions(-) diff --git a/packages/ui-components/styles/markdown.css b/packages/ui-components/styles/markdown.css index b651d65edac81..862cd6450f05e 100644 --- a/packages/ui-components/styles/markdown.css +++ b/packages/ui-components/styles/markdown.css @@ -171,47 +171,3 @@ main { } } } - -details { - @apply border-b - border-neutral-200 - px-0 - pb-4 - text-neutral-900 - dark:border-neutral-800 - dark:text-white; - - summary::marker { - @apply hidden - content-['']; - } - - summary { - @apply cursor-default - select-none - after:float-right - after:pr-4 - after:text-2xl - after:text-neutral-400 - after:content-['›'] - dark:after:text-neutral-600; - - > * { - @apply inline; - } - } - - &[open] summary { - @apply pb-4 - after:rotate-90 - after:transform - after:pt-4; - } - - > *:not(summary) { - @apply flex - select-none - flex-col - gap-4; - } -} From bb5c03c03e207789c61e0f94cb0246b43238907f Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Fri, 30 May 2025 00:27:45 +0300 Subject: [PATCH 15/38] chore: details styles moved into the site app --- .../components/MDX/Details/index.module.css | 41 +++++++++++++++++++ apps/site/components/MDX/Details/index.tsx | 21 ++++++++++ apps/site/next.mdx.use.mjs | 6 +++ apps/site/pages/en/download/simplified.mdx | 11 ++--- 4 files changed, 72 insertions(+), 7 deletions(-) create mode 100644 apps/site/components/MDX/Details/index.module.css create mode 100644 apps/site/components/MDX/Details/index.tsx diff --git a/apps/site/components/MDX/Details/index.module.css b/apps/site/components/MDX/Details/index.module.css new file mode 100644 index 0000000000000..e9f2739a72192 --- /dev/null +++ b/apps/site/components/MDX/Details/index.module.css @@ -0,0 +1,41 @@ +@reference "../../../styles/index.css"; + +.root { + @apply border-b + border-neutral-200 + px-0 + pb-4 + text-neutral-900 + dark:border-neutral-800 + dark:text-white; + + summary { + @apply cursor-default + select-none + list-none + after:float-right + after:pr-4 + after:text-2xl + after:text-neutral-400 + after:content-['›'] + dark:after:text-neutral-600; + + h3 { + @apply inline; + } + } + + &[open] summary { + @apply pb-4 + after:rotate-90 + after:transform + after:pt-4; + } + + .detail { + @apply flex + select-none + flex-col + gap-4; + } +} diff --git a/apps/site/components/MDX/Details/index.tsx b/apps/site/components/MDX/Details/index.tsx new file mode 100644 index 0000000000000..803730a7db7e0 --- /dev/null +++ b/apps/site/components/MDX/Details/index.tsx @@ -0,0 +1,21 @@ +import type { FC, PropsWithChildren } from 'react'; + +import styles from './index.module.css'; + +type DetailsProps = { + summary: React.ReactNode; +}; + +const Details: FC> = ({ + children, + summary, +}) => ( +

+ +

{summary}

+
+
{children}
+
+); + +export default Details; diff --git a/apps/site/next.mdx.use.mjs b/apps/site/next.mdx.use.mjs index 40ab027792b7a..624a369bc8965 100644 --- a/apps/site/next.mdx.use.mjs +++ b/apps/site/next.mdx.use.mjs @@ -3,6 +3,7 @@ import DownloadReleasesTable from './components/Downloads/DownloadReleasesTable'; import DownloadsTable from './components/Downloads/DownloadsTable'; import UpcomingMeetings from './components/MDX/Calendar/UpcomingMeetings'; +import Details from './components/MDX/Details'; import WithBadgeGroup from './components/withBadgeGroup'; import WithBanner from './components/withBanner'; import WithNodeRelease from './components/withNodeRelease'; @@ -14,8 +15,13 @@ import WithSimplifiedDownload from './components/withSimplifiedDownload'; * @satisfies {import('mdx/types').MDXComponents} */ export const mdxComponents = { + // Renders a table with Node.js Releases with diffrent platforms and architectures DownloadReleasesTable: DownloadReleasesTable, + // HOC for providing the Simplified Download Page properties WithSimplifiedDownload: WithSimplifiedDownload, + // Renders a Details Component with a summary and content + Details: Details, + // Renders a table with all Node.js Downloads DownloadsTable: DownloadsTable, // HOC for getting Node.js Release Metadata WithNodeRelease: WithNodeRelease, diff --git a/apps/site/pages/en/download/simplified.mdx b/apps/site/pages/en/download/simplified.mdx index 990d969764fc8..bec78ab3bf781 100644 --- a/apps/site/pages/en/download/simplified.mdx +++ b/apps/site/pages/en/download/simplified.mdx @@ -51,11 +51,8 @@ Learn more about [Node.js releases](/about/previous-releases), including the rel <>

Other versions

{minors.map(({ binaries, installers, version, release }) => ( -
- -

Node.js® {version}

-
-
+
+ <>

Binary Downloads

Installer Packages

@@ -75,8 +72,8 @@ Learn more about [Node.js releases](/about/previous-releases), including the rel {' '} tarball.

-
-
+ +
))} ) From 7f9c14bb10eb80c0114b0b6373fbe80f5ba051ac Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Fri, 30 May 2025 00:28:58 +0300 Subject: [PATCH 16/38] fix: win x64, mac arm64 semver compat --- apps/site/util/downloadUtils/constants.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/site/util/downloadUtils/constants.json b/apps/site/util/downloadUtils/constants.json index 526e738fb0357..53984cb44f2b7 100644 --- a/apps/site/util/downloadUtils/constants.json +++ b/apps/site/util/downloadUtils/constants.json @@ -8,7 +8,10 @@ "platforms": [ { "label": "x64", - "value": "x64" + "value": "x64", + "compatibility": { + "semver": [">= 4.0.0"] + } }, { "label": "x86", @@ -40,7 +43,7 @@ "label": "ARM64", "value": "arm64", "compatibility": { - "semver": [">= 19.9.0"] + "semver": [">= 16.0.0"] } } ] From 107b9f76f943bbf7001f1d2a22155dd135aef892 Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Fri, 30 May 2025 00:29:41 +0300 Subject: [PATCH 17/38] refactor: self review --- .../Downloads/DownloadsTable/index.tsx | 4 +- apps/site/components/withLayout.tsx | 4 +- apps/site/components/withMarkdownContent.tsx | 10 ++-- .../components/withSimplifiedDownload.tsx | 48 ++++++++++--------- apps/site/layouts/DownloadSimple.tsx | 4 +- apps/site/next.dynamic.constants.mjs | 10 ++-- apps/site/util/downloadUtils/simple.tsx | 24 ++++++++-- 7 files changed, 61 insertions(+), 43 deletions(-) diff --git a/apps/site/components/Downloads/DownloadsTable/index.tsx b/apps/site/components/Downloads/DownloadsTable/index.tsx index b6433bf7f0db6..7a818c35446f2 100644 --- a/apps/site/components/Downloads/DownloadsTable/index.tsx +++ b/apps/site/components/Downloads/DownloadsTable/index.tsx @@ -5,10 +5,10 @@ import type { FC } from 'react'; import Link from '#site/components/Link'; import { OperatingSystemLabel } from '#site/util/downloadUtils'; -import type { ParsedArtifact } from '#site/util/downloadUtils/simple'; +import type { NodeDownloadArtifact } from '#site/util/downloadUtils/simple'; type DownloadsTableProps = { - source: Array; + source: Array; }; const DownloadsTable: FC = ({ source }) => { diff --git a/apps/site/components/withLayout.tsx b/apps/site/components/withLayout.tsx index fd6ba98ae325a..8a4f0ee191980 100644 --- a/apps/site/components/withLayout.tsx +++ b/apps/site/components/withLayout.tsx @@ -5,7 +5,7 @@ import ArticlePageLayout from '#site/layouts/ArticlePage'; import BlogLayout from '#site/layouts/Blog'; import DefaultLayout from '#site/layouts/Default'; import DownloadLayout from '#site/layouts/Download'; -import DownloadSimple from '#site/layouts/DownloadSimple'; +import DownloadSimpleLayout from '#site/layouts/DownloadSimple'; import GlowingBackdropLayout from '#site/layouts/GlowingBackdrop'; import LearnLayout from '#site/layouts/Learn'; import PostLayout from '#site/layouts/Post'; @@ -19,7 +19,7 @@ const layouts = { 'blog-post': PostLayout, 'blog-category': BlogLayout, download: DownloadLayout, - 'download-simple': DownloadSimple, + 'download-simple': DownloadSimpleLayout, article: ArticlePageLayout, } satisfies Record; diff --git a/apps/site/components/withMarkdownContent.tsx b/apps/site/components/withMarkdownContent.tsx index 3177c8d281f39..46cdf9a819ff2 100644 --- a/apps/site/components/withMarkdownContent.tsx +++ b/apps/site/components/withMarkdownContent.tsx @@ -6,16 +6,16 @@ import { dynamicRouter } from '#site/next.dynamic'; const getMarkdownContent = async (locale: string, file: Array) => { const filePathname = dynamicRouter.getPathname(file); - // We retrieve the source of the Markdown file by doing an educated guess - // of what possible files could be the source of the page, since the extension - // context is lost from `getStaticProps` as a limitation of Next.js itself + // Retrieves the Markdown file source content based on the file path and locale + // Uses dynamic routing to locate and load the appropriate markdown file + // for the given locale and file path segments const { source, filename } = await dynamicRouter.getMarkdownFile( locale, filePathname ); - // This parses the source Markdown content and returns a React Component - // from the Markdown File + // Parses the Markdown/MDX source content and transforms it into a React component + // Handles both standard Markdown and MDX files const { content } = await dynamicRouter.getMDXContent(source, filename); return content; diff --git a/apps/site/components/withSimplifiedDownload.tsx b/apps/site/components/withSimplifiedDownload.tsx index 68d19e5f86100..62bf42425a3b1 100644 --- a/apps/site/components/withSimplifiedDownload.tsx +++ b/apps/site/components/withSimplifiedDownload.tsx @@ -14,7 +14,7 @@ *
*/ -import type MetaBar from '@node-core/ui-components/Containers/MetaBar/index.jsx'; +import type MetaBar from '@node-core/ui-components/Containers/MetaBar'; import { getTranslations } from 'next-intl/server'; import type { ComponentProps, FC } from 'react'; @@ -23,6 +23,7 @@ import getReleaseData from '#site/next-data/releaseData'; import { buildMetaBarItems, buildReleaseArtifacts, + extractVersionFromPath, groupReleasesByStatus, } from '#site/util/downloadUtils/simple'; @@ -47,16 +48,12 @@ const WithSimplifiedDownload: FC = async ({ getTranslations(), ]); - // Group and localize sidebar items - const mappedSidebarItems = groupReleasesByStatus(releaseData, pathname); - const localizedSidebarItems = mappedSidebarItems.map(item => ({ - ...item, - groupName: t(`layouts.simpleDownload.statusNames.${item.groupName}`), - })); - // Extract version from pathname - const version = pathname?.split('/').pop(); - if (!version) return null; + const version = extractVersionFromPath(pathname); + + if (!version) { + return null; + } // Find the matching release const release = releaseData.find( @@ -64,20 +61,27 @@ const WithSimplifiedDownload: FC = async ({ major === Number(version) || (isLts === true && version === 'simplified') ); - if (release) { - const releaseArtifacts = buildReleaseArtifacts(release); - const metabarItems = buildMetaBarItems(release, t); - - return ( - - ); + if (!release) { + return null; } - return null; + const releaseArtifacts = buildReleaseArtifacts(release); + const metabarItems = buildMetaBarItems(release, t); + + // Group and localize sidebar items + const mappedSidebarItems = groupReleasesByStatus(releaseData, pathname); + const localizedSidebarItems = mappedSidebarItems.map(item => ({ + ...item, + groupName: t(`layouts.simpleDownload.statusNames.${item.groupName}`), + })); + + return ( + + ); }; export default WithSimplifiedDownload; diff --git a/apps/site/layouts/DownloadSimple.tsx b/apps/site/layouts/DownloadSimple.tsx index 8d126655f6b79..daf220fd7e83c 100644 --- a/apps/site/layouts/DownloadSimple.tsx +++ b/apps/site/layouts/DownloadSimple.tsx @@ -9,7 +9,7 @@ import WithSimplifiedDownload from '#site/components/withSimplifiedDownload'; import ArticleLayout from './Article'; -const DownloadLayout: FC = async () => ( +const DownloadSimpleLayout: FC = () => ( <> @@ -33,4 +33,4 @@ const DownloadLayout: FC = async () => ( ); -export default DownloadLayout; +export default DownloadSimpleLayout; diff --git a/apps/site/next.dynamic.constants.mjs b/apps/site/next.dynamic.constants.mjs index b8814d5246a70..f7af3a5bf388a 100644 --- a/apps/site/next.dynamic.constants.mjs +++ b/apps/site/next.dynamic.constants.mjs @@ -20,12 +20,8 @@ export const IGNORED_ROUTES = [ locale !== defaultLocale.code && /^blog/.test(pathname), // This is used to ignore all pathnames that are empty ({ locale, pathname }) => locale.length && !pathname.length, - // This is used to ignore all simplified download routes except for the English language - ({ locale, pathname }) => - locale !== defaultLocale.code && /^download/.test(pathname), - // This is used to ignore all simplified download routes except for the English language - ({ locale, pathname }) => - locale !== defaultLocale.code && /^simplified/.test(pathname), + // This is used to ignore download routes for major versions and simplified download page + ({ pathname }) => /^download\/(\d+|simplified)$/.test(pathname), ]; /** @@ -36,6 +32,8 @@ export const IGNORED_ROUTES = [ * @type {Map} A Map of pathname and Layout Name */ export const DYNAMIC_ROUTES = new Map([ + // Creates dynamic routes for simplified download pages for each major version + // (e.g., /download/18, /download/20) ...provideReleaseData().map(({ major }) => [ `download/${major}`, 'download-simple', diff --git a/apps/site/util/downloadUtils/simple.tsx b/apps/site/util/downloadUtils/simple.tsx index fbc8080259e15..3a76c2e8a24d1 100644 --- a/apps/site/util/downloadUtils/simple.tsx +++ b/apps/site/util/downloadUtils/simple.tsx @@ -18,7 +18,7 @@ import { DIST_URL, BASE_CHANGELOG_URL } from '#site/next.constants'; const RELEASE_POST_URL = '/blog/release/'; -export type ParsedArtifact = { +export type NodeDownloadArtifact = { file: string; kind: DownloadKind; os: UserOS; @@ -59,12 +59,12 @@ type CompatibleArtifactOptions = { /** * Returns a list of compatible artifacts for the given options. */ -function getCompatibleArtifacts({ +const getCompatibleArtifacts = ({ platforms = PLATFORMS, exclude = [], version, kind = 'binary', -}: CompatibleArtifactOptions): Array { +}: CompatibleArtifactOptions): Array => { return Object.entries(platforms).flatMap(([os, items]) => { if (exclude.includes(os)) return []; @@ -92,7 +92,7 @@ function getCompatibleArtifacts({ }; }); }); -} +}; export const buildReleaseArtifacts = ({ versionWithPrefix, @@ -229,3 +229,19 @@ export const groupReleasesByStatus = ( // Return the grouped items as an array for sidebar consumption return Object.values(grouped); }; + +export const extractVersionFromPath = ( + pathname: string | undefined +): string | null => { + if (!pathname) return null; + + const segments = pathname.split('/').filter(Boolean); + const version = segments.pop(); + + // Check version format (number or 'simplified') + if (!version || (!version.match(/^\d+$/) && version !== 'simplified')) { + return null; + } + + return version; +}; From b07ba56ccbdb34808201fa0c9f14709ffcebf087 Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Fri, 30 May 2025 00:37:20 +0300 Subject: [PATCH 18/38] docs: downloads table --- apps/site/next.mdx.use.mjs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/site/next.mdx.use.mjs b/apps/site/next.mdx.use.mjs index 624a369bc8965..05cdb157886b1 100644 --- a/apps/site/next.mdx.use.mjs +++ b/apps/site/next.mdx.use.mjs @@ -15,13 +15,12 @@ import WithSimplifiedDownload from './components/withSimplifiedDownload'; * @satisfies {import('mdx/types').MDXComponents} */ export const mdxComponents = { - // Renders a table with Node.js Releases with diffrent platforms and architectures DownloadReleasesTable: DownloadReleasesTable, // HOC for providing the Simplified Download Page properties WithSimplifiedDownload: WithSimplifiedDownload, // Renders a Details Component with a summary and content Details: Details, - // Renders a table with all Node.js Downloads + // Renders a table with Node.js Releases with diffrent platforms and architectures DownloadsTable: DownloadsTable, // HOC for getting Node.js Release Metadata WithNodeRelease: WithNodeRelease, From 5b30781b676144d685b2195ea73dbffa1137b7a7 Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Sat, 31 May 2025 14:21:28 +0300 Subject: [PATCH 19/38] docs: fileoverview removed --- apps/site/components/withSimplifiedDownload.tsx | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/apps/site/components/withSimplifiedDownload.tsx b/apps/site/components/withSimplifiedDownload.tsx index 62bf42425a3b1..54eddf8736895 100644 --- a/apps/site/components/withSimplifiedDownload.tsx +++ b/apps/site/components/withSimplifiedDownload.tsx @@ -1,19 +1,3 @@ -/** - * @fileoverview - * Higher-order component to provide simplified download data and sidebar items - * to its child component, based on the current route and release data. - * - * - Fetches release data and translations - * - Determines the current version from the URL - * - Finds the matching release and builds download artifacts - * - Localizes sidebar group names - * - * Usage: - * - * {(props) => } - * - */ - import type MetaBar from '@node-core/ui-components/Containers/MetaBar'; import { getTranslations } from 'next-intl/server'; import type { ComponentProps, FC } from 'react'; From 17cc8a0edeb82d518b66c062c107d019bd247098 Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Sat, 31 May 2025 14:57:38 +0300 Subject: [PATCH 20/38] fix: layout import --- apps/site/layouts/DownloadSimple.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/site/layouts/DownloadSimple.tsx b/apps/site/layouts/DownloadSimple.tsx index daf220fd7e83c..a8675fab290e3 100644 --- a/apps/site/layouts/DownloadSimple.tsx +++ b/apps/site/layouts/DownloadSimple.tsx @@ -6,8 +6,7 @@ import WithMetaBar from '#site/components/withMetaBar'; import WithNavBar from '#site/components/withNavBar'; import WithProgressionSidebar from '#site/components/withProgressionSidebar'; import WithSimplifiedDownload from '#site/components/withSimplifiedDownload'; - -import ArticleLayout from './Article'; +import ArticleLayout from '#site/layouts/Article'; const DownloadSimpleLayout: FC = () => ( <> From a92ad35e369621142dfdb837ee0e1aa6c053374f Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Sat, 31 May 2025 15:04:04 +0300 Subject: [PATCH 21/38] Update apps/site/next.mdx.use.mjs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Caner Akdas --- apps/site/next.mdx.use.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/site/next.mdx.use.mjs b/apps/site/next.mdx.use.mjs index 05cdb157886b1..01eb7f84e31ab 100644 --- a/apps/site/next.mdx.use.mjs +++ b/apps/site/next.mdx.use.mjs @@ -20,7 +20,7 @@ export const mdxComponents = { WithSimplifiedDownload: WithSimplifiedDownload, // Renders a Details Component with a summary and content Details: Details, - // Renders a table with Node.js Releases with diffrent platforms and architectures + // Renders a table with Node.js Releases with different platforms and architectures DownloadsTable: DownloadsTable, // HOC for getting Node.js Release Metadata WithNodeRelease: WithNodeRelease, From c7264ea34c458cf6007990cd73c3a6e649f6eb3c Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Mon, 2 Jun 2025 19:15:55 +0300 Subject: [PATCH 22/38] refactor: layout changes --- .../Downloads/DownloadsTable/index.tsx | 4 +- .../MinorReleasesTable/index.module.css | 17 ++ .../Downloads/MinorReleasesTable/index.tsx | 95 +++++---- .../components/MDX/Details/index.module.css | 41 ---- apps/site/components/MDX/Details/index.tsx | 21 -- apps/site/components/withMarkdownContent.tsx | 2 +- apps/site/components/withMetaBar.tsx | 13 +- .../components/withProgressionSidebar.tsx | 44 ++-- .../components/withSimplifiedDownload.tsx | 46 ++--- apps/site/layouts/DownloadSimple.tsx | 26 +-- .../site/next-data/generators/releaseData.mjs | 6 +- apps/site/next.dynamic.constants.mjs | 16 +- apps/site/next.mdx.use.mjs | 10 +- apps/site/pages/en/download/simplified.mdx | 112 ++++------ apps/site/types/releases.ts | 6 +- apps/site/util/downloadUtils/simple.tsx | 193 +++++------------- packages/i18n/locales/en.json | 19 +- 17 files changed, 232 insertions(+), 439 deletions(-) delete mode 100644 apps/site/components/MDX/Details/index.module.css delete mode 100644 apps/site/components/MDX/Details/index.tsx diff --git a/apps/site/components/Downloads/DownloadsTable/index.tsx b/apps/site/components/Downloads/DownloadsTable/index.tsx index 7a818c35446f2..dbb0f93c9b6d0 100644 --- a/apps/site/components/Downloads/DownloadsTable/index.tsx +++ b/apps/site/components/Downloads/DownloadsTable/index.tsx @@ -19,10 +19,10 @@ const DownloadsTable: FC = ({ source }) => {
- - diff --git a/apps/site/components/Downloads/MinorReleasesTable/index.module.css b/apps/site/components/Downloads/MinorReleasesTable/index.module.css index 5740edadd7de7..838ade77d4a6e 100644 --- a/apps/site/components/Downloads/MinorReleasesTable/index.module.css +++ b/apps/site/components/Downloads/MinorReleasesTable/index.module.css @@ -6,3 +6,20 @@ items-center gap-2; } + +.scrollable { + @apply scrollbar-thin + flex + snap-x + snap-mandatory + overflow-x-auto; + + > table { + @apply shrink-0 + snap-start; + + thead th { + @apply last:md:w-56; + } + } +} diff --git a/apps/site/components/Downloads/MinorReleasesTable/index.tsx b/apps/site/components/Downloads/MinorReleasesTable/index.tsx index c2cf17ec975a5..43ca24fb17497 100644 --- a/apps/site/components/Downloads/MinorReleasesTable/index.tsx +++ b/apps/site/components/Downloads/MinorReleasesTable/index.tsx @@ -15,50 +15,67 @@ type MinorReleasesTableProps = { releases: Array; }; +const chunkedReleases = (releases: Array, size: number) => { + const count = Math.ceil(releases.length / size); + + return Array.from({ length: count }, (_, index) => + releases.slice(index * size, (index + 1) * size) + ); +}; + export const MinorReleasesTable: FC = ({ releases, }) => { const t = useTranslations('components.minorReleasesTable'); + const releaseGroups = chunkedReleases(releases, 8); return ( -
{t('components.simpleDownloadTable.fileName')}{t('components.downloadsTable.fileName')} - {t('components.simpleDownloadTable.operatingSystem')} + {t('components.downloadsTable.operatingSystem')} - {t('components.simpleDownloadTable.architecture')} + {t('components.downloadsTable.architecture')}
+ {release.file} + {OperatingSystemLabel[release.os]} + {release.architecture}
{t('components.downloadsTable.fileName')} + {t('components.downloadsTable.operatingSystem')} + {t('components.downloadsTable.architecture')}
- - - - - - - - {releases.map(release => ( - - - - - ))} - -
{t('version')}{t('links')}
v{release.version} -
- - {t('actions.release')} - - - - {t('actions.changelog')} - - - - {t('actions.docs')} - -
-
+
+ {releaseGroups.map(releases => ( + + + + + + + + + {releases.map(release => ( + + + + + ))} + +
{t('version')}{t('links')}
+ + v{release.version} + + +
+ + {t('actions.release')} + + + + {t('actions.changelog')} + + + + {t('actions.docs')} + +
+
+ ))} +
); }; diff --git a/apps/site/components/MDX/Details/index.module.css b/apps/site/components/MDX/Details/index.module.css deleted file mode 100644 index e9f2739a72192..0000000000000 --- a/apps/site/components/MDX/Details/index.module.css +++ /dev/null @@ -1,41 +0,0 @@ -@reference "../../../styles/index.css"; - -.root { - @apply border-b - border-neutral-200 - px-0 - pb-4 - text-neutral-900 - dark:border-neutral-800 - dark:text-white; - - summary { - @apply cursor-default - select-none - list-none - after:float-right - after:pr-4 - after:text-2xl - after:text-neutral-400 - after:content-['›'] - dark:after:text-neutral-600; - - h3 { - @apply inline; - } - } - - &[open] summary { - @apply pb-4 - after:rotate-90 - after:transform - after:pt-4; - } - - .detail { - @apply flex - select-none - flex-col - gap-4; - } -} diff --git a/apps/site/components/MDX/Details/index.tsx b/apps/site/components/MDX/Details/index.tsx deleted file mode 100644 index 803730a7db7e0..0000000000000 --- a/apps/site/components/MDX/Details/index.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import type { FC, PropsWithChildren } from 'react'; - -import styles from './index.module.css'; - -type DetailsProps = { - summary: React.ReactNode; -}; - -const Details: FC> = ({ - children, - summary, -}) => ( -
- -

{summary}

-
-
{children}
-
-); - -export default Details; diff --git a/apps/site/components/withMarkdownContent.tsx b/apps/site/components/withMarkdownContent.tsx index 46cdf9a819ff2..9747729c3e3c3 100644 --- a/apps/site/components/withMarkdownContent.tsx +++ b/apps/site/components/withMarkdownContent.tsx @@ -29,7 +29,7 @@ const WithMarkdownContent: FC = async ({ file }) => { const locale = await getLocale(); const content = await getMarkdownContent(locale, file); - return content || null; + return content; }; export default WithMarkdownContent; diff --git a/apps/site/components/withMetaBar.tsx b/apps/site/components/withMetaBar.tsx index cc7b36508f693..a7dd313ad8167 100644 --- a/apps/site/components/withMetaBar.tsx +++ b/apps/site/components/withMetaBar.tsx @@ -3,7 +3,7 @@ import MetaBar from '@node-core/ui-components/Containers/MetaBar'; import GitHubIcon from '@node-core/ui-components/Icons/Social/GitHub'; import { useFormatter, useLocale, useTranslations } from 'next-intl'; -import type { ComponentProps, FC } from 'react'; +import type { FC } from 'react'; import Link from '#site/components/Link'; import WithAvatarGroup from '#site/components/withAvatarGroup'; @@ -14,11 +14,7 @@ import { TRANSLATION_URL } from '#site/next.constants.mjs'; import { defaultLocale } from '#site/next.locales.mjs'; import { getGitHubBlobUrl } from '#site/util/gitHubUtils'; -type WithMetaBarProps = { - items?: ComponentProps['items']; -}; - -const WithMetaBar: FC = ({ items }) => { +const WithMetaBar: FC = () => { const { headings, readingTime, frontmatter, filename } = useClientContext(); const formatter = useFormatter(); const lastUpdated = frontmatter.date @@ -49,11 +45,8 @@ const WithMetaBar: FC = ({ items }) => { heading={t('components.metabar.tableOfContents')} as={Link} items={{ - ...items, [t('components.metabar.lastUpdated')]: lastUpdated, - ...(readingTime.minutes >= 1 && { - [t('components.metabar.readingTime')]: readingTimeText, - }), + [t('components.metabar.readingTime')]: readingTimeText, ...(usernames.length && { [t( `components.metabar.${usernames.length > 1 ? 'authors' : 'author'}` diff --git a/apps/site/components/withProgressionSidebar.tsx b/apps/site/components/withProgressionSidebar.tsx index 074f980341f92..a98d27e801f85 100644 --- a/apps/site/components/withProgressionSidebar.tsx +++ b/apps/site/components/withProgressionSidebar.tsx @@ -4,53 +4,39 @@ import ProgressionSidebar from '@node-core/ui-components/Common/ProgressionSideb import { usePathname } from 'next/navigation'; import { useLocale, useTranslations } from 'next-intl'; import type { RichTranslationValues } from 'next-intl'; -import type { ComponentProps, FC } from 'react'; +import type { FC } from 'react'; import Link from '#site/components/Link'; import { useSiteNavigation } from '#site/hooks/server'; import { useRouter } from '#site/navigation.mjs'; import type { NavigationKeys } from '#site/types'; -type Group = ComponentProps['groups'][number]; - -type WithProgressionSidebarProps = - | { - navKey: NavigationKeys; - context?: Record; - groups?: never; - } - | { - groups: Array; - navKey?: never; - context?: never; - }; +type WithProgressionSidebarProps = { + navKey: NavigationKeys; + context?: Record; +}; -const WithProgressionSidebar: FC = props => { +const WithProgressionSidebar: FC = ({ + navKey, + context, +}) => { const { getSideNavigation } = useSiteNavigation(); const pathname = usePathname(); const locale = useLocale(); const t = useTranslations(); const { push } = useRouter(); + const [[, sidebarNavigation]] = getSideNavigation([navKey], context); - let groups: Array = []; - - if ('navKey' in props && props.navKey) { - const [[, sidebarNavigation]] = getSideNavigation( - [props.navKey], - props.context - ); - - groups = sidebarNavigation.items.map(([, { label, items }]) => ({ + const mappedProgressionSidebarItems = sidebarNavigation.items.map( + ([, { label, items }]) => ({ groupName: label, items: items.map(([, item]) => item), - })); - } else if ('groups' in props) { - groups = props.groups; - } + }) + ); return ( & { - sidebarItems: ReturnType; - metabarItems: ComponentProps['items']; -}; +type SimplifiedDownload = ReturnType; type WithSimplifiedDownloadProps = { children: FC; @@ -27,13 +20,10 @@ const WithSimplifiedDownload: FC = async ({ children: Component, }) => { const { pathname } = getClientContext(); - const [releaseData, t] = await Promise.all([ - getReleaseData(), - getTranslations(), - ]); + const releaseData = await getReleaseData(); // Extract version from pathname - const version = extractVersionFromPath(pathname); + const { version, major } = extractVersionFromPath(pathname) || {}; if (!version) { return null; @@ -41,31 +31,23 @@ const WithSimplifiedDownload: FC = async ({ // Find the matching release const release = releaseData.find( - ({ major, isLts }) => - major === Number(version) || (isLts === true && version === 'simplified') + release => + release.major === major || + (release.isLts === true && version === 'simplified') ); if (!release) { return null; } - const releaseArtifacts = buildReleaseArtifacts(release); - const metabarItems = buildMetaBarItems(release, t); - - // Group and localize sidebar items - const mappedSidebarItems = groupReleasesByStatus(releaseData, pathname); - const localizedSidebarItems = mappedSidebarItems.map(item => ({ - ...item, - groupName: t(`layouts.simpleDownload.statusNames.${item.groupName}`), - })); - - return ( - + const majorVersions = releaseData.map(release => release.versionWithPrefix); + const releaseArtifacts = buildReleaseArtifacts( + release, + version, + majorVersions ); + + return ; }; export default WithSimplifiedDownload; diff --git a/apps/site/layouts/DownloadSimple.tsx b/apps/site/layouts/DownloadSimple.tsx index a8675fab290e3..31262957c0cdd 100644 --- a/apps/site/layouts/DownloadSimple.tsx +++ b/apps/site/layouts/DownloadSimple.tsx @@ -2,31 +2,19 @@ import type { FC } from 'react'; import WithFooter from '#site/components/withFooter'; import WithMarkdownContent from '#site/components/withMarkdownContent'; -import WithMetaBar from '#site/components/withMetaBar'; import WithNavBar from '#site/components/withNavBar'; -import WithProgressionSidebar from '#site/components/withProgressionSidebar'; -import WithSimplifiedDownload from '#site/components/withSimplifiedDownload'; -import ArticleLayout from '#site/layouts/Article'; + +import styles from './layouts.module.css'; const DownloadSimpleLayout: FC = () => ( <> - - - {({ sidebarItems, metabarItems }) => ( - <> - -
-
- -
- -
- - )} -
-
+
+
+ +
+
diff --git a/apps/site/next-data/generators/releaseData.mjs b/apps/site/next-data/generators/releaseData.mjs index 319f5608815f9..09349b4afe5f8 100644 --- a/apps/site/next-data/generators/releaseData.mjs +++ b/apps/site/next-data/generators/releaseData.mjs @@ -77,8 +77,12 @@ const generateReleaseData = async () => { const status = getNodeReleaseStatus(new Date(), support); const minorVersions = Object.entries(major.releases).map(([, release]) => ({ - version: release.semver.raw, + modules: release.modules.version || '', + npm: release.dependencies.npm || '', releaseDate: release.releaseDate, + v8: release.dependencies.v8, + version: release.semver.raw, + versionWithPrefix: `v${release.semver.raw}`, })); const majorVersion = latestVersion.semver.major; diff --git a/apps/site/next.dynamic.constants.mjs b/apps/site/next.dynamic.constants.mjs index f7af3a5bf388a..ad371de297bc4 100644 --- a/apps/site/next.dynamic.constants.mjs +++ b/apps/site/next.dynamic.constants.mjs @@ -21,7 +21,7 @@ export const IGNORED_ROUTES = [ // This is used to ignore all pathnames that are empty ({ locale, pathname }) => locale.length && !pathname.length, // This is used to ignore download routes for major versions and simplified download page - ({ pathname }) => /^download\/(\d+|simplified)$/.test(pathname), + ({ pathname }) => /^download\/(v\d+(\.\d+)*|simplified)$/.test(pathname), ]; /** @@ -32,12 +32,14 @@ export const IGNORED_ROUTES = [ * @type {Map} A Map of pathname and Layout Name */ export const DYNAMIC_ROUTES = new Map([ - // Creates dynamic routes for simplified download pages for each major version - // (e.g., /download/18, /download/20) - ...provideReleaseData().map(({ major }) => [ - `download/${major}`, - 'download-simple', - ]), + // Creates dynamic routes for simplified download pages for each version + // (e.g., /download/v18.20.8, /download/v20.19.2) + ...provideReleaseData() + .flatMap(({ minorVersions, versionWithPrefix }) => [ + `download/${versionWithPrefix}`, + ...minorVersions.map(minor => `download/${minor.versionWithPrefix}`), + ]) + .map(version => [version, 'download-simple']), // Provides Routes for all Blog Categories ...blogData.categories.map(c => [`blog/${c}`, 'blog-category']), // Provides Routes for all Blog Categories w/ Pagination diff --git a/apps/site/next.mdx.use.mjs b/apps/site/next.mdx.use.mjs index 01eb7f84e31ab..9470b0e10471b 100644 --- a/apps/site/next.mdx.use.mjs +++ b/apps/site/next.mdx.use.mjs @@ -2,13 +2,13 @@ import DownloadReleasesTable from './components/Downloads/DownloadReleasesTable'; import DownloadsTable from './components/Downloads/DownloadsTable'; +import { MinorReleasesTable } from './components/Downloads/MinorReleasesTable'; +import { ReleaseOverview } from './components/Downloads/ReleaseOverview'; import UpcomingMeetings from './components/MDX/Calendar/UpcomingMeetings'; -import Details from './components/MDX/Details'; import WithBadgeGroup from './components/withBadgeGroup'; import WithBanner from './components/withBanner'; import WithNodeRelease from './components/withNodeRelease'; import WithSimplifiedDownload from './components/withSimplifiedDownload'; - /** * A full list of React Components that we want to pass through to MDX * @@ -18,8 +18,6 @@ export const mdxComponents = { DownloadReleasesTable: DownloadReleasesTable, // HOC for providing the Simplified Download Page properties WithSimplifiedDownload: WithSimplifiedDownload, - // Renders a Details Component with a summary and content - Details: Details, // Renders a table with Node.js Releases with different platforms and architectures DownloadsTable: DownloadsTable, // HOC for getting Node.js Release Metadata @@ -30,4 +28,8 @@ export const mdxComponents = { WithBadgeGroup: WithBadgeGroup, // Renders an container for Upcoming Node.js Meetings UpcomingMeetings: UpcomingMeetings, + // Renders the Release Overview for a specified version + ReleaseOverview: ReleaseOverview, + // Renders a table with all the Minor Releases for a Major Version + MinorReleasesTable: MinorReleasesTable, }; diff --git a/apps/site/pages/en/download/simplified.mdx b/apps/site/pages/en/download/simplified.mdx index bec78ab3bf781..29d529f19266d 100644 --- a/apps/site/pages/en/download/simplified.mdx +++ b/apps/site/pages/en/download/simplified.mdx @@ -3,79 +3,53 @@ title: Download Node.js® layout: download-simple --- -# Download Node.js® - - {({ binaries, installers, version }) => ( + {({ binaries, installers, version, release, sources, majors }) => ( <> -

Node.js® {version} – Binary Downloads

+

Download Node.js® {version}

+ +
    +
  • + Learn more about{' '} + Node.js releases, + including the release schedule and LTS status. +
  • +
  • + Signed SHASUMS{' '} + for release files. How to{' '} + + verify + {' '} + signed SHASUMS. +
  • +
  • + Download a signed{' '} + + Node.js {version} source + {' '} + tarball. +
  • +
+

Binary Downloads

-

Node.js® {version} – Installer Packages

+

Installer Packages

+

Minor versions

+ +
+ +

Other releases

+
+
    + {majors.map(version => ( +
  • + +

    Node.js {version}

    + +
  • + ))} +
+
)}
- -Learn more about [Node.js releases](/about/previous-releases), including the release schedule and LTS status. - - - {({ version, release }) => ( - <> -

- Read the{' '} - changelog or{' '} - blog post for this version. -

-

- Signed SHASUMS for - release files. How to{' '} - - verify - {' '} - signed SHASUMS. -

-

- Download a signed{' '} - - Node.js {version} source - {' '} - tarball. -

- - )} -
- - - {({ minors }) => - minors.length > 0 && ( - <> -

Other versions

- {minors.map(({ binaries, installers, version, release }) => ( -
- <> -

Binary Downloads

- -

Installer Packages

- -

- Read the{' '} - - changelog - {' '} - or blog post for this - version. -

-

- Download a signed{' '} - - Node.js {version} source - {' '} - tarball. -

- -
- ))} - - ) - } -
diff --git a/apps/site/types/releases.ts b/apps/site/types/releases.ts index cf71f7bea406d..ac9732d86d803 100644 --- a/apps/site/types/releases.ts +++ b/apps/site/types/releases.ts @@ -20,8 +20,12 @@ export interface NodeReleaseSource { } export interface MinorVersion { - version: string; + npm?: string; + modules?: string; releaseDate: string; + v8: string; + version: string; + versionWithPrefix: string; } export interface NodeRelease extends NodeReleaseSource { diff --git a/apps/site/util/downloadUtils/simple.tsx b/apps/site/util/downloadUtils/simple.tsx index 3a76c2e8a24d1..e35273cd6b97a 100644 --- a/apps/site/util/downloadUtils/simple.tsx +++ b/apps/site/util/downloadUtils/simple.tsx @@ -1,9 +1,5 @@ -import type ProgressionSidebarGroup from '@node-core/ui-components/Common/ProgressionSidebar/ProgressionSidebarGroup'; -import type { ComponentProps } from 'react'; -import { satisfies } from 'semver'; +import semVer from 'semver'; -import FormattedTime from '#site/components/Common/FormattedTime'; -import type getReleaseData from '#site/next-data/releaseData'; import type { NodeRelease } from '#site/types/releases'; import type { UserOS, UserPlatform } from '#site/types/userOS'; import type { DownloadDropdownItem } from '#site/util/downloadUtils'; @@ -14,9 +10,7 @@ import { import type { DownloadKind } from '#site/util/getNodeDownloadUrl'; import { getNodeDownloadUrl } from '#site/util/getNodeDownloadUrl'; -import { DIST_URL, BASE_CHANGELOG_URL } from '#site/next.constants'; - -const RELEASE_POST_URL = '/blog/release/'; +import { DIST_URL } from '#site/next.constants'; export type NodeDownloadArtifact = { file: string; @@ -45,7 +39,7 @@ function isCompatible( return ( (osList?.includes(os) ?? true) && (platformList?.includes(platform) ?? true) && - (versions?.every(r => satisfies(version, r)) ?? true) + (versions?.every(r => semVer.satisfies(version, r)) ?? true) ); } @@ -94,154 +88,61 @@ const getCompatibleArtifacts = ({ }); }; -export const buildReleaseArtifacts = ({ - versionWithPrefix, - version, - minorVersions, -}: NodeRelease) => ({ - binaries: getCompatibleArtifacts({ - version: versionWithPrefix, - kind: 'binary', - }), - release: { - shasum: getNodeDownloadUrl({ version: versionWithPrefix, kind: 'shasum' }), - source: getNodeDownloadUrl({ version: versionWithPrefix, kind: 'source' }), - changelog: `${BASE_CHANGELOG_URL}${version}`, - blogPost: `${RELEASE_POST_URL}${versionWithPrefix}`, - }, - installers: getCompatibleArtifacts({ - exclude: OS_NOT_SUPPORTING_INSTALLERS, - version: versionWithPrefix, - kind: 'installer', - }), - version: versionWithPrefix, - minors: minorVersions - .filter(minor => `v${minor.version}` !== versionWithPrefix) // Exclude the current version - .map(minor => { - const versionWithPrefix = `v${minor.version}`; - - return { - binaries: getCompatibleArtifacts({ - version: versionWithPrefix, - kind: 'binary', - }), - installers: getCompatibleArtifacts({ - exclude: OS_NOT_SUPPORTING_INSTALLERS, - version: versionWithPrefix, - kind: 'installer', - }), - version: versionWithPrefix, - release: { - source: getNodeDownloadUrl({ - version: versionWithPrefix, - kind: 'source', - }), - changelog: `${BASE_CHANGELOG_URL}${minor.version}`, - blogPost: `${RELEASE_POST_URL}${versionWithPrefix}`, - }, - }; - }), -}); - -export const buildMetaBarItems = ( - { - codename, - status, - currentStart, - releaseDate, - minorVersions, - modules, - npm, - v8, - }: NodeRelease, - t: (key: string) => string -) => ({ - ...(codename && { - [t('layouts.simpleDownload.codename')]: codename, - }), - [t('layouts.simpleDownload.status')]: t( - `layouts.simpleDownload.statusNames.${status}` - ), - [t('layouts.simpleDownload.firstReleased')]: ( - - ), - [t('layouts.simpleDownload.lastUpdated')]: ( - - ), - [t('layouts.simpleDownload.minorVersions')]: minorVersions.length, - [t('layouts.simpleDownload.nApiVersion')]: modules, - [t('layouts.simpleDownload.npmVersion')]: npm, - [t('layouts.simpleDownload.v8Version')]: v8, -}); - -type SidebarGroup = ComponentProps; - -export const groupReleasesByStatus = ( - releaseData: Awaited>, - pathname: string -): Array => { - let simplified = false; - - // Reduce the release data into a record grouped by release status (e.g., 'LTS', 'Current') - const grouped = releaseData.reduce>( - (acc, release) => { - const statusKey = release.status; - - // Initialize the group if it doesn't exist yet - if (!acc[statusKey]) { - acc[statusKey] = { - groupName: statusKey, - items: [], - }; - - // Check if the current pathname indicates a simplified download page - if (statusKey === 'LTS' && pathname.endsWith('simplified')) { - simplified = true; - } - } - - // Build the label: always include major version, optionally codename - const labelParts = [`v${release.major}`]; - if (release.codename) { - labelParts.push(release.codename); - } - - // Add the release to the group's items - if (simplified) { - acc[statusKey].items.push({ - label: labelParts.join(' '), - link: `/download/simplified`, - }); +export const buildReleaseArtifacts = ( + release: NodeRelease, + version: string, + majors: Array +) => { + const minorVersion = release.minorVersions.find( + ({ versionWithPrefix }) => versionWithPrefix === version + ); - simplified = false; - } else { - acc[statusKey].items.push({ - label: labelParts.join(' '), - link: `/download/${release.major}`, - }); - } + const enrichedRelease = { + ...release, + ...minorVersion, + }; - return acc; + return { + binaries: getCompatibleArtifacts({ + version: version, + kind: 'binary', + }), + installers: getCompatibleArtifacts({ + exclude: OS_NOT_SUPPORTING_INSTALLERS, + version: version, + kind: 'installer', + }), + sources: { + shasum: getNodeDownloadUrl({ + version: version, + kind: 'shasum', + }), + tarball: getNodeDownloadUrl({ + version: version, + kind: 'source', + }), }, - {} - ); - - // Return the grouped items as an array for sidebar consumption - return Object.values(grouped); + version: version, + minors: enrichedRelease.minorVersions, + release: enrichedRelease, + majors: majors, + }; }; -export const extractVersionFromPath = ( - pathname: string | undefined -): string | null => { +export const extractVersionFromPath = (pathname: string | undefined) => { if (!pathname) return null; const segments = pathname.split('/').filter(Boolean); const version = segments.pop(); + const major = semVer.major(version || ''); - // Check version format (number or 'simplified') - if (!version || (!version.match(/^\d+$/) && version !== 'simplified')) { + // Check version format like (v22.0.4 or 'simplified') + if ( + !version || + (!version.match(/^v\d+(\.\d+)*$/) && version !== 'simplified') + ) { return null; } - return version; + return { version, major }; }; diff --git a/packages/i18n/locales/en.json b/packages/i18n/locales/en.json index f3a8d657bae45..87fc22f4d4760 100644 --- a/packages/i18n/locales/en.json +++ b/packages/i18n/locales/en.json @@ -172,6 +172,7 @@ "minorReleasesTable": { "version": "Version", "links": "Links", + "showMore": "Show more", "actions": { "release": "Release", "changelog": "Changelog", @@ -323,22 +324,6 @@ } } }, - "simpleDownload": { - "statusNames": { - "Current": "Current", - "LTS": "LTS", - "Maintenance": "Maintenance", - "End-of-life": "End of Life" - }, - "codename": "Codename", - "status": "Status", - "firstReleased": "First Released", - "lastUpdated": "Last Updated", - "minorVersions": "Minor Versions", - "nApiVersion": "N-API Version", - "npmVersion": "npm Version", - "v8Version": "V8 Version" - }, "logo": "Node.js logo" } -} \ No newline at end of file +} From 199c3e65e9c7901ce932a7d64572793efb70ff5b Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Mon, 2 Jun 2025 20:01:48 +0300 Subject: [PATCH 23/38] refactor: renamed to download archive --- .../Downloads/DownloadsTable/index.tsx | 2 +- .../Downloads/Release/ReleaseCodeBox.tsx | 2 +- ...edDownload.tsx => withDownloadArchive.tsx} | 22 ++++++-------- apps/site/components/withLayout.tsx | 4 +-- ...DownloadSimple.tsx => DownloadArchive.tsx} | 6 ++-- apps/site/next.dynamic.constants.mjs | 8 ++--- apps/site/next.mdx.use.mjs | 7 +++-- .../download/{simplified.mdx => archive.mdx} | 6 ++-- apps/site/types/layouts.ts | 2 +- .../downloadUtils/{simple.tsx => archive.tsx} | 29 ++++++++++++++----- packages/i18n/locales/en.json | 2 +- 11 files changed, 51 insertions(+), 39 deletions(-) rename apps/site/components/{withSimplifiedDownload.tsx => withDownloadArchive.tsx} (58%) rename apps/site/layouts/{DownloadSimple.tsx => DownloadArchive.tsx} (73%) rename apps/site/pages/en/download/{simplified.mdx => archive.mdx} (95%) rename apps/site/util/downloadUtils/{simple.tsx => archive.tsx} (83%) diff --git a/apps/site/components/Downloads/DownloadsTable/index.tsx b/apps/site/components/Downloads/DownloadsTable/index.tsx index dbb0f93c9b6d0..bcdea982dc9e5 100644 --- a/apps/site/components/Downloads/DownloadsTable/index.tsx +++ b/apps/site/components/Downloads/DownloadsTable/index.tsx @@ -5,7 +5,7 @@ import type { FC } from 'react'; import Link from '#site/components/Link'; import { OperatingSystemLabel } from '#site/util/downloadUtils'; -import type { NodeDownloadArtifact } from '#site/util/downloadUtils/simple'; +import type { NodeDownloadArtifact } from '#site/util/downloadUtils/archive'; type DownloadsTableProps = { source: Array; diff --git a/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx b/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx index 7a9d1c8cb4c97..8fcd41cd66164 100644 --- a/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx +++ b/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx @@ -106,7 +106,7 @@ const ReleaseCodeBox: FC = () => { > {t.rich('layouts.download.codeBox.noScriptDetected', { link: text => ( - + {text} ), diff --git a/apps/site/components/withSimplifiedDownload.tsx b/apps/site/components/withDownloadArchive.tsx similarity index 58% rename from apps/site/components/withSimplifiedDownload.tsx rename to apps/site/components/withDownloadArchive.tsx index f51cb88d88a2c..087d84da6e6d0 100644 --- a/apps/site/components/withSimplifiedDownload.tsx +++ b/apps/site/components/withDownloadArchive.tsx @@ -5,36 +5,32 @@ import getReleaseData from '#site/next-data/releaseData'; import { buildReleaseArtifacts, extractVersionFromPath, -} from '#site/util/downloadUtils/simple'; + findReleaseByVersion, +} from '#site/util/downloadUtils/archive'; -type SimplifiedDownload = ReturnType; +type DownloadArchive = ReturnType; -type WithSimplifiedDownloadProps = { - children: FC; +type WithDownloadArchiveProps = { + children: FC; }; /** * Provides download artifacts and sidebar items to its child component */ -const WithSimplifiedDownload: FC = async ({ +const WithDownloadArchive: FC = async ({ children: Component, }) => { const { pathname } = getClientContext(); const releaseData = await getReleaseData(); // Extract version from pathname - const { version, major } = extractVersionFromPath(pathname) || {}; + const version = extractVersionFromPath(pathname); if (!version) { return null; } - // Find the matching release - const release = releaseData.find( - release => - release.major === major || - (release.isLts === true && version === 'simplified') - ); + const release = findReleaseByVersion(releaseData, version); if (!release) { return null; @@ -50,4 +46,4 @@ const WithSimplifiedDownload: FC = async ({ return ; }; -export default WithSimplifiedDownload; +export default WithDownloadArchive; diff --git a/apps/site/components/withLayout.tsx b/apps/site/components/withLayout.tsx index 8a4f0ee191980..c7f948a5946dc 100644 --- a/apps/site/components/withLayout.tsx +++ b/apps/site/components/withLayout.tsx @@ -5,7 +5,7 @@ import ArticlePageLayout from '#site/layouts/ArticlePage'; import BlogLayout from '#site/layouts/Blog'; import DefaultLayout from '#site/layouts/Default'; import DownloadLayout from '#site/layouts/Download'; -import DownloadSimpleLayout from '#site/layouts/DownloadSimple'; +import DownloadArchiveLayout from '#site/layouts/DownloadArchive'; import GlowingBackdropLayout from '#site/layouts/GlowingBackdrop'; import LearnLayout from '#site/layouts/Learn'; import PostLayout from '#site/layouts/Post'; @@ -19,7 +19,7 @@ const layouts = { 'blog-post': PostLayout, 'blog-category': BlogLayout, download: DownloadLayout, - 'download-simple': DownloadSimpleLayout, + 'download-archive': DownloadArchiveLayout, article: ArticlePageLayout, } satisfies Record; diff --git a/apps/site/layouts/DownloadSimple.tsx b/apps/site/layouts/DownloadArchive.tsx similarity index 73% rename from apps/site/layouts/DownloadSimple.tsx rename to apps/site/layouts/DownloadArchive.tsx index 31262957c0cdd..7050c2ce4af36 100644 --- a/apps/site/layouts/DownloadSimple.tsx +++ b/apps/site/layouts/DownloadArchive.tsx @@ -6,13 +6,13 @@ import WithNavBar from '#site/components/withNavBar'; import styles from './layouts.module.css'; -const DownloadSimpleLayout: FC = () => ( +const DownloadArchiveLayout: FC = () => ( <>
- +
@@ -20,4 +20,4 @@ const DownloadSimpleLayout: FC = () => ( ); -export default DownloadSimpleLayout; +export default DownloadArchiveLayout; diff --git a/apps/site/next.dynamic.constants.mjs b/apps/site/next.dynamic.constants.mjs index ad371de297bc4..ce01ed8764e0c 100644 --- a/apps/site/next.dynamic.constants.mjs +++ b/apps/site/next.dynamic.constants.mjs @@ -20,8 +20,8 @@ export const IGNORED_ROUTES = [ locale !== defaultLocale.code && /^blog/.test(pathname), // This is used to ignore all pathnames that are empty ({ locale, pathname }) => locale.length && !pathname.length, - // This is used to ignore download routes for major versions and simplified download page - ({ pathname }) => /^download\/(v\d+(\.\d+)*|simplified)$/.test(pathname), + // This is used to ignore download routes for major versions and downloads archive page + ({ pathname }) => /^download\/(v\d+(\.\d+)*|archive)$/.test(pathname), ]; /** @@ -32,14 +32,14 @@ export const IGNORED_ROUTES = [ * @type {Map} A Map of pathname and Layout Name */ export const DYNAMIC_ROUTES = new Map([ - // Creates dynamic routes for simplified download pages for each version + // Creates dynamic routes for downloads archive pages for each version // (e.g., /download/v18.20.8, /download/v20.19.2) ...provideReleaseData() .flatMap(({ minorVersions, versionWithPrefix }) => [ `download/${versionWithPrefix}`, ...minorVersions.map(minor => `download/${minor.versionWithPrefix}`), ]) - .map(version => [version, 'download-simple']), + .map(version => [version, 'download-archive']), // Provides Routes for all Blog Categories ...blogData.categories.map(c => [`blog/${c}`, 'blog-category']), // Provides Routes for all Blog Categories w/ Pagination diff --git a/apps/site/next.mdx.use.mjs b/apps/site/next.mdx.use.mjs index 9470b0e10471b..ae9440dcb3dcb 100644 --- a/apps/site/next.mdx.use.mjs +++ b/apps/site/next.mdx.use.mjs @@ -7,8 +7,9 @@ import { ReleaseOverview } from './components/Downloads/ReleaseOverview'; import UpcomingMeetings from './components/MDX/Calendar/UpcomingMeetings'; import WithBadgeGroup from './components/withBadgeGroup'; import WithBanner from './components/withBanner'; +import WithDownloadArchive from './components/withDownloadArchive'; import WithNodeRelease from './components/withNodeRelease'; -import WithSimplifiedDownload from './components/withSimplifiedDownload'; + /** * A full list of React Components that we want to pass through to MDX * @@ -16,8 +17,8 @@ import WithSimplifiedDownload from './components/withSimplifiedDownload'; */ export const mdxComponents = { DownloadReleasesTable: DownloadReleasesTable, - // HOC for providing the Simplified Download Page properties - WithSimplifiedDownload: WithSimplifiedDownload, + // HOC for providing the Download Archive Page properties + WithDownloadArchive: WithDownloadArchive, // Renders a table with Node.js Releases with different platforms and architectures DownloadsTable: DownloadsTable, // HOC for getting Node.js Release Metadata diff --git a/apps/site/pages/en/download/simplified.mdx b/apps/site/pages/en/download/archive.mdx similarity index 95% rename from apps/site/pages/en/download/simplified.mdx rename to apps/site/pages/en/download/archive.mdx index 29d529f19266d..5dd919a3ddd6a 100644 --- a/apps/site/pages/en/download/simplified.mdx +++ b/apps/site/pages/en/download/archive.mdx @@ -1,9 +1,9 @@ --- title: Download Node.js® -layout: download-simple +layout: download-archive --- - + {({ binaries, installers, version, release, sources, majors }) => ( <>

Download Node.js® {version}

@@ -52,4 +52,4 @@ layout: download-simple )} -
+ diff --git a/apps/site/types/layouts.ts b/apps/site/types/layouts.ts index fabc10237e979..ff6ad599eca79 100644 --- a/apps/site/types/layouts.ts +++ b/apps/site/types/layouts.ts @@ -6,5 +6,5 @@ export type Layouts = | 'blog-category' | 'blog-post' | 'download' - | 'download-simple' + | 'download-archive' | 'article'; diff --git a/apps/site/util/downloadUtils/simple.tsx b/apps/site/util/downloadUtils/archive.tsx similarity index 83% rename from apps/site/util/downloadUtils/simple.tsx rename to apps/site/util/downloadUtils/archive.tsx index e35273cd6b97a..bab2410046fcb 100644 --- a/apps/site/util/downloadUtils/simple.tsx +++ b/apps/site/util/downloadUtils/archive.tsx @@ -129,20 +129,35 @@ export const buildReleaseArtifacts = ( }; }; +/** + * Extracts the version from the pathname. + * It expects the version to be in the format 'v22.0.4' or 'archive'. + */ export const extractVersionFromPath = (pathname: string | undefined) => { if (!pathname) return null; const segments = pathname.split('/').filter(Boolean); const version = segments.pop(); - const major = semVer.major(version || ''); - // Check version format like (v22.0.4 or 'simplified') - if ( - !version || - (!version.match(/^v\d+(\.\d+)*$/) && version !== 'simplified') - ) { + // Check version format like (v22.0.4 or 'archive') + if (!version || (!version.match(/^v\d+(\.\d+)*$/) && version !== 'archive')) { return null; } - return { version, major }; + return version; +}; + +/** + * Finds the appropriate release based on version, if 'archive' is passed, + * it returns the latest LTS release. + */ +export const findReleaseByVersion = ( + releaseData: Array, + version: string | 'archive' +) => { + if (version === 'archive') { + return releaseData.find(release => release.status === 'LTS'); + } + + return releaseData.find(release => semVer.major(version) === release.major); }; diff --git a/packages/i18n/locales/en.json b/packages/i18n/locales/en.json index 87fc22f4d4760..af39365625b38 100644 --- a/packages/i18n/locales/en.json +++ b/packages/i18n/locales/en.json @@ -310,7 +310,7 @@ "unsupportedVersionWarning": "This version is out of maintenance. Please use a currently supported version. Understand EOL support.", "communityPlatformInfo": "Installation methods that involve community software are supported by the teams maintaining that software.", "externalSupportInfo": "If you encounter any issues please visit {platform}'s website", - "noScriptDetected": "This page requires JavaScript. You can download Node.js without JavaScript by visiting the simplified download page directly.", + "noScriptDetected": "This page requires JavaScript. You can download Node.js without JavaScript by visiting the downloads archive page directly.", "platformInfo": { "default": "{platform} and their installation scripts are not maintained by the Node.js project.", "nvm": "\"nvm\" is a cross-platform Node.js version manager.", From 8a43b85b0245340093a272695ffccab601efd77e Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Tue, 3 Jun 2025 18:45:48 +0300 Subject: [PATCH 24/38] refactor: enhance buildReleaseArtifacts --- apps/site/components/withDownloadArchive.tsx | 10 +++------- apps/site/pages/en/download/archive.mdx | 4 ++-- apps/site/util/downloadUtils/archive.tsx | 5 +---- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/apps/site/components/withDownloadArchive.tsx b/apps/site/components/withDownloadArchive.tsx index 087d84da6e6d0..bedf2fe585b00 100644 --- a/apps/site/components/withDownloadArchive.tsx +++ b/apps/site/components/withDownloadArchive.tsx @@ -11,7 +11,7 @@ import { type DownloadArchive = ReturnType; type WithDownloadArchiveProps = { - children: FC; + children: FC }>; }; /** @@ -37,13 +37,9 @@ const WithDownloadArchive: FC = async ({ } const majorVersions = releaseData.map(release => release.versionWithPrefix); - const releaseArtifacts = buildReleaseArtifacts( - release, - version, - majorVersions - ); + const releaseArtifacts = buildReleaseArtifacts(release, version); - return ; + return ; }; export default WithDownloadArchive; diff --git a/apps/site/pages/en/download/archive.mdx b/apps/site/pages/en/download/archive.mdx index 5dd919a3ddd6a..2a80c895f0646 100644 --- a/apps/site/pages/en/download/archive.mdx +++ b/apps/site/pages/en/download/archive.mdx @@ -4,7 +4,7 @@ layout: download-archive --- - {({ binaries, installers, version, release, sources, majors }) => ( + {({ binaries, installers, version, release, sources, majorVersions }) => ( <>

Download Node.js® {version}

@@ -41,7 +41,7 @@ layout: download-archive

Other releases

    - {majors.map(version => ( + {majorVersions.map(version => (
  • Node.js {version}

    diff --git a/apps/site/util/downloadUtils/archive.tsx b/apps/site/util/downloadUtils/archive.tsx index bab2410046fcb..29bc95a99aafe 100644 --- a/apps/site/util/downloadUtils/archive.tsx +++ b/apps/site/util/downloadUtils/archive.tsx @@ -90,8 +90,7 @@ const getCompatibleArtifacts = ({ export const buildReleaseArtifacts = ( release: NodeRelease, - version: string, - majors: Array + version: string ) => { const minorVersion = release.minorVersions.find( ({ versionWithPrefix }) => versionWithPrefix === version @@ -123,9 +122,7 @@ export const buildReleaseArtifacts = ( }), }, version: version, - minors: enrichedRelease.minorVersions, release: enrichedRelease, - majors: majors, }; }; From 1e6e0f49e05a66492d636332748bc2f9563e8515 Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Wed, 4 Jun 2025 22:09:25 +0300 Subject: [PATCH 25/38] docs: updating according to layout/naming --- apps/site/components/Downloads/MinorReleasesTable/index.tsx | 2 ++ apps/site/components/withDownloadArchive.tsx | 6 ++++-- apps/site/next.dynamic.constants.mjs | 2 +- apps/site/util/downloadUtils/archive.tsx | 4 ++++ 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/site/components/Downloads/MinorReleasesTable/index.tsx b/apps/site/components/Downloads/MinorReleasesTable/index.tsx index 43ca24fb17497..b4048fba2fd76 100644 --- a/apps/site/components/Downloads/MinorReleasesTable/index.tsx +++ b/apps/site/components/Downloads/MinorReleasesTable/index.tsx @@ -27,6 +27,8 @@ export const MinorReleasesTable: FC = ({ releases, }) => { const t = useTranslations('components.minorReleasesTable'); + // Chunk minor releases into groups of 8 for scrollable display. + // This is to ensure that the table does not become too wide and remains user-friendly const releaseGroups = chunkedReleases(releases, 8); return ( diff --git a/apps/site/components/withDownloadArchive.tsx b/apps/site/components/withDownloadArchive.tsx index bedf2fe585b00..69f5448975aab 100644 --- a/apps/site/components/withDownloadArchive.tsx +++ b/apps/site/components/withDownloadArchive.tsx @@ -15,7 +15,8 @@ type WithDownloadArchiveProps = { }; /** - * Provides download artifacts and sidebar items to its child component + * Higher-order component that extracts version from pathname, + * fetches release data, and provides download artifacts to child component */ const WithDownloadArchive: FC = async ({ children: Component, @@ -26,10 +27,11 @@ const WithDownloadArchive: FC = async ({ // Extract version from pathname const version = extractVersionFromPath(pathname); - if (!version) { + if (version == null) { return null; } + // Find the release data for the given version const release = findReleaseByVersion(releaseData, version); if (!release) { diff --git a/apps/site/next.dynamic.constants.mjs b/apps/site/next.dynamic.constants.mjs index ce01ed8764e0c..8c84f1b566081 100644 --- a/apps/site/next.dynamic.constants.mjs +++ b/apps/site/next.dynamic.constants.mjs @@ -20,7 +20,7 @@ export const IGNORED_ROUTES = [ locale !== defaultLocale.code && /^blog/.test(pathname), // This is used to ignore all pathnames that are empty ({ locale, pathname }) => locale.length && !pathname.length, - // This is used to ignore download routes for major versions and downloads archive page + // This is used to ignore download routes for Node.js versions and downloads archive page ({ pathname }) => /^download\/(v\d+(\.\d+)*|archive)$/.test(pathname), ]; diff --git a/apps/site/util/downloadUtils/archive.tsx b/apps/site/util/downloadUtils/archive.tsx index 29bc95a99aafe..a3052267b4b9d 100644 --- a/apps/site/util/downloadUtils/archive.tsx +++ b/apps/site/util/downloadUtils/archive.tsx @@ -88,6 +88,10 @@ const getCompatibleArtifacts = ({ }); }; +/** + * Builds the release artifacts for a given Node.js release and version. + * It retrieves binaries, installers, and source files based on the version. + */ export const buildReleaseArtifacts = ( release: NodeRelease, version: string From 52b6cdc5c8104349a7e7b46ee3a4e0abbec63566 Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Wed, 4 Jun 2025 23:12:27 +0300 Subject: [PATCH 26/38] Update apps/site/util/downloadUtils/archive.tsx Co-authored-by: Aviv Keller Signed-off-by: Caner Akdas --- apps/site/util/downloadUtils/archive.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/site/util/downloadUtils/archive.tsx b/apps/site/util/downloadUtils/archive.tsx index a3052267b4b9d..5492e8a9da175 100644 --- a/apps/site/util/downloadUtils/archive.tsx +++ b/apps/site/util/downloadUtils/archive.tsx @@ -135,7 +135,9 @@ export const buildReleaseArtifacts = ( * It expects the version to be in the format 'v22.0.4' or 'archive'. */ export const extractVersionFromPath = (pathname: string | undefined) => { - if (!pathname) return null; + if (!pathname) { + return null; + } const segments = pathname.split('/').filter(Boolean); const version = segments.pop(); From 2759b68b965c62c1a8161976e8052f32ebb2d854 Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Thu, 5 Jun 2025 00:17:57 +0300 Subject: [PATCH 27/38] chore: review updates --- .../Downloads/MinorReleasesTable/index.tsx | 12 ++------ apps/site/components/withDownloadArchive.tsx | 2 +- .../__tests__/getNodeDownloadUrl.test.mjs | 28 ++++++------------- apps/site/util/arrayUtils.ts | 12 ++++++++ apps/site/util/downloadUtils/archive.tsx | 2 +- 5 files changed, 25 insertions(+), 31 deletions(-) create mode 100644 apps/site/util/arrayUtils.ts diff --git a/apps/site/components/Downloads/MinorReleasesTable/index.tsx b/apps/site/components/Downloads/MinorReleasesTable/index.tsx index b4048fba2fd76..8629837f57fd1 100644 --- a/apps/site/components/Downloads/MinorReleasesTable/index.tsx +++ b/apps/site/components/Downloads/MinorReleasesTable/index.tsx @@ -7,6 +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 { chunk } from '#site/util/arrayUtils'; import { getNodeApiLink } from '#site/util/getNodeApiLink'; import styles from './index.module.css'; @@ -15,21 +16,14 @@ type MinorReleasesTableProps = { releases: Array; }; -const chunkedReleases = (releases: Array, size: number) => { - const count = Math.ceil(releases.length / size); - - return Array.from({ length: count }, (_, index) => - releases.slice(index * size, (index + 1) * size) - ); -}; - export const MinorReleasesTable: FC = ({ releases, }) => { const t = useTranslations('components.minorReleasesTable'); + // Chunk minor releases into groups of 8 for scrollable display. // This is to ensure that the table does not become too wide and remains user-friendly - const releaseGroups = chunkedReleases(releases, 8); + const releaseGroups = chunk(releases, 8); return (
    diff --git a/apps/site/components/withDownloadArchive.tsx b/apps/site/components/withDownloadArchive.tsx index 69f5448975aab..6d2ffe2beaa6d 100644 --- a/apps/site/components/withDownloadArchive.tsx +++ b/apps/site/components/withDownloadArchive.tsx @@ -22,7 +22,6 @@ const WithDownloadArchive: FC = async ({ children: Component, }) => { const { pathname } = getClientContext(); - const releaseData = await getReleaseData(); // Extract version from pathname const version = extractVersionFromPath(pathname); @@ -32,6 +31,7 @@ const WithDownloadArchive: FC = async ({ } // Find the release data for the given version + const releaseData = await getReleaseData(); const release = findReleaseByVersion(releaseData, version); if (!release) { diff --git a/apps/site/util/__tests__/getNodeDownloadUrl.test.mjs b/apps/site/util/__tests__/getNodeDownloadUrl.test.mjs index ae8bbad354e0b..d2504459fe3de 100644 --- a/apps/site/util/__tests__/getNodeDownloadUrl.test.mjs +++ b/apps/site/util/__tests__/getNodeDownloadUrl.test.mjs @@ -8,48 +8,36 @@ const version = 'v18.16.0'; describe('getNodeDownloadUrl', () => { it('should return the correct download URL for Mac', () => { const os = 'MAC'; - const bitness = 86; + const platform = 86; const expectedUrl = 'https://nodejs.org/dist/v18.16.0/node-v18.16.0.pkg'; - assert.equal( - getNodeDownloadUrl({ version: version, os: os, platform: bitness }), - expectedUrl - ); + assert.equal(getNodeDownloadUrl({ version, os, platform }), expectedUrl); }); it('should return the correct download URL for Windows (32-bit)', () => { const os = 'WIN'; - const bitness = 86; + const platform = 86; const expectedUrl = 'https://nodejs.org/dist/v18.16.0/node-v18.16.0-x86.msi'; - assert.equal( - getNodeDownloadUrl({ version: version, os: os, platform: bitness }), - expectedUrl - ); + assert.equal(getNodeDownloadUrl({ version, os, platform }), expectedUrl); }); it('should return the correct download URL for Windows (64-bit)', () => { const os = 'WIN'; - const bitness = 64; + const platform = 64; const expectedUrl = 'https://nodejs.org/dist/v18.16.0/node-v18.16.0-x64.msi'; - assert.equal( - getNodeDownloadUrl({ version: version, os: os, platform: bitness }), - expectedUrl - ); + assert.equal(getNodeDownloadUrl({ version, os, platform }), expectedUrl); }); it('should return the default download URL for other operating systems', () => { const os = 'OTHER'; - const bitness = 86; + const platform = 86; const expectedUrl = 'https://nodejs.org/dist/v18.16.0/node-v18.16.0.tar.gz'; - assert.equal( - getNodeDownloadUrl({ version: version, os: os, platform: bitness }), - expectedUrl - ); + assert.equal(getNodeDownloadUrl({ version, os, platform }), expectedUrl); }); describe('MAC', () => { diff --git a/apps/site/util/arrayUtils.ts b/apps/site/util/arrayUtils.ts new file mode 100644 index 0000000000000..f5f5ca4885d11 --- /dev/null +++ b/apps/site/util/arrayUtils.ts @@ -0,0 +1,12 @@ +/** + * Splits an array into smaller chunks of a specified size. + */ +export const chunk = (array: Array, size: number): Array> => { + // Number of chunks needed + const count = Math.ceil(array.length / size); + + // Create an array of chunks by slicing the original array + return Array.from({ length: count }, (_, index) => + array.slice(index * size, (index + 1) * size) + ); +}; diff --git a/apps/site/util/downloadUtils/archive.tsx b/apps/site/util/downloadUtils/archive.tsx index a3052267b4b9d..b3040b097d7e3 100644 --- a/apps/site/util/downloadUtils/archive.tsx +++ b/apps/site/util/downloadUtils/archive.tsx @@ -141,7 +141,7 @@ export const extractVersionFromPath = (pathname: string | undefined) => { const version = segments.pop(); // Check version format like (v22.0.4 or 'archive') - if (!version || (!version.match(/^v\d+(\.\d+)*$/) && version !== 'archive')) { + if (!version || !version.match(/^v\d+(\.\d+)*|archive$/)) { return null; } From 2eaa8931107ac0d7ccd37fdae818e0652ba52c8f Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Thu, 5 Jun 2025 01:21:45 +0300 Subject: [PATCH 28/38] fix: scroll and archive artifacts --- .../MinorReleasesTable/index.module.css | 26 ++++-- .../Downloads/MinorReleasesTable/index.tsx | 85 ++++++++----------- apps/site/components/withDownloadArchive.tsx | 5 +- apps/site/pages/en/download/archive.mdx | 6 +- apps/site/util/arrayUtils.ts | 12 --- 5 files changed, 63 insertions(+), 71 deletions(-) delete mode 100644 apps/site/util/arrayUtils.ts diff --git a/apps/site/components/Downloads/MinorReleasesTable/index.module.css b/apps/site/components/Downloads/MinorReleasesTable/index.module.css index 838ade77d4a6e..0a366db1957ed 100644 --- a/apps/site/components/Downloads/MinorReleasesTable/index.module.css +++ b/apps/site/components/Downloads/MinorReleasesTable/index.module.css @@ -10,16 +10,26 @@ .scrollable { @apply scrollbar-thin flex - snap-x + max-h-[21rem] + snap-y snap-mandatory - overflow-x-auto; + overflow-y-auto; - > table { - @apply shrink-0 - snap-start; + thead { + @apply rounded-t-xs + sticky + top-px + bg-white + shadow-[0_-1px_0_0_var(--color-neutral-200)] + dark:bg-neutral-950 + dark:shadow-[0_-1px_0_0_var(--color-neutral-800)]; + } + + thead th { + @apply last:md:w-56; + } - thead th { - @apply last:md:w-56; - } + tr { + @apply snap-start; } } diff --git a/apps/site/components/Downloads/MinorReleasesTable/index.tsx b/apps/site/components/Downloads/MinorReleasesTable/index.tsx index 8629837f57fd1..70d65fdedfe73 100644 --- a/apps/site/components/Downloads/MinorReleasesTable/index.tsx +++ b/apps/site/components/Downloads/MinorReleasesTable/index.tsx @@ -7,7 +7,6 @@ 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 { chunk } from '#site/util/arrayUtils'; import { getNodeApiLink } from '#site/util/getNodeApiLink'; import styles from './index.module.css'; @@ -21,57 +20,45 @@ export const MinorReleasesTable: FC = ({ }) => { const t = useTranslations('components.minorReleasesTable'); - // Chunk minor releases into groups of 8 for scrollable display. - // This is to ensure that the table does not become too wide and remains user-friendly - const releaseGroups = chunk(releases, 8); - return (
    - {releaseGroups.map(releases => ( - - - - - - - - - {releases.map(release => ( - - + + ))} + +
    {t('version')}{t('links')}
    - - v{release.version} + + + + + + + + + + {releases.map(release => ( + + + - - - ))} - -
    {t('version')}{t('links')}
    + + v{release.version} + + +
    + + {t('actions.changelog')} -
    -
    - - {t('actions.release')} - - - - {t('actions.changelog')} - - - - {t('actions.docs')} - -
    -
    - ))} + + + {t('actions.docs')} + + +
    ); }; diff --git a/apps/site/components/withDownloadArchive.tsx b/apps/site/components/withDownloadArchive.tsx index 6d2ffe2beaa6d..5bdd55b0141d5 100644 --- a/apps/site/components/withDownloadArchive.tsx +++ b/apps/site/components/withDownloadArchive.tsx @@ -39,7 +39,10 @@ const WithDownloadArchive: FC = async ({ } const majorVersions = releaseData.map(release => release.versionWithPrefix); - const releaseArtifacts = buildReleaseArtifacts(release, version); + const releaseArtifacts = buildReleaseArtifacts( + release, + version === 'archive' ? release.versionWithPrefix : version + ); return ; }; diff --git a/apps/site/pages/en/download/archive.mdx b/apps/site/pages/en/download/archive.mdx index 2a80c895f0646..7701fd16087e5 100644 --- a/apps/site/pages/en/download/archive.mdx +++ b/apps/site/pages/en/download/archive.mdx @@ -6,7 +6,11 @@ layout: download-archive {({ binaries, installers, version, release, sources, majorVersions }) => ( <> -

    Download Node.js® {version}

    +

    Node.js Download Archive

    +

    + {version} + {release.codename && ` (${release.codename})`} +

    • diff --git a/apps/site/util/arrayUtils.ts b/apps/site/util/arrayUtils.ts deleted file mode 100644 index f5f5ca4885d11..0000000000000 --- a/apps/site/util/arrayUtils.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Splits an array into smaller chunks of a specified size. - */ -export const chunk = (array: Array, size: number): Array> => { - // Number of chunks needed - const count = Math.ceil(array.length / size); - - // Create an array of chunks by slicing the original array - return Array.from({ length: count }, (_, index) => - array.slice(index * size, (index + 1) * size) - ); -}; From 31400af74036c96806d5336f0982434c7607dbb4 Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Thu, 5 Jun 2025 05:11:16 +0300 Subject: [PATCH 29/38] refactor: minor versions table --- .../MinorReleasesTable/index.module.css | 34 +++++------ .../Downloads/MinorReleasesTable/index.tsx | 57 +++++++++++++++---- .../ReleaseOverview/index.module.css | 30 +++++----- .../Downloads/ReleaseOverview/index.tsx | 38 +++++++------ apps/site/pages/en/download/archive.mdx | 16 +++--- packages/i18n/locales/en.json | 3 +- packages/ui-components/styles/markdown.css | 11 ++++ 7 files changed, 116 insertions(+), 73 deletions(-) diff --git a/apps/site/components/Downloads/MinorReleasesTable/index.module.css b/apps/site/components/Downloads/MinorReleasesTable/index.module.css index 0a366db1957ed..645f033173934 100644 --- a/apps/site/components/Downloads/MinorReleasesTable/index.module.css +++ b/apps/site/components/Downloads/MinorReleasesTable/index.module.css @@ -1,35 +1,29 @@ @reference "../../../styles/index.css"; -.links { +.additionalLinks { @apply flex h-4 items-center gap-2; } +.items { + @apply flex + h-9 + gap-2; +} + .scrollable { @apply scrollbar-thin flex - max-h-[21rem] - snap-y - snap-mandatory + max-h-[29rem] overflow-y-auto; +} - thead { - @apply rounded-t-xs - sticky - top-px - bg-white - shadow-[0_-1px_0_0_var(--color-neutral-200)] - dark:bg-neutral-950 - dark:shadow-[0_-1px_0_0_var(--color-neutral-800)]; - } - - thead th { - @apply last:md:w-56; - } +.information { + @apply md:w-96; +} - tr { - @apply snap-start; - } +.links { + @apply md:w-44; } diff --git a/apps/site/components/Downloads/MinorReleasesTable/index.tsx b/apps/site/components/Downloads/MinorReleasesTable/index.tsx index 70d65fdedfe73..97fa5b59886ea 100644 --- a/apps/site/components/Downloads/MinorReleasesTable/index.tsx +++ b/apps/site/components/Downloads/MinorReleasesTable/index.tsx @@ -1,15 +1,19 @@ 'use client'; +import { CodeBracketSquareIcon } from '@heroicons/react/24/outline'; import Separator from '@node-core/ui-components/Common/Separator'; +import NpmIcon from '@node-core/ui-components/Icons/PackageManager/Npm'; import { useTranslations } from 'next-intl'; import type { FC } from 'react'; import Link from '#site/components/Link'; +import LinkWithArrow from '#site/components/LinkWithArrow'; import { BASE_CHANGELOG_URL } from '#site/next.constants.mjs'; import type { MinorVersion } from '#site/types'; import { getNodeApiLink } from '#site/util/getNodeApiLink'; import styles from './index.module.css'; +import { ReleaseOverviewItem } from '../ReleaseOverview'; type MinorReleasesTableProps = { releases: Array; @@ -18,19 +22,21 @@ type MinorReleasesTableProps = { export const MinorReleasesTable: FC = ({ releases, }) => { - const t = useTranslations('components.minorReleasesTable'); + const t = useTranslations('components'); return (
      - - + + + - {releases.map(release => ( + diff --git a/apps/site/components/Downloads/ReleaseOverview/index.module.css b/apps/site/components/Downloads/ReleaseOverview/index.module.css index 5043967ac27f1..da1541398edbc 100644 --- a/apps/site/components/Downloads/ReleaseOverview/index.module.css +++ b/apps/site/components/Downloads/ReleaseOverview/index.module.css @@ -15,24 +15,24 @@ gap-4 lg:grid-cols-3; } +} - .item { - @apply flex - items-center - gap-2; +.item { + @apply flex + items-center + gap-2; - h1 { - @apply text-sm - font-semibold; - } + dt { + @apply text-sm + font-semibold; + } - h2 { - @apply text-xs - font-normal; - } + dd { + @apply text-xs + font-normal; + } - svg { - @apply size-4; - } + svg { + @apply size-4; } } diff --git a/apps/site/components/Downloads/ReleaseOverview/index.tsx b/apps/site/components/Downloads/ReleaseOverview/index.tsx index a29118a8e400f..af38c606d5cfc 100644 --- a/apps/site/components/Downloads/ReleaseOverview/index.tsx +++ b/apps/site/components/Downloads/ReleaseOverview/index.tsx @@ -13,23 +13,25 @@ import type { NodeRelease } from '#site/types'; import styles from './index.module.css'; -type ItemProps = { +type ReleaseOverviewItemProps = { Icon: FC>; title: ReactNode; subtitle: ReactNode; }; -const Item: FC = ({ Icon, title, subtitle }) => { - return ( -
      - -
      -

      {subtitle}

      -

      {title}

      -
      -
      - ); -}; +export const ReleaseOverviewItem: FC = ({ + Icon, + title, + subtitle, +}) => ( +
      + +
      +
      {subtitle}
      +
      {title}
      +
      +
      +); type ReleaseOverviewProps = { release: NodeRelease; @@ -41,36 +43,36 @@ export const ReleaseOverview: FC = ({ release }) => { return (
      - } subtitle={t('components.releaseOverview.firstReleased')} /> - } subtitle={t('components.releaseOverview.lastUpdated')} /> - {release.modules && ( - )} {release.npm && ( - )} - -

      Binary Downloads

      - -

      Installer Packages

      - -

      Minor versions

      -
      -

      Other releases

      +

      Other releases

        {majorVersions.map(version => (
      • -

        Node.js {version}

        +

        Node.js {version}

      • ))}
      +

      Binary Downloads

      + +

      Installer Packages

      + +

      Minor versions

      + )} diff --git a/packages/i18n/locales/en.json b/packages/i18n/locales/en.json index eaba42c856be2..1686edcdc34c7 100644 --- a/packages/i18n/locales/en.json +++ b/packages/i18n/locales/en.json @@ -179,6 +179,7 @@ "version": "Version", "links": "Links", "showMore": "Show more", + "information": "Version Informations", "actions": { "release": "Release", "changelog": "Changelog", @@ -343,4 +344,4 @@ }, "logo": "Node.js logo" } -} +} \ No newline at end of file diff --git a/packages/ui-components/styles/markdown.css b/packages/ui-components/styles/markdown.css index 862cd6450f05e..81590c3c02a9f 100644 --- a/packages/ui-components/styles/markdown.css +++ b/packages/ui-components/styles/markdown.css @@ -170,4 +170,15 @@ main { @apply sm:border-l-0; } } + + details { + h1, + h2, + h3, + h4, + h5, + h6 { + @apply inline; + } + } } From e516c0b905ce6165ebdaf6034df3e317e8c1c55a Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Thu, 5 Jun 2025 05:24:05 +0300 Subject: [PATCH 30/38] chore: lint --- packages/i18n/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/i18n/locales/en.json b/packages/i18n/locales/en.json index 1686edcdc34c7..9614d782e3ec4 100644 --- a/packages/i18n/locales/en.json +++ b/packages/i18n/locales/en.json @@ -344,4 +344,4 @@ }, "logo": "Node.js logo" } -} \ No newline at end of file +} From 1417339fe7c189299636372a824a6a865abce673 Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Thu, 5 Jun 2025 05:47:22 +0300 Subject: [PATCH 31/38] feat: release alert box --- .../Downloads/ReleaseModal/index.tsx | 12 ++------ apps/site/components/withReleaseAlertBox.tsx | 30 +++++++++++++++++++ apps/site/next.mdx.use.mjs | 3 ++ apps/site/pages/en/download/archive.mdx | 1 + packages/ui-components/styles/markdown.css | 23 +++++++++----- 5 files changed, 52 insertions(+), 17 deletions(-) create mode 100644 apps/site/components/withReleaseAlertBox.tsx diff --git a/apps/site/components/Downloads/ReleaseModal/index.tsx b/apps/site/components/Downloads/ReleaseModal/index.tsx index 83a272a7214a0..7c10fce93289f 100644 --- a/apps/site/components/Downloads/ReleaseModal/index.tsx +++ b/apps/site/components/Downloads/ReleaseModal/index.tsx @@ -1,4 +1,3 @@ -import AlertBox from '@node-core/ui-components/Common/AlertBox'; import Modal from '@node-core/ui-components/Common/Modal'; import { useTranslations } from 'next-intl'; import type { FC } from 'react'; @@ -6,6 +5,7 @@ import type { FC } from 'react'; import { MinorReleasesTable } from '#site/components/Downloads/MinorReleasesTable'; import { ReleaseOverview } from '#site/components/Downloads/ReleaseOverview'; import LinkWithArrow from '#site/components/LinkWithArrow'; +import WithReleaseAlertBox from '#site/components/withReleaseAlertBox'; import type { NodeRelease } from '#site/types'; type ReleaseModalProps = { @@ -32,15 +32,7 @@ const ReleaseModal: FC = ({ return ( - {release.status === 'End-of-life' && ( - - {t('components.releaseModal.unsupportedVersionWarning')} - - )} + {release.releaseAnnounceLink && ( diff --git a/apps/site/components/withReleaseAlertBox.tsx b/apps/site/components/withReleaseAlertBox.tsx new file mode 100644 index 0000000000000..44254fad65387 --- /dev/null +++ b/apps/site/components/withReleaseAlertBox.tsx @@ -0,0 +1,30 @@ +import AlertBox from '@node-core/ui-components/Common/AlertBox'; +import { useTranslations } from 'next-intl'; +import type { FC } from 'react'; + +import type { NodeReleaseStatus } from '#site/types'; + +type WithReleaseAlertBoxProps = { + status: NodeReleaseStatus; +}; + +const WithReleaseAlertBox: FC = ({ status }) => { + const t = useTranslations(); + + switch (status) { + case 'End-of-life': + return ( + + {t('components.releaseModal.unsupportedVersionWarning')} + + ); + default: + return null; + } +}; + +export default WithReleaseAlertBox; diff --git a/apps/site/next.mdx.use.mjs b/apps/site/next.mdx.use.mjs index ae9440dcb3dcb..bd5b54e48f1be 100644 --- a/apps/site/next.mdx.use.mjs +++ b/apps/site/next.mdx.use.mjs @@ -9,6 +9,7 @@ import WithBadgeGroup from './components/withBadgeGroup'; import WithBanner from './components/withBanner'; import WithDownloadArchive from './components/withDownloadArchive'; import WithNodeRelease from './components/withNodeRelease'; +import WithReleaseAlertBox from './components/withReleaseAlertBox'; /** * A full list of React Components that we want to pass through to MDX @@ -23,6 +24,8 @@ export const mdxComponents = { DownloadsTable: DownloadsTable, // HOC for getting Node.js Release Metadata WithNodeRelease: WithNodeRelease, + // Renders an alert box with the given release status + WithReleaseAlertBox: WithReleaseAlertBox, // HOC for providing Banner Data WithBanner: WithBanner, // HOC for providing Badge Data diff --git a/apps/site/pages/en/download/archive.mdx b/apps/site/pages/en/download/archive.mdx index 756275d962fb1..b96310962faec 100644 --- a/apps/site/pages/en/download/archive.mdx +++ b/apps/site/pages/en/download/archive.mdx @@ -11,6 +11,7 @@ layout: download-archive {version} {release.codename && ` (${release.codename})`} +
      • diff --git a/packages/ui-components/styles/markdown.css b/packages/ui-components/styles/markdown.css index 81590c3c02a9f..6451c1a8875c1 100644 --- a/packages/ui-components/styles/markdown.css +++ b/packages/ui-components/styles/markdown.css @@ -172,13 +172,22 @@ main { } details { - h1, - h2, - h3, - h4, - h5, - h6 { - @apply inline; + summary * { + @apply inline + pl-2; + } + + a { + h1, + h2, + h3, + h4, + h5, + h6 { + @apply inline + text-green-600 + dark:text-green-400; + } } } } From 7358d164cd558e4270af53c43b6b90f13da0f6a8 Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Thu, 5 Jun 2025 05:55:25 +0300 Subject: [PATCH 32/38] chore: previous releases links --- apps/site/pages/en/download/current.mdx | 2 +- apps/site/pages/en/download/index.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/site/pages/en/download/current.mdx b/apps/site/pages/en/download/current.mdx index 16e78d6a8e1d7..a3c838e5f70e3 100644 --- a/apps/site/pages/en/download/current.mdx +++ b/apps/site/pages/en/download/current.mdx @@ -30,7 +30,7 @@ Learn how to Node.js source tarball. Check out our nightly binaries or -all previous releases +all previous releases or the unofficial binaries for other platforms. diff --git a/apps/site/pages/en/download/index.mdx b/apps/site/pages/en/download/index.mdx index 16e78d6a8e1d7..a3c838e5f70e3 100644 --- a/apps/site/pages/en/download/index.mdx +++ b/apps/site/pages/en/download/index.mdx @@ -30,7 +30,7 @@ Learn how to Node.js source tarball. Check out our nightly binaries or -all previous releases +all previous releases or the unofficial binaries for other platforms. From b832299134c640d26c1ee007d5ffe7fc14029c3b Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Thu, 5 Jun 2025 06:14:36 +0300 Subject: [PATCH 33/38] refactor: version navigation and content --- apps/site/components/withDownloadArchive.tsx | 12 +++++++++--- apps/site/pages/en/download/archive.mdx | 10 +++++----- apps/site/util/downloadUtils/archive.tsx | 11 +++++++++++ 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/apps/site/components/withDownloadArchive.tsx b/apps/site/components/withDownloadArchive.tsx index 5bdd55b0141d5..5d43d73f19e72 100644 --- a/apps/site/components/withDownloadArchive.tsx +++ b/apps/site/components/withDownloadArchive.tsx @@ -6,12 +6,18 @@ import { buildReleaseArtifacts, extractVersionFromPath, findReleaseByVersion, + getDownloadArchiveNavigation, } from '#site/util/downloadUtils/archive'; type DownloadArchive = ReturnType; +type Navigation = { + label: string; + href: string; +}; + type WithDownloadArchiveProps = { - children: FC }>; + children: FC }>; }; /** @@ -38,13 +44,13 @@ const WithDownloadArchive: FC = async ({ return null; } - const majorVersions = releaseData.map(release => release.versionWithPrefix); + const navigation = getDownloadArchiveNavigation(releaseData); const releaseArtifacts = buildReleaseArtifacts( release, version === 'archive' ? release.versionWithPrefix : version ); - return ; + return ; }; export default WithDownloadArchive; diff --git a/apps/site/pages/en/download/archive.mdx b/apps/site/pages/en/download/archive.mdx index b96310962faec..6d7d89478b355 100644 --- a/apps/site/pages/en/download/archive.mdx +++ b/apps/site/pages/en/download/archive.mdx @@ -4,7 +4,7 @@ layout: download-archive --- - {({ binaries, installers, version, release, sources, majorVersions }) => ( + {({ binaries, installers, version, release, sources, navigation }) => ( <>

        Node.js Download Archive

        @@ -40,10 +40,10 @@ layout: download-archive

        Other releases

          - {majorVersions.map(version => ( -
        • - -

          Node.js {version}

          + {navigation.map(({ href, label }) => ( +
        • + +

          {label}

        • ))} diff --git a/apps/site/util/downloadUtils/archive.tsx b/apps/site/util/downloadUtils/archive.tsx index 9edadea5d2ead..eb7ecdfb4d2fc 100644 --- a/apps/site/util/downloadUtils/archive.tsx +++ b/apps/site/util/downloadUtils/archive.tsx @@ -88,6 +88,17 @@ const getCompatibleArtifacts = ({ }); }; +/** + * Generates the navigation links for the Node.js download archive + * It creates a list of links for each major release, formatted with the major + * version and codename if available. + */ +export const getDownloadArchiveNavigation = (releases: Array) => + releases.map(({ major, codename, versionWithPrefix }) => ({ + label: `Node.js v${major} ${codename ? `(${codename})` : ''}`, + href: `/download/${versionWithPrefix}`, + })); + /** * Builds the release artifacts for a given Node.js release and version. * It retrieves binaries, installers, and source files based on the version. From 0fa1f1d84f31eafa5b1de6f920d1dc9b9a57b6f5 Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Fri, 6 Jun 2025 19:48:24 +0300 Subject: [PATCH 34/38] fix: separator, modal border color --- packages/ui-components/Common/Modal/index.module.css | 1 + packages/ui-components/Common/Separator/index.module.css | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/ui-components/Common/Modal/index.module.css b/packages/ui-components/Common/Modal/index.module.css index 5b399767fdf7e..42e8b79c10e0c 100644 --- a/packages/ui-components/Common/Modal/index.module.css +++ b/packages/ui-components/Common/Modal/index.module.css @@ -26,6 +26,7 @@ focus:outline-none sm:my-20 xl:p-12 + dark:border-neutral-800 dark:bg-neutral-950; } diff --git a/packages/ui-components/Common/Separator/index.module.css b/packages/ui-components/Common/Separator/index.module.css index 61d7dc140faa0..49ef6d6896fec 100644 --- a/packages/ui-components/Common/Separator/index.module.css +++ b/packages/ui-components/Common/Separator/index.module.css @@ -2,7 +2,8 @@ .root { @apply shrink-0 - bg-neutral-800; + bg-neutral-200 + dark:bg-neutral-800; &.horizontal { @apply h-px From 2b17e6b345f4cca383fc7f0cf9971fc70b66d4ef Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Fri, 6 Jun 2025 20:05:08 +0300 Subject: [PATCH 35/38] feat: DownloadReleasesTable download archive redirects --- .../components/Downloads/DownloadReleasesTable/index.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/site/components/Downloads/DownloadReleasesTable/index.tsx b/apps/site/components/Downloads/DownloadReleasesTable/index.tsx index fa2180f5f7fbe..68900ad7ae013 100644 --- a/apps/site/components/Downloads/DownloadReleasesTable/index.tsx +++ b/apps/site/components/Downloads/DownloadReleasesTable/index.tsx @@ -4,6 +4,7 @@ import type { FC } from 'react'; import FormattedTime from '#site/components/Common/FormattedTime'; import DetailsButton from '#site/components/Downloads/DownloadReleasesTable/DetailsButton'; +import Link from '#site/components/Link'; import getReleaseData from '#site/next-data/releaseData'; const DownloadReleasesTable: FC = async () => { @@ -26,7 +27,11 @@ const DownloadReleasesTable: FC = async () => {
      {releaseData.map(release => ( - +
      {t('version')}{t('links')}{t('minorReleasesTable.version')} + {t('minorReleasesTable.information')} + {t('minorReleasesTable.links')}
      @@ -39,20 +45,49 @@ export const MinorReleasesTable: FC = ({ -
      +
      + {release.modules && ( + <> + + + + )} + {release.npm && ( + <> + + + + )} + +
      +
      +
      - {t('actions.changelog')} + {t('minorReleasesTable.actions.docs')} - - {t('actions.docs')} - + {t('minorReleasesTable.actions.changelog')} +
      v{release.major} + + v{release.major} + + {release.codename || '-'} From f533ebc237d5066ddafc1956928fdf7abefcc060 Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Fri, 6 Jun 2025 20:13:48 +0300 Subject: [PATCH 36/38] refactor: markdown content editing and Node.js logo added --- .../Downloads/ReleaseModal/index.tsx | 2 +- apps/site/pages/en/download/archive.mdx | 41 +++++++++++-------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/apps/site/components/Downloads/ReleaseModal/index.tsx b/apps/site/components/Downloads/ReleaseModal/index.tsx index 7c10fce93289f..cab38313e5303 100644 --- a/apps/site/components/Downloads/ReleaseModal/index.tsx +++ b/apps/site/components/Downloads/ReleaseModal/index.tsx @@ -26,7 +26,7 @@ const ReleaseModal: FC = ({ : 'components.releaseModal.titleWithoutCodename'; const modalHeading = t(modalHeadingKey, { - version: release.major, + version: `v${release.major}`, codename: release.codename ?? '', }); diff --git a/apps/site/pages/en/download/archive.mdx b/apps/site/pages/en/download/archive.mdx index 6d7d89478b355..d0a28259669fb 100644 --- a/apps/site/pages/en/download/archive.mdx +++ b/apps/site/pages/en/download/archive.mdx @@ -7,34 +7,38 @@ layout: download-archive {({ binaries, installers, version, release, sources, navigation }) => ( <>

      Node.js Download Archive

      -

      + +

      + Node.js Logo {version} {release.codename && ` (${release.codename})`}

      + + +
        +
      • - Learn more about{' '} - Node.js releases, - including the release schedule and LTS status. + Learn more about Node.js releases, including the release schedule and LTS status.
      • +
      • - Signed SHASUMS{' '} - for release files. How to{' '} - - verify - {' '} - signed SHASUMS. + Signed SHASUMS for release files. How to verify signed SHASUMS.
      • +
      • - Download a signed{' '} - - Node.js {version} source - {' '} - tarball. + Download a signed Node.js {version} source tarball.
      • +
      +

      Other releases

      @@ -49,12 +53,17 @@ layout: download-archive ))}
      +

      Binary Downloads

      +

      Installer Packages

      +

      Minor versions

      - )} + +)} + From 1c8abdd2bd3d65c8b732e53f74e2f0e256a274ff Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Fri, 6 Jun 2025 20:14:30 +0300 Subject: [PATCH 37/38] feat: home page button redirects --- .../Downloads/DownloadButton/index.module.css | 19 --- .../Downloads/DownloadButton/index.tsx | 55 ------- apps/site/next.mdx.use.client.mjs | 3 - apps/site/pages/en/index.mdx | 23 ++- apps/site/pages/es/index.mdx | 135 ----------------- apps/site/pages/fa/index.mdx | 133 ----------------- apps/site/pages/fr/index.mdx | 133 ----------------- apps/site/pages/id/index.mdx | 135 ----------------- apps/site/pages/ja/index.mdx | 133 ----------------- apps/site/pages/ko/index.mdx | 137 ----------------- apps/site/pages/pt/index.mdx | 135 ----------------- apps/site/pages/tr/index.mdx | 139 ------------------ apps/site/pages/uk/index.mdx | 134 ----------------- apps/site/pages/zh-cn/index.mdx | 132 ----------------- apps/site/pages/zh-tw/index.mdx | 133 ----------------- 15 files changed, 10 insertions(+), 1569 deletions(-) delete mode 100644 apps/site/components/Downloads/DownloadButton/index.module.css delete mode 100644 apps/site/components/Downloads/DownloadButton/index.tsx delete mode 100644 apps/site/pages/es/index.mdx delete mode 100644 apps/site/pages/fa/index.mdx delete mode 100644 apps/site/pages/fr/index.mdx delete mode 100644 apps/site/pages/id/index.mdx delete mode 100644 apps/site/pages/ja/index.mdx delete mode 100644 apps/site/pages/ko/index.mdx delete mode 100644 apps/site/pages/pt/index.mdx delete mode 100644 apps/site/pages/tr/index.mdx delete mode 100644 apps/site/pages/uk/index.mdx delete mode 100644 apps/site/pages/zh-cn/index.mdx delete mode 100644 apps/site/pages/zh-tw/index.mdx diff --git a/apps/site/components/Downloads/DownloadButton/index.module.css b/apps/site/components/Downloads/DownloadButton/index.module.css deleted file mode 100644 index 7cc12d7f700e3..0000000000000 --- a/apps/site/components/Downloads/DownloadButton/index.module.css +++ /dev/null @@ -1,19 +0,0 @@ -@reference "../../../styles/index.css"; - -.downloadButton { - @apply justify-center; - - &.primary { - @apply inline-flex - dark:hidden; - } - - &.special { - @apply hidden - dark:inline-flex; - } - - svg { - @apply dark:opacity-50; - } -} diff --git a/apps/site/components/Downloads/DownloadButton/index.tsx b/apps/site/components/Downloads/DownloadButton/index.tsx deleted file mode 100644 index d835af5ac0d0d..0000000000000 --- a/apps/site/components/Downloads/DownloadButton/index.tsx +++ /dev/null @@ -1,55 +0,0 @@ -'use client'; - -import { CloudArrowDownIcon } from '@heroicons/react/24/outline'; -import classNames from 'classnames'; -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 styles from './index.module.css'; - -type DownloadButtonProps = { release: NodeRelease }; - -const DownloadButton: FC> = ({ - release: { versionWithPrefix }, - children, -}) => { - const { os, bitness, architecture } = useClientContext(); - - const platform = getUserPlatform(architecture, bitness); - const downloadLink = getNodeDownloadUrl({ - version: versionWithPrefix, - os: os, - platform: platform, - }); - - return ( - <> - - - - - ); -}; - -export default DownloadButton; diff --git a/apps/site/next.mdx.use.client.mjs b/apps/site/next.mdx.use.client.mjs index b9e0f64d0daa1..e466a1d1a9a3b 100644 --- a/apps/site/next.mdx.use.client.mjs +++ b/apps/site/next.mdx.use.client.mjs @@ -4,7 +4,6 @@ import Blockquote from '@node-core/ui-components/Common/Blockquote'; import MDXCodeTabs from '@node-core/ui-components/MDX/CodeTabs'; import Button from './components/Common/Button'; -import DownloadButton from './components/Downloads/DownloadButton'; import DownloadLink from './components/Downloads/DownloadLink'; import BlogPostLink from './components/Downloads/Release/BlogPostLink'; import ChangelogLink from './components/Downloads/Release/ChangelogLink'; @@ -36,8 +35,6 @@ export const clientMdxComponents = { LinkWithArrow: LinkWithArrow, // Regular links (without arrow) Link: Link, - // Renders a Download Button - DownloadButton: DownloadButton, // Renders a Download Link DownloadLink: DownloadLink, // Group of components that enable you to select versions for Node.js diff --git a/apps/site/pages/en/index.mdx b/apps/site/pages/en/index.mdx index 95de44c70f2a1..f3b043107dd88 100644 --- a/apps/site/pages/en/index.mdx +++ b/apps/site/pages/en/index.mdx @@ -14,19 +14,16 @@ layout: home -
      - - {({ release }) => ( - <> - Download Node.js (LTS) - - Downloads Node.js {release.versionWithPrefix} - 1 with long-term support. - Node.js can also be installed via version managers. - - - )} - +
      + +
      + + + + + + +
      {({ release }) => ( diff --git a/apps/site/pages/es/index.mdx b/apps/site/pages/es/index.mdx deleted file mode 100644 index 2d590f93c6251..0000000000000 --- a/apps/site/pages/es/index.mdx +++ /dev/null @@ -1,135 +0,0 @@ ---- -title: Ejecuta JavaScript en cualquier parte -layout: home ---- - -
      - - -
      -

      Ejecuta JavaScript en cualquier parte

      - - Node.js® es un entorno de ejecución de JavaScript multiplataforma, - de código abierto y gratuito que permite a los desarrolladores crear servidores, - aplicaciones web, herramientas de línea de comando y scripts. - -
      - -
      - - {({ release }) => ( - <> - Descargar Node.js (LTS) - - Descarga Node.js {release.versionWithPrefix} - 1 con soporte a largo plazo. - Node.js también puede ser instalado a través de gestores de versiones. - - - )} - - - - {({ release }) => ( - - ¿Quieres nuevas funciones más pronto? - ConsigueNode.js {release.versionWithPrefix} - 1 en vez. - - )} - - -
      -
      - -
      -
      - ```js displayName="Create an HTTP Server" - // server.mjs - import { createServer } from 'node:http'; - - const server = createServer((req, res) => { - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.end('Hello World!\n'); - }); - - // starts a simple http server locally on port 3000 - server.listen(3000, '127.0.0.1', () => { - console.log('Listening on 127.0.0.1:3000'); - }); - - // run with `node server.mjs` - ``` - - ```js displayName="Write Tests" - // tests.mjs - import assert from 'node:assert'; - import test from 'node:test'; - - test('that 1 is equal 1', () => { - assert.strictEqual(1, 1); - }); - - test('that throws as 1 is not equal 2', () => { - // throws an exception because 1 != 2 - assert.strictEqual(1, 2); - }); - - // run with `node tests.mjs` - ``` - - ```js displayName="Read and Hash a File" - // crypto.mjs - import { createHash } from 'node:crypto'; - import { readFile } from 'node:fs/promises'; - - const hasher = createHash('sha1'); - - hasher.setEncoding('hex'); - // ensure you have a `package.json` file for this test! - hasher.write(await readFile('package.json')); - hasher.end(); - - const fileHash = hasher.read(); - - // run with `node crypto.mjs` - ``` - - ```js displayName="Streams Pipeline" - // streams.mjs - import { createReadStream, createWriteStream } from 'node:fs'; - import { pipeline } from 'node:stream/promises'; - import { createGzip } from 'node:zlib'; - - // ensure you have a `package.json` file for this test! - await pipeline( - createReadStream('package.json'), - createGzip(), - createWriteStream('package.json.gz') - ); - - // run with `node streams.mjs` - ``` - - ```js displayName="Work with Threads" - // threads.mjs - import { Worker, isMainThread, - workerData, parentPort } from 'node:worker_threads'; - - if (isMainThread) { - const data = 'some data'; - const worker = new Worker(import.meta.filename, { workerData: data }); - worker.on('message', msg => console.log('Reply from Thread:', msg)); - } else { - const source = workerData; - parentPort.postMessage(btoa(source.toUpperCase())); - } - - // run with `node threads.mjs` - ``` - -
      - -Aprenda más sobre lo que Node.js puede ofrecer con nuestros [Materiales de aprendizaje](/learn). - -
      diff --git a/apps/site/pages/fa/index.mdx b/apps/site/pages/fa/index.mdx deleted file mode 100644 index d1fcd30f94bea..0000000000000 --- a/apps/site/pages/fa/index.mdx +++ /dev/null @@ -1,133 +0,0 @@ ---- -title: اجرا جاوااسکریپت در همه جا -layout: home ---- - -
      - - -
      -

      اجرا جاوااسکریپت در همه جا

      - - Node.js یک محیط اجرای جاوااسکریپت متن‌باز، رایگان و چندسکویی است - که به توسعه دهندگان اجازه می‌دهد تا سرورها، وب اپلیکیشن‌ها - ابزارهای خط فرمان و اسکریپت‌ها را ایجاد کنند. - -
      - -
      - - {({ release }) => ( - <> - دانلود Node.js (نسخه پایدار) - - دانلود Node.js {release.versionWithPrefix} - 1 با پشتیبانی بلندمدت. - Node.js همچنین از طریق package managers نیز قابل نصب است. - - - )} - - - - {({ release }) => ( - - می‌خواهید زودتر از ویژگی‌های جدید برخوردار شوید؟ - به‌جای آن Node.js {release.versionWithPrefix} - 1 را دریافت کنید. - - )} - - -
      -
      - -
      -
      - ```js displayName="Create an HTTP Server" - // server.mjs - import { createServer } from 'node:http'; - - const server = createServer((req, res) => { - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.end('Hello World!\n'); - }); - - // starts a simple http server locally on port 3000 - server.listen(3000, '127.0.0.1', () => { - console.log('Listening on 127.0.0.1:3000'); - }); - - // run with `node server.mjs` - ``` - - ```js displayName="Write Tests" - // tests.mjs - import assert from 'node:assert'; - import test from 'node:test'; - - test('that 1 is equal 1', () => { - assert.strictEqual(1, 1); - }); - - test('that throws as 1 is not equal 2', () => { - // throws an exception because 1 != 2 - assert.strictEqual(1, 2); - }); - - // run with `node tests.mjs` - ``` - - ```js displayName="Read and Hash a File" - // crypto.mjs - import { createHash } from 'node:crypto'; - import { readFile } from 'node:fs/promises'; - - const hasher = createHash('sha1'); - - hasher.setEncoding('hex'); - // ensure you have a `package.json` file for this test! - hasher.write(await readFile('package.json')); - hasher.end(); - - const fileHash = hasher.read(); - - // run with `node crypto.mjs` - ``` - - ```js displayName="Streams Pipeline" - // streams.mjs - import { createReadStream, createWriteStream } from 'node:fs'; - import { pipeline } from 'node:stream/promises'; - import { createGzip } from 'node:zlib'; - - // ensure you have a `package.json` file for this test! - await pipeline( - createReadStream('package.json'), - createGzip(), - createWriteStream('package.json.gz') - ); - - // run with `node streams.mjs` - ``` - - ```js displayName="Work with Threads" - // threads.mjs - import { Worker, isMainThread, - workerData, parentPort } from 'node:worker_threads'; - - if (isMainThread) { - const data = 'some data'; - const worker = new Worker(import.meta.filename, { workerData: data }); - worker.on('message', msg => console.log('Reply from Thread:', msg)); - } else { - const source = workerData; - parentPort.postMessage(btoa(source.toUpperCase())); - } - - // run with `node threads.mjs` - ``` - -
      - با [مطالب آموزشی](/learn) ما، بیشتر در مورد اینکه Node.js چه امکاناتی را ارائه می دهد بیاموزید. -
      diff --git a/apps/site/pages/fr/index.mdx b/apps/site/pages/fr/index.mdx deleted file mode 100644 index 2b72207fc2c0f..0000000000000 --- a/apps/site/pages/fr/index.mdx +++ /dev/null @@ -1,133 +0,0 @@ ---- -title: Exécuter du JavaScript partout -layout: home ---- - -
      - - -
      -

      Exécuter du JavaScript partout

      - - Node.js® est un environnement d'exécution JavaScript gratuit, open-source et multiplateforme qui permet aux développeurs de créer des serveurs, des applications web, des outils en ligne de commande et des scripts. - -
      - -
      - - {({ release }) => ( - <> - Télécharger Node.js (LTS) - - Téléchargements Node.js {release.versionWithPrefix} - 1 avec un support à long terme. - Node.js peut également être installé via le gestionnaire de versions. - - - )} - - - - {({ release }) => ( - - Vous voulez de nouvelles fonctionnalités plus tôt ? - Obtenez Node.js {release.versionWithPrefix} - 1 à la place. - - )} - - -
      -
      - -
      -
      - ```js displayName="Create an HTTP Server" - // server.mjs - import { createServer } from 'node:http'; - - const server = createServer((req, res) => { - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.end('Hello World!\n'); - }); - - // starts a simple http server locally on port 3000 - server.listen(3000, '127.0.0.1', () => { - console.log('Listening on 127.0.0.1:3000'); - }); - - // run with `node server.mjs` - ``` - - ```js displayName="Write Tests" - // tests.mjs - import assert from 'node:assert'; - import test from 'node:test'; - - test('that 1 is equal 1', () => { - assert.strictEqual(1, 1); - }); - - test('that throws as 1 is not equal 2', () => { - // throws an exception because 1 != 2 - assert.strictEqual(1, 2); - }); - - // run with `node tests.mjs` - ``` - - ```js displayName="Read and Hash a File" - // crypto.mjs - import { createHash } from 'node:crypto'; - import { readFile } from 'node:fs/promises'; - - const hasher = createHash('sha1'); - - hasher.setEncoding('hex'); - // ensure you have a `package.json` file for this test! - hasher.write(await readFile('package.json')); - hasher.end(); - - const fileHash = hasher.read(); - - // run with `node crypto.mjs` - ``` - - ```js displayName="Streams Pipeline" - // streams.mjs - import { createReadStream, createWriteStream } from 'node:fs'; - import { pipeline } from 'node:stream/promises'; - import { createGzip } from 'node:zlib'; - - // ensure you have a `package.json` file for this test! - await pipeline( - createReadStream('package.json'), - createGzip(), - createWriteStream('package.json.gz') - ); - - // run with `node streams.mjs` - ``` - - ```js displayName="Work with Threads" - // threads.mjs - import { Worker, isMainThread, - workerData, parentPort } from 'node:worker_threads'; - - if (isMainThread) { - const data = 'some data'; - const worker = new Worker(import.meta.filename, { workerData: data }); - worker.on('message', msg => console.log('Reply from Thread:', msg)); - } else { - const source = workerData; - parentPort.postMessage(btoa(source.toUpperCase())); - } - - // run with `node threads.mjs` - ``` - -
      - -Apprenez-en plus sur ce que Node.js est capable d'offrir avec notre [Matériel d'apprentissage](/learn). - -
      diff --git a/apps/site/pages/id/index.mdx b/apps/site/pages/id/index.mdx deleted file mode 100644 index 504b17b19c1a7..0000000000000 --- a/apps/site/pages/id/index.mdx +++ /dev/null @@ -1,135 +0,0 @@ ---- -title: Jalankan JavaScript Di Mana Saja -layout: home ---- - -
      - - -
      -

      Jalankan JavaScript Di Mana Saja

      - - Node.js® adalah lingkungan runtime JavaScript gratis dan sumber terbuka yang - lintas platform, yang memungkinkan pengembang membuat server, aplikasi web, - alat baris perintah, dan skrip. - -
      - -
      - - {({ release }) => ( - <> - Unduh Node.js (LTS) - - Unduhan Node.js {release.versionWithPrefix} - 1 dengan dukungan jangka panjang (LTS). - Node.js juga dapat diinstal melalui manajer paket. - - - )} - - - - {({ release }) => ( - - Ingin fitur baru lebih cepat? - Dapatkan Node.js {release.versionWithPrefix} - 1 sebagai gantinya. - - )} - - -
      -
      - -
      -
      - ```js displayName="Create an HTTP Server" - // server.mjs - import { createServer } from 'node:http'; - - const server = createServer((req, res) => { - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.end('Hello World!\n'); - }); - - // starts a simple http server locally on port 3000 - server.listen(3000, '127.0.0.1', () => { - console.log('Listening on 127.0.0.1:3000'); - }); - - // run with `node server.mjs` - ``` - - ```js displayName="Write Tests" - // tests.mjs - import assert from 'node:assert'; - import test from 'node:test'; - - test('that 1 is equal 1', () => { - assert.strictEqual(1, 1); - }); - - test('that throws as 1 is not equal 2', () => { - // throws an exception because 1 != 2 - assert.strictEqual(1, 2); - }); - - // run with `node tests.mjs` - ``` - - ```js displayName="Read and Hash a File" - // crypto.mjs - import { createHash } from 'node:crypto'; - import { readFile } from 'node:fs/promises'; - - const hasher = createHash('sha1'); - - hasher.setEncoding('hex'); - // ensure you have a `package.json` file for this test! - hasher.write(await readFile('package.json')); - hasher.end(); - - const fileHash = hasher.read(); - - // run with `node crypto.mjs` - ``` - - ```js displayName="Streams Pipeline" - // streams.mjs - import { createReadStream, createWriteStream } from 'node:fs'; - import { pipeline } from 'node:stream/promises'; - import { createGzip } from 'node:zlib'; - - // ensure you have a `package.json` file for this test! - await pipeline( - createReadStream('package.json'), - createGzip(), - createWriteStream('package.json.gz') - ); - - // run with `node streams.mjs` - ``` - - ```js displayName="Work with Threads" - // threads.mjs - import { Worker, isMainThread, - workerData, parentPort } from 'node:worker_threads'; - - if (isMainThread) { - const data = 'some data'; - const worker = new Worker(import.meta.filename, { workerData: data }); - worker.on('message', msg => console.log('Reply from Thread:', msg)); - } else { - const source = workerData; - parentPort.postMessage(btoa(source.toUpperCase())); - } - - // run with `node threads.mjs` - ``` - -
      - -Pelajari lebih lanjut tentang apa yang bisa ditawarkan Node.js melalui [Bahan Pembelajaran](/learn) kami. - -
      diff --git a/apps/site/pages/ja/index.mdx b/apps/site/pages/ja/index.mdx deleted file mode 100644 index ad52b8da6fe26..0000000000000 --- a/apps/site/pages/ja/index.mdx +++ /dev/null @@ -1,133 +0,0 @@ ---- -title: どこでもJavaScriptを使おう -layout: home ---- - -
      - - -
      -

      どこでもJavaScriptを使おう

      - - Node.js®はクロスプラットフォームに対応したフリーでオープンソースのJavaScript実行環境です。開発者にサーバー、ウェブアプリ、コマンドラインツール、スクリプトなどを開発する環境を提供します。 - -
      - -
      - - {({ release }) => ( - <> - Node.js(LTS)をダウンロードする - - 長期サポート版Node.js {release.versionWithPrefix} - 1をダウンロードする。 - バージョンマネージャーを利用したインストール方法もあります。 - - - )} - - - - {({ release }) => ( - - 最新の機能をすぐに試したい場合は - Node.js {release.versionWithPrefix} - 1をインストールできます。 - - )} - - -
      -
      - -
      -
      - ```js displayName="Create an HTTP Server" - // server.mjs - import { createServer } from 'node:http'; - - const server = createServer((req, res) => { - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.end('Hello World!\n'); - }); - - // starts a simple http server locally on port 3000 - server.listen(3000, '127.0.0.1', () => { - console.log('Listening on 127.0.0.1:3000'); - }); - - // run with `node server.mjs` - ``` - - ```js displayName="Write Tests" - // tests.mjs - import assert from 'node:assert'; - import test from 'node:test'; - - test('that 1 is equal 1', () => { - assert.strictEqual(1, 1); - }); - - test('that throws as 1 is not equal 2', () => { - // throws an exception because 1 != 2 - assert.strictEqual(1, 2); - }); - - // run with `node tests.mjs` - ``` - - ```js displayName="Read and Hash a File" - // crypto.mjs - import { createHash } from 'node:crypto'; - import { readFile } from 'node:fs/promises'; - - const hasher = createHash('sha1'); - - hasher.setEncoding('hex'); - // ensure you have a `package.json` file for this test! - hasher.write(await readFile('package.json')); - hasher.end(); - - const fileHash = hasher.read(); - - // run with `node crypto.mjs` - ``` - - ```js displayName="Streams Pipeline" - // streams.mjs - import { createReadStream, createWriteStream } from 'node:fs'; - import { pipeline } from 'node:stream/promises'; - import { createGzip } from 'node:zlib'; - - // ensure you have a `package.json` file for this test! - await pipeline( - createReadStream('package.json'), - createGzip(), - createWriteStream('package.json.gz') - ); - - // run with `node streams.mjs` - ``` - - ```js displayName="Work with Threads" - // threads.mjs - import { Worker, isMainThread, - workerData, parentPort } from 'node:worker_threads'; - - if (isMainThread) { - const data = 'some data'; - const worker = new Worker(import.meta.filename, { workerData: data }); - worker.on('message', msg => console.log('Reply from Thread:', msg)); - } else { - const source = workerData; - parentPort.postMessage(btoa(source.toUpperCase())); - } - - // run with `node threads.mjs` - ``` - -
      - -私たちの[学習教材](/learn)でNode.jsでできることをさらに学んでみましょう。 - -
      diff --git a/apps/site/pages/ko/index.mdx b/apps/site/pages/ko/index.mdx deleted file mode 100644 index a4119867639ba..0000000000000 --- a/apps/site/pages/ko/index.mdx +++ /dev/null @@ -1,137 +0,0 @@ ---- -title: 어디서든 JavaScript를 실행하세요 -layout: home ---- - -
      - - -
      -

      어디서든 JavaScript를 실행하세요

      - -Node.js®는 무료, 오픈소스, 다중 플랫폼 JavaScript 런타임 환경으로 개발자 여러분이 서버, 웹 애플리케이션, 명령어 작성 도구와 스크립트를 만들도록 해줍니다. - -
      - -
      - - {({ release }) => ( - <> - Node.js 다운로드 (LTS) - - Node.js 다운로드 {release.versionWithPrefix} - 1 LTS. - Node.js는 패키지 관리자를 통해서도 다운로드 할 수 있습니다. - - - )} - - - - {({ release }) => ( - - 새로운 기능을 먼저 경험하고 싶다면 - Node.js {release.versionWithPrefix} - 1 를 다운 받으세요. - - )} - -
      -
      - -
      -
      - ```js displayName="Create an HTTP Server" - // server.mjs - import { createServer } from 'node:http'; - -const server = createServer((req, res) => { -res.writeHead(200, { 'Content-Type': 'text/plain' }); -res.end('Hello World!\n'); -}); - -// starts a simple http server locally on port 3000 -server.listen(3000, '127.0.0.1', () => { -console.log('Listening on 127.0.0.1:3000'); -}); - -// run with `node server.mjs` - -```` - -```js displayName="Write Tests" -// tests.mjs -import assert from 'node:assert'; -import test from 'node:test'; - -test('that 1 is equal 1', () => { - assert.strictEqual(1, 1); -}); - -test('that throws as 1 is not equal 2', () => { - // throws an exception because 1 != 2 - assert.strictEqual(1, 2); -}); - -// run with `node tests.mjs` -```` - -```js displayName="Read and Hash a File" -// crypto.mjs -import { createHash } from 'node:crypto'; -import { readFile } from 'node:fs/promises'; - -const hasher = createHash('sha1'); - -hasher.setEncoding('hex'); -// ensure you have a `package.json` file for this test! -hasher.write(await readFile('package.json')); -hasher.end(); - -const fileHash = hasher.read(); - -// run with `node crypto.mjs` -``` - -```js displayName="Streams Pipeline" -// streams.mjs -import { createReadStream, createWriteStream } from 'node:fs'; -import { pipeline } from 'node:stream/promises'; -import { createGzip } from 'node:zlib'; - -// ensure you have a `package.json` file for this test! -await pipeline( - createReadStream('package.json'), - createGzip(), - createWriteStream('package.json.gz') -); - -// run with `node streams.mjs` -``` - -```js displayName="Work with Threads" -// threads.mjs -import { - Worker, - isMainThread, - workerData, - parentPort, -} from 'node:worker_threads'; - -if (isMainThread) { - const data = 'some data'; - const worker = new Worker(import.meta.filename, { workerData: data }); - worker.on('message', msg => console.log('Reply from Thread:', msg)); -} else { - const source = workerData; - parentPort.postMessage(btoa(source.toUpperCase())); -} - -// run with `node threads.mjs` -``` - -
      - -Node.js가 제공하는 [학습 자료](/learn)를 통해 더 많은 정보를 알아보세요. - -
      diff --git a/apps/site/pages/pt/index.mdx b/apps/site/pages/pt/index.mdx deleted file mode 100644 index aafd3aa3adbcf..0000000000000 --- a/apps/site/pages/pt/index.mdx +++ /dev/null @@ -1,135 +0,0 @@ ---- -title: Executar a JavaScript em Toda Parte -layout: home ---- - -
      - - -
      -

      Executar a JavaScript em Toda Parte

      - - Node.js® é um ambiente de execução de JavaScript multiplataforma, - de código-aberto e gratuita, que permite aos programadores criar servidores, aplicações - da Web, ferramentas de linha de comando e programas de automação de tarefas. - -
      - -
      - - {({ release }) => ( - <> - Descarregar a Node.js (LTS) - - Descarrega a Node.js {release.versionWithPrefix} - 1 com suporte de longo prazo. - A Node.js também pode ser instalada por meio de gestores de versão. - - - )} - - - - {({ release }) => ( - - Queremos novas funcionalidades mais cedo? - Podemos então obter a Node.js {release.versionWithPrefix} - 1. - - )} - - -
      -
      - -
      -
      - ```js displayName="Create an HTTP Server" - // server.mjs - import { createServer } from 'node:http'; - - const server = createServer((req, res) => { - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.end('Hello World!\n'); - }); - - // starts a simple http server locally on port 3000 - server.listen(3000, '127.0.0.1', () => { - console.log('Listening on 127.0.0.1:3000'); - }); - - // run with `node server.mjs` - ``` - - ```js displayName="Write Tests" - // tests.mjs - import assert from 'node:assert'; - import test from 'node:test'; - - test('that 1 is equal 1', () => { - assert.strictEqual(1, 1); - }); - - test('that throws as 1 is not equal 2', () => { - // throws an exception because 1 != 2 - assert.strictEqual(1, 2); - }); - - // run with `node tests.mjs` - ``` - - ```js displayName="Read and Hash a File" - // crypto.mjs - import { createHash } from 'node:crypto'; - import { readFile } from 'node:fs/promises'; - - const hasher = createHash('sha1'); - - hasher.setEncoding('hex'); - // ensure you have a `package.json` file for this test! - hasher.write(await readFile('package.json')); - hasher.end(); - - const fileHash = hasher.read(); - - // run with `node crypto.mjs` - ``` - - ```js displayName="Streams Pipeline" - // streams.mjs - import { createReadStream, createWriteStream } from 'node:fs'; - import { pipeline } from 'node:stream/promises'; - import { createGzip } from 'node:zlib'; - - // ensure you have a `package.json` file for this test! - await pipeline( - createReadStream('package.json'), - createGzip(), - createWriteStream('package.json.gz') - ); - - // run with `node streams.mjs` - ``` - - ```js displayName="Work with Threads" - // threads.mjs - import { Worker, isMainThread, - workerData, parentPort } from 'node:worker_threads'; - - if (isMainThread) { - const data = 'some data'; - const worker = new Worker(import.meta.filename, { workerData: data }); - worker.on('message', msg => console.log('Reply from Thread:', msg)); - } else { - const source = workerData; - parentPort.postMessage(btoa(source.toUpperCase())); - } - - // run with `node threads.mjs` - ``` - -
      - -É possível saber que mais a Node.js consegue oferecer com os nossos [materiais de estudo](/learn). - -
      diff --git a/apps/site/pages/tr/index.mdx b/apps/site/pages/tr/index.mdx deleted file mode 100644 index 7d4f9f71acba1..0000000000000 --- a/apps/site/pages/tr/index.mdx +++ /dev/null @@ -1,139 +0,0 @@ ---- -title: JavaScript'i Her Yerde Çalıştırın -layout: home ---- - -
      - - -
      -

      Her Yerde JavaScript Çalıştırın

      - - Node.js®, ücretsiz, açık kaynaklı, çapraz platform JavaScript çalıştırma ortamıdır. - Geliştiricilere sunucular, web uygulamaları, komut satırı araçları ve betikler oluşturma - imkanı sağlar. - -
      - -
      - - {({ release }) => ( - <> - Node.js'i İndir (LTS) - - Node.js'i indir {release.versionWithPrefix} - 1 uzun vadeli destek ile indirin. - Node.js ayrıca paket yöneticileri aracılığıyla da kurulabilir. - - - )} - - - - {({ release }) => ( - - Yeni özellikleri daha erken mi istiyorsunuz?{" "} - - Node.js{" "} - - {release.versionWithPrefix} - - {" "} - 1{" "} - alabilirsiniz. - - )} - - -
      -
      - -
      -
      - ```js displayName="Create an HTTP Server" - // server.mjs - import { createServer } from 'node:http'; - - const server = createServer((req, res) => { - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.end('Hello World!\n'); - }); - - // starts a simple http server locally on port 3000 - server.listen(3000, '127.0.0.1', () => { - console.log('Listening on 127.0.0.1:3000'); - }); - - // run with `node server.mjs` - ``` - - ```js displayName="Write Tests" - // tests.mjs - import assert from 'node:assert'; - import test from 'node:test'; - - test('that 1 is equal 1', () => { - assert.strictEqual(1, 1); - }); - - test('that throws as 1 is not equal 2', () => { - // throws an exception because 1 != 2 - assert.strictEqual(1, 2); - }); - - // run with `node tests.mjs` - ``` - - ```js displayName="Read and Hash a File" - // crypto.mjs - import { createHash } from 'node:crypto'; - import { readFile } from 'node:fs/promises'; - - const hasher = createHash('sha1'); - - hasher.setEncoding('hex'); - // ensure you have a `package.json` file for this test! - hasher.write(await readFile('package.json')); - hasher.end(); - - const fileHash = hasher.read(); - - // run with `node crypto.mjs` - ``` - - ```js displayName="Streams Pipeline" - // streams.mjs - import { createReadStream, createWriteStream } from 'node:fs'; - import { pipeline } from 'node:stream/promises'; - import { createGzip } from 'node:zlib'; - - // ensure you have a `package.json` file for this test! - await pipeline( - createReadStream('package.json'), - createGzip(), - createWriteStream('package.json.gz') - ); - - // run with `node streams.mjs` - ``` - - ```js displayName="Work with Threads" - // threads.mjs - import { Worker, isMainThread, - workerData, parentPort } from 'node:worker_threads'; - - if (isMainThread) { - const data = 'some data'; - const worker = new Worker(import.meta.filename, { workerData: data }); - worker.on('message', msg => console.log('Reply from Thread:', msg)); - } else { - const source = workerData; - parentPort.postMessage(btoa(source.toUpperCase())); - } - - // run with `node threads.mjs` - ``` - -
      - Node.js'in sunabileceklerini daha yakından keşfetmek için [Öğrenme materyallerimize](/learn) göz atın. -
      diff --git a/apps/site/pages/uk/index.mdx b/apps/site/pages/uk/index.mdx deleted file mode 100644 index 3dc5d595ee0c7..0000000000000 --- a/apps/site/pages/uk/index.mdx +++ /dev/null @@ -1,134 +0,0 @@ ---- -title: Запускайте JavaScript будь‑де -layout: home ---- - -
      - - -
      -

      Запускайте JavaScript будь‑де

      - - Node.js® — це безплатне, кросплатформне середовище виконання JavaScript із відкритим кодом, - яке дозволяє розробникам створювати сервери, вебзастосунки, інструменти командного рядка та скрипти. - -
      - -
      - - {({ release }) => ( - <> - Завантажити Node.js (LTS) - - Завантажує Node.js {release.versionWithPrefix} - 1 із довгостроковою підтримкою. - Node.js також можна встановити через менеджери версій. - - - )} - - - - {({ release }) => ( - - Хочете отримати нові функції швидше? - Завантажте Node.js {release.versionWithPrefix} - 1. - - )} - - -
      -
      - -
      -
      - ```js displayName="Create an HTTP Server" - // server.mjs - import { createServer } from 'node:http'; - - const server = createServer((req, res) => { - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.end('Hello World!\n'); - }); - - // starts a simple http server locally on port 3000 - server.listen(3000, '127.0.0.1', () => { - console.log('Listening on 127.0.0.1:3000'); - }); - - // run with `node server.mjs` - ``` - - ```js displayName="Write Tests" - // tests.mjs - import assert from 'node:assert'; - import test from 'node:test'; - - test('that 1 is equal 1', () => { - assert.strictEqual(1, 1); - }); - - test('that throws as 1 is not equal 2', () => { - // throws an exception because 1 != 2 - assert.strictEqual(1, 2); - }); - - // run with `node tests.mjs` - ``` - - ```js displayName="Read and Hash a File" - // crypto.mjs - import { createHash } from 'node:crypto'; - import { readFile } from 'node:fs/promises'; - - const hasher = createHash('sha1'); - - hasher.setEncoding('hex'); - // ensure you have a `package.json` file for this test! - hasher.write(await readFile('package.json')); - hasher.end(); - - const fileHash = hasher.read(); - - // run with `node crypto.mjs` - ``` - - ```js displayName="Streams Pipeline" - // streams.mjs - import { createReadStream, createWriteStream } from 'node:fs'; - import { pipeline } from 'node:stream/promises'; - import { createGzip } from 'node:zlib'; - - // ensure you have a `package.json` file for this test! - await pipeline( - createReadStream('package.json'), - createGzip(), - createWriteStream('package.json.gz') - ); - - // run with `node streams.mjs` - ``` - - ```js displayName="Work with Threads" - // threads.mjs - import { Worker, isMainThread, - workerData, parentPort } from 'node:worker_threads'; - - if (isMainThread) { - const data = 'some data'; - const worker = new Worker(import.meta.filename, { workerData: data }); - worker.on('message', msg => console.log('Reply from Thread:', msg)); - } else { - const source = workerData; - parentPort.postMessage(btoa(source.toUpperCase())); - } - - // run with `node threads.mjs` - ``` - -
      - -Дізнайтеся більше про можливості Node.js із [нашими навчальними матеріалами](/learn). - -
      diff --git a/apps/site/pages/zh-cn/index.mdx b/apps/site/pages/zh-cn/index.mdx deleted file mode 100644 index 1a8db0b4cefa7..0000000000000 --- a/apps/site/pages/zh-cn/index.mdx +++ /dev/null @@ -1,132 +0,0 @@ ---- -title: 在任何地方运行 JavaScript -layout: home ---- - -
      - - -
      -

      在任何地方运行 JavaScript

      - - Node.js® 是一个免费、开源、跨平台的 JavaScript 运行时环境, 它让开发人员能够创建服务器 - Web 应用、命令行工具和脚本。 - -
      - -
      - - {({ release }) => ( - <> - 下载 Node.js (LTS) - - 下载 Node.js {release.versionWithPrefix} - 1 长期支持版本。 - Node.js 也可以通过 软件包管理器 进行安装。 - - - )} - - - - {({ release }) => ( - - 想要更快获得新功能吗? - 获取 Node.js {release.versionWithPrefix} - 1 版本吧。 - - )} - - -
      -
      - -
      -
      - ```js displayName="Create an HTTP Server" - // server.mjs - import { createServer } from 'node:http'; - - const server = createServer((req, res) => { - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.end('Hello World!\n'); - }); - - // starts a simple http server locally on port 3000 - server.listen(3000, '127.0.0.1', () => { - console.log('Listening on 127.0.0.1:3000'); - }); - - // run with `node server.mjs` - ``` - - ```js displayName="Write Tests" - // tests.mjs - import assert from 'node:assert'; - import test from 'node:test'; - - test('that 1 is equal 1', () => { - assert.strictEqual(1, 1); - }); - - test('that throws as 1 is not equal 2', () => { - // throws an exception because 1 != 2 - assert.strictEqual(1, 2); - }); - - // run with `node tests.mjs` - ``` - - ```js displayName="Read and Hash a File" - // crypto.mjs - import { createHash } from 'node:crypto'; - import { readFile } from 'node:fs/promises'; - - const hasher = createHash('sha1'); - - hasher.setEncoding('hex'); - // ensure you have a `package.json` file for this test! - hasher.write(await readFile('package.json')); - hasher.end(); - - const fileHash = hasher.read(); - - // run with `node crypto.mjs` - ``` - - ```js displayName="Streams Pipeline" - // streams.mjs - import { createReadStream, createWriteStream } from 'node:fs'; - import { pipeline } from 'node:stream/promises'; - import { createGzip } from 'node:zlib'; - - // ensure you have a `package.json` file for this test! - await pipeline( - createReadStream('package.json'), - createGzip(), - createWriteStream('package.json.gz') - ); - - // run with `node streams.mjs` - ``` - - ```js displayName="Work with Threads" - // threads.mjs - import { Worker, isMainThread, - workerData, parentPort } from 'node:worker_threads'; - - if (isMainThread) { - const data = 'some data'; - const worker = new Worker(import.meta.filename, { workerData: data }); - worker.on('message', msg => console.log('Reply from Thread:', msg)); - } else { - const source = workerData; - parentPort.postMessage(btoa(source.toUpperCase())); - } - - // run with `node threads.mjs` - ``` - -
      - 通过我们的[学习资料](/learn)了解 Node.js 。 -
      diff --git a/apps/site/pages/zh-tw/index.mdx b/apps/site/pages/zh-tw/index.mdx deleted file mode 100644 index ab67d2e2d9670..0000000000000 --- a/apps/site/pages/zh-tw/index.mdx +++ /dev/null @@ -1,133 +0,0 @@ ---- -title: 隨時隨地執行 JavaScript -layout: home ---- - -
      - - -
      -

      隨時隨地執行 JavaScript

      - - Node.js® 是一款免費的跨平台開源 JavaScript 執行環境,供開發者建立伺服器、網頁應用程式、命令列工具與指令稿。 - -
      - -
      - - {({ release }) => ( - <> - 下載 Node.js (LTS) - - 下載享有長期支援的 Node.js {release.versionWithPrefix} - 1。 - 您也可以透過版本管理程式來安裝 Node.js。 - - - )} - - - - {({ release }) => ( - - 想要更快體驗新功能嗎? - 那就下載 Node.js {release.versionWithPrefix} - 1 吧。 - - )} - - -
      -
      - -
      -
      - ```js displayName="Create an HTTP Server" - // server.mjs - import { createServer } from 'node:http'; - - const server = createServer((req, res) => { - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.end('Hello World!\n'); - }); - - // starts a simple http server locally on port 3000 - server.listen(3000, '127.0.0.1', () => { - console.log('Listening on 127.0.0.1:3000'); - }); - - // run with `node server.mjs` - ``` - - ```js displayName="Write Tests" - // tests.mjs - import assert from 'node:assert'; - import test from 'node:test'; - - test('that 1 is equal 1', () => { - assert.strictEqual(1, 1); - }); - - test('that throws as 1 is not equal 2', () => { - // throws an exception because 1 != 2 - assert.strictEqual(1, 2); - }); - - // run with `node tests.mjs` - ``` - - ```js displayName="Read and Hash a File" - // crypto.mjs - import { createHash } from 'node:crypto'; - import { readFile } from 'node:fs/promises'; - - const hasher = createHash('sha1'); - - hasher.setEncoding('hex'); - // ensure you have a `package.json` file for this test! - hasher.write(await readFile('package.json')); - hasher.end(); - - const fileHash = hasher.read(); - - // run with `node crypto.mjs` - ``` - - ```js displayName="Streams Pipeline" - // streams.mjs - import { createReadStream, createWriteStream } from 'node:fs'; - import { pipeline } from 'node:stream/promises'; - import { createGzip } from 'node:zlib'; - - // ensure you have a `package.json` file for this test! - await pipeline( - createReadStream('package.json'), - createGzip(), - createWriteStream('package.json.gz') - ); - - // run with `node streams.mjs` - ``` - - ```js displayName="Work with Threads" - // threads.mjs - import { Worker, isMainThread, - workerData, parentPort } from 'node:worker_threads'; - - if (isMainThread) { - const data = 'some data'; - const worker = new Worker(import.meta.filename, { workerData: data }); - worker.on('message', msg => console.log('Reply from Thread:', msg)); - } else { - const source = workerData; - parentPort.postMessage(btoa(source.toUpperCase())); - } - - // run with `node threads.mjs` - ``` - -
      - -我們提供大量[學習資源](/learn),探索 Node.js 的無限潛能。 - -
      From d2418948b6a3729fc35473092dc685c8166a02f5 Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Sun, 8 Jun 2025 19:32:31 +0300 Subject: [PATCH 38/38] refactor: minor text and styling updates --- .../Downloads/Release/ReleaseCodeBox.tsx | 16 +++++---------- apps/site/components/withReleaseAlertBox.tsx | 20 ++++++++++++++++--- apps/site/pages/en/download/archive.mdx | 6 +++--- apps/site/pages/en/index.mdx | 2 +- 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx b/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx index f111b66ec1e45..656f198a8cb18 100644 --- a/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx +++ b/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx @@ -10,6 +10,7 @@ import { useContext, useMemo } from 'react'; import CodeBox from '#site/components/Common/CodeBox'; import Link from '#site/components/Link'; import LinkWithArrow from '#site/components/LinkWithArrow'; +import WithReleaseAlertBox from '#site/components/withReleaseAlertBox'; import { createSval } from '#site/next.jsx.compiler.mjs'; import { ReleaseContext, @@ -114,17 +115,10 @@ const ReleaseCodeBox: FC = () => { - {release.status === 'End-of-life' && ( - - {t.rich('layouts.download.codeBox.unsupportedVersionWarning', { - link: text => {text}, - })} - - )} + {!currentPlatform || currentPlatform.recommended || ( = ({ status }) => { +const WithReleaseAlertBox: FC = ({ + status, + link, +}) => { const t = useTranslations(); + const getAlertContent = () => { + if (link) { + return t.rich('layouts.download.codeBox.unsupportedVersionWarning', { + link: text => {text}, + }); + } + + return t('components.releaseModal.unsupportedVersionWarning'); + }; + switch (status) { case 'End-of-life': return ( - {t('components.releaseModal.unsupportedVersionWarning')} + {getAlertContent()} ); default: diff --git a/apps/site/pages/en/download/archive.mdx b/apps/site/pages/en/download/archive.mdx index d0a28259669fb..3ad29f3a1289f 100644 --- a/apps/site/pages/en/download/archive.mdx +++ b/apps/site/pages/en/download/archive.mdx @@ -6,7 +6,7 @@ layout: download-archive {({ binaries, installers, version, release, sources, navigation }) => ( <> -

      Node.js Download Archive

      +

      Node.js® Download Archive

      - + -
        +
        • Learn more about Node.js releases, including the release schedule and LTS status. diff --git a/apps/site/pages/en/index.mdx b/apps/site/pages/en/index.mdx index f3b043107dd88..96f029e6aabc6 100644 --- a/apps/site/pages/en/index.mdx +++ b/apps/site/pages/en/index.mdx @@ -29,7 +29,7 @@ layout: home {({ release }) => ( Want new features sooner? - Get Node.js {release.versionWithPrefix} + Get Node.js {release.versionWithPrefix} 1 instead. )}