diff --git a/COLLABORATOR_GUIDE.md b/COLLABORATOR_GUIDE.md index c4842f7e3e029..00b0939cf28fd 100644 --- a/COLLABORATOR_GUIDE.md +++ b/COLLABORATOR_GUIDE.md @@ -13,6 +13,9 @@ - [Best practices when creating a Component](#best-practices-when-creating-a-component) - [How a new Component should look like when freshly created](#how-a-new-component-should-look-like-when-freshly-created) - [Best practices for Component development in general](#best-practices-for-component-development-in-general) +- [The new Downloads page](#the-new-downloads-page) + - [Adding a Download Installation Method](#adding-a-download-installation-method) + - [Adding a Download Package Manager](#adding-a-download-package-manager) - [Unit Tests and Storybooks](#unit-tests-and-storybooks) - [General Guidelines for Unit Tests](#general-guidelines-for-unit-tests) - [General Guidelines for Storybooks](#general-guidelines-for-storybooks) @@ -259,6 +262,117 @@ export default MyComponent; Use utilities or Hooks when you need a Reactive state - Avoid making your Component too big. Deconstruct it into smaller Components/Hooks whenever possible +## The new Downloads page + +### Adding a Download Installation Method + +To add a new download installation method, follow these steps: + +1. **Update `INSTALL_METHODS` in `apps/site/util/downloadUtils.tsx`:** + + - Add a new entry to the `INSTALL_METHODS` array. + - Each entry should have the following properties: + - `iconImage`: The React component of the icon image for the installation method. This should be an SVG component stored within `apps/site/components/Icons/InstallationMethod` and must follow the other icon component references (being a `FC` supporting `SVGSVGElement` props). + - Don't forget to add it on the `index.tsx` file from the `InstallationMethod` folder. + - `recommended`: A boolean indicating if this method is recommended. This property is available only for official installation methods. + - `url`: The URL for the installation method. + - `value`: The key of the installation method, which must be unique. + + Example: + + ```javascript + // filepath: /nodejs.org/apps/site/util/downloadUtils.tsx + // See full reference of INSTALL_METHODS within `downloadUtils.tsx` + export const INSTALL_METHODS = [ + // ...existing methods... + { + iconImage: , + url: 'https://example.com/install', + value: 'exampleMethod', + }, + ]; + ``` + +2. **Add translation key in `packages/i18n/locales/en.json`:** + + - Add an entry under `layouts.download.codeBox.platformInfo` for the `info` property of the new installation method. + + Example: + + ```json + // filepath: /nodejs.org/packages/i18n/locales/en.json + { + "layouts": { + "download": { + "codeBox": { + "platformInfo": { + "exampleMethod": "Example installation method description." + } + } + } + } + } + ``` + +3. **Update `InstallationMethodLabel` and `InstallationMethod` in `@/types/release.ts`:** + + - Add the new method to the `InstallationMethodLabel` and `InstallationMethod` types. + + Example: + + ```typescript + // filepath: /nodejs.org/apps/site/types/release.ts + export type InstallationMethod = 'exampleMethod' | 'anotherMethod' | ...; + + export const InstallationMethodLabel: Record = { + exampleMethod: 'Example Method', + anotherMethod: 'Another Method', + // ...existing methods... + }; + ``` + +4. **Add a snippet in `apps/site/snippets/en/download`:** + + - Create a new file with the same key as the `value` property (e.g., `exampleMethod.bash`). + - Add the installation instructions in this file. + - The snippet file can use JavaScript template syntax and has access to a `props` variable of type `ReleaseContextType`. + + Example: + + ```bash + // filepath: /nodejs.org/apps/site/snippets/en/download/exampleMethod.bash + echo "Installing Node.js version ${props.version} using Example Method" + ``` + +5. **Configure `compatibility` within the `INSTALL_METHODS` object in `downloadUtils.ts`:** + +- Use the `compatibility` property to enable/list the installation method for specific OSs, Node.js version ranges, or architectures/platforms. + +Example: + +```javascript +// filepath: /nodejs.org/apps/site/util/downloadUtils.tsx +// See full reference of compatibility property within `downloadUtils.tsx` +export const INSTALL_METHODS = [ + { + iconImage: 'path/to/icon.svg', + url: 'https://example.com/install', + value: 'exampleMethod', + compatibility: { + os: ['LINUX', 'MAC'], + semver: ['>=14.0.0'], + platform: ['x64', 'arm64'], + }, + }, +]; +``` + +By following these steps, you can successfully add a new download installation method to the Node.js website. + +### Adding a Download Package Manager + +You can add a PACKAGE_MANAGER the same way as adding an INSTALLATION_METHOD (from the section above, "Adding a Download Installation Method") but it should be added to the PACKAGE_MANAGERS object in `apps/site/util/downloadUtils.tsx`. + ## Unit Tests and Storybooks Each new feature or bug fix should be accompanied by a unit test (when deemed valuable). diff --git a/apps/site/components/Downloads/DownloadReleasesTable.tsx b/apps/site/components/Downloads/DownloadReleasesTable.tsx index 2893cca1e776f..0c0acff74e59c 100644 --- a/apps/site/components/Downloads/DownloadReleasesTable.tsx +++ b/apps/site/components/Downloads/DownloadReleasesTable.tsx @@ -1,9 +1,10 @@ import { getTranslations } from 'next-intl/server'; import type { FC } from 'react'; +import LinkWithArrow from '@/components/LinkWithArrow'; import getReleaseData from '@/next-data/releaseData'; +import { BASE_CHANGELOG_URL } from '@/next.constants.mjs'; import { getNodeApiLink } from '@/util/getNodeApiLink'; -import { getNodeJsChangelog } from '@/util/getNodeJsChangelog'; // This is a React Async Server Component // Note that Hooks cannot be used in a RSC async component @@ -17,11 +18,12 @@ const DownloadReleasesTable: FC = async () => { - - - - - + + + + + + @@ -35,17 +37,19 @@ const DownloadReleasesTable: FC = async () => { ))} diff --git a/apps/site/components/Downloads/Release/ChangelogLink.tsx b/apps/site/components/Downloads/Release/ChangelogLink.tsx index 740c0adaec12d..ff3e08d616ea9 100644 --- a/apps/site/components/Downloads/Release/ChangelogLink.tsx +++ b/apps/site/components/Downloads/Release/ChangelogLink.tsx @@ -3,8 +3,8 @@ import type { FC, PropsWithChildren } from 'react'; import { useContext } from 'react'; -import LinkWithArrow from '@/components/Downloads/Release/LinkWithArrow'; import Link from '@/components/Link'; +import LinkWithArrow from '@/components/LinkWithArrow'; import { BASE_CHANGELOG_URL } from '@/next.constants.mjs'; import { ReleaseContext } from '@/providers/releaseProvider'; diff --git a/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx b/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx index e8a1684aef5f5..70de6d02fc989 100644 --- a/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx +++ b/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx @@ -8,14 +8,13 @@ import AlertBox from '@/components/Common/AlertBox'; import CodeBox from '@/components/Common/CodeBox'; import Skeleton from '@/components/Common/Skeleton'; import Link from '@/components/Link'; +import LinkWithArrow from '@/components/LinkWithArrow'; import { createSval } from '@/next.jsx.compiler.mjs'; import { ReleaseContext, ReleasesContext } from '@/providers/releaseProvider'; import type { ReleaseContextType } from '@/types/release'; import { INSTALL_METHODS } from '@/util/downloadUtils'; import { highlightToHtml } from '@/util/getHighlighter'; -import LinkWithArrow from './LinkWithArrow'; - // Creates a minimal JavaScript interpreter for parsing the JavaScript code from the snippets // Note: that the code runs inside a sandboxed environment and cannot interact with any code outside of the sandbox // It also does not have access to any Global or Window objects, nor it can execute code on the end-user's browser diff --git a/apps/site/components/Icons/Platform/Choco.tsx b/apps/site/components/Icons/InstallationMethod/Choco.tsx similarity index 100% rename from apps/site/components/Icons/Platform/Choco.tsx rename to apps/site/components/Icons/InstallationMethod/Choco.tsx diff --git a/apps/site/components/Icons/Platform/Docker.tsx b/apps/site/components/Icons/InstallationMethod/Docker.tsx similarity index 100% rename from apps/site/components/Icons/Platform/Docker.tsx rename to apps/site/components/Icons/InstallationMethod/Docker.tsx diff --git a/apps/site/components/Icons/Platform/FNM.tsx b/apps/site/components/Icons/InstallationMethod/FNM.tsx similarity index 100% rename from apps/site/components/Icons/Platform/FNM.tsx rename to apps/site/components/Icons/InstallationMethod/FNM.tsx diff --git a/apps/site/components/Icons/Platform/Homebrew.tsx b/apps/site/components/Icons/InstallationMethod/Homebrew.tsx similarity index 100% rename from apps/site/components/Icons/Platform/Homebrew.tsx rename to apps/site/components/Icons/InstallationMethod/Homebrew.tsx diff --git a/apps/site/components/Icons/Platform/NVM.tsx b/apps/site/components/Icons/InstallationMethod/NVM.tsx similarity index 100% rename from apps/site/components/Icons/Platform/NVM.tsx rename to apps/site/components/Icons/InstallationMethod/NVM.tsx diff --git a/apps/site/components/Icons/InstallationMethod/index.ts b/apps/site/components/Icons/InstallationMethod/index.ts new file mode 100644 index 0000000000000..6e2a87de11aee --- /dev/null +++ b/apps/site/components/Icons/InstallationMethod/index.ts @@ -0,0 +1,7 @@ +import Choco from '@/components/Icons/InstallationMethod/Choco'; +import Docker from '@/components/Icons/InstallationMethod/Docker'; +import FNM from '@/components/Icons/InstallationMethod/FNM'; +import Homebrew from '@/components/Icons/InstallationMethod/Homebrew'; +import NVM from '@/components/Icons/InstallationMethod/NVM'; + +export default { Choco, Docker, FNM, Homebrew, NVM }; diff --git a/apps/site/components/Icons/Platform/index.ts b/apps/site/components/Icons/Platform/index.ts deleted file mode 100644 index 8e362d2bdaf6d..0000000000000 --- a/apps/site/components/Icons/Platform/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import Choco from '@/components/Icons/Platform/Choco'; -import Docker from '@/components/Icons/Platform/Docker'; -import FNM from '@/components/Icons/Platform/FNM'; -import Homebrew from '@/components/Icons/Platform/Homebrew'; -import NVM from '@/components/Icons/Platform/NVM'; - -export default { Choco, Docker, FNM, Homebrew, NVM }; diff --git a/apps/site/components/Downloads/Release/LinkWithArrow.tsx b/apps/site/components/LinkWithArrow.tsx similarity index 100% rename from apps/site/components/Downloads/Release/LinkWithArrow.tsx rename to apps/site/components/LinkWithArrow.tsx diff --git a/apps/site/components/__design__/platform-logos.stories.tsx b/apps/site/components/__design__/platform-logos.stories.tsx index 18f1adfd45d19..87c773c121066 100644 --- a/apps/site/components/__design__/platform-logos.stories.tsx +++ b/apps/site/components/__design__/platform-logos.stories.tsx @@ -1,7 +1,7 @@ import type { Meta as MetaObj, StoryObj } from '@storybook/react'; +import InstallMethodIcons from '@/components/Icons/InstallationMethod'; import OSIcons from '@/components/Icons/OperatingSystem'; -import PlatformIcons from '@/components/Icons/Platform'; export const PlatformLogos: StoryObj = { render: () => ( @@ -13,12 +13,12 @@ export const PlatformLogos: StoryObj = {
- - - + + +
- +
), diff --git a/apps/site/i18n.tsx b/apps/site/i18n.tsx index 88cd1cf3e9341..b11472b589d77 100644 --- a/apps/site/i18n.tsx +++ b/apps/site/i18n.tsx @@ -1,4 +1,5 @@ import { importLocale } from '@node-core/website-i18n'; +import defaultMessages from '@node-core/website-i18n/locales/en.json'; import { getRequestConfig } from 'next-intl/server'; import { availableLocaleCodes, defaultLocale } from '@/next.locales.mjs'; @@ -7,12 +8,6 @@ import deepMerge from './util/deepMerge'; // Loads the Application Locales/Translations Dynamically const loadLocaleDictionary = async (locale: string) => { - // This enables HMR on the English Locale, so that instant refresh - // happens while we add/change texts on the source locale - const defaultMessages = await import( - '@node-core/website-i18n/locales/en.json' - ).then(f => f.default); - if (locale === defaultLocale.code) { return defaultMessages; } @@ -20,7 +15,7 @@ const loadLocaleDictionary = async (locale: string) => { if (availableLocaleCodes.includes(locale)) { // Other languages don't really require HMR as they will never be development languages // so we can load them dynamically - const messages = await importLocale(locale); + const messages = importLocale(locale); // Use default messages as fallback return deepMerge(defaultMessages, messages); diff --git a/apps/site/next.mdx.use.client.mjs b/apps/site/next.mdx.use.client.mjs index 40b5959d455ca..88d11389a9ec9 100644 --- a/apps/site/next.mdx.use.client.mjs +++ b/apps/site/next.mdx.use.client.mjs @@ -7,7 +7,6 @@ import DownloadLink from './components/Downloads/DownloadLink'; import BlogPostLink from './components/Downloads/Release/BlogPostLink'; import ChangelogLink from './components/Downloads/Release/ChangelogLink'; import InstallationMethodDropdown from './components/Downloads/Release/InstallationMethodDropdown'; -import LinkWithArrow from './components/Downloads/Release/LinkWithArrow'; import OperatingSystemDropdown from './components/Downloads/Release/OperatingSystemDropdown'; import PackageManagerDropdown from './components/Downloads/Release/PackageManagerDropdown'; import PlatformDropdown from './components/Downloads/Release/PlatformDropdown'; @@ -15,6 +14,7 @@ import PrebuiltDownloadButtons from './components/Downloads/Release/PrebuiltDown import ReleaseCodeBox from './components/Downloads/Release/ReleaseCodeBox'; import VersionDropdown from './components/Downloads/Release/VersionDropdown'; import Link from './components/Link'; +import LinkWithArrow from './components/LinkWithArrow'; import MDXCodeBox from './components/MDX/CodeBox'; import MDXCodeTabs from './components/MDX/CodeTabs'; import MDXImage from './components/MDX/Image'; diff --git a/apps/site/pages/en/about/previous-releases.mdx b/apps/site/pages/en/about/previous-releases.mdx index 14488ef23cf68..0ad5938f64e67 100644 --- a/apps/site/pages/en/about/previous-releases.mdx +++ b/apps/site/pages/en/about/previous-releases.mdx @@ -14,12 +14,27 @@ Production applications should only use _Active LTS_ or _Maintenance LTS_ releas ![Releases](https://raw.githubusercontent.com/nodejs/Release/main/schedule.svg?sanitize=true) -Full details regarding Node.js release schedule are available [on GitHub](https://github.com/nodejs/release#release-schedule). +Full details regarding the Node.js release schedule are available [on GitHub](https://github.com/nodejs/release#release-schedule). ### Commercial Support -Commercial support for versions past Maintenance phase is available through our OpenJS Ecosystem Sustainability Program partner [HeroDevs](https://herodevs.com/). +Commercial support for versions past the Maintenance phase is available through our OpenJS Ecosystem Sustainability Program partner [HeroDevs](https://herodevs.com/). -## Looking for latest release of a version branch? +## Looking for the latest release of a version branch? + +## Official versus Community + +The Node.js website offers numerous installation methods that allow Node.js to be installed in a non-interactive manner, +for example, via CLIs, OS package managers (such as `apt`), or Node.js version managers (such as `nvm`). + +The Node.js project, in an attempt to popularize and advertise community efforts, has introduced a new Downloads page that lists both Official and Community installation methods, providing more versatility and options for users. +With this change, we introduced the concept of "Official" and "Community" installation methods. In order to be considered "Official", the installation method must meet the following requirements: + +| Requirements | +| ---------------------------------------------------------------------------------------------------------------------------------- | +| New Node.js releases must be available simultaneously upon the official release | +| Project maintainers have a close relationship with Node.js, including direct communication | +| Installation method downloads the official binaries bundled by the Node.js project | +| Installation method does **not** build from source when binaries are available, or alter the official binaries provided by Node.js | diff --git a/apps/site/util/__tests__/getNodeJsChangelog.test.mjs b/apps/site/util/__tests__/getNodeJsChangelog.test.mjs deleted file mode 100644 index d225ee21179fe..0000000000000 --- a/apps/site/util/__tests__/getNodeJsChangelog.test.mjs +++ /dev/null @@ -1,51 +0,0 @@ -import { getNodeJsChangelog } from '@/util/getNodeJsChangelog'; - -describe('getNodeJsChangelog', () => { - it('returns the correct changelog URL for major version >= 4', () => { - const version = '14.2.0'; - const expectedUrl = - 'https://github.com/nodejs/node/blob/main/doc/changelogs/CHANGELOG_V14.md#14.2.0'; - - const result = getNodeJsChangelog(version); - - expect(result).toBe(expectedUrl); - }); - - it('returns the correct changelog URL for major version >= 1', () => { - const version = '1.8.3'; - const expectedUrl = - 'https://github.com/nodejs/node/blob/main/doc/changelogs/CHANGELOG_IOJS.md#1.8.3'; - - const result = getNodeJsChangelog(version); - - expect(result).toBe(expectedUrl); - }); - - it('returns the correct changelog URL for minor version 12 or 10', () => { - const version1 = '6.12.0'; - const expectedUrl1 = - 'https://github.com/nodejs/node/blob/main/doc/changelogs/CHANGELOG_V6.md#6.12.0'; - - const result1 = getNodeJsChangelog(version1); - - expect(result1).toBe(expectedUrl1); - - const version2 = '8.10.0'; - const expectedUrl2 = - 'https://github.com/nodejs/node/blob/main/doc/changelogs/CHANGELOG_V8.md#8.10.0'; - - const result2 = getNodeJsChangelog(version2); - - expect(result2).toBe(expectedUrl2); - }); - - it('returns the correct changelog URL for other versions', () => { - const version = '0.12.7'; - const expectedUrl = - 'https://github.com/nodejs/node/blob/main/doc/changelogs/CHANGELOG_V012.md#0.12.7'; - - const result = getNodeJsChangelog(version); - - expect(result).toBe(expectedUrl); - }); -}); diff --git a/apps/site/util/downloadUtils.tsx b/apps/site/util/downloadUtils.tsx index 7b756641f4d3c..4720ed990a156 100644 --- a/apps/site/util/downloadUtils.tsx +++ b/apps/site/util/downloadUtils.tsx @@ -1,9 +1,9 @@ import satisfies from 'semver/functions/satisfies'; import type { SelectValue } from '@/components/Common/Select'; +import InstallMethodIcons from '@/components/Icons/InstallationMethod'; import OSIcons from '@/components/Icons/OperatingSystem'; import PackageManagerIcons from '@/components/Icons/PackageManager'; -import PlatformIcons from '@/components/Icons/Platform'; import type { NodeReleaseStatus } from '@/types'; import type * as Types from '@/types/release'; import type { UserOS, UserPlatform } from '@/types/userOS'; @@ -161,7 +161,7 @@ export const INSTALL_METHODS: Array< label: InstallationMethodLabel.NVM, value: 'NVM', compatibility: { os: ['MAC', 'LINUX', 'OTHER'] }, - iconImage: , + iconImage: , recommended: true, url: 'https://github.com/nvm-sh/nvm', info: 'layouts.download.codeBox.platformInfo.nvm', @@ -170,7 +170,7 @@ export const INSTALL_METHODS: Array< label: InstallationMethodLabel.FNM, value: 'FNM', compatibility: { os: ['MAC', 'LINUX', 'WIN'] }, - iconImage: , + iconImage: , url: 'https://github.com/Schniz/fnm', info: 'layouts.download.codeBox.platformInfo.fnm', }, @@ -178,7 +178,7 @@ export const INSTALL_METHODS: Array< label: InstallationMethodLabel.BREW, value: 'BREW', compatibility: { os: ['MAC', 'LINUX'], releases: ['Current', 'LTS'] }, - iconImage: , + iconImage: , url: 'https://brew.sh/', info: 'layouts.download.codeBox.platformInfo.brew', }, @@ -186,7 +186,7 @@ export const INSTALL_METHODS: Array< label: InstallationMethodLabel.CHOCO, value: 'CHOCO', compatibility: { os: ['WIN'] }, - iconImage: , + iconImage: , url: 'https://chocolatey.org/', info: 'layouts.download.codeBox.platformInfo.choco', }, @@ -194,7 +194,7 @@ export const INSTALL_METHODS: Array< label: InstallationMethodLabel.DOCKER, value: 'DOCKER', compatibility: { os: ['WIN', 'MAC', 'LINUX'] }, - iconImage: , + iconImage: , recommended: true, url: 'https://docs.docker.com/get-started/get-docker/', info: 'layouts.download.codeBox.platformInfo.docker', diff --git a/apps/site/util/getNodeJsChangelog.ts b/apps/site/util/getNodeJsChangelog.ts deleted file mode 100644 index 9b3b7d7f1fba4..0000000000000 --- a/apps/site/util/getNodeJsChangelog.ts +++ /dev/null @@ -1,51 +0,0 @@ -import semVer from 'semver'; - -/** - * Returns the URL of the Node.js changelog for the specified version. - * - * @param version The version of Node.js to get the changelog for. - * @returns The URL of the Node.js changelog for the specified version. - */ -export const getNodeJsChangelog = (version: string): string => { - const changelogsUrl = - 'https://github.com/nodejs/node/blob/main/doc/changelogs'; - - // Parse the version string and get the major and minor versions - const cleanVersion = semVer.clean(version) as string; - const majorVersion = semVer.major(cleanVersion); - const minorVersion = semVer.minor(cleanVersion); - - // Determine the URL of the changelog based on the version - if (majorVersion >= 4) { - return `${changelogsUrl}/CHANGELOG_V${majorVersion}.md#${cleanVersion}`; - } - - if (majorVersion >= 1) { - return `${changelogsUrl}/CHANGELOG_IOJS.md#${cleanVersion}`; - } - - if (minorVersion === 12 || minorVersion === 10) { - return `${changelogsUrl}/CHANGELOG_V${majorVersion}${minorVersion}.md#${cleanVersion}`; - } - - return `https://github.com/nodejs/node-v0.x-archive/blob/${version}/ChangeLog`; -}; - -export const getNodeJsChangelogAuthor = (changelog: string) => { - // looking for the @author part of the release header, eg: - // ## 2016-03-08, Version 5.8.0 (Stable). @Fishrock123 - // ## 2015-10-13, Version 4.2.1 'Argon' (LTS), @jasnell - // ## 2015-09-08, Version 4.0.0 (Stable), @rvagg - const [, changelogAuthor] = - changelog.match(/^## .*? \([^)]+\)[,.] @(\S+)/m) || []; - - return changelogAuthor || 'The Node.js Project'; -}; - -export const getNodeJsChangelogSlug = (changelog: string) => { - // looking for the release header eg: - // ## 2016-03-08, Version 5.8.0 (Stable) - const [, changelogHeading] = changelog.match(/^## (.*)$/m) || []; - - return changelogHeading || ''; -}; diff --git a/packages/i18n/locales/en.json b/packages/i18n/locales/en.json index b26a0ab739471..9f56b5c8fa6f9 100644 --- a/packages/i18n/locales/en.json +++ b/packages/i18n/locales/en.json @@ -138,9 +138,16 @@ } }, "downloadReleasesTable": { - "changelog": "Changelog", - "releases": "Releases", - "docs": "Docs" + "version": "Node.js", + "nApiVersion": "N-API", + "npmVersion": "npm", + "codename": "Codename", + "releaseDate": "Released at", + "actions": { + "changelog": "Changelog", + "releases": "Releases", + "docs": "Docs" + } }, "pagination": { "next": "Next",
Node.js VersionModule VersionCodenameRelease Datenpm{t('components.downloadReleasesTable.version')}{t('components.downloadReleasesTable.nApiVersion')}{t('components.downloadReleasesTable.codename')}{t('components.downloadReleasesTable.releaseDate')}{t('components.downloadReleasesTable.npmVersion')}
v{release.npm} - - {t('components.downloadReleasesTable.releases')} - - - {t('components.downloadReleasesTable.changelog')} - - - {t('components.downloadReleasesTable.docs')} - + {t('components.downloadReleasesTable.actions.releases')} + + + + {t('components.downloadReleasesTable.actions.changelog')} + + + + {t('components.downloadReleasesTable.actions.docs')} +