Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: official vs community and instructions for downloads page #7365

Merged
merged 15 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions COLLABORATOR_GUIDE.md
ovflowd marked this conversation as resolved.
Show resolved Hide resolved
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/utils/downloadUtils.tsx`:**
bmuenzenmeyer marked this conversation as resolved.
Show resolved Hide resolved

- 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/utils/downloadUtils.tsx
bmuenzenmeyer marked this conversation as resolved.
Show resolved Hide resolved
// 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/download`:**
ovflowd marked this conversation as resolved.
Show resolved Hide resolved

- 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/download/exampleMethod.bash
ovflowd marked this conversation as resolved.
Show resolved Hide resolved
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/utils/downloadUtils.tsx
bmuenzenmeyer marked this conversation as resolved.
Show resolved Hide resolved
// 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/utils/downloadUtils.tsx`.
bmuenzenmeyer marked this conversation as resolved.
Show resolved Hide resolved

## 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
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.

10 changes: 5 additions & 5 deletions apps/site/components/__design__/platform-logos.stories.tsx
ovflowd marked this conversation as resolved.
Show resolved Hide resolved
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
ovflowd marked this conversation as resolved.
Show resolved Hide resolved
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://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?

<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
Loading