diff --git a/.browserslistrc b/.browserslistrc deleted file mode 100644 index a26ad8491dfa0..0000000000000 --- a/.browserslistrc +++ /dev/null @@ -1,13 +0,0 @@ -# https://github.com/browserslist/browserslist#readme - -defaults and supports es6-module -maintained node versions - -[production] - -cover 95% -not dead - -[development] - -defaults diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 0000000000000..f53ec0ae2900b --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,11 @@ +# Changesets + +Changesets in this directory describe user-facing changes to published packages. Add one by running: + +```shell +pnpm changeset +``` + +Commit the generated Markdown file with the package change. The publishing workflow consumes these files to update package versions and changelogs before publishing to npm. + +See the [package publishing guide](../docs/package-publishing.md) for the complete release process. diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 0000000000000..19d462977d6da --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.1.4/schema.json", + "changelog": [ + "@changesets/changelog-github", + { "repo": "nodejs/nodejs.org" } + ], + "commit": false, + "fixed": [], + "linked": [], + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/.changeset/import-aliases-no-preconditions.md b/.changeset/import-aliases-no-preconditions.md new file mode 100644 index 0000000000000..252399d58c0f9 --- /dev/null +++ b/.changeset/import-aliases-no-preconditions.md @@ -0,0 +1,8 @@ +--- +'@node-core/ui-components': patch +--- + +Resolve the `#ui/*` import alias to the compiled output by default, so consumers +of the published package no longer need to opt into a bundler-specific +`rolldown` resolution condition. The uncompiled sources stay reachable through +the new `source` condition, which this repository's own tooling opts into. diff --git a/.changeset/shiki-more-more.md b/.changeset/shiki-more-more.md new file mode 100644 index 0000000000000..9f206d3fe804a --- /dev/null +++ b/.changeset/shiki-more-more.md @@ -0,0 +1,5 @@ +--- +'@node-core/rehype-shiki': minor +--- + +Add support for all Shiki languages when the `minimal` preset is not selected. diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index f8dbaece5a6f9..0000000000000 --- a/.eslintignore +++ /dev/null @@ -1,17 +0,0 @@ -node_modules - -# Next.js & Vercel Directories -.next -.turbo -.swc -build - -# We don't want to lint/prettify the Coverage Results -coverage -junit.xml - -# We shouldn't lint statically generated Storybook files -storybook-static - -# This file naturally might break conventional rules -global.d.ts diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 81546edb05bb8..0000000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "extends": "prettier", - "overrides": [ - { - "files": ["public/**/*.js"], - "env": { "browser": true } - }, - { - "files": ["components/**/*.stories.tsx"], - "extends": "plugin:storybook/recommended" - }, - { - "files": ["**/{__tests__,__mocks__}/*.mjs"], - "env": { "jest": true } - }, - { - "files": ["**/*.{js,mjs,ts,tsx,mdx}"], - "env": { "es6": true, "node": true }, - "plugins": ["import"], - "extends": ["eslint:recommended", "next", "next/core-web-vitals"], - "rules": { - "import/order": [ - "error", - { - "groups": [ - "builtin", - "external", - "internal", - ["sibling", "parent"], - "index", - "unknown" - ], - "newlines-between": "always", - "alphabetize": { - "order": "asc", - "caseInsensitive": true - } - } - ] - } - }, - { - "files": ["**/*.ts?(x)"], - "plugins": ["@typescript-eslint", "no-relative-import-paths"], - "extends": [ - "plugin:@typescript-eslint/recommended", - "plugin:import/recommended", - "plugin:import/typescript" - ], - "parser": "@typescript-eslint/parser", - "rules": { - "@typescript-eslint/consistent-type-imports": "error", - "@typescript-eslint/array-type": ["error", { "default": "generic" }], - "no-relative-import-paths/no-relative-import-paths": [ - "warn", - { "allowSameFolder": true, "prefix": "@" } - ] - } - }, - { - "files": ["**/*.md?(x)"], - "extends": "plugin:mdx/recommended", - "rules": { - "react/jsx-no-undef": "off", - "@next/next/no-img-element": "off" - } - }, - { - "files": ["**/*.{mdx,tsx}"], - "rules": { - "@typescript-eslint/consistent-type-definitions": ["error", "type"], - "react/no-unescaped-entities": "off", - "react/function-component-definition": [ - "error", - { - "namedComponents": "arrow-function", - "unnamedComponents": "arrow-function" - } - ], - "no-restricted-syntax": [ - "error", - { - "selector": "ImportDeclaration[source.value='react'][specifiers.0.type='ImportDefaultSpecifier']", - "message": "Default React import not allowed since we use the TypeScript jsx-transform. If you need a global type that collides with a React named export (such as `MouseEvent`), try using `globalThis.MouseHandler`" - }, - { - "selector": "ImportDeclaration[source.value='react'] :matches(ImportNamespaceSpecifier)", - "message": "Named * React import is not allowed. Please import what you need from React with Named Imports" - } - ] - } - } - ] -} diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000000..158da6920f6c7 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,50 @@ +# Website Development +* @nodejs/nodejs-website + +# Infrastructure +.github @nodejs/web-infra +.husky @nodejs/web-infra +.nvmrc @nodejs/web-infra +codecov.yml @nodejs/web-infra +packages/ui-components/scripts/publish.mjs @nodejs/web-infra + +# Dependencies +pnpm-workspace.yaml @nodejs/nodejs-website @nodejs/web-infra +pnpm-lock.yaml @nodejs/web-infra + +# Framework +apps/site/next.config.mjs @nodejs/web-infra +apps/site/next.dynamic.mjs @nodejs/web-infra +apps/site/middleware.ts @nodejs/web-infra +apps/site/navigation.mjs @nodejs/web-infra +apps/site/playwright.config.mjs @nodejs/web-infra + +# Package Ecosystem +package.json @nodejs/nodejs-website +turbo.json @nodejs/nodejs-website @nodejs/web-infra + +# Web Infrastructure +crowdin.yml @nodejs/web-infra +apps/site/redirects.json @nodejs/web-infra +apps/site/site.json @nodejs/web-infra +platforms @nodejs/web-infra + +# Critical Documents +LICENSE @nodejs/tsc +CONTRIBUTING.md @nodejs/nodejs-website @nodejs/web-infra +docs @nodejs/nodejs-website @nodejs/web-infra +SECURITY.md @nodejs/security-wg +apps/site/public/.well-known/security.txt @nodejs/security-wg + +# Node.js Release Blog Posts +apps/site/pages/en/blog/release @nodejs/releasers +apps/site/pages/en/blog/announcements @nodejs/releasers +apps/site/pages/en/blog/vulnerability @nodejs/releasers + +# Marketing +apps/site/pages/en/about/partners.mdx @nodejs/marketing +apps/site/pages/en/about/branding.mdx @nodejs/marketing + +# The following users DO NOT have write access, and their review is requested +# via a GitHub action. +apps/site/pages/en/blog/migrations @nodejs/userland-migrations diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index b027d3ca3c480..ce6f540b49da4 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,14 @@ blank_issues_enabled: true contact_links: + - name: Node.js Status Page + url: https://status.nodejs.org + about: 'Need to check if there is any ongoing incidents?' - name: Report an API Docs Issue on the Node.js Website url: https://github.com/nodejs/node/issues/new?assignees=&labels=doc&template=3-api-ref-docs-problem.yml about: 'Is something wrong with the API Docs? Did you face a bug with the API Docs?' + - name: Report an issue with downloading Node.js + url: https://github.com/nodejs/release-cloudflare-worker/issues/new + about: 'Is something wrong with Node.js downloads?' - name: Report a Translation Issue on the Node.js Website url: https://crowdin.com/project/nodejs-web about: 'Is something wrong in a specific translation? Do you believe a language can get improved? Do you have suggestions?' diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 4e45488126dad..fc641b4b78268 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -26,7 +26,7 @@ You can check the items by adding an `x` between the brackets, like this: `[x]` --> - [ ] I have read the [Contributing Guidelines](https://github.com/nodejs/nodejs.org/blob/main/CONTRIBUTING.md) and made commit messages that follow the guideline. -- [ ] I have run `npx turbo format` to ensure the code follows the style guide. -- [ ] I have run `npx turbo test` to check if all tests are passing. -- [ ] I have run `npx turbo build` to check if the website builds without errors. +- [ ] I have run `pnpm format` to ensure the code follows the style guide. +- [ ] I have run `pnpm test` to check if all tests are passing. +- [ ] I have run `pnpm build` to check if the website builds without errors. - [ ] I've covered new added functionality with unit tests if necessary. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e9a555083b2da..bb9dc3da2e04c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,68 +5,102 @@ updates: schedule: interval: monthly labels: + - 'dependencies' + - 'github_actions' - 'github_actions:pull-request' + - 'auto-merge' commit-message: - prefix: meta + prefix: chore(deps) + cooldown: + default-days: 7 open-pull-requests-limit: 10 + - package-ecosystem: npm directory: '/' versioning-strategy: increase schedule: interval: monthly labels: + - 'dependencies' + - 'javascript' - 'github_actions:pull-request' + - 'auto-merge' commit-message: - prefix: meta + prefix: chore(deps) + cooldown: + default-days: 7 groups: lint: patterns: + - '@eslint/*' - '@typescript-eslint/*' - - 'eslint' - - 'eslint-*' - - 'stylelint' - - 'stylelint-*' + - acorn + - eslint + - eslint-* + - lint-staged + - prettier + - prettier-* + - stylelint + - stylelint-* + - typescript-eslint + - unified exclude-patterns: - 'eslint-plugin-storybook' - storybook: - patterns: - - 'storybook' - - '@storybook/*' - - 'eslint-plugin-storybook' - testing: - patterns: - - '@testing-library/*' - - '@types/testing-library*' - - '@types/jest' - - 'jest' - - 'jest-*' - next-js: - patterns: - - 'next' - - 'turbo' - - 'next-mdx-remote' - - 'next-sitemap' - - 'next-themes' - - '@vercel/*' + - 'prettier-plugin-tailwindcss' mdx: patterns: - '@vcarl/remark-headings' + - '@shikijs/*' - '@mdx-js/*' - - 'rehype-*' - - 'remark-*' + - hast-util-* + - rehype-* + - remark-* + - shiki + - sval + - unist-util-* + - vfile + - vfile-* + - reading-time + - twoslash + orama: + patterns: + - '@orama/*' + - '@oramacloud/*' + radix: + patterns: + - '@radix-ui/*' react: patterns: - 'react' - 'react-dom' - '@types/react' - - '@types/react-dom' - tailwind: + storybook: + patterns: + - 'storybook' + - '@storybook/*' + - 'eslint-plugin-storybook' + styling: patterns: - '@savvywombat/tailwindcss-grid-areas' + - '@tailwindcss/*' - 'prettier-plugin-tailwindcss' - 'tailwindcss' + testing: + patterns: + - '@testing-library/*' + - '@reporters/*' + - global-jsdom + - jsdom + - tsx + vercel: + patterns: + - '@next/*' + - '@opentelemetry/*' + - '@vercel/*' + - next + - next-* + - turbo ignore: - # Manually update major versions of @types/node with the version specified within .nvmrc - dependency-name: '@types/node' update-types: ['version-update:semver-major'] open-pull-requests-limit: 10 diff --git a/.github/scorecard.yml b/.github/scorecard.yml new file mode 100644 index 0000000000000..e461abd489a61 --- /dev/null +++ b/.github/scorecard.yml @@ -0,0 +1,8 @@ +# annotations tell scorecard that we have mitigated a concern. automation is only so good at establishing context +# https://github.com/ossf/scorecard/blob/main/config/README.md#annotating-your-project +annotations: + # our workflows only run when a maintainer allows it + - checks: + - dangerous-workflow + reasons: + - reason: remediated diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml new file mode 100644 index 0000000000000..2fa6e29c31ab4 --- /dev/null +++ b/.github/workflows/auto-merge.yml @@ -0,0 +1,33 @@ +name: Auto-merge PRs + +on: + schedule: + - cron: '*/15 * * * *' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + +permissions: {} + +jobs: + auto-merge: + name: Auto-merge + if: github.repository == 'nodejs/nodejs.org' + runs-on: ubuntu-latest + permissions: + # Required to approve and merge pull requests + pull-requests: write + # Required to merge pull requests via merge queue + contents: write + + steps: + - name: Harden Runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - uses: nodejs/web-team/actions/auto-merge-prs@b087df186d25f8792fb85cc7794f68718726b8ee + with: + merge-method: queue diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1a59e94f0c124..d822f64cf115a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,11 +10,10 @@ on: push: branches: - main - pull_request_target: + pull_request: branches: - main - types: - - labeled + types: [opened, synchronize, reopened, ready_for_review] merge_group: defaults: @@ -26,16 +25,12 @@ permissions: contents: read actions: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: build: - # This Job should run either on `merge_groups` or `push` events - # or `pull_request_target` event with a `labeled` action with a label named `github_actions:pull-request` - # since we want to run Website Builds on all these 3 occasions. As this allows us to be certain the that builds are passing - if: | - (github.event_name == 'push' || github.event_name == 'merge_group') || - (github.event_name == 'pull_request_target' && - github.event.label.name == 'github_actions:pull-request') - name: Build on ${{ matrix.os }} runs-on: ${{ matrix.os }} @@ -45,18 +40,10 @@ jobs: os: [ubuntu-latest, windows-latest] steps: - - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + - uses: nodejs/web-team/actions/setup-environment@9f3c83af227d721768d9dbb63009a47ed4f4282f with: - egress-policy: audit - - - name: Provide Turborepo Arguments - # This step is responsible for providing a reusable string that can be used within other steps and jobs - # that use the `turbo` cli command as a way of easily providing shared arguments to the `turbo` command - id: turborepo_arguments - # See https://turbo.build/repo/docs/reference/command-line-reference/run#--cache-dir - # See https://turbo.build/repo/docs/reference/command-line-reference/run#--force - run: echo "turbo_args=--force=true --cache-dir=.turbo/cache" >> "$GITHUB_OUTPUT" + pnpm: true + use-version-file: true - name: Use GNU tar instead BSD tar # This ensures that we use GNU `tar` which is more efficient for extracting caches's @@ -64,67 +51,107 @@ jobs: shell: cmd run: echo C:\Program Files\Git\usr\bin>>"%GITHUB_PATH%" - - name: Git Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: - # Since we checkout the HEAD of the current Branch, if the Pull Request comes from a Fork - # we want to clone the fork's repository instead of the base repository - # this allows us to have the correct history tree of the perspective of the Pull Request's branch - # If the Workflow is running on `merge_group` or `push` events it fallsback to the base repository - repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} - # We checkout the branch itself instead of a specific SHA (Commit) as we want to ensure that this Workflow - # is always running with the latest `ref` (changes) of the Pull Request's branch - # If the Workflow is running on `merge_group` or `push` events it fallsback to `github.ref` which will often be `main` - # or the merge_group `ref` - ref: ${{ github.event.pull_request.head.ref || github.ref }} - # We only need to fetch the last commit from the head_ref - # since we're not using the `--filter` operation from turborepo - # We don't use the `--filter` as we always want to force builds regardless of having changes or not - # this ensures that our bundle analysis script always runs and that we always ensure next.js is building - # regardless of having code changes or not - fetch-depth: 1 - - - name: Set up Node.js - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 - with: - # We want to ensure that the Node.js version running here respects our supported versions - node-version-file: '.nvmrc' - cache: 'npm' - - - name: Install npm packages - # We want to avoid npm from running the Audit Step and Funding messages on a CI environment - # We also use `npm i` instead of `npm ci` so that the node_modules/.cache folder doesn't get deleted - # We also use `--omit=dev` to avoid installing devDependencies as we don't need them during the build step - run: npm i --no-audit --no-fund --userconfig=/dev/null --omit=dev + path: ${{ github.workspace }}/apps/site/.next/cache + key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }} + restore-keys: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}- - - name: Build Next.js (ISR) + - name: Build Next.js # We want a ISR build on CI to ensure that regular Next.js builds work as expected. - # We want to enforce that the actual `turbo@latest` package is used instead of a possible hijack from the user - # the `${{ steps.turborepo_arguments.outputs.turbo_args }}` is a string substitution coming from a previous step - run: npx --package=turbo@latest -- turbo build ${{ steps.turborepo_arguments.outputs.turbo_args }} + run: node_modules/.bin/turbo build --filter=@node-core/website --cache-dir=.turbo/cache env: # We want to ensure we have enough RAM allocated to the Node.js process # this should be a last resort in case by any chances the build memory gets too high # but in general this should never happen NODE_OPTIONS: '--max_old_space_size=4096' + # See https://github.com/vercel/next.js/pull/90949 + TURBOPACK_STATS: ${{ matrix.os == 'ubuntu-latest' }} - - name: Build Next.js (Static) - # We only run full static builds within Pull Requests. As they're not needed on `merge_group` or `push` events - # Note that we skip full static builds on Crowdin-based Pull Requests as these PRs should only contain translation changes - if: | - (github.event_name == 'push') || - (github.event_name == 'pull_request_target' && - github.event.pull_request.head.ref != 'chore/crowdin') - # We want to enforce that the actual `turbo@latest` package is used instead of a possible hijack from the user - # the `${{ steps.turborepo_arguments.outputs.turbo_args }}` is a string substitution coming from a previous step - run: npx --package=turbo@latest -- turbo deploy ${{ steps.turborepo_arguments.outputs.turbo_args }} + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + if: matrix.os == 'ubuntu-latest' + with: + name: webpack-stats + path: apps/site/.next/diagnostics/route-bundle-stats.json + + - name: Build Next.js (Static Export) + # We want to generate a static build, as it is a requirement of our website. + run: node_modules/.bin/turbo deploy --filter=@node-core/website --cache-dir=.turbo/cache env: # We want to ensure we have enough RAM allocated to the Node.js process # this should be a last resort in case by any chances the build memory gets too high # but in general this should never happen NODE_OPTIONS: '--max_old_space_size=4096' + # We want to ensure that static exports for all locales do not occur on `pull_request` events + # TODO: The output of this is too large, and it crashes the GitHub Runner + NEXT_PUBLIC_STATIC_EXPORT_LOCALE: false # ${{ github.event_name == 'push' }} + + compare-bundle-size: + name: Compare Bundle Size + runs-on: ubuntu-latest + needs: build + if: github.event_name == 'pull_request' + + steps: + - name: Harden Runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit - - name: Sync Orama Cloud - if: github.ref == 'refs/heads/main' + - name: Git Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Download Stats (HEAD) + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: webpack-stats + path: head-stats + + - name: Get Run ID from BASE + id: base-run + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + GH_TOKEN: ${{ github.token }} run: | - npm run sync-orama + ID=$(gh run list -c "$BASE_SHA" -w build.yml -s success -L 1 --json databaseId --jq ".[].databaseId") + echo "run_id=$ID" >> "$GITHUB_OUTPUT" + + - name: Download Stats (BASE) + id: base-stats + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: webpack-stats + path: base-stats + run-id: ${{ steps.base-run.outputs.run_id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Compare Bundle Size + id: compare-bundle-size + if: steps.base-stats.outcome == 'success' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + HEAD_STATS_PATH: ./head-stats/route-bundle-stats.json + BASE_STATS_PATH: ./base-stats/route-bundle-stats.json + with: + script: | + const { compare } = await import('${{github.workspace}}/apps/site/scripts/compare-size/index.mjs') + await compare({core}) + + - name: Prepare Comment + if: steps.base-stats.outcome == 'success' + env: + COMMENT: ${{ steps.compare-bundle-size.outputs.comment }} + run: | + mkdir -p pr-comment + printf '%s' "$COMMENT" > pr-comment/comment.md + printf '%s' 'compare_bundle_size' > pr-comment/tag.txt + + - name: Upload Comment + if: steps.base-stats.outcome == 'success' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: pr-comment + path: pr-comment/ diff --git a/.github/workflows/bump-security-txt-expiry.yml b/.github/workflows/bump-security-txt-expiry.yml new file mode 100644 index 0000000000000..97cb139347bf1 --- /dev/null +++ b/.github/workflows/bump-security-txt-expiry.yml @@ -0,0 +1,65 @@ +# Security Notes +# Only selected Actions are allowed within this repository. Please refer to (https://github.com/nodejs/nodejs.org/settings/actions) +# for the full list of available actions. If you want to add a new one, please reach out a maintainer with Admin permissions. +# REVIEWERS, please always double-check security practices before merging a PR that contains Workflow changes!! +# AUTHORS, please only use actions with explicit SHA references, and avoid using `@master` or `@main` references or `@version` tags. + +name: Bump security.txt Expiry + +on: + workflow_dispatch: + schedule: + - cron: '0 8 1 1 *' # once a year, Jan 1 at 08:00 UTC + +permissions: {} + +jobs: + bump-expiry: + runs-on: ubuntu-latest + if: github.repository_owner == 'nodejs' + permissions: + contents: write + pull-requests: write + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Bump the Expires year + id: update-expiry + env: + SECURITY_TXT: apps/site/public/.well-known/security.txt + run: | + set -euo pipefail + old=$(sed -n 's/^Expires: *//p' "$SECURITY_TXT") + year=${old%%-*} + new="$((year + 1))${old#"$year"}" + sed -i "s|^Expires: .*|Expires: ${new}|" "$SECURITY_TXT" + { + echo "old=${old}" + echo "new=${new}" + } >> "$GITHUB_OUTPUT" + + - name: Open pull request + uses: gr2m/create-or-update-pull-request-action@b65137ca591da0b9f43bad7b24df13050ea45d1b # v1.10.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + update-pull-request-title-and-body: true + branch: chore/update-security-txt + body: | + Updates the `Expiry` on our [`security.txt`](https://nodejs.org/.well-known/security.txt) from ${{ steps.update-expiry.outputs.old }} to ${{ steps.update-expiry.outputs.new }} + + Before merging, please explicitly confirm that: + - The security contact listed is still correct and actively monitored + - The linked security policy is still accurate + - The project's preferred vulnerability disclosure path has not changed + + cc @nodejs/security-wg + + Check this workflow's logs at ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}. + commit-message: 'meta: update security `Expiry` from ${{ steps.update-expiry.outputs.old }} to ${{ steps.update-expiry.outputs.new }}' + title: 'meta: update security `Expiry` from ${{ steps.update-expiry.outputs.old }} to ${{ steps.update-expiry.outputs.new }}' + draft: true + labels: security diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml new file mode 100644 index 0000000000000..48af4d5ab7836 --- /dev/null +++ b/.github/workflows/chromatic.yml @@ -0,0 +1,72 @@ +# Security Notes +# Only selected Actions are allowed within this repository. Please refer to (https://github.com/nodejs/nodejs.org/settings/actions) +# for the full list of available actions. If you want to add a new one, please reach out a maintainer with Admin permissions. +# REVIEWERS, please always double-check security practices before merging a PR that contains Workflow changes!! +# AUTHORS, please only use actions with explicit SHA references, and avoid using `@master` or `@main` references or `@version` tags. + +name: Chromatic + +on: + merge_group: + push: + branches: + - main + paths: + - packages/ui-components/** + - .github/workflows/chromatic.yml + pull_request: + branches: + - main + paths: + - packages/ui-components/** + - .github/workflows/chromatic.yml + workflow_dispatch: + +defaults: + run: + # This ensures that the working directory is the root of the repository + working-directory: ./ + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name != 'push' }} + +permissions: + contents: read + actions: read + +jobs: + chromatic: + # We only need to run Storybook Builds and Storybook Visual Regression Tests within Pull Requests that actually + # introduce changes to the Storybook. Hence, we skip running these on Crowdin PRs and Dependabot PRs + if: | + github.event_name != 'pull_request' || + ( + github.actor != 'dependabot[bot]' && + github.event.pull_request.head.ref != 'chore/crowdin' + ) + + name: Chromatic + runs-on: ubuntu-latest + + environment: + name: Storybook + url: ${{ steps.chromatic-deploy.outputs.storybookUrl }} + + steps: + - uses: nodejs/web-team/actions/setup-environment@9f3c83af227d721768d9dbb63009a47ed4f4282f + with: + pnpm: true + use-version-file: true + fetch-depth: 0 + + - name: Start Visual Regression Tests (Chromatic) + # This assigns the Environment Deployment for Storybook + id: chromatic-deploy + uses: chromaui/action@14cfaef73576e69f95f47f60058063f46ca38719 # v18.1.0 + with: + workingDir: packages/ui-components + buildScriptName: storybook:build + projectToken: ${{ vars.CHROMATIC_PROJECT_TOKEN }} + exitOnceUploaded: true + onlyChanged: true diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index aeffcd359ad03..d62bbce624f35 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,14 +1,3 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# name: 'CodeQL' on: @@ -17,6 +6,7 @@ on: pull_request: # The branches below must be a subset of the branches above branches: ['main'] + types: [opened, synchronize, reopened, ready_for_review] schedule: - cron: '0 0 * * 1' @@ -26,53 +16,8 @@ permissions: jobs: analyze: name: Analyze - runs-on: ubuntu-latest + uses: nodejs/web-team/.github/workflows/codeql.yml@9f3c83af227d721768d9dbb63009a47ed4f4282f permissions: actions: read contents: read security-events: write - - strategy: - fail-fast: false - matrix: - language: ['javascript', 'typescript'] - # CodeQL supports [ $supported-codeql-languages ] - # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support - - steps: - - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 - with: - egress-policy: audit - - - name: Checkout repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 - - # ℹ️ Command-line programs to run using the OS shell. - # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - - # If the Autobuild fails above, remove it and uncomment the following three lines. - # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. - - # - run: | - # echo "Run, Build Application using script" - # ./location_of_script_within_repo/buildscript.sh - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 - with: - category: '/language:${{matrix.language}}' diff --git a/.github/workflows/create-release-post.yml b/.github/workflows/create-release-post.yml new file mode 100644 index 0000000000000..a008351424834 --- /dev/null +++ b/.github/workflows/create-release-post.yml @@ -0,0 +1,67 @@ +# Security Notes +# Only selected Actions are allowed within this repository. Please refer to (https://github.com/nodejs/nodejs.org/settings/actions) +# for the full list of available actions. If you want to add a new one, please reach out a maintainer with Admin permissions. +# REVIEWERS, please always double-check security practices before merging a PR that contains Workflow changes!! +# AUTHORS, please only use actions with explicit SHA references, and avoid using `@master` or `@main` references or `@version` tags. + +name: Create Release Blog Post + +on: + workflow_dispatch: + inputs: + version: + description: The version to generate a blog post for. + type: string + required: true + +defaults: + run: + # This ensures that the working directory is the root of the repository + working-directory: ./ + +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ inputs.version }} + cancel-in-progress: false + +jobs: + create-post: + name: Create Release Blog Post + runs-on: ubuntu-latest + permissions: + # Required to push the release branch + contents: write + # Required to create the pull request + pull-requests: write + + steps: + - uses: nodejs/web-team/actions/setup-environment@9f3c83af227d721768d9dbb63009a47ed4f4282f + with: + pnpm: true + use-version-file: true + + - run: node --run scripts:release-post -- "$VERSION" + working-directory: apps/site + id: release-post + env: + VERSION: ${{ inputs.version }} + + - name: Open pull request + uses: gr2m/create-or-update-pull-request-action@b65137ca591da0b9f43bad7b24df13050ea45d1b # v1.10.1 + # Creates a PR or update the Action's existing PR, or + # no-op if the base branch is already up-to-date. + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + update-pull-request-title-and-body: true + branch: release-${{ inputs.version }} + body: | + Creates a new blog post for ${{ inputs.version }} + + Check this workflow's logs at ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}. + commit-message: 'feat(blog): create post for ${{ inputs.version }}' + labels: fast-track + title: 'feat(blog): create post for ${{ inputs.version }}' + assignees: ${{ steps.release-post.outputs.author }} + draft: true diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 96d9044dcd93c..3b0d4f38d0255 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -1,32 +1,18 @@ -# Dependency Review Action -# -# This Action will scan dependency manifest files that change as part of a Pull Request, -# surfacing known-vulnerable versions of the packages declared or updated in the PR. -# Once installed, if the workflow run is marked as required, -# PRs introducing known-vulnerable packages will be blocked from merging. -# -# Source repository: https://github.com/actions/dependency-review-action name: Review Dependencies on: - pull_request_target: + pull_request: branches: - main +# Cancel any runs on the same branch +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read jobs: dependency-review: - runs-on: ubuntu-latest - steps: - - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 - with: - egress-policy: audit - - - name: Git Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - - name: Review Dependencies - uses: actions/dependency-review-action@9129d7d40b8c12c1ed0f60400d00c92d437adcce # v4.1.3 + uses: nodejs/web-team/.github/workflows/dependency-review.yml@9f3c83af227d721768d9dbb63009a47ed4f4282f diff --git a/.github/workflows/leave-comment.yml b/.github/workflows/leave-comment.yml new file mode 100644 index 0000000000000..d79ec042481d5 --- /dev/null +++ b/.github/workflows/leave-comment.yml @@ -0,0 +1,97 @@ +name: Leave Comment + +on: + workflow_run: # zizmor: ignore[dangerous-triggers] + # While it's true (as reported by Zizmor) that many `workflow_run` + # workflows are unsafe, we are using it purely to paste a code comment + # on a pull request. If an attacker were to attempt to modify the _only_ + # file of data we collect from their unsafe code, the worst they could do + # is leave a comment on their own PR, which has no harmful impact + # whatsover. + # + # Any Workflow that uploads a `pr-comment` artifact should be listed here + workflows: ['Build', 'Lighthouse'] + types: [completed] + +permissions: + contents: read + actions: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.workflow_run.id }} + cancel-in-progress: true + +jobs: + leave-comment: + name: Leave Comment + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + - name: Harden Runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Download Comment Artifact + # The Workflow may not have produced a comment (e.g. the comparison was skipped), so this is + # allowed to fail and every subsequent step is gated on it having succeeded. + id: download + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: pr-comment + path: pr-comment + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Resolve Pull Request Number + id: pr + if: steps.download.outcome == 'success' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const run = context.payload.workflow_run; + + // 1. For same-repo Pull Requests the run is already linked to its PR(s). + if (run.pull_requests && run.pull_requests.length) { + core.setOutput('number', run.pull_requests[0].number); + return; + } + + // 2. For forks that list is empty, so find the open Pull Request who has the + // correct branch information + const match = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + head: `${context.payload.workflow_run.head_repository.owner.login}:${context.payload.workflow_run.head_branch}`, + sort: 'updated', + direction: 'desc', + per_page: 1, + }).then(r => r.data[0]); + + if (!match) { + core.info(`No open pull request found for HEAD ${run.head_sha}`); + return; + } + + core.setOutput('number', match.number); + + - name: Read Comment Tag + id: meta + if: steps.download.outcome == 'success' + run: | + tag="$(tr -cd 'A-Za-z0-9_-' < pr-comment/tag.txt)" + echo "tag=$tag" >> "$GITHUB_OUTPUT" + + - name: Add Comment to PR + # The comment body is untrusted markdown, so it is passed as a file (data) rather than + # interpolated into an expression or shell command. + if: steps.download.outcome == 'success' && steps.pr.outputs.number != '' + uses: thollander/actions-comment-pull-request@e2c37e53a7d2227b61585343765f73a9ca57eda9 # v3.0.0 + with: + file-path: pr-comment/comment.md + comment-tag: ${{ steps.meta.outputs.tag }} + pr-number: ${{ steps.pr.outputs.number }} diff --git a/.github/workflows/lighthouse.yml b/.github/workflows/lighthouse.yml index a2be790c5261f..b49f72e712bd9 100644 --- a/.github/workflows/lighthouse.yml +++ b/.github/workflows/lighthouse.yml @@ -1,5 +1,7 @@ # Security Notes -# This workflow uses `pull_request_target`, so will run against all PRs automatically (without approval), be careful with allowing any user-provided code to be run here +# This Workflow runs in the untrusted `pull_request` context and therefore must not rely on any +# repository secrets. It does not comment on the Pull Request itself; instead it uploads a +# `pr-comment` artifact which the trusted `Leave Comment` Workflow posts once this Workflow completes. # Only selected Actions are allowed within this repository. Please refer to (https://github.com/nodejs/nodejs.org/settings/actions) # for the full list of available actions. If you want to add a new one, please reach out a maintainer with Admin permissions. # REVIEWERS, please always double-check security practices before merging a PR that contains Workflow changes!! @@ -9,7 +11,7 @@ name: Lighthouse on: - pull_request_target: + pull_request: branches: - main types: @@ -23,8 +25,10 @@ defaults: permissions: contents: read actions: read - # This permission is required by `thollander/actions-comment-pull-request` - pull-requests: write + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true jobs: lighthouse-ci: @@ -32,88 +36,74 @@ jobs: if: | startsWith(github.event.pull_request.head.ref, 'dependabot/') == false && github.event.label.name == 'github_actions:pull-request' - name: Lighthouse Report runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - - name: Git Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - with: - # Since we checkout the HEAD of the current Branch, if the Pull Request comes from a Fork - # we want to clone the fork's repository instead of the base repository - # this allows us to have the correct history tree of the perspective of the Pull Request's branch - # If the Workflow is running on `merge_group` or `push` events it fallsback to the base repository - repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} - # We checkout the branch itself instead of a specific SHA (Commit) as we want to ensure that this Workflow - # is always running with the latest `ref` (changes) of the Pull Request's branch - # If the Workflow is running on `merge_group` or `push` events it fallsback to `github.ref` which will often be `main` - # or the merge_group `ref` - ref: ${{ github.event.pull_request.head.ref || github.ref }} - - - name: Add Comment to PR - # Signal that a lighthouse run is about to start - uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0 - with: - message: | - Running Lighthouse audit... - # Used later to edit the existing comment - comment_tag: 'lighthouse_audit' - - name: Capture Vercel Preview - uses: patrickedqvist/wait-for-vercel-preview@dca4940010f36d2d44caa487087a09b57939b24a # v1.3.1 - id: vercel_preview_url + id: deployment + uses: patrickedqvist/wait-for-vercel-preview@d7982701e6fcd3ae073bff929e408e004404d38d # v1.3.3 with: token: ${{ secrets.GITHUB_TOKEN }} - # timeout after 5 minutes - max_timeout: 300 - # check every 10 seconds - check_interval: 10 + max_timeout: 300 # timeout after 5 minutes + check_interval: 10 # check every 10 seconds + + - name: Git Checkout + # Only needed for the Lighthouse formatting script; no credentials are persisted. + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Audit Preview URL with Lighthouse # Conduct the lighthouse audit id: lighthouse_audit - uses: treosh/lighthouse-ci-action@1b0e7c33270fbba31a18a0fbb1de7cc5256b6d39 # v11.4.0 + uses: treosh/lighthouse-ci-action@3e7e23fb74242897f95c0ba9cabad3d0227b9b18 # v12.6.2 with: # Defines the settings and assertions to audit configPath: './.lighthouserc.json' # These URLS capture critical pages / site functionality. urls: | - ${{ steps.vercel_preview_url.outputs.url }}/en - ${{ steps.vercel_preview_url.outputs.url }}/en/about - ${{ steps.vercel_preview_url.outputs.url }}/en/about/previous-releases - ${{ steps.vercel_preview_url.outputs.url }}/en/download - ${{ steps.vercel_preview_url.outputs.url }}/en/blog + ${{ steps.deployment.outputs.url }}/en + ${{ steps.deployment.outputs.url }}/en/about + ${{ steps.deployment.outputs.url }}/en/about/previous-releases + ${{ steps.deployment.outputs.url }}/en/download + ${{ steps.deployment.outputs.url }}/en/download/archive/current + ${{ steps.deployment.outputs.url }}/en/blog uploadArtifacts: true # save results as a action artifacts temporaryPublicStorage: true # upload lighthouse report to the temporary storage - name: Format Lighthouse Score # Transform the audit results into a single, friendlier output id: format_lighthouse_score - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: # using env as input to our script # see https://github.com/actions/github-script#use-env-as-input LIGHTHOUSE_RESULT: ${{ steps.lighthouse_audit.outputs.manifest }} LIGHTHOUSE_LINKS: ${{ steps.lighthouse_audit.outputs.links }} - VERCEL_PREVIEW_URL: ${{ steps.vercel_preview_url.outputs.url }} + VERCEL_PREVIEW_URL: ${{ steps.deployment.outputs.url }} with: # Run as a separate file so we do not have to inline all of our formatting logic. # See https://github.com/actions/github-script#run-a-separate-file for more info. script: | - const { formatLighthouseResults } = await import('${{github.workspace}}/scripts/lighthouse/index.mjs') + const { formatLighthouseResults } = await import('${{github.workspace}}/apps/site/scripts/lighthouse/index.mjs') await formatLighthouseResults({core}) - - name: Add Comment to PR - # Replace the previous message with our formatted lighthouse results - uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0 + - name: Prepare Comment + env: + COMMENT: ${{ steps.format_lighthouse_score.outputs.comment }} + run: | + mkdir -p pr-comment + printf '%s' "$COMMENT" > pr-comment/comment.md + printf '%s' 'lighthouse_audit' > pr-comment/tag.txt + + - name: Upload Comment + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - # Reference the previously created comment - comment_tag: 'lighthouse_audit' - message: | - ${{ steps.format_lighthouse_score.outputs.comment }} + name: pr-comment + path: pr-comment/ diff --git a/.github/workflows/lint-and-tests.yml b/.github/workflows/lint-and-tests.yml index 0853864144d8e..c61cfafcdb3fb 100644 --- a/.github/workflows/lint-and-tests.yml +++ b/.github/workflows/lint-and-tests.yml @@ -1,97 +1,59 @@ # Security Notes # Only selected Actions are allowed within this repository. Please refer to (https://github.com/nodejs/nodejs.org/settings/actions) # for the full list of available actions. If you want to add a new one, please reach out a maintainer with Admin permissions. -# REVIEWERS, please always double-check security practices before merging a PR that contains Workflow changes!! +# REVIEWERS, please always double-check security practices before merging a PR that contains workflow changes!! # AUTHORS, please only use actions with explicit SHA references, and avoid using `@master` or `@main` references or `@version` tags. name: Linting and Tests +# This workflow should run either on `merge_group`, `pull_request`, or `push` events +# since we want to run lint checks against any changes on pull requests, or the final patch on merge groups +# or if direct pushes happen to main (or when changes in general land on the `main` (default) branch) +# Note that the reason why we run this on pushes against `main` is that on rare cases, maintainers might do direct pushes against `main` + on: push: branches: - main - pull_request_target: + pull_request: branches: - main - types: - - labeled + types: [opened, synchronize, reopened, ready_for_review] merge_group: -defaults: - run: - # This ensures that the working directory is the root of the repository - working-directory: ./ - +# The permissions specified below apply to workflows triggered by `merge_group`, `push`, and `pull_request` events that originate from the same repository (non-fork). +# However, workflows triggered by `pull_request` events from forked repositories are treated differently for security reasons: +# - These workflows **do not** have access to any secrets configured in the repository. +# - They are also **not granted any permissions** to perform actions on the base repository. +# +# This is a deliberate security restriction designed to prevent potential abuse through malicious pull requests from forks. +# For a deeper explanation and best practices for securing your GitHub Actions workflows, particularly against so-called "pwn requests", +# refer to https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/ permissions: contents: read actions: read - # This permission is required by `MishaKav/jest-coverage-comment` - pull-requests: write - -jobs: - base: - name: Base Tasks - runs-on: ubuntu-latest - outputs: - turbo_args: ${{ steps.turborepo_arguments.outputs.turbo_args }} - - steps: - - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 - with: - egress-policy: audit - - name: Provide Turborepo Arguments - # This step is responsible for providing a reusable string that can be used within other steps and jobs - # that use the `turbo` cli command as a way of easily providing shared arguments to the `turbo` command - id: turborepo_arguments - # See https://turbo.build/repo/docs/reference/command-line-reference/run#--cache-dir - # See https://turbo.build/repo/docs/reference/command-line-reference/run#--force - run: echo "turbo_args=--force=true --cache-dir=.turbo/cache" >> "$GITHUB_OUTPUT" +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} +jobs: lint: - # This Job should run either on `merge_groups` or `push` events - # or `pull_request_target` event with a `labeled` action with a label named `github_actions:pull-request` - # since we want to run lint checks against any changes on pull requests, or the final patch on merge groups - # or if direct pushes happen to main (or when changes in general land on the `main` (default) branch) - # Note that the reason why we run this on pushes against `main` is that on rare cases, maintainers might do direct pushes against `main` - if: | - (github.event_name == 'push' || github.event_name == 'merge_group') || - (github.event_name == 'pull_request_target' && - github.event.label.name == 'github_actions:pull-request') - - name: Lint + name: Quality checks runs-on: ubuntu-latest - needs: [base] steps: - - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 - with: - egress-policy: audit - - - name: Git Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: nodejs/web-team/actions/setup-environment@9f3c83af227d721768d9dbb63009a47ed4f4282f with: - # Since we checkout the HEAD of the current Branch, if the Pull Request comes from a Fork - # we want to clone the fork's repository instead of the base repository - # this allows us to have the correct history tree of the perspective of the Pull Request's branch - # If the Workflow is running on `merge_group` or `push` events it fallsback to the base repository - repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} - # We checkout the branch itself instead of a specific SHA (Commit) as we want to ensure that this Workflow - # is always running with the latest `ref` (changes) of the Pull Request's branch - # If the Workflow is running on `merge_group` or `push` events it fallsback to `github.ref` which will often be `main` - # or the merge_group `ref` - ref: ${{ github.event.pull_request.head.ref || github.ref }} + pnpm: true + use-version-file: true - name: Restore Lint Cache - uses: actions/cache/restore@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: | .turbo/cache - node_modules/.cache .eslintmdcache - .eslintjscache .stylelintcache .prettiercache # We want to restore Turborepo Cache and ESlint and Prettier Cache @@ -99,45 +61,19 @@ jobs: # as they will only run on files that have changed since the last cached run # this might of course lead to certain files not being checked against the linter, but the chances # of such situation from happening are very slim as the checksums of both files would need to match - key: cache-lint-${{ hashFiles('package-lock.json') }}- + key: cache-lint-${{ hashFiles('pnpm-lock.yaml') }}-${{ hashFiles('.turbo/cache/**') }} restore-keys: | - cache-lint-${{ hashFiles('package-lock.json') }}- + cache-lint-${{ hashFiles('pnpm-lock.yaml') }}- cache-lint- - - name: Set up Node.js - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 - with: - # We want to ensure that the Node.js version running here respects our supported versions - node-version-file: '.nvmrc' - cache: 'npm' - - - name: Install npm packages - # We want to avoid npm from running the Audit Step and Funding messages on a CI environment - # We also use `npm i` instead of `npm ci` so that the node_modules/.cache folder doesn't get deleted - run: npm i --no-audit --no-fund --ignore-scripts --userconfig=/dev/null - - - name: Run `turbo lint` - id: eslint-step + - name: Run quality checks with `turbo` # We run the ESLint and Prettier commands on all Workflow triggers of the `Lint` job, besides if # the Pull Request comes from a Crowdin Branch, as we don't want to run ESLint and Prettier on Crowdin PRs # Note: Linting and Prettifying of files on Crowdin PRs is handled by the `translations-pr.yml` Workflow if: | (github.event_name == 'push' || github.event_name == 'merge_group') || - (github.event_name == 'pull_request_target' && - github.event.pull_request.head.ref != 'chore/crowdin') - # We want to enforce that the actual `turbo@latest` package is used instead of a possible hijack from the user - # the `${{ needs.base.outputs.turbo_args }}` is a string substitution happening from the base job - run: npx --package=turbo@latest -- turbo lint ${{ needs.base.outputs.turbo_args }} - - - name: Run `turbo prettier` - if: steps.eslint-step.outcome == 'success' - # We want to enforce that the actual `turbo@latest` package is used instead of a possible hijack from the user - # the `${{ needs.base.outputs.turbo_args }}` is a string substitution happening from the base job - run: npx --package=turbo@latest -- turbo prettier ${{ needs.base.outputs.turbo_args }} - - - name: Run `tsc build` - # We want to ensure that the whole codebase is passing and successfully compiles with TypeScript - run: npx --package=typescript@latest -- tsc --build . + (github.event_name == 'pull_request' && github.event.pull_request.head.ref != 'chore/crowdin') + run: node_modules/.bin/turbo lint lint:types prettier --cache-dir=.turbo/cache - name: Save Lint Cache # We only want to save caches on `push` events or `pull_request_target` events @@ -147,107 +83,42 @@ jobs: # other Pull Requests and PRs coming from forks if: | github.event_name == 'push' || - (github.event_name == 'pull_request_target' && + (github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'dependabot/') == false && github.event.pull_request.head.ref != 'chore/crowdin') - uses: actions/cache/save@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 + uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: | .turbo/cache - node_modules/.cache .eslintmdcache - .eslintjscache .stylelintcache .prettiercache - key: cache-lint-${{ hashFiles('package-lock.json') }}-${{ hashFiles('.turbo/cache/**') }} + key: cache-lint-${{ hashFiles('pnpm-lock.yaml') }}-${{ hashFiles('.turbo/cache/**') }} tests: - # This Job should run either on `merge_groups` or `push` events - # or `pull_request_target` event with a `labeled` action with a label named `github_actions:pull-request` - # since we want to run lint checks against any changes on pull requests and on final patches against a pull request. - # We don't need to execute the tests again on pushes against (`main`) as the merge group should already handle that - if: | - (github.event_name == 'push' || github.event_name == 'merge_group') || - (github.event_name == 'pull_request_target' && - github.event.label.name == 'github_actions:pull-request') - name: Tests runs-on: ubuntu-latest - needs: [base] - - environment: - name: Storybook - url: ${{ steps.chromatic-deploy.outputs.storybookUrl }} steps: - - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 - with: - egress-policy: audit - - - name: Git Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: nodejs/web-team/actions/setup-environment@9f3c83af227d721768d9dbb63009a47ed4f4282f with: - # Since we checkout the HEAD of the current Branch, if the Pull Request comes from a Fork - # we want to clone the fork's repository instead of the base repository - # this allows us to have the correct history tree of the perspective of the Pull Request's branch - # If the Workflow is running on `merge_group` or `push` events it fallsback to the base repository - repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} - # We checkout the branch itself instead of a specific SHA (Commit) as we want to ensure that this Workflow - # is always running with the latest `ref` (changes) of the Pull Request's branch - # If the Workflow is running on `merge_group` or `push` events it fallsback to `github.ref` which will often be `main` - # or the merge_group `ref` - ref: ${{ github.event.pull_request.head.ref || github.ref }} - # The Chromatic (@chromaui/action) Action requires a full history of the current branch in order to be able to compare - # previous changes and previous commits and determine which Storybooks should be tested against and what should be built - fetch-depth: 0 - - - name: Set up Node.js - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 - with: - # We want to ensure that the Node.js version running here respects our supported versions - node-version-file: '.nvmrc' - cache: 'npm' - - - name: Install npm packages - # We want to avoid npm from running the Audit Step and Funding messages on a CI environment - # We also use `npm i` instead of `npm ci` so that the node_modules/.cache folder doesn't get deleted - run: npm i --no-audit --no-fund --userconfig=/dev/null + pnpm: true + use-version-file: true - name: Run Unit Tests # We want to run Unit Tests in every circumstance, including Crowdin PRs and Dependabot PRs to ensure # that changes to dependencies or translations don't break the Unit Tests - # We want to enforce that the actual `turbo@latest` package is used instead of a possible hijack from the user - # the `${{ needs.base.outputs.turbo_args }}` is a string substitution happening from the base job - run: npx --package=turbo@latest -- turbo test:unit ${{ needs.base.outputs.turbo_args }} -- --ci --coverage + run: node --run test:ci -- --cache-dir=.turbo/cache - - name: Start Visual Regression Tests (Chromatic) - # This assigns the Environment Deployment for Storybook - id: chromatic-deploy - # We only need to run Storybook Builds and Storybook Visual Regression Tests within Pull Requests that actually - # introduce changes to the Storybook. Hence, we skip running these on Crowdin PRs and Dependabot PRs - if: | - github.event_name == 'push' || - (github.event_name == 'pull_request_target' && - startsWith(github.event.pull_request.head.ref, 'dependabot/') == false && - github.event.pull_request.head.ref != 'chore/crowdin') - # sha reference has no stable git tag reference or URL. see https://github.com/chromaui/chromatic-cli/issues/797 - uses: chromaui/action@807600692d28833b717c155e15ed20905cdc865c + - name: Upload test coverage to Codecov + if: ${{ !cancelled() && github.event_name != 'merge_group' }} + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: - buildScriptName: storybook:build - projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} - exitOnceUploaded: true - onlyChanged: true + files: ./apps/site/lcov.info,./packages/*/lcov.info - - name: Jest Coverage Comment - # We don't need to post the Jest Coverage comment on Crowdin PRs and Dependabot PRs - # as in general they introduce no changes to the Unit Tests and the Codebase - # We reuse the checks from the Chromatic Deploy step as they're the same conditions - if: steps.chromatic-deploy.outcome == 'success' - # This comments the current Jest Coverage Report containing JUnit XML reports - # and a Code Coverage Summary - uses: MishaKav/jest-coverage-comment@c2d5cfd6c32e8799c6deb0fd76a8e2d9ad8b35c2 # v1.0.25 + - name: Upload test results to Codecov + if: ${{ !cancelled() && github.event_name != 'merge_group' }} + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: - title: 'Unit Test Coverage Report' - junitxml-path: ./junit.xml - junitxml-title: Unit Test Report + report_type: test_results + files: ./apps/site/junit.xml,./packages/*/junit.xml diff --git a/.github/workflows/notify-on-push.yml b/.github/workflows/notify-on-push.yml new file mode 100644 index 0000000000000..9ac17633e30bd --- /dev/null +++ b/.github/workflows/notify-on-push.yml @@ -0,0 +1,24 @@ +on: + push: + branches: + - main + +name: Notify on Push +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.sha }} + cancel-in-progress: false + +jobs: + notify_on_push: + name: Notify on any direct push to `main` + if: > + github.repository == 'nodejs/nodejs.org' && + github.actor != 'github-merge-queue[bot]' + runs-on: ubuntu-latest + steps: + - uses: nodejs/web-team/actions/notify-on-push@9f3c83af227d721768d9dbb63009a47ed4f4282f + with: + webhook: ${{ secrets.SLACK_WEBHOOK }} diff --git a/.github/workflows/playwright-cloudflare-open-next.yml b/.github/workflows/playwright-cloudflare-open-next.yml new file mode 100644 index 0000000000000..b049a3d08ddde --- /dev/null +++ b/.github/workflows/playwright-cloudflare-open-next.yml @@ -0,0 +1,64 @@ +# Security Notes +# Only selected Actions are allowed within this repository. Please refer to (https://github.com/nodejs/nodejs.org/settings/actions) +# for the full list of available actions. If you want to add a new one, please reach out a maintainer with Admin permissions. +# REVIEWERS, please always double-check security practices before merging a PR that contains Workflow changes!! +# AUTHORS, please only use actions with explicit SHA references, and avoid using `@master` or `@main` references or `@version` tags. + +name: Playwright Tests on Cloudflare Open-Next + +on: + push: + branches: + - main + pull_request: + branches: + - main + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + actions: read + +jobs: + playwright: + name: Playwright Tests + runs-on: ubuntu-latest + + steps: + - uses: nodejs/web-team/actions/setup-environment@9f3c83af227d721768d9dbb63009a47ed4f4282f + with: + pnpm: true + use-version-file: true + fetch-depth: 2 + + - name: Get Playwright version + id: playwright-version + working-directory: apps/site + run: echo "version=$(node_modules/.bin/playwright --version | awk '{print $2}')" >> $GITHUB_OUTPUT + + - name: Cache Playwright browsers + id: playwright-cache + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ~/.cache/ms-playwright + key: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }} + + - name: Install Playwright Browsers + working-directory: apps/site + run: node_modules/.bin/playwright install --with-deps + + - name: Build open-next site + run: node_modules/.bin/turbo build --filter=@node-core/platform-cloudflare --cache-dir=.turbo/cache + + - name: Run Playwright tests + run: pnpm --filter=@node-core/platform-cloudflare test:e2e + + - name: Upload Playwright test results + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: playwright-report + path: apps/site/playwright-report/ diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 0000000000000..f09764aafd418 --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,72 @@ +# Security Notes +# Only selected Actions are allowed within this repository. Please refer to (https://github.com/nodejs/nodejs.org/settings/actions) +# for the full list of available actions. If you want to add a new one, please reach out a maintainer with Admin permissions. +# REVIEWERS, please always double-check security practices before merging a PR that contains Workflow changes!! +# AUTHORS, please only use actions with explicit SHA references, and avoid using `@master` or `@main` references or `@version` tags. +# MERGE QUEUE NOTE: This Workflow does not run on `merge_group` trigger, as this Workflow is not required for Merge Queue's + +name: Playwright Tests + +on: + pull_request: + branches: + - main + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + actions: read + +jobs: + playwright: + # We want to skip on Dependabot PRs + if: startsWith(github.event.pull_request.head.ref, 'dependabot/') == false + name: Playwright Tests + runs-on: ubuntu-latest + + steps: + - uses: nodejs/web-team/actions/setup-environment@9f3c83af227d721768d9dbb63009a47ed4f4282f + with: + pnpm: true + use-version-file: true + fetch-depth: 2 + + - name: Capture Vercel Preview + id: deployment + uses: patrickedqvist/wait-for-vercel-preview@d7982701e6fcd3ae073bff929e408e004404d38d # v1.3.3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + max_timeout: 300 # timeout after 5 minutes + check_interval: 10 # check every 10 seconds + + - name: Get Playwright version + id: playwright-version + working-directory: apps/site + run: echo "version=$(node_modules/.bin/playwright --version | awk '{print $2}')" >> $GITHUB_OUTPUT + + - name: Cache Playwright browsers + id: playwright-cache + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ~/.cache/ms-playwright + key: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }} + + - name: Install Playwright Browsers + working-directory: apps/site + run: node_modules/.bin/playwright install --with-deps + + - name: Run Playwright tests + working-directory: apps/site + run: node --run playwright + env: + PLAYWRIGHT_BASE_URL: ${{ steps.deployment.outputs.url }} + + - name: Upload Playwright test results + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: playwright-report + path: apps/site/playwright-report/ diff --git a/.github/workflows/pnpm-updater.yml b/.github/workflows/pnpm-updater.yml new file mode 100644 index 0000000000000..6419dcd780c71 --- /dev/null +++ b/.github/workflows/pnpm-updater.yml @@ -0,0 +1,70 @@ +# Security Notes +# Only selected Actions are allowed within this repository. Please refer to (https://github.com/nodejs/nodejs.org/settings/actions) +# for the full list of available actions. If you want to add a new one, please reach out a maintainer with Admin permissions. +# REVIEWERS, please always double-check security practices before merging a PR that contains Workflow changes!! +# AUTHORS, please only use actions with explicit SHA references, and avoid using `@master` or `@main` references or `@version` tags. + +name: PNPM updater + +on: + workflow_dispatch: + schedule: + - cron: '0 0 1 * *' + +permissions: {} + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + +jobs: + update-pnpm: + name: Update pnpm + runs-on: ubuntu-latest + permissions: + # Required to push changes to the update branch + contents: write + # Required to create the pull request + pull-requests: write + + steps: + - name: Harden Runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Git Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Setup pnpm + uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + + - name: Update pnpm and package.json + id: update-pnpm + run: | + echo "old=$(pnpm --version)" >> $GITHUB_OUTPUT + pnpm self-update + echo "new=$(pnpm --version)" >> $GITHUB_OUTPUT + cat <<< $(jq '.devEngines.packageManager.version = (.packageManager | split("@")[1])' package.json) > package.json + + - name: Open pull request + if: steps.update-pnpm.outputs.old != steps.update-pnpm.outputs.new + uses: gr2m/create-or-update-pull-request-action@b65137ca591da0b9f43bad7b24df13050ea45d1b # v1.10.1 + # Creates a PR or update the Action's existing PR, or + # no-op if the base branch is already up-to-date. + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + update-pull-request-title-and-body: true + branch: chore/update-pnpm + body: | + Updates pnpm from ${{ steps.update-pnpm.outputs.old }} to ${{ steps.update-pnpm.outputs.new }} + + cc @nodejs/web-infra + + Check this workflow's logs at ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}. + commit-message: 'meta: update pnpm from ${{ steps.update-pnpm.outputs.old }} to ${{ steps.update-pnpm.outputs.new }}' + title: 'meta: update pnpm from ${{ steps.update-pnpm.outputs.old }} to ${{ steps.update-pnpm.outputs.new }}' + draft: true diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml new file mode 100644 index 0000000000000..c12447643fd12 --- /dev/null +++ b/.github/workflows/publish-packages.yml @@ -0,0 +1,104 @@ +name: Publish Packages + +# Changesets opens or updates a version PR when release notes are present. Once that PR is merged, +# this workflow publishes the versioned packages to npm. + +on: + push: + # For security reasons, this should never be set to anything but `main` + branches: [main] + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +env: + COMMIT_SHA: ${{ github.sha }} + +jobs: + release: + name: Create Release PR or Publish + runs-on: ubuntu-latest + # Never attempt to publish from forks (no trusted-publisher match / secrets). + if: github.repository == 'nodejs/nodejs.org' + permissions: + contents: write + id-token: write + pull-requests: write + steps: + - name: Harden Runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Verify commit authenticity + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Get commit data from GitHub API to verify its authenticity + COMMIT_DATA=$(gh api repos/${{ github.repository }}/commits/$COMMIT_SHA) + # Check if commit signature is verified (GPG signed) + VERIFIED=$(echo "$COMMIT_DATA" | jq -r '.commit.verification.verified') + # Check if commit was made through GitHub's web interface (merge queue) + COMMITTER=$(echo "$COMMIT_DATA" | jq -r '.commit.committer.email') + + # Security checks to ensure we only publish from verified and trusted sources + if [[ "$VERIFIED" != "true" ]]; then + echo "❌ Unverified commit! Aborting." + exit 1 + fi + + if [[ "$COMMITTER" != "noreply@github.com" ]]; then + echo "❌ Not merged with the merge queue! Aborting." + exit 1 + fi + + echo "✅ Commit is verified and trusted." + + - uses: nodejs/web-team/actions/setup-environment@9f3c83af227d721768d9dbb63009a47ed4f4282f + with: + pnpm: true + use-version-file: true + registry-url: 'https://registry.npmjs.org' + fetch-depth: 0 + + - name: Create release pull request or publish packages + id: changesets + uses: changesets/action@a45c4d594aa4e2c509dc14a9f2b3b67ba3780d0d # v1.9.0 + with: + commit: 'chore: version packages' + title: 'chore: version packages' + version: node --run changeset:version + publish: node --run release + commitMode: github-api + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Format published packages + if: steps.changesets.outputs.published == 'true' + id: notification + env: + PUBLISHED_PACKAGES: ${{ steps.changesets.outputs.publishedPackages }} + run: | + { + echo 'packages<)"' <<< "$PUBLISHED_PACKAGES" + echo 'EOF' + } >> "$GITHUB_OUTPUT" + + - name: Notify + if: steps.changesets.outputs.published == 'true' + uses: rtCamp/action-slack-notify@33ca3be66c6f378fe1610fd1d5258632dbed5e58 # v2.4.0 + env: + SLACK_COLOR: '#43853D' + SLACK_ICON: https://github.com/nodejs.png?size=48 + SLACK_TITLE: ':rocket: Packages Published' + SLACK_MESSAGE: | + ${{ steps.notification.outputs.packages }} + :bust_in_silhouette: *Published by*: ${{ github.triggering_actor }} + :octocat: *Commit*: + SLACK_USERNAME: nodejs-bot + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} diff --git a/.github/workflows/pull-request-label.yml b/.github/workflows/pull-request-label.yml index 873a2c8bd7201..4642bc449cc78 100644 --- a/.github/workflows/pull-request-label.yml +++ b/.github/workflows/pull-request-label.yml @@ -18,9 +18,11 @@ defaults: # This ensures that the working directory is the root of the repository working-directory: ./ -permissions: - # This permission is required by `actions-ecosystem/action-remove-label` - pull-requests: write +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true jobs: # This Job removes the `github_actions:pull-request` label after it got applied @@ -29,9 +31,12 @@ jobs: remove_pull_request_label: name: Remove Pull Request Label runs-on: ubuntu-latest + permissions: + # Required by `actions-ecosystem/action-remove-label` to remove labels + pull-requests: write steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit diff --git a/.github/workflows/pull-request-policy.yml b/.github/workflows/pull-request-policy.yml new file mode 100644 index 0000000000000..2277df7ecdb70 --- /dev/null +++ b/.github/workflows/pull-request-policy.yml @@ -0,0 +1,54 @@ +# Security Notes +# Only selected Actions are allowed within this repository. Please refer to (https://github.com/nodejs/nodejs.org/settings/actions) +# for the full list of available actions. If you want to add a new one, please reach out a maintainer with Admin permissions. +# REVIEWERS, please always double-check security practices before merging a PR that contains workflow changes!! +# AUTHORS, please only use actions with explicit SHA references, and avoid using `@master` or `@main` references or `@version` tags. + +name: Pull Request Policy + +on: + pull_request: + branches: + - main + paths: + - packages/** + types: [opened, edited, synchronize, reopened, ready_for_review] + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + changesets: + name: Changesets + runs-on: ubuntu-latest + if: | + github.event_name == 'pull_request' && + !(github.event.pull_request.head.repo.full_name == github.repository && + github.event.pull_request.head.ref == 'changeset-release/main') + steps: + - uses: nodejs/web-team/actions/setup-environment@9f3c83af227d721768d9dbb63009a47ed4f4282f + with: + pnpm: true + use-version-file: true + fetch-depth: 0 + + - run: pnpm changeset status --since origin/main + + conventional-commits: + name: Conventional commits + runs-on: ubuntu-latest + steps: + - name: Check PR Title Format + env: + PR_TITLE: ${{ github.event.pull_request.title }} + run: | + REGEX="^[a-z][a-z0-9-]*(\([a-z0-9_.\/-]+\))?!?: .+" + + if [[ ! "$PR_TITLE" =~ $REGEX || "$PR_TITLE" == *. ]]; then + echo 'PR titles must use [optional scope][!]: and must not end with a period.' + exit 1 + fi diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 7f950c7a75016..026761dc3e4fd 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -1,7 +1,3 @@ -# This workflow uses actions that are not certified by GitHub. They are provided -# by a third party and are governed by separate terms of service, privacy -# policy and support documentation. - name: OpenSSF Scorecard Review on: # For Branch-Protection check. Only the default branch is supported. See @@ -15,13 +11,12 @@ on: branches: - main -# Declare default permissions as read only. -permissions: read-all +permissions: {} jobs: analysis: name: Scorecard analysis - runs-on: ubuntu-latest + uses: nodejs/web-team/.github/workflows/scorecard.yml@b62c434f5e530041c288a40280567849449e74be permissions: # Needed to upload the results to code-scanning dashboard. security-events: write @@ -29,36 +24,3 @@ jobs: id-token: write contents: read actions: read - - steps: - - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 - with: - egress-policy: audit - - - name: Git Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - with: - persist-credentials: false - - - name: Run Scorecard Analysis - uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1 - with: - results_file: results.sarif - results_format: sarif - publish_results: true - - # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF - # format to the repository Actions tab. - - name: Upload Artifacts - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 - with: - name: SARIF file - path: results.sarif - retention-days: 5 - - # Upload the results to GitHub's code scanning dashboard. - - name: Upload Scan Results - uses: github/codeql-action/upload-sarif@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 - with: - sarif_file: results.sarif diff --git a/.github/workflows/tmp-cloudflare-open-next-deploy.yml b/.github/workflows/tmp-cloudflare-open-next-deploy.yml new file mode 100644 index 0000000000000..da8cba163dda9 --- /dev/null +++ b/.github/workflows/tmp-cloudflare-open-next-deploy.yml @@ -0,0 +1,63 @@ +# Security Notes +# Only selected Actions are allowed within this repository. Please refer to (https://github.com/nodejs/nodejs.org/settings/actions) +# for the full list of available actions. If you want to add a new one, please reach out a maintainer with Admin permissions. +# REVIEWERS, please always double-check security practices before merging a PR that contains Workflow changes!! +# AUTHORS, please only use actions with explicit SHA references, and avoid using `@master` or `@main` references or `@version` tags. + +# Note: this is a temporary workflow for deploying the OpenNext version of the site (hosted on https://node-test.org) +# this version of the site is just used for testing purposes and ideally we want to keep it in sync with the +#  official website + +name: Deploy the Cloudflare OpenNext test version of the site (https://node-test.org) + +on: + workflow_dispatch: + push: + branches: + - main + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + deploy: + name: Deploy Open-Next site + runs-on: ubuntu-latest + + steps: + - name: Harden Runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Git Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Set up pnpm + uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + + - name: Set up Node.js + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + # We want to ensure that the Node.js version running here respects our supported versions + node-version-file: '.nvmrc' + cache: 'pnpm' + + - name: Install packages + run: pnpm install --frozen-lockfile + + - name: Build open-next site + run: node_modules/.bin/turbo build --filter=@node-core/platform-cloudflare --cache-dir=.turbo/cache + + - name: Deploy open-next site + run: pnpm --filter=@node-core/platform-cloudflare run deploy + env: + CF_WORKERS_SCRIPTS_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: 07be8d2fbc940503ca1be344714cb0d1 diff --git a/.github/workflows/translations-pr-lint.yml b/.github/workflows/translations-pr-lint.yml new file mode 100644 index 0000000000000..d6529c6f06e19 --- /dev/null +++ b/.github/workflows/translations-pr-lint.yml @@ -0,0 +1,58 @@ +# This Workflow is used to comment on PRs that have changes that touch Translated Files +# and then comments on their PRs mentioning that they should not do so + +name: Incoming Translation Checks + +on: + # run when someone tries to manually change localized content + pull_request_target: + branches: + - main + paths: + - 'apps/site/pages/**/*.md' + - 'apps/site/pages/**/*.mdx' + - '!apps/site/pages/en/**/*.md' + - '!apps/site/pages/en/**/*.mdx' + - 'packages/i18n/src/locales/*.json' + - '!packages/i18n/src/locales/en.json' + - 'apps/site/snippets/**/*.bash' + - '!apps/site/snippets/en/**/*.bash' + +# Cancel any runs on the same branch +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: {} + +jobs: + comment_on_translation_pr: + # This comment should always be posted on forks, or from internal PRs not originating from Crowdin (which are direct branches) + if: | + (github.event.pull_request.head.repo.full_name != 'nodejs/nodejs.org') || + (github.event.pull_request.head.repo.full_name == 'nodejs/nodejs.org' && github.event.pull_request.head.ref != 'chore/crowdin') + + name: Comment on Translation PR + runs-on: ubuntu-latest + + permissions: + # This permission is required by `thollander/actions-comment-pull-request` + pull-requests: write + + steps: + - name: Harden Runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - uses: thollander/actions-comment-pull-request@e2c37e53a7d2227b61585343765f73a9ca57eda9 # v3.0.0 + with: + message: | + > [!NOTE]\ + > Your Pull Request seems to be updating **Translations** of the Node.js Website. + > + > Whilst we appreciate your intent; Any Translation update should be done through our [Crowdin Project](https://crowdin.com/project/nodejs-web). + > We recommend giving a read on our [Translation Guidelines](https://github.com/nodejs/nodejs.org/blob/main/docs/translation.md). + > + > Thank you! + comment-tag: use_crowdin diff --git a/.github/workflows/translations-pr.yml b/.github/workflows/translations-pr.yml deleted file mode 100644 index dd6ca73ea0eb1..0000000000000 --- a/.github/workflows/translations-pr.yml +++ /dev/null @@ -1,130 +0,0 @@ -# This Workflow is used to comment on PRs that have changes that touch Translated Files -# and then comments on their PRs mentioning that they should not do so - -name: Crowdin Checks - -on: - pull_request_target: - branches: - - main - paths: - - 'pages/**/*.md' - - 'pages/**/*.mdx' - - '!pages/en/**/*.md' - - '!pages/en/**/*.mdx' - - 'i18n/locales/*.json' - - '!i18n/locales/en.json' - -permissions: - actions: read - -jobs: - comment_on_translation_pr: - # This comment should only be posted on PRs that come from users and not from Crowdin - if: | - github.event.pull_request.head.repo.full_name == 'nodejs/nodejs.org' && - github.event.pull_request.head.ref != 'chore/crowdin' - - name: Comment on Translation PR - runs-on: ubuntu-latest - - permissions: - # This permission is required by `thollander/actions-comment-pull-request` - pull-requests: write - - steps: - - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 - with: - egress-policy: audit - - - uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0 - with: - message: | - > [!NOTE]\ - > Your Pull Request seems to be updating **Translations** of the Node.js Website. - > - > Whilst we appreciate your intent; Any Translation update should be done through our [Crowdin Project](https://crowdin.com/project/nodejs-web). - > We recommend giving a read on our [Translation Guidelines](https://github.com/nodejs/nodejs.org/blob/main/TRANSLATION.md). - > - > Thank you! - comment_tag: use_crowdin - - format_crowdin_pull_request: - # We should only run the automated Format Command on Crowdin-based Pull Requests - if: | - github.event.pull_request.head.repo.full_name == 'nodejs/nodejs.org' && - github.event.pull_request.head.ref == 'chore/crowdin' - - name: Format Crowdin Pull Request - runs-on: ubuntu-latest - - permissions: - # This permission is required by `stefanzweifel/git-auto-commit-action` - contents: write - - steps: - - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 - with: - egress-policy: audit - - - name: Git Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - with: - # By default Git Checkout on `pull-request-target` will checkout - # the `default` branch of the Pull Request. We want to checkout - # the actual branch of the Pull Request. - ref: ${{ github.event.pull_request.head.ref }} - - - name: Restore Lint Cache - uses: actions/cache/restore@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 - with: - path: | - .eslintmdcache - .prettiercache - # We want to restore Turborepo Cache and ESlint and Prettier Cache - # The ESLint and Prettier cache's are useful to reduce the overall runtime of ESLint and Prettier - # as they will only run on files that have changed since the last cached run - # this might of course lead to certain files not being checked against the linter, but the chances - # of such situation from happening are very slim as the checksums of both files would need to match - key: cache-lint-${{ hashFiles('package-lock.json') }}- - restore-keys: | - cache-lint-${{ hashFiles('package-lock.json') }}- - cache-lint- - - - name: Set up Node.js - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 - with: - # We want to ensure that the Node.js version running here respects our supported versions - node-version-file: '.nvmrc' - cache: 'npm' - - - name: Install npm packages - # We want to avoid npm from running the Audit Step and Funding messages on a CI environment - # We also use `npm i` instead of `npm ci` so that the node_modules/.cache folder doesn't get deleted - run: npm i --no-audit --no-fund --ignore-scripts --userconfig=/dev/null - - - name: Run `npx lint:md --fix` - # This runs a specific version of ESLint with only the Translation Pages Globbing - # This avoid that unrelated changes get linted/modified within this PR - run: npx eslint "pages/**/*.md?(x)" --fix --cache --cache-strategy=metadata --cache-file=.eslintmdcache - - - name: Run `npx prettier --write` - # This runs a specific version of Prettier with only the Translation Pages Globbing - # This avoid that unrelated changes get prettied/modified within this PR - run: npx prettier "{pages,i18n}/**/*.{json,md,mdx}" --check --write --cache --cache-strategy=metadata --cache-location=.prettiercache - - - name: Push Changes back to Pull Request - uses: stefanzweifel/git-auto-commit-action@8756aa072ef5b4a080af5dc8fef36c5d586e521d # v5.0.0 - with: - commit_options: '--no-verify --signoff' - commit_message: 'chore: automated format of translated files' - - - name: Save Lint Cache - uses: actions/cache/save@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 - with: - path: | - .eslintmdcache - .prettiercache - key: cache-lint-${{ hashFiles('package-lock.json') }}-${{ hashFiles('.eslintmdcache') }} diff --git a/.github/workflows/translations-sync.yml b/.github/workflows/translations-sync.yml new file mode 100644 index 0000000000000..241455aafe1e6 --- /dev/null +++ b/.github/workflows/translations-sync.yml @@ -0,0 +1,128 @@ +# This action automates the synchronization of our crowdin translations, so that a human does not need to kick it off from the crowdin UI +# It also formats incoming content because it is often not adherent to our rules post-translation. + +# See translations-upload.yml for automation to upload our source content +# See translations-pr-lint.yml for quality control we conduct on ingress of new translations. +name: Crowdin Download + +on: + workflow_dispatch: # Allow running when we want to, for events such as urgent translation mistakes or 100% completed languages + schedule: + - cron: '0 5 * * 5' # At 05:00 on Fridays. This guarantees that we have the 72 hour weekend time to review translations. + +# Cancel any runs on the same branch +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +env: + BRANCH_NAME: chore/crowdin + +jobs: + synchronize-with-crowdin: + name: Synchronize with Crowdin + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + token: ${{ secrets.CROWDIN_GITHUB_BOT_TOKEN }} + persist-credentials: false + + # see all the options at https://github.com/crowdin/github-action + - name: Crowdin PR + uses: crowdin/github-action@c7af9bc98b01694653031fef2a0dc6c7888ce9bc # v2.17.0 + with: + # do not upload anything - this is a one-way operation download + upload_sources: false + upload_translations: false + # the rest of this controls how the PR comes in with new translations + download_translations: true + localization_branch_name: ${{ env.BRANCH_NAME }} + create_pull_request: true + pull_request_title: 'chore(i18n): sync translations from crowdin' + pull_request_body: 'New Crowdin translations from the [Node.js Crowdin project](https://crowdin.com/project/nodejs-web)' + commit_message: 'chore(i18n): sync translations from crowdin' + env: + GITHUB_TOKEN: ${{ secrets.CROWDIN_GITHUB_BOT_TOKEN }} + # A numeric ID, found at https://crowdin.com/project/nodejs-web/tools/api + CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} + # Created from https://crowdin.com/settings#api-key logged in using nodejs-crowdin-bot + CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} + + format_crowdin_pull_request: + name: Format Crowdin Pull Request + needs: synchronize-with-crowdin + runs-on: ubuntu-latest + + permissions: + # This permission is required by `stefanzweifel/git-auto-commit-action` + contents: write + + steps: + - uses: nodejs/web-team/actions/setup-environment@9f3c83af227d721768d9dbb63009a47ed4f4282f + with: + pnpm: true + use-version-file: true + ref: ${{ env.BRANCH_NAME }} + token: ${{ secrets.CROWDIN_GITHUB_BOT_TOKEN }} + fetch-depth: 2 + + - name: Restore Lint Cache + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: | + apps/site/.eslintmdcache + apps/site/.prettiercache + # We want to restore Turborepo Cache and ESlint and Prettier Cache + # The ESLint and Prettier cache's are useful to reduce the overall runtime of ESLint and Prettier + # as they will only run on files that have changed since the last cached run + # this might of course lead to certain files not being checked against the linter, but the chances + # of such situation from happening are very slim as the checksums of both files would need to match + key: cache-lint-${{ hashFiles('pnpm-lock.yaml') }}-${{ hashFiles('apps/site/.eslintmdcache') }} + restore-keys: | + cache-lint-${{ hashFiles('pnpm-lock.yaml') }}- + cache-lint- + + - name: Add changeset if the files changed + run: | + CHANGED_FILES=$(git diff --name-only HEAD^1 HEAD) + if [ -n "$CHANGED_FILES" ]; then + cat > .changeset/crowdin-translations.md <<'EOF' + --- + '@node-core/website-i18n': patch + --- + + Update translations from Crowdin. + EOF + fi + + - name: Run ESLint + working-directory: apps/site + run: node --run lint:md -- --fix + + - name: Run Prettier + run: node --run prettier:fix + + - name: Push Changes back to Pull Request + uses: stefanzweifel/git-auto-commit-action@4a55954c782fc1ea30b9056cd3e7a2b40ca8887d # v7.2.0 + with: + commit_options: '--no-verify --signoff' + commit_message: 'chore(i18n): format translated files' + branch: ${{ env.BRANCH_NAME }} + + - name: Save Lint Cache + uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: | + apps/site/.eslintmdcache + apps/site/.prettiercache + key: cache-lint-${{ hashFiles('pnpm-lock.yaml') }}-${{ hashFiles('apps/site/.eslintmdcache') }} diff --git a/.github/workflows/translations-upload.yml b/.github/workflows/translations-upload.yml new file mode 100644 index 0000000000000..c561c7df98750 --- /dev/null +++ b/.github/workflows/translations-upload.yml @@ -0,0 +1,47 @@ +# This action automates the upload of our source content to crowdin. +# See translations-sync.yml for the automation to download new translations on a schedule +# See translations-pr-lint.yml for quality control we conduct on ingress of new translations. +name: Crowdin Upload + +on: + push: + branches: [main] + +# Cancel any runs on the same branch +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + upload-to-crowdin: + name: Upload to Crowdin + runs-on: ubuntu-latest + + steps: + - name: Harden Runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + # see all the options at https://github.com/crowdin/github-action + - name: crowdin action + uses: crowdin/github-action@c7af9bc98b01694653031fef2a0dc6c7888ce9bc # v2.17.0 + with: + # only upload sources, ensuring this is a one-way operation + upload_sources: true + upload_translations: false + download_translations: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # A numeric ID, found at https://crowdin.com/project/nodejs-web/tools/api + CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} + # Created from https://crowdin.com/settings#api-key logged in using nodejs-crowdin-bot + CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml new file mode 100644 index 0000000000000..f2e1f2f2c7fb8 --- /dev/null +++ b/.github/workflows/zizmor.yml @@ -0,0 +1,32 @@ +name: Zizmor + +on: + push: + branches: + - main + pull_request: + branches: + - main + +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + zizmor: + name: Zizmor Security Analysis + runs-on: ubuntu-latest + permissions: + contents: read + actions: read + security-events: write + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Run zizmor 🌈 + uses: zizmorcore/zizmor-action@6fc4b006235f201fdab3722e17240ab420d580e5 # v0.6.1 diff --git a/.gitignore b/.gitignore index 7f47879b973a9..a756afeb5c8ef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,28 @@ # Commonly ignored Node.js files node_modules +.env.* + +# npm Compatibility +# https://github.com/nodejs/nodejs.org/discussions/5334#discussioncomment-12827850 npm-debug.log .npm -.env.local +package-lock.json # Next.js Build Output -.next -build +apps/site/.next +apps/site/build +apps/site/public/blog-data.json +apps/site/next-env.d.ts + +# Generated Build Artifacts +platforms/cloudflare/generated -# Jest -coverage -.swc +# Test Runner junit.xml +lcov.info + +# Distributed Files +dist # Storybook storybook-static @@ -24,13 +35,24 @@ build-storybook.log cache # Cache Files -.eslintjscache .eslintmdcache .stylelintcache .prettiercache # TypeScript tsconfig.tsbuildinfo +dist/ + +# Cloudflare Build Output +apps/site/.open-next +apps/site/.wrangler + +## Playwright +test-results +playwright-report + +## MacOS Ignored Files +.DS_Store -# Sentry Config File -.sentryclirc +## Other Files +.env diff --git a/.husky/pre-commit b/.husky/pre-commit index 98819fd136e06..5f31f933a76bf 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,8 +1,7 @@ -#!/usr/bin/env sh -. "$(dirname -- "$0")/_/husky.sh" - # lint and format staged files -npx lint-staged -# verify typescript staged files -npx tsc --build . +node --run lint:staged + +# verify typescript fully + +node --run lint:types diff --git a/.lighthouserc.json b/.lighthouserc.json index eb1c22031ae30..be61cf8385d95 100644 --- a/.lighthouserc.json +++ b/.lighthouserc.json @@ -3,7 +3,8 @@ "collect": { "numberOfRuns": 1, "settings": { - "preset": "desktop" + "preset": "desktop", + "skipAudits": ["is-crawlable"] } }, "assert": { diff --git a/.lintstagedrc.json b/.lintstagedrc.json index 63f877513b56b..91a0e156e4ab9 100644 --- a/.lintstagedrc.json +++ b/.lintstagedrc.json @@ -1,5 +1,3 @@ { - "**/*.{js,mjs,ts,tsx,md,mdx}": ["eslint --fix", "prettier --check --write"], - "**/*.css": ["stylelint --allow-empty-input", "prettier --write"], - "**/*.{json,yml}": ["prettier --check --write"] + "**/*.{js,mjs,ts,tsx,md,mdx,json.yml}": ["prettier --check --write"] } diff --git a/.npmrc b/.npmrc deleted file mode 100644 index 4a440c6a74c7e..0000000000000 --- a/.npmrc +++ /dev/null @@ -1,2 +0,0 @@ -# See bug in peerdeps resolution: https://github.com/npm/cli/issues/2999 -legacy-peer-deps = false diff --git a/.nvmrc b/.nvmrc index 209e3ef4b6247..54c65116f15a6 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -20 +v24 diff --git a/.postcssrc.json b/.postcssrc.json deleted file mode 100644 index f48fb87dce6e0..0000000000000 --- a/.postcssrc.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "plugins": { - "postcss-mixins": {}, - "postcss-simple-vars": {}, - "postcss-calc": {}, - "postcss-import": {}, - "tailwindcss/nesting": {}, - "tailwindcss": {}, - "autoprefixer": {} - } -} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 723e338d33061..0000000000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,18 +0,0 @@ -repos: -- repo: https://github.com/gitleaks/gitleaks - rev: v8.16.3 - hooks: - - id: gitleaks -- repo: https://github.com/jumanjihouse/pre-commit-hooks - rev: 3.0.0 - hooks: - - id: shellcheck -- repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.38.0 - hooks: - - id: eslint -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 - hooks: - - id: end-of-file-fixer - - id: trailing-whitespace diff --git a/.prettierignore b/.prettierignore index 513b005d5c368..b0a2a81b9e2e2 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,19 +1,20 @@ # Commonly ignored Node.js files node_modules + +# npm Compatibility +# https://github.com/nodejs/nodejs.org/discussions/5334#discussioncomment-12827850 npm-debug.log .npm +package-lock.json # Next.js Build Output .next build +next-env.d.ts -# Next.js Generated Files -public/static/documents - -# Jest -coverage -.swc +# Test Runner junit.xml +lcov.info # Storybook storybook-static @@ -26,7 +27,6 @@ build-storybook.log cache # Cache Files -.eslintjscache .eslintmdcache .stylelintcache .prettiercache @@ -37,6 +37,12 @@ tsconfig.tsbuildinfo # Metadata Files CODEOWNERS +# Public Folders +apps/site/public + +# Distributed Files +dist + # Prettier's Handlebar parser is limited and chokes on some syntax features # https://github.com/prettier/prettier/issues/11834 scripts/release-post/template.hbs diff --git a/.prettierrc.json b/.prettierrc.json index ede5abb88a3bb..c69d55a94d950 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -8,5 +8,6 @@ "bracketSpacing": true, "bracketSameLine": false, "arrowParens": "avoid", - "plugins": ["prettier-plugin-tailwindcss"] + "plugins": ["prettier-plugin-tailwindcss"], + "tailwindPreserveWhitespace": true } diff --git a/.remarkrc.json b/.remarkrc.json deleted file mode 100644 index 40370dea6d613..0000000000000 --- a/.remarkrc.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "settings": { - "bullet": "-", - "resourceLink": true - }, - "plugins": [ - "remark-frontmatter", - "remark-preset-lint-node", - ["remark-gfm", false], - ["remark-lint-fenced-code-flag", false], - ["remark-lint-first-heading-level", false], - ["remark-lint-maximum-line-length", false], - ["remark-lint-no-file-name-articles", false], - ["remark-lint-no-literal-urls", false], - ["remark-lint-no-unused-definitions", false], - ["remark-lint-no-undefined-references", false], - ["remark-lint-prohibited-strings", false], - ["remark-lint-unordered-list-marker-style", "-"], - ["remark-preset-lint-node/remark-lint-nodejs-links.js", false] - ] -} diff --git a/.storybook/main.ts b/.storybook/main.ts deleted file mode 100644 index 4247bcec94030..0000000000000 --- a/.storybook/main.ts +++ /dev/null @@ -1,60 +0,0 @@ -import classNames from 'classnames'; -import type { StorybookConfig } from '@storybook/nextjs'; - -const rootClasses = classNames( - // note: this is hard-coded sadly as next/font can only be loaded within next.js context - '__variable_open-sans-normal', - // note: this is hard-coded sadly as next/font can only be loaded within next.js context - '__variable_ibm-plex-mono-normal' -); - -const config: StorybookConfig = { - stories: ['../components/**/*.stories.tsx'], - logLevel: 'error', - staticDirs: ['../public'], - typescript: { reactDocgen: false, check: false }, - core: { disableTelemetry: true, disableWhatsNewNotifications: true }, - framework: { - name: '@storybook/nextjs', - options: { builder: { useSWC: true } }, - }, - previewBody: - // This `' + - // This adds the base styling for dark/light themes within Storybook. This is a Storybook-only style - ``, - addons: [ - '@storybook/addon-controls', - '@storybook/addon-interactions', - '@storybook/addon-themes', - '@storybook/addon-viewport', - ], - webpack: async config => ({ - ...config, - // We want to conform as much as possible with our target settings - target: 'browserslist', - // Performance Hints do not make sense on Storybook as it is bloated by design - performance: { hints: false }, - // `nodevu` is a Node.js-specific package that requires Node.js modules - // this is incompatible with Storybook. So we just mock the module - resolve: { ...config.resolve, alias: { '@nodevu/core': false } }, - // We need to configure `node:` APIs as Externals to WebPack - // since essentially they're not supported on the browser - externals: { - 'node:fs': 'commonjs fs', - 'node:url': 'commonjs url', - 'node:path': 'commonjs path', - 'node:readline': 'commonjs readline', - }, - // Removes Pesky Critical Dependency Warnings due to `next/font` - ignoreWarnings: [ - e => - e.message.includes('Critical dep') || - e.message.includes('was not found in'), - ], - }), -}; - -export default config; diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx deleted file mode 100644 index 9ede2e3a49e1a..0000000000000 --- a/.storybook/preview.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { NextIntlClientProvider } from 'next-intl'; - -import { withThemeByDataAttribute } from '@storybook/addon-themes'; -import { NotificationProvider } from '@/providers/notificationProvider'; -import { STORYBOOK_MODES, STORYBOOK_SIZES } from '@/.storybook/constants'; -import type { Preview, ReactRenderer } from '@storybook/react'; - -import englishLocale from '@/i18n/locales/en.json'; - -import '../next.fonts'; -import '../styles/index.css'; - -const preview: Preview = { - parameters: { - nextjs: { router: { basePath: '' }, appDirectory: true }, - chromatic: { modes: STORYBOOK_MODES }, - viewport: { defaultViewport: 'large', viewports: STORYBOOK_SIZES }, - }, - decorators: [ - Story => ( - - - - - - ), - withThemeByDataAttribute({ - themes: { light: '', dark: 'dark' }, - defaultTheme: 'light', - attributeName: 'data-theme', - }), - ], -}; - -export default preview; diff --git a/.stylelintignore b/.stylelintignore deleted file mode 100644 index cb71469e033ce..0000000000000 --- a/.stylelintignore +++ /dev/null @@ -1,17 +0,0 @@ -# Next.js files -.next -.turbo -.swc -build - -# Public Folder -public - -# Jest -coverage - -# Storybook -storybook-static - -# Old Styles -styles/old diff --git a/.stylelintrc.mjs b/.stylelintrc.mjs deleted file mode 100644 index 3fea6d8b268c8..0000000000000 --- a/.stylelintrc.mjs +++ /dev/null @@ -1,45 +0,0 @@ -// These are all the custom `@` (at) rules that we use within our custom PostCSS plugins -const CUSTOM_AT_RULES = [ - // Tailwind-specific at-rules - 'apply', - 'layer', - 'responsive', - 'screen', - 'tailwind', - 'variants', - // PostCSS-specific at-rules - 'define-mixin', - 'mixin', -]; - -// Enforces certain selectors to be only in camelCase notation -// We use these for id selectors and classname selectors -const ONLY_ALLOW_CAMEL_CASE_SELECTORS = [ - /^(?:[a-z]+(?:[A-Z][a-z]*)*)$/, - { message: s => `Expected '${s}' to be in camelCase` }, -]; - -export default { - extends: ['stylelint-config-standard'], - plugins: ['stylelint-order', 'stylelint-selector-bem-pattern'], - rules: { - // Enforces Element Class Names to be camelCase - 'selector-class-pattern': ONLY_ALLOW_CAMEL_CASE_SELECTORS, - // Enforces Element IDs to be camelCase - 'selector-id-pattern': ONLY_ALLOW_CAMEL_CASE_SELECTORS, - // Allow Tailwind-based CSS Rules - 'at-rule-no-unknown': [true, { ignoreAtRules: CUSTOM_AT_RULES }], - // Allow the Global CSS Selector - 'selector-pseudo-class-no-unknown': [ - true, - { ignorePseudoClasses: ['global'] }, - ], - // Enforces the order of the CSS properties to be in alphabetical order - 'order/properties-alphabetical-order': true, - 'no-descending-specificity': null, - // Disables the Level-4 Media Queries; Since they're more exotic and less known - 'media-feature-range-notation': 'prefix', - // Adopts the import notation from `postcss-import` - 'import-notation': 'string', - }, -}; diff --git a/.vscode/settings.json b/.vscode/settings.json index a7e0023eb6be3..f45e0fc02c2f8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,6 @@ "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode", "javascript.updateImportsOnFileMove.enabled": "always", - "typescript.updateImportsOnFileMove.enabled": "always" + "typescript.updateImportsOnFileMove.enabled": "always", + "typescript.tsdk": "node_modules/typescript/lib" } diff --git a/CODEOWNERS b/CODEOWNERS deleted file mode 100644 index bddc07e721ee8..0000000000000 --- a/CODEOWNERS +++ /dev/null @@ -1,23 +0,0 @@ -# Website Development -* @nodejs/nodejs-website - -# Infrastructure -.github @nodejs/web-infra -.husky @nodejs/web-infra - -# Framework -next.config.mjs @nodejs/web-infra -next.dynamic.mjs @nodejs/web-infra - -# Node.js Release Blog Posts -/pages/en/blog/release @nodejs/releasers -/pages/en/blog/announcements @nodejs/releasers - -# Package Ecosystem -package.json @nodejs/nodejs-website -turbo.json @nodejs/nodejs-website @nodejs/web-infra - -# Web Infrastructure -crowdin.yml @nodejs/web-infra -redirects.json @nodejs/web-infra -site.json @nodejs/web-infra diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 8fff30e0a6cca..0000000000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,4 +0,0 @@ -# Code of Conduct - -- [Node.js Code of Conduct](https://github.com/nodejs/admin/blob/HEAD/CODE_OF_CONDUCT.md) -- [Node.js Moderation Policy](https://github.com/nodejs/admin/blob/HEAD/Moderation-Policy.md) diff --git a/COLLABORATOR_GUIDE.md b/COLLABORATOR_GUIDE.md deleted file mode 100644 index a8849666a90c6..0000000000000 --- a/COLLABORATOR_GUIDE.md +++ /dev/null @@ -1,502 +0,0 @@ -# Node.js Collaborator Guide - -- [Issues and Pull Requests](#issues-and-pull-requests) -- [Accepting Modifications](#accepting-modifications) - - [Involving the Website Team](#involving-the-website-team) -- [Technologies used in the Website](#technologies-used-in-the-website) -- [Code editing](#code-editing) - - [Adding new pages](#adding-new-pages) - - [Create the page content](#create-the-page-content) - - [Translating pages](#translating-pages) -- [Creating Components](#creating-react-components) - - [Styling a Component](#styling-a-component) - - [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) -- [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) -- [Remarks on Technologies used](#remarks-on-technologies-used) -- [Seeking additional clarification](#seeking-additional-clarification) - -This document contains information for Collaborators of the Node.js website project regarding maintaining the code, documentation, and issues. - -Collaborators should be familiar with the guidelines for new contributors in [CONTRIBUTING.md](./CONTRIBUTING.md). - -## Issues and Pull Requests - -Courtesy should always be shown to individuals submitting issues and pull requests to the Node.js website project. - -Collaborators should feel free to take full responsibility for managing issues and pull requests they feel qualified to handle, as long as this is done while being mindful of these guidelines, the opinions of other Collaborators and guidance of the Website Group. - -Collaborators may **close** any issue or pull request they believe is not relevant to the future of the Node.js project. -Where this is unclear, the issue should be left open for several days for additional discussion. -Where this does not yield input from Node.js Collaborators or additional evidence that the issue has relevance, then the issue may be closed. -Remember that issues can always be re-opened if necessary. - -> \[!IMPORTANT]\ -> We recommend Collaborators to avoid Updating/Rebasing PRs unnecessarily, since we use [GitHub Merge Queues](https://github.blog/2023-07-12-github-merge-queue-is-generally-available/) -> to merge Pull Requests, which automatically rebases and runs CI-checks against the latest base branch. - -## Accepting Modifications - -All Node.js code and documentation modifications should be performed via GitHub pull requests. -Only the Website Team can merge their work and should do so carefully. - -All pull requests must be reviewed and accepted by a Collaborator with sufficient expertise who can take full responsibility for the change. -In the case of pull requests proposed by an existing Collaborator, an additional Collaborator is required for sign-off. - -Pull Requests can only be merged after all CI Checks have passed. -As usual, CI Checks need to be manually triggered by adding a `github_actions:pull-request` label to the Pull Request. - -In some cases, it may be necessary to summon a qualified Collaborator to a pull request for review by @-mention. - -If you are unsure about the modification and are not prepared to take full responsibility for the change, defer to another Collaborator. - -We recommend collaborators follow the guidelines on the [Contributing Guide](./CONTRIBUTING.md#before-merging) for reviewing and merging Pull Requests. - -### Involving the Website Team - -Collaborators may opt to elevate pull requests or issues to the group for discussion by mentioning `@nodejs/nodejs-website`. This should be done where a pull request: - -- has a significant impact on the codebase, -- is inherently controversial; or -- has failed to reach a consensus amongst the Collaborators who are actively participating in the discussion. - -The Website group should be the final arbiter where needed. - -## Technologies used in the Website - -The Node.js Website is built upon [React][] and [Next.js][] respectively, the UI Rendering Engine and the Framework that builds the Website; - -The Website also uses several other Open Source libraries (not limited to) listed below: - -- Styling is done with [PostCSS][] and CSS Modules - - We use a combination of PostCSS plugins to create a [Sass](https://sass-lang.com/) alike environment - - We recommend reading the documentation of our plugins in case of doubt - - [PostCSS Mixins](https://github.com/postcss/postcss-mixins) - - [PostCSS Import](https://github.com/postcss/postcss-import) - - [PostCSS Simple Vars](https://github.com/postcss/postcss-simple-vars) -- [Tailwind][] is used as our CSS Framework and the Foundation of our Design System -- [Hero Icons](https://heroicons.com/) is an SVG Icon Library used within our Codebase -- [Radix UI][] is a collection of customizable UI components -- [Shiki][] is a Syntax Highlighter used for our Codeboxes - - The syntax highlighting is done within the processing of the Markdown files with the MDX compiler as a Rehype plugin. -- [MDX][] and Markdown are used for structuring the Content of the Website -- [`next-intl`][] is the i18n Library adopted within the Website - - It provides an excellent integration with Next.js, But it also supports standalone support for i18n if it eventually migrates from Next.js to something else. - - Supports React Server Components and Next.js Middlewares -- [`next-sitemap`](https://www.npmjs.com/package/next-sitemap) is used for Sitemap and `robots.txt` Generation -- We use [Rehype](https://github.com/rehypejs/rehype) and [Remark](https://github.com/remarkjs/remark) to extend MDX functionality -- We use [Storybook](https://storybook.js.org/) for Manual Testing and Visual Regression Tests of our React Components - - Storybook also provides a sandboxed environment, which is very useful whilst for crafting React Components -- We use [Sentry](https://sentry.io/about) for reporting Exceptions and monitoring the performance and reliability of the application - -## Code Editing - -### Structure of this Repository - -- React Components are defined on `/components` -- React Templates are defined on `/layouts` -- Global Stylesheets are declared on `/styles` - - Styles are done with [PostCSS][] -- Public files are stored on `/public` - - Static Images, JavaScript files, and others are stored within `/public/static` -- Internationalisation is done on `/i18n` - - React Localisation Data is stored within `/i18n/locales` - - We use the [ICU Message Syntax](https://formatjs.io/docs/core-concepts/icu-syntax/) for Translations - - Configuration for Locales is done within `/i18n/config.json` -- Website Content is defined within `/pages` - - Initial development usually happens in English: `/pages/en` - - Localized versions of `/pages/en` are done within `/pages/{localeCode}` - - All content is in Markdown and is per locale. - - The top of each Markdown file is a YAML (Frontmatter) block for page-specific localization information passed to various templates. - - The bulk of the Markdown content for each page is referenced as `{children}` on their respective JSX Layout (`layouts/`) -- Multi-Purpose React Hooks are defined on `/hooks` -- Multi-Purpose TypeScript definitions are defined on `/types` -- React Context Providers are defined on `/providers` -- Build-time Data Fetching Scripts are defined on `/next-data` - - Used for Node.js Release data fetching - - Generation of build-time indexes such as blog data -- Multi-Purpose Scripts are stored within `/scripts` - - Such as Node.js Release Blog Post generation -- Storybook Configuration is done within `/.storybook` - - We use an almost out-of-the-box Storybook Experience with a few extra customisations - -### Adding new Pages - -1. Create new page content including the layout, title and copy. -2. Update the relevant `/layout` to add a link to the new page. - -#### Create the page content - -Create a new markdown file in `/pages/en`. - -At the top of the markdown file, within the Markdown Frontmatter, set a page the title and layout. - -```markdown ---- -title: Title of the Page -layout: layout-name ---- - -[Content of the Page] -``` - -> \[!NOTE]\ -> A list of currently available Layouts is provided within `components/withLayout` on the `layoutComponents` map.\ -> This is a temporary map and this map might change its location and be defined in a different way in the future. - -### Translating Pages - -See the [Translation Guidelines](./TRANSLATION.md) for the website translation policy. - -## Creating React Components - -The Node.js Website uses [React][] as a Frontend Library to develop the Website. -React allows us to create user interfaces with a modern take on Web Development. - -If you're unfamiliar with React or Web Development in general, we encourage a read before taking on complex issues and tasks as this repository is **not for educational purposes** and we expect you to have a basic understanding of the technologies used. - -We also recommend getting familiar with technologies such as [Next.js][], [MDX][], [PostCSS][], and "concepts" such as "CSS Modules" and "CSS-in-JS". - -### Styling a Component - -As mentioned, we write all Component-styles in separate `.module.css` files. This is like writing any CSS in a separate file (besides the fact that we use [PostCSS][]). - -This concept of writing styles on dedicated CSS files and importing them within JavaScript (or React) is a pattern named **[CSS Module](https://github.com/css-modules/css-modules)**. -These allow us to write PostCSS (or regular CSS, or any flavor of CSS if you have a way of interpreting it) within a `.module.css` and import the class names directly to our React Components. -We recommend reading guides on "Styling React Components with CSS Modules", which there are many available on the web. - -It's important to mention that we use [Tailwind][] as a CSS Framework. Hence, margins, paddings, font sizes, font weights, colors, and other sorts of styles are all provided with Tailwind. -We recommend reading [Tailwind Docs](https://tailwindcss.com/docs/preflight) to get familiar with Tailwind's styles. -We also recommend reading [this guide for setting up Tailwind on your IDE](https://tailwindcss.com/docs/editor-setup). - -Finally, if you're unfamiliar with how to use Tailwind or how to use Tailwind within CSS Modules, we recommend reading [this guide](https://tailwindcss.com/docs/using-with-preprocessors). - -#### Example of a CSS Module - -```css -.myComponent { - @apply some - tailwind - classes; -} -``` - -#### Guidelines when writing CSS - -- We use camelCase for defining CSS classes -- We use Tailwind's `@apply` selector to apply Tailwind Tokens - - We discourage the usage of any plain CSS styles and tokens, when in doubt ask for help - - We require that you define one Tailwind Token per line, just as shown on the example above, since this improves readability -- Only write CSS within CSS Modules, avoid writing CSS within JavaScript files -- We recommend creating mixins for reusable animations, effects and more - - You can create Mixins within the `styles/mixins` folder - -> \[!NOTE]\ -> Tailwind is already configured for this repository. You don't need to import any Tailwind module within your CSS module.\ -> You can apply Tailwind Tokens with Tailwind's `@apply` CSS rule. [Read more about applying Tailwind classes with `@apply`](https://tailwindcss.com/docs/functions-and-directives#apply). - -> \[!IMPORTANT]\ -> When using IDEs such as Visual Studio Code, we recommend installing the official [Stylelint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint) -> and [Tailwind](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) Extensions.\ -> These are recommended Extensions for IntelliSense, Syntax Highlighting and Error Checking when styling your Component. - -### Best practices when creating a Component - -- All React Components should be placed within the `components` folder. -- Each Component should be placed, whenever possible, within a sub-folder, which we call the "Domain" of the Component - - The domain represents where these Components belong to or where they will be used. - - For example, Components used within Article Pages or that are part of the structure of an Article or the Article Layouts, - should be placed within `components/Article` -- Each component should have its folder with the name of the Component -- The structure of each component folder follows the following template: - ```text - - ComponentName - - index.tsx // the component itself - - index.module.css // all styles of the component are placed there - - index.stories.tsx // component Storybook stories - - __tests__ // component tests (such as unit tests, etc) - - index.test.mjs // unit tests should be done in ESM and not TypeScript - ``` -- React Hooks belonging to a single Component should be placed within the Component's folder - - If the Hook as a wider usability or can be used by other components, it should be placed in the root `hooks` folder. -- If the Component has "sub-components" they should follow the same philosophy as the Component itself. - - For example, if the Component `ComponentName` has a sub-component called `SubComponentName`, - then it should be placed within `ComponentName/SubComponentName` - -#### How a new Component should look like when freshly created - -```tsx -import type { FC } from 'react'; - -import styles from './index.module.css'; - -type MyComponentProps = {}; // The types of the Props of your Component - -const MyComponent: FC = ({ prop1, prop2... }) => ( - // Actual code of my Component -); - -export default MyComponent; -``` - -### Best practices for Component development in general - -- Only spread props `{ ... }` on the definition of the Component (Avoid having a variable named `props`) -- Avoid importing `React`, only import the modules from React that you need -- When importing types, use `import type { NameOfImport } from 'module'` -- When defining a Component, use the `FC` type from React to define the type of the Component - - When using `children` as a prop, use the `FC>` type instead - - Alternatively you can define your type as `type MyComponentProps = PropsWithChildren<{ my other props }>` -- Each Props type should be prefixed by the name of the Component -- Components should always be the `default` export of a React Component file -- Avoid using DOM/Web APIs/`document`/`window` API access within a React Component. - Use utilities or Hooks when you need a Reactive state -- Avoid making your Component too big. Deconstruct it into smaller Components/Hooks whenever possible - -## Unit Tests and Storybooks - -Each new feature or bug fix should be accompanied by a unit test (when deemed valuable). -We use [Jest][] as our test runner and [React Testing Library][] for our React unit tests. - -We also use [Storybook][] to document our components. -Each component should have a storybook story that documents the component's usage. - -Visual Regression Testing is automatically done via [Chromatic](https://www.chromatic.com/) to ensure that Components are rendered correctly. - -### General Guidelines for Unit Tests - -Unit Tests are fundamental to ensure that code changes do not disrupt the functionalities of the Node.js Website: - -- Unit tests should be written as `.mjs` files. -- We recommend adding unit tests for content covering `util`, `scripts`, `hooks`, and `components` whenever possible. -- Unit Tests should ensure that a given change's functionality is working as expected. -- When creating unit tests for React components, we recommend that the tests cover all the possible states of the component. -- We also recommend mocking external dependencies, if unsure about how to mock a particular dependency, raise the question on your Pull Request. - - We recommend using [Jest's Mock Functions](https://jestjs.io/docs/en/mock-functions) for mocking dependencies. - - We recommend using [Jest's Mock Modules](https://jestjs.io/docs/en/manual-mocks) for mocking dependencies unavailable on the Node.js runtime. - - Common Providers and Contexts from the lifecycle of our App, such as [`react-intl`][] should not be mocked but given an empty or fake context whenever possible. -- We recommend reading previous unit tests from the codebase for inspiration and code guidelines. - -### General Guidelines for Storybooks - -Storybooks are an essential part of our development process. They help us to document our components and to ensure that the components are working as expected. - -They also allow Developers to preview Components and be able to test them manually/individually to the smallest unit of the Application. (The individual Component itself). - -**Storybooks should be fully typed and follow the following template:** - -```tsx -import type { Meta as MetaObj, StoryObj } from '@storybook/react'; -import NameOfComponent from '@components/PathTo/YourComponent'; - -type Story = StoryObj; -type Meta = MetaObj; - -// If the component has any props that are interactable, they should be passed here -// We recommend reading Storybook docs for args: https://storybook.js.org/docs/react/writing-stories/args -export const Default: Story = {}; - -// If the Component has more than one State/Layout/Variant, there should be one Story for each variant -export const AnotherStory: Story = { - args: {}, -}; - -export default { component: NameOfComponent } as Meta; -``` - -- Stories should have `args` whenever possible, we want to be able to test the different aspects of a Component -- Please follow the template above to keep the Storybooks as consistent as possible -- We recommend reading previous Storybooks from the codebase for inspiration and code guidelines. -- If you need to decorate/wrap your Component/Story with a Container/Provider, please use [Storybook Decorators](https://storybook.js.org/docs/react/writing-stories/decorators) - -## Remarks on Technologies Used - -The Node.js Website is a somewhat complex application and at times non-trivial solutions have been implemented to solve certain technical challenges. -Historical decision making can be largely found on past issues, conversations on Slack and GitHub discussions. However, we also wish to highlight some of the notable development decisions that we have made here. - -### Why Next.js? - -We've found that Next.js is simply versatile, hackable, stable, community-maintained and has a great ecosystem. -The reasoning goes deeper, but as a long-term Framework it is the most suitable choice. - -#### Why do we continue to support static builds? - -It was decided together with the TSC (Technical Steering Committee) that the Node.js Website should always support fully static builds that do not depend on any 3rd party services. -This is to ensure that the Website is always available and that we do not depend on any 3rd party services to serve our content. - -(For example, if we abandon Vercel, our Website should still completely work as standalone as possible) - -#### What is `next.dynamic.mjs`? - -Our whole Website uses a custom renderer for rendering the pages. -As you might have seen, within the `pages` directory we have [Next.js Dynamic Route](https://nextjs.org/docs/pages/building-your-application/routing/dynamic-routes) named `[...path].tsx` that matches against all possible routes of the Website. - -This means that each `.md(x)` file within `pages/` is not rendered by Next.js regular App Tree (`pages/_document.tsx` and `pages/_app.tsx`) but a custom render tree. - -This custom render uses `getStaticPaths` and [Incremental Static Generation](https://nextjs.org/docs/pages/building-your-application/data-fetching/incremental-static-regeneration) to generate the full list of supported pages of the Website. -For example, this allows us to generate Localized Pages for every page that is not translated, by telling Next.js to create a localised path. -`next.dynamic.mjs` is responsible for getting a full list of the source pages (`pages/en`) and identifying which pages have been translated. - -Non-translated pages will have their Localized contexts and translated React message-bags (`react-intl`) but the content will be the same as the source page (English). -Whereas localized pages will have localized context and content. - -This custom solution is also able to decide what paths should be compiled during runtime. -This is a combination of rules defined on `next.constants.mjs` and `[...path].tsx`. - -The `[...path].tsx` file ultimately utilizes the `theme.tsx` file as its layout source. -This setup enables the loading of the Layout Provider and MDX Provider, which in turn, encapsulate and manage the rendering of any child content or components. This includes both content and components provided by the Layout Provider and the transformed MDX content originating from the `.md(x)` source page. - -#### What is `next.data.mjs`? - -This file is responsible for loading, fetching and generating build-time required information such as Node.js Release data, Blog Posts Metadata (for pagination and indexation), RSS feeds, etc. - -#### What is `site.json`? - -This file is used for defining Website Metadata, such as which RSS feeds should be generated, Social Media Information and other Metadata used during the Website build-time. -We use a JSON format to ease collaboration. - -#### What is `next.locales.mjs` and why not use Next.js built-in i18n? - -While Next.js provides a built-in i18n feature, it doesn't offer the flexibility we require. Our specific needs include the ability to generate comprehensive lists of supported locales and pages for each locale. Additionally, we operate with a subfolder-based approach for content, as opposed to the extension-based approach (e.g., `filename.language.md(x)`) that is compatible with Next.js's built-in i18n. - -We opted for the subfolder approach to maintain consistency with our previous Node.js website's content structure and to ensure long-term maintainability, rather than relying on Next.js's i18n functionality. - -#### What is `next.rewrites.mjs`? - -This file is responsible for defining the rewrite rules for the Website. -It is used for defining redirects and other rewrite rules. (Such as Internal Redirects and External ones). - -The `redirects.json` file specifies two types of operations: rewrites and redirects. - -- Rewrites: These operations do not alter the original URL but instead render the content from the rewritten URL. It's important to note that the rewritten URL path must be valid and exist on the website. -- Redirects: Redirect operations, on the other hand, change the original URL to a new one. While these new URLs can be either internal (within the website) or external (leading to a different domain), it is more common for redirects to point externally. - -This file contains a simple template engine that supports `/:locale` to indicate that this path should also be available under all available locales as prefix. - -#### Why do we use Next.js Middlewares? - -We have a simple Next.js Middleware that is responsible for handling initial Locale detection and redirection. -It detects browser locales and redirects to the most suitable locale for the user. And it fallbacks to `/en` if no suitable locale is found. - -#### What are Layouts? - -Layouts Wrap the content of the Markdown files. -They are responsible for adding additional styling and structure surrounding the content of the Markdown files. - -Layouts are defined within the `layouts` folder. -They are React Components that receive the `children` prop, which is the transformed MDX content of the Markdown file. - -Each Page layout is configured within their Markdown's Frontmatter as `layout: name-of-layout`. - -### How we style the Website? - -We use [PostCSS][] to style the Node.js Website; PostCSS is a CSS Preprocessor, like Sass and Less. - -#### How exactly do we style Components? - -We style each individual React Component with a dedicated CSS file (A CSS Module) that uses CSS syntax (with the extra powerups of PostCSS). - -The [Styling a Component](#styling-a-component) section contains a more detailed guide on how we style our Components. - -#### Why we use PostCSS over Sass or Less? - -The main advantage of PostCSS is its minimal pluggable API that allows us to extend the native CSS-syntax with custom plugins. - -Next.js natively supports PostCSS and always uses PostCSS as part of the bundling and building process. -By not using Sass or Less we remove another dependency from our build process and remove one layer of preprocessing our styles (CSS). - -We currently use a set of PostCSS plugins that create a SCSS-a-like environment. - -#### What PostCSS Plugins we use? - -- `postcss-mixins`: Allows us to use Sass-like Mixins -- `postcss-import`: Allows us to use Sass-like Imports -- `postcss-simple-vars`: Allows us to use Sass-like Variables -- `postcss-nested`: Allows us to use Sass-like Nesting -- `postcss-calc`: Strips `calc` expressions and replaces them with the result - -It is important to mention that even though we use SCSS-like syntax, we do not use SCSS, and some of these plugins -are not 100% compatible with the SCSS syntax. -For example, `postcss-mixins` does not support `@include` and `@extend` directives (and it uses `@define-mixin` for defining Mixins and `@mixin` for including Mixins). - -#### Do we use a CSS Framework? - -The Node.js Website uses Tailwind as a CSS Framework for crafting our React Components and style the Website. - -[Tailwind][] is an utility-first CSS Framework. It allows us to create a Design System that is easy to maintain and extend. It also allows us to create a consistent Design Language across the Website. - -#### Font Families on the Website - -We use `next/fonts` Open Sans as the default font for the Node.js Website. -The font is configured as a CSS variable and then configured on `tailwind.config.js` as the default font for the Website. - -#### Why we use RadixUI? - -- It is a minimalistic component library broken down in individual packages for each Component -- It already handles all WAI-ARIA and Accessibility shortcuts/bindings needed for Interactive Elements -- It allows us to focus on designing interactive Components without the effort of adding all the surrounding sugar and code needed to make the Component accessibility-friendly. - -### Why MDX? - -MDX is an extension on Markdown that allows us to add JSX Components within Markdown. -Besides that, MDX is also a pluggable parser built on top of `unified` which supports Rehype and Remark Plugins. -MDX is becoming the standard for parsing human-content on React/Next.js-based Applications. - -**Some of the plugins that we use include:** - -- `remark-gfm`: Allows us to bring GitHub Flavoured Markdown within MDX -- `remark-headings`: Generates Metadata for Markdown Headings - - This allows us to build the Table of Contents for each Page, for example. -- `rehype-autolink-headings`: Allows us to add Anchor Links to Markdown Headings -- `rehype-slug`: Allows us to add IDs to Markdown Headings - -#### Syntax Highlighting (Shiki) and Vercel - -Shiki is integrated on our workflow as a Rehype Plugin, see the `next.mdx.shiki.mjs` file. We also use the `nord` theme for Shiki and a subset of the supported languages as defined on the `shiki.config.mjs` file. - -### Vercel - -We use Vercel as our hosting provider. It is a great platform that offers an excellent CI/CD pipeline which allows us to deploy our website with ease. - -It is important to mention that there are some rules on our Vercel Deployments such as: - -- Branches starting with `dependabot` (Dependabot Automated PRs) or `gh` (GitHub Merge Queues) are not deployed to Vercel. -- Vercel Deployments are triggered for all other branches during `push` activities. -- We have a custom install script that executes `npm ci --omit=dev` (the same way we do on our CI Pipelines) - - Hence if Builds fail unexpectedly, make sure that your dependency that is being used during build-time is on `dependencies` and not `devDependencies`. Checkout out [DEPENDENCY_PINNING.md](./DEPENDENCY_PINNING.md) for more information. -- Our sponsorship with Vercel is maintained by the OpenJS Foundation - -### Why we have a `.vscode` folder - -The repository defines an optimized configuration for code editing. This is optional and is not required to contribute to the project. However, the settings and extensions specified help create a uniform and more efficient developer experience. This configuration is found in the `.vscode` directory: - -- `extensions.json` suggests VSCode extensions that make the editor more compatible with the code. For example, a Tailwind extension creates auto-complete intellisense for tailwind styles within our components. Eslint, prettier, and editorconfig extensions read their respective config files and automatically format or lint code as written. This helps save CI feedback loops when a contribution does not meet our standards. -- `settings.json` contains some common sense defaults that aide development and enforce consistency across the codebase. For example, we want files formatted on save and we want prettier to be used as the formatter. Without these settings, new contributors may have different authoring experiences when contributing, leading to inconsistent code and CI failures. We also disable VSCode's default CSS parser so PostCSS and Tailwind syntax are respected. - -Defining a `.vscode` configuration like this also aides browser-only development using [GitHub's Codespaces feature](https://github.com/features/codespaces). The web-based GUI will read these same configuration files and setup the remote development environment the same way every time. - -### Why we have an `.npmrc` file - -The npm ecosystem resolution and installation of `peerDependencies` installation [changed in recent versions](https://nodejs.org/en/blog/npm/peer-dependencies#using-peer-dependencies). The project documents what version of `Node.js` and `npm` to use via the [`.nvmrc` file](https://github.com/nodejs/nodejs.org/blob/main/.nvmrc). Not all contributors have tooling to automatically read this file and adhere to the correct version, however. To ensure all contributors install dependencies the same way, a local `.npmrc` file directly configures peerDependency installation behavior. - -## Seeking additional clarification - -A lot of the current structure is due to retro-compatibility, keeping a simple and familiar file structure and keeping files that have historical reasons or needs. - -If you're unfamiliar or curious about something, we recommend opening a Discussion on this GitHub Repository. - -[Jest]: https://jestjs.io/ -[React Testing Library]: https://testing-library.com/docs/react-testing-library/intro/ -[Storybook]: https://storybook.js.org/ -[`react-intl`]: https://formatjs.io/docs/react-intl/ -[Next.js]: https://nextjs.org/ -[MDX]: https://mdxjs.com/ -[PostCSS]: https://postcss.org/ -[React]: https://react.dev/ -[Shiki]: https://github.com/shikijs/shiki -[Tailwind]: https://tailwindcss.com/ -[Radix UI]: https://www.radix-ui.com/ -[`next-intl`]: https://www.npmjs.com/package/next-intl diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1c8b1347ca211..20fdec94a24b8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,249 +1,143 @@ # Node.js Website Contributing Guide -Thank you for your interest in contributing to the Node.js Website. Before you proceed, briefly go through the following: +## Table of Contents -- [Code of Conduct](https://github.com/nodejs/node/blob/HEAD/CODE_OF_CONDUCT.md) -- [Contributing](#contributing) - - [Becoming a collaborator](#becoming-a-collaborator) -- [Getting started](#getting-started) - - [CLI Commands](#cli-commands) -- [Commit Guidelines](#commit-guidelines) -- [Pull Request Policy](#pull-request-policy) -- [Developer's Certificate of Origin 1.1](#developers-certificate-of-origin-11) +- [Quick Start](#quick-start) +- [Code of Conduct](#code-of-conduct) + - [Developer's Certificate of Origin 1.1](#developers-certificate-of-origin-11) +- [Ways to Contribute](#ways-to-contribute) + - [For All Contributors](#for-all-contributors) + - [For Developers](#for-developers) +- [Development Workflow](#development-workflow) + - [1. Set Up Your Environment](#1-set-up-your-environment) + - [2. Make Your Changes](#2-make-your-changes) + - [3. Test Your Changes](#3-test-your-changes) + - [4. Submit Your Contribution](#4-submit-your-contribution) +- [Documentation Structure](#documentation-structure) +- [Getting Help](#getting-help) +- [Project Maintainers](#project-maintainers) +- [License](#license) -## Contributing +--- -Any individual is welcome to contribute to the Node.js Website. The repository currently has two kinds of contribution personas: +Thank you for your interest in contributing to the Node.js Website! This guide will help you get started with contributing to our project. -- A **Contributor** is any individual who creates an issue/PR, comments on an issue/PR, or contributes in some other way. -- A **Collaborator** is a contributor with write access to the repository. See [here](#becoming-a-collaborator) on how to become a collaborator. +## Quick Start -You can find more details and guides about Collaborating with this repository through our [Collaborator Guide](./COLLABORATOR_GUIDE.md). +New to contributing? Start here: -### Becoming a Collaborator +1. **[Getting Started](./docs/getting-started.md)** - Set up your development environment and make your first contribution +2. **[Code Style](./docs/code-style.md)** - Learn our coding standards and formatting guidelines +3. **[Adding Pages](./docs/adding-pages.md)** - Create new pages and content for the website -A collaborator of the Node.js Website repository is a member of the Node.js Website Team. +## Code of Conduct -The Website Team is responsible for the technical development of the Node.js Website; thus, it is expected -that team members have significant knowledge about modern Web Technologies and Web Standards. +Before contributing, please read and follow our [Code of Conduct](https://github.com/nodejs/node/blob/HEAD/CODE_OF_CONDUCT.md). -Note that regular contributors do not need to become "Collaborators" as any contribution is appreciated (even without a status), and a Collaborator status -is a formality that comes with obligations. +### Developer's Certificate of Origin 1.1 -If you're an active contributor seeking to become a member, we recommend you contact one of the existing Team Members for guidance. - -
- What's the process for becoming a Collaborator? - -- You must be actively contributing to this repository. -- Contributions must include significant code reviews or code contributions. -- A nomination must be done by an existing Team Member of the Website Team with an Issue - - The Issue must explain and describe why the nominated person is a good addition to the team - - The Issue must contain links to relevant contributions through: - - Code Reviews - - Comments on Issues and PRs - - Authoring of PRs or Issues - - Comments or Authoring of Discussions -- The nomination must have at least three existing members of the Website Team agree with the nomination. - - This can be done through commenting with "agreement" (showing support) or reacting to the Issue with a :+1: (Thumbs-up Emoji) -- The Issue must be open for at least 72 hours without an objection from an existing member of the Website Team - - The nomination cannot pass until all open objections are resolved. - - Objections from the TSC or Core Collaborators are also counted as valid objections. - -
- -## Getting started - -The steps below will give you a general idea of how to prepare your local environment for the Node.js Website and general steps -for getting things done and landing your contribution. - -1. Click the fork button in the top right to clone the [Node.js Website Repository](https://github.com/nodejs/nodejs.org/fork) - -2. Clone your fork using SSH, GitHub CLI, or HTTPS. - - ```bash - git clone git@github.com:/nodejs.org.git # SSH - git clone https://github.com//nodejs.org.git # HTTPS - gh repo clone /nodejs.org # GitHub CLI - ``` - -3. Change into the nodejs.org directory. - - ```bash - cd nodejs.org - ``` - -4. Create a remote to keep your fork and local clone up-to-date. - - ```bash - git remote add upstream git@github.com:nodejs/nodejs.org.git # SSH - git remote add upstream https://github.com/nodejs/nodejs.org.git # HTTPS - gh repo sync nodejs/nodejs.org # GitHub CLI - ``` - -5. Create a new branch for your work. - - ```bash - git checkout -b name-of-your-branch - ``` - -6. Run the following to install the dependencies and start a local preview of your work. - - ```bash - npm ci # installs this project's dependencies - npx turbo serve # starts a preview of your local changes - ``` - -7. Perform your changes. In case you're unfamiliar with the structure of this repository, we recommend a read on the [Collaborator Guide](./COLLABORATOR_GUIDE.md) - -8. Perform a merge to sync your current branch with the upstream branch. - - ```bash - git fetch upstream - git merge upstream/main - ``` - -9. Run `npx turbo format` to confirm that linting and formatting are passing. - - ```bash - npx turbo format - ``` - -10. Once you're happy with your changes, add and commit them to your branch, then push the branch to your fork. - - ```bash - cd ~/nodejs.org - git add . - git commit -m "some message" - git push -u origin name-of-your-branch - ``` - - > [!NOTE]\ - > By default if you run the Website (either via `npm run serve` or `npm run build`) two files on the `public` folder will be generated. - > - > You don't need to reset/discard these files, as by default we use Git Hooks that simply ignore these files during commit. - > Note that these files are generated and should **not** be committed. (`public/node-release-data.json` and `public/blog-posts-data.json`) - - > [!IMPORTANT]\ - > Before committing and opening a Pull Request, please go first through our [Commit](#commit-guidelines) and [Pull Request](#pull-request-policy) guidelines outlined below. +``` +By contributing to this project, I certify that: -11. Create a Pull Request. +- (a) The contribution was created in whole or in part by me and I have the right to + submit it under the open source license indicated in the file; or +- (b) The contribution is based upon previous work that, to the best of my knowledge, + is covered under an appropriate open source license and I have the right under that + license to submit that work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am permitted to submit under a + different license), as indicated in the file; or +- (c) The contribution was provided directly to me by some other person who certified + (a), (b) or (c) and I have not modified it. +- (d) I understand and agree that this project and the contribution are public and that + a record of the contribution (including all personal information I submit with it, + including my sign-off) is maintained indefinitely and may be redistributed consistent + with this project or the open source license(s) involved. -> [!NOTE]\ -> We ask for PR authors to avoid to rebase/update their PRs with the base branch (`main`) unnecessarily. -> We use [GitHub Merge Queues](https://github.blog/2023-07-12-github-merge-queue-is-generally-available/) -> which means that before merge the PRs get automatically updated and checked against the latest changes on the base branch. -> -> This also reduces the amount of times we need to run our CI checks, as every new push requires freshly new CI-checks. +``` -### CLI Commands +## Ways to Contribute -This repository contains several scripts and commands for performing numerous tasks. The most relevant ones are described below. +### For All Contributors -
- Commands for Running & Building the Website +- **Report Issues**: Found a bug or have a feature request? [Open an issue](https://github.com/nodejs/nodejs.org/issues/new/choose) +- **Improve Documentation**: Help make our docs clearer and more comprehensive +- **Add Content**: Create new articles, blog posts, or improve existing content +- **Fix Bugs**: Look for issues labeled [`good first issue`](https://github.com/nodejs/nodejs.org/labels/good%20first%20issue) +- **Translate Content**: Help make Node.js documentation accessible worldwide -- `npx turbo serve` runs Next.js's Local Development Server, listening by default on `http://localhost:3000/`. -- `npx turbo build` builds the Application on Production mode. The output is by default within `.next` folder. - - This is used for the Node.js Vercel Deployments (Preview & Production) -- `npx turbo deploy` builds the Application on Export Production Mode. The output is by default within `build` folder. - - This is used for the Node.js Legacy Website Server (DigitalOcean) -- `npx turbo start` starts a web server running serving the built content from `npx turbo build` +### For Developers -
+- **Create Components**: Build reusable React components following our [component guidelines](./docs/creating-components.md) +- **Write Tests**: Improve test coverage with our [testing guidelines](./docs/writing-tests.md) +- **Enhance Features**: Add new functionality to improve user experience -
- Commands for Maintenance Tasks and Tests +## Development Workflow -- `npx turbo lint` runs the linter for all the js files. - - `npx turbo lint:fix` attempts to fix any linting errors -- `npx turbo prettier` runs the prettier for all the js files. - - `npx turbo prettier:fix` attempts to fix any style errors -- `npx turbo format` formats and fixes the whole codebase -- `npx turbo scripts:release-post` generates a release post for the current release - - **Usage:** `npx turbo scripts:release-post -- --version=vXX.X.X --force` -- `npx turbo storybook` starts Storybook's local server -- `npx turbo storybook:build` builds Storybook as a static web application for publishing -- `npx turbo test` runs all tests locally - - `npx turbo test:unit` runs jest (unit-tests) locally +### 1. Set Up Your Environment -
+```bash +# Fork and clone the repository +git clone https://github.com/YOUR_USERNAME/nodejs.org.git +cd nodejs.org -## Commit Guidelines +# Install dependencies +pnpm install --frozen-lockfile -This project follows the [Conventional Commits][] specification. +# Start development server +node --run dev +``` -Commits should be signed. You can read more about [Commit Signing][] here. +For detailed setup instructions, see [Getting Started](./docs/getting-started.md). -### Commit Message Guidelines +### 2. Make Your Changes -- Commit messages must include a "type" as described on Conventional Commits -- Commit messages **must** start with a capital letter -- Commit messages **must not** end with a period `.` +- **New Pages**: Follow our [page creation guide](./docs/adding-pages.md) +- **Components**: See [creating components](./docs/creating-components.md) +- **Styling**: Follow our [code style guidelines](./docs/code-style.md) -### Pre-commit Hooks +### 3. Test Your Changes -This project uses [Husky][] for Git pre-commit hooks. +```bash +# Format and lint code +node --run format -- Some JSON files are generated during Build time with empty files as placeholders. Build time happens when you run `npx turbo serve` or `npx turbo build`. -- We don't want to commit those unnecessary changes. Since these files exist in the repository, `.gitignore` won't work for them. As a workaround, we have a pre-commit hook to discard those changes. +# Run tests +node --run test -## Pull Request Policy +# Test build +node --run build +``` -This policy governs how contributions should land within this repository. The lines below state the checks and policies to be followed before merging and in the act of merging. +### 4. Submit Your Contribution -### Before merging +1. **Create a branch**: `git checkout -b your-feature-branch` +2. **Commit changes**: Follow our [commit guidelines](./docs/code-style.md#commit-guidelines) +3. **Push to your fork**: `git push origin your-feature-branch` +4. **Open a Pull Request**: Use our [pull request template](.github/PULL_REQUEST_TEMPLATE.md) -We recommend a read on our [Collaborator Guide](COLLABORATOR_GUIDE.md#accepting-modifications) for in-depth details on how we accept contributions into this repository. -The list below describes some merging and approval rules adopted in this repository. +## Documentation Structure -- Pull Requests must be open for at least 48 hours (Or 72 hours if the PR was authored on the weekend). - - Pull requests might be immediately merged if they contain critical bug fixes, short errata (e.g., typos from previous PRs), or any critical change considered a "showstopper" for the website's operation. - - This kind of PRs should only be done by existing collaborators with write-access and/or signed off by administrators/maintainers. - - This rule cannot be used for updates on the `COLLABORATOR_GUIDE.md`, `CONTRIBUTING.md` guide, `CODEOWNERS`, GitHub Actions, or any security-impacting file or document that changes the governing policies of this repository. - - Pull requests might be "fast-tracked", meaning they can be merged before the usual 48 hours' notice if a "fast-track" label is added. - - The person that is fast-tracking the PR (adding the label) must also comment on the PR that they're requesting the PR to be fast-tracked - - The comment must mention `@nodejs/nodejs-website` and must have at least one 👍 (or any other sort of approval reaction) if the person fast-tracking the PR is the author of the PR. - - Fast-tracking is only allowed for small bug fixes, small feature changes, localization changes, or other non-critical/highly-impacting changes not covered by the previous rule that allows PRs to be merged immediately. - - Fast-tracking cannot be used for updates on the `COLLABORATOR_GUIDE.md`, `CONTRIBUTING.md` guide, `CODEOWNERS`, GitHub Actions, or any security-impacting file or document that changes the governing policies of this repository. -- There must be no objections after forty-eight (48) hours (Or seventy-two (72) hours if the PR was authored on the weekend). - - If there are disagreements consensus should be sought. Lack of consensus might require escalation to the Website Team Maintainers. -- At least one approval is required for any PR to be merged. -- Tests must be included in Pull Requests for new features or bug fixes. You are responsible for fixing any test(s) that fail. +Our documentation is organized in the [`docs/`](./docs/) directory, so check out it's **[README](./docs/README.md)** for navigation. -Each contribution is accepted only if there is no objection to it by a collaborator. During the review, collaborators may request that a specific contributor who is an expert in a particular area give an "LGTM" before the PR can be merged. +## Getting Help -If an objection is raised in a pull request by another collaborator, all collaborators involved should try to arrive at a consensus by addressing the concerns through discussion, compromise, or withdrawal of the proposed change(s). +- **Questions?** Start a [Discussion](https://github.com/nodejs/nodejs.org/discussions) +- **Found a bug?** [Open an issue](https://github.com/nodejs/nodejs.org/issues/new/choose) +- **Need clarification?** Comment on existing issues or PRs +- **Want to chat?** Join the Node.js community on [OpenJS Foundation Slack](https://openjs-foundation.slack.com/) -### When merging +## Project Maintainers -- All required Status-checks must have passed. - - **Note:** If you're a collaborator, remember to run the CI checks by labeling the PR with the `github_actions:pull-request` label. -- Please make sure that all discussions are resolved. -- [`squash`][] pull requests made up of multiple commits +This project is maintained by the [Node.js Website Team](https://github.com/nodejs/web-team/blob/main/MEMBERS.md#nodejs-website-team-nodejsnodejs-website). For questions about governance or high-level project direction, you can: -## Developer's Certificate of Origin 1.1 +- Mention `@nodejs/nodejs-website` in issues or PRs +- Contact team members directly for guidance +- Escalate to the [Node.js Technical Steering Committee](https://github.com/nodejs/TSC) if needed -``` -By contributing to this project, I certify that: +## License -- (a) The contribution was created in whole or in part by me and I have the right to - submit it under the open source license indicated in the file; or -- (b) The contribution is based upon previous work that, to the best of my knowledge, - is covered under an appropriate open source license and I have the right under that - license to submit that work with modifications, whether created in whole or in part - by me, under the same open source license (unless I am permitted to submit under a - different license), as indicated in the file; or -- (c) The contribution was provided directly to me by some other person who certified - (a), (b) or (c) and I have not modified it. -- (d) I understand and agree that this project and the contribution are public and that - a record of the contribution (including all personal information I submit with it, - including my sign-off) is maintained indefinitely and may be redistributed consistent - with this project or the open source license(s) involved. +By contributing to this project, you agree that your contributions will be licensed under the project's [MIT License](./LICENSE). -``` +--- -[`squash`]: https://help.github.com/en/articles/about-pull-request-merges#squash-and-merge-your-pull-request-commits -[Conventional Commits]: https://www.conventionalcommits.org/ -[Commit Signing]: https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits -[Husky]: https://typicode.github.io/husky/ +**Ready to contribute?** Start with our [Getting Started guide](./docs/getting-started.md) and join the Node.js community in building better web experiences for developers worldwide! 🚀 diff --git a/DEPENDENCY_PINNING.md b/DEPENDENCY_PINNING.md deleted file mode 100644 index 57da5ec043cf3..0000000000000 --- a/DEPENDENCY_PINNING.md +++ /dev/null @@ -1,37 +0,0 @@ -## Dependency Pinning - -Based on the initial discussions from [this discussion thread](https://github.com/nodejs/nodejs.org/discussions/5491), we've decided to use a more strict strategy for handling npm dependencies within the Node.js Website. - -The intent here is to prevent the build process, or the website itself, from breaking due to changes in dependencies. As some dependencies do not respect semantic versioning, this is a real concern. Pinning dependencies also ensures that we stay fixed on a specific dependency version. For security updates, Dependabot is still configured to give us security alerts when specific dependencies got security advisories. - -### When adding dependencies - -The following recommendations are in order when adding a new dependency: - -- A dependency should be a `dependencies` if it is part of the build process of the Website or used within runtime code. - - Some non-code dependencies are required for the bootstrap of the repository. They are either used on basic build scripts or Git Hooks. Examples include `husky`, `lint-staged` and others. -- A dependency should be a `devDependencies` if it is not invoked anywhere within the codebase. - - This applies to runtimes, tooling, utility/dev-only commands, type packages and others -- Sometimes, a dependency should be a `devDependency` even if invoked within the codebase, but only used within a development environment or during a test runner. Examples include `jest`, `storybook` and others. -- A dependency should be a `peerDependencies` if it is a runtime dependency of the Website, but the Website itself does not install it. Examples include `react`, `react-dom` and others. - -### When pinning dependencies - -When adding dependencies, you should consider if that dependency should be saved as an exact dependency (`--save-exact`) or use either a `^` or `~` version range operator. The following guidelines are in order: - -- A dependency, in general, should be pinned to its exact dependency if it's either a tooling or a CLI dependency. Examples include `husky`, `prettier`, `jest` and others. -- A dependency should generally use `~` if we're interested in patch updates (such as hot-fixes and bug-fixes) and the package is part of the Development or Testing Environment. (Such as `storybook`, for example) -- A dependency should generally use `^` if they're part of the Website Application itself, such as `react`, `react-intl` etc. This is done because we intentionally want to get these dependencies' latest features and bug-fixes. - - If we're not interested in getting the latest features and bug fixes, we should consider using `~` instead. -- Node. js-only dependencies used in scripts or during the build process of the Website (not used within actual Application code) should use `~` instead. Examples include `glob`, `@nodevu/core` -- TypeScript type packages of corresponding packages should follow the same `semver` of their respective packages - -### About manual updates - -This document intends to outline the strategy we have when adding dependencies. We also recommend that Team members only add new dependencies when explicitly needed. The more dependencies, the harder it gets to review and understand the complexity of the Website Application. You can avoid adding new dependencies if possible. - -Manual updates should be avoided. Dependabot is configured for updating our dependencies. Updating a dependency is only needed if the update breaks the current `semver` constraint. In these situations, good judgement should be used (as a dependency should generally only be updated in these scenarios if the updated version brings new features desired by the team). - -An exception to the manual rule is the version of Node.js defined within `.nvmrc`, and the corresponding `@types/node` dependency. The `@types/node` semver MAJOR version should be kept in sync with the version of Node.js the site is built with. - -Lastly, in general, a Pull Request should not contain `package-lock.json` changes, nor changes to dependencies on `package.json`. diff --git a/GOVERNANCE.md b/GOVERNANCE.md deleted file mode 100644 index 837b3716cae9b..0000000000000 --- a/GOVERNANCE.md +++ /dev/null @@ -1,47 +0,0 @@ -# Node.js Web Team Governance - -The Node.js Web Team (@nodejs/web) is a team in the Node.js Project that is composed by a set of subteams. Each containing specific responsibilities and goals. - -### Node.js Website Team (`@nodejs/nodejs-website`) - -The Node.js Website Team is responsible for the day-to-day technical development of the Node.js Website. This is primarily the development of the website itself, adding new features, pages and components, but also fixing any security issues in the website code, handling operational maintenance, and so on. - -The maintainers on the Node.js Website Team are responsible for steering the technical direction of the Node.js Website, and reserve the right to make final decisions on any issues or pull requests, in line with the Contribution Guidelines, Collaborator Guidelines, the Code of Conduct and the overall Governance premises of the Node.js project. - -Members of this team are nominated through the guidelines provided in the Collaborator Guidelines within this repository. - -### Node.js Web Infra Team (`@nodejs/web-infra`) - -The Node.js Web Infra Team is responsible for maintaining the Infrastructure relating to Node.js's Web Presence. The Node.js Web Infra team has the responsibilities of: - -- Maintaining CI/CD pipelines related to Web Infrastructure -- Maintaining our Infrastructure Providers\* -- Have technical ownership on best-standards and best-practices for our Web Infrastructure (such as Web Frameworks that we use) - -Web Infra Team members should have access to be able to maintain the services mentioned above. - -Members of this team are nominated either by the Node.js Technical Steering Committee (TSC) or the Node.js Build WG and follow the guidelines provided in the Collaborator Guidelines of the Node.js Build WG. Note that members of the Node.js Web Team might also recommend people for nomination. - -\* This team has access to infrastructure providers directly related to the Website only, such as Vercel. Other providers that are shared beyond the Website may be controlled by other teams (for example, the Node.js Build WG owns Cloudflare). - -### Node.js Web Standards Team (`@nodejs/web-standards`) - -The Node.js Web Standards Team is composed of Node.js Collaborators and External Collaborators that have extensive experience or expertisè on Web Standards, such as Ecma262. The Standards Team is responsible for guiding and serving as points of contact when either Node.js Collaborators, the Node.js Technical Steering Committee (TSC), or the Web Team, requires assistance or guidance regarding Web Standards. - -Members of this team are nominated by the Node.js Technical Steering Committee (TSC). Note that members of the Node.js Web Team might also recommend people for nomination. - -### Node.js UX & Design Team (`@nodejs/ux-and-design`) - -The Node.js UX & Design Team is composed of Node.js Collaborators and External Collaborators that have experience or expertisè with UX & Design. The UX & Design Team is responsible for guiding and serving as points of contact when members of the Node.js Web Team require assistance or guidance regarding UX & Design. - -Often members of this team will collaborate on providing best practices and guidelines for the Node.js Website, on matters of UX & Design. Members of this team are also responsible for providing feedback on the Node.js Website, and providing feedback on the Node.js Website's design. (For example, when a discussion arises regarding best practices on topics such as CSS, accessibility, UX flows and intent, or component design, the UX & Design Team has a say on the matter). - -Members of this team are nominated by the Node.js Technical Steering Committee (TSC). Note that members of the Node.js Web Team might also recommend people for nomination. - -## The Interoperability of the Node.js Web Team - -As seen above, the different teams under the Node.js Web Team umbrella are responsible for having the oversight on different aspects of Node.js's Web-related projects. However, it is important to note that the Node.js Web Team is not a set of siloed teams, but rather a set of teams that work together to achieve the same goal: Providing the best Web Experience for Node.js. - -Following this line of thought, the Web Infra Team is responsible for the technical aspects of the Node.js Website (Infrastructure, Framework, CI/CD, etc); The Website Team is responsible for the day-to-day development of the Node.js Website; The UX and Design Team advise on Design Matters and the Web Standards Team advise on best-practices for Web APIs and Web Technologies/Standards. - -But above all, the Web Team should work together to better the Web Experience for Node.js, aiming to provide the best experience for Node.js users. diff --git a/README.md b/README.md index 28b5ab68a86a3..fa8ee3f021916 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,14 @@
- - + +

- Node.js Website built using Next.js with TypeScript, SCSS and MDXv2 + Node.js Website built using Next.js with TypeScript, CSS Modules/Tailwind, and MDXv3

@@ -36,20 +36,20 @@ ## What is this repo? -[Nodejs.org](https://nodejs.org/) by the [OpenJS Foundation](https://openjsf.org/) is the official website for the Node.js® JavaScript runtime. This repo is the source code for the website. It is built using [Next.js](https://nextjs.org), a React Framework. +[Nodejs.org](https://nodejs.org/), maintained by the [OpenJS Foundation](https://openjsf.org/), is the official website for the Node.js® JavaScript runtime. This repo is the source code for the website. It is built using [Next.js](https://nextjs.org), a React Framework. ```bash -npm ci -npx turbo serve +pnpm install --frozen-lockfile +pnpm dev -# listening at localhost:3000 +# Listening at http://localhost:3000 ``` ## Contributing This project adopts the Node.js [Code of Conduct][]. -Any person who wants to contribute to the Website is welcome! Please read [Contribution Guidelines][] and see the [Figma Design][] to understand better the structure of this repository. +Anyone who wants to contribute to the website is welcome! Please read [Contribution Guidelines][] and see the [Figma Design][] to understand better the structure of this repository. > \[!IMPORTANT]\ > Please read our [Translation Guidelines][] before contributing to Translation and Localization of the Website @@ -80,11 +80,10 @@ This repository does not contain the codebase or related infrastructure that ser These are maintained in different repositories and we urge users to open **issues in their respective repositories**, for bug reports, feature requests or any matter related to these endpoints. -- [`release-cloudflare-worker`](https://github.com/nodejs/release-cloudflare-worker): The codebase responsible for serving the Node.js Distribution Binaries, API Docs and any other assets from the links mentioned above. +- [`release-cloudflare-worker`](https://github.com/nodejs/release-cloudflare-worker): The codebase responsible for serving the Node.js Distribution Binaries, API docs and any other assets from the links mentioned above. - We use Cloudflare R2 Buckets for storing our Assets and Cloudflare Workers for serving these Assets to the Web. - [`node/doc/api`](https://github.com/nodejs/node/tree/main/doc/api): The source code of our API docs, it contains all the Node.js API Documentation Markdown files - - [`node/doc`](https://github.com/nodejs/node/tree/main/doc) contains the HTML templates, CSS styles and JavaScript code that runs on the client-side of our API Docs generated pages. - - [`node/tools/doc`](https://github.com/nodejs/node/tree/main/tools/doc) contains the tooling that validates, lints, builds and compiles our API Docs. Also responsible for generating what you see when accessing `https://nodejs.org/api/`. +- [`doc-kit`](https://github.com/nodejs/doc-kit): The tooling that builds and compiles our API docs. Also responsible for generating what you see when accessing `https://nodejs.org/api/`. ## Relevant Links @@ -94,6 +93,8 @@ These are maintained in different repositories and we urge users to open **issue [Collaborator Guide][] +[Governance Document][] + [Figma Design][] [Content vs Code][] @@ -102,6 +103,8 @@ These are maintained in different repositories and we urge users to open **issue [Translation Guidelines][] +[Status Page](https://status.nodejs.org/) of the Node.js web infrastructure. + ## Thanks - Thanks to all contributors and collaborators that make this project possible. @@ -111,11 +114,14 @@ These are maintained in different repositories and we urge users to open **issue - A really warm thank you to Cloudflare as we would not be able to serve our community without their immense support. - Thanks to [Sentry](https://sentry.io/welcome/) for providing an open source license for their error reporting, monitoring and diagnostic tools. - Thanks to [Crowdin](https://crowdin.com/) for providing a platform that allows us to localize the Node.js Website and collaborate with translators. +- Thanks to [Orama](https://docs.oramasearch.com/) for providing a search platform that indexes our expansive content and provides lightning-fast results for our users. +- Thanks to [DigitalOcean](https://www.digitalocean.com/) for generously providing Node.js with credits as part of their open source program. [code of conduct]: https://github.com/nodejs/admin/blob/main/CODE_OF_CONDUCT.md [contribution guidelines]: https://github.com/nodejs/nodejs.org/blob/main/CONTRIBUTING.md -[content vs code]: https://github.com/nodejs/nodejs.org/blob/main/CONTENT_VS_CODE.md -[dependency pinning]: https://github.com/nodejs/nodejs.org/blob/main/DEPENDENCY_PINNING.md -[collaborator guide]: https://github.com/nodejs/nodejs.org/blob/main/COLLABORATOR_GUIDE.md -[figma design]: https://www.figma.com/file/pu1vZPqNIM7BePd6W8APA5/Node.js -[translation guidelines]: https://github.com/nodejs/nodejs.org/blob/main/TRANSLATION.md +[content vs code]: https://github.com/nodejs/nodejs.org/blob/main/docs/content-vs-code.md +[dependency pinning]: https://github.com/nodejs/nodejs.org/blob/main/docs/dependency-pinning.md +[collaborator guide]: https://github.com/nodejs/nodejs.org/blob/main/docs/collaborator-guide.md +[governance document]: https://github.com/nodejs/web-team/blob/main/GOVERNANCE.md +[figma design]: https://www.figma.com/file/a10cjjw3MzvRQMPT9FP3xz +[translation guidelines]: https://github.com/nodejs/nodejs.org/blob/main/docs/translation.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000000..d6a456a375a0f --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,20 @@ +# Security + +## Reporting a vulnerability to Node.js Website + +Please report security issues **privately** using the **GitHub Security Advisory** +workflow ([Security → “Report a vulnerability”](https://github.com/nodejs/nodejs.org/security/advisories/new)). + +Do **not** open a public GitHub issue for security problems. + +We aim to acknowledge reports within **7 business days**. +If you do **not** receive an acknowledgement within **7 business days**, +forward your report to **[tsc@nodejs.org](mailto:tsc@nodejs.org)**. + +## Disclosure & advisories + +Confirmed vulnerabilities will be published as a **GitHub Security Advisory** +(and assigned a CVE when applicable). Notices are also shared via: + +- Node.js blog advisories: [https://nodejs.org/blog/vulnerability/](https://nodejs.org/blog/vulnerability/) + when necessary. diff --git a/TRANSLATION.md b/TRANSLATION.md deleted file mode 100644 index f94f9152dcddf..0000000000000 --- a/TRANSLATION.md +++ /dev/null @@ -1,85 +0,0 @@ -# Node.js Website Translation Policy - -Node.js is a global platform and so this site has many translations. We use [Crowdin](https://crowdin.com) to translate the Node.js Website - -The site's translation into languages other than English is handled by [Crowdin translators](https://support.crowdin.com/translation-process-overview/). - -We use [`next-intl`](https://next-intl-docs.vercel.app/) as our Internationalization Library. We recommend reading its documentation for API usage. - -## How to translate - -1. Request to join the Node.js Website project on [Crowdin](https://crowdin.com/project/nodejs-web) -2. [Select the language you want to translate](https://support.crowdin.com/joining-translation-project/#starting-translation) -3. [Start translating](https://support.crowdin.com/online-editor/) - -### Any questions or feedbacks on Translations - -If you have any questions or feedbacks on current translations, you can [start a discussion](https://crowdin.com/project/nodejs-web/discussions) by choosing the "New Topic" and your language from the right dropdown, or a [conversation](https://support.crowdin.com/conversations/) by adding your translators. - -## How to add a new language - -Go on `/i18n/config.json` and add the new language to the `locales` array. - -Fill the language object with the following fields: - -```json -{ - "code": "fr", - "localName": "Français", - "name": "French", - "langDir": "ltr", - "dateFormat": "DD.MM.YYYY", - "hrefLang": "fr", - "enabled": true -} -``` - -| Field Name | Description | Examples | -| ------------ | ------------------------------------------------------------------------------------------------------ | ------------ | -| `code` | The language code. It must be the same as the folder name | `fr` | -| `localName` | The language name in its own language (it's use in language selector) | `Français` | -| `name` | The language name in English | `French` | -| `langDir` | The direction of the language. `ltr` for left to right, `rtl` for right to left | `ltr` | -| `dateFormat` | The date format. It must be a valid [moment.js format](https://momentjs.com/docs/#/displaying/format/) | `DD.MM.YYYY` | -| `hrefLang` | The language code in [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) format | `fr` | -| `enabled` | If the language is enabled or not | `true` | - -Please also add the new locale file to the locales folder `/i18n/locales`. - -## Adding new Translation Keys - -If you're making a new Component and adding Translation Keys for your Component, they should follow these guidelines: - -- Only add the new translation keys on the `i18n/locales/en.json` file. Crowdin will handle on syncing the files and letting translators know there are new keys to be translated -- The translation keys should have the prefix as the canonical path of your Component. If your Component is `components/Common/MyComponent` the prefix key should be `components.common.myComponent` - - The Translation Key suffix should be easy to understand and semantic. For example, if the key is about "the text of a button that when interacted it copies content to the clipboard", the suffix should probably be `copyButton.title`. The final translation key would be `components.common.myComponent.copyButton.title` - - Translation Keys should be in Camel Case only. - - The values of each Translation Key should follow the [ICU Message Syntax](https://next-intl-docs.vercel.app/docs/usage/messages#rendering-icu-messages) -- All new Translation keys should be added at the bottom of the `i18n/locales/en.json` file. Since this makes it easier for Translators to notice that there are new Translation keys to be translated. - -#### Notes about Translation Keys - -It's important to mention that we use nested translation keys within the Locale files. This means that if your translation key is `components.common.myComponent.something`, you should actually define the key and value within: - -```json -{ - "components": { - ..., - "common": { - ..., - "myComponent": { - "something": "value of translation key" - } - } - } -} -``` - -### Translations and Unit Testing - -Translation Keys should not be translated during Unit Testing. If your Component uses, for example `useTranslations`, you should provide the `` surrounding your `testing-library` render logic, or you can create a wrapper for your test. Note that you should not import the English messages to your Unit Test as: - -- Unit Testing should test a Component functionality. -- Unit Tests should not rely on text, titles, or string bags, as these texts will change arbitrarily and make the test suite fail. - - In this case, you should test your component by aria-text, or other `aria-*` attributes or even by class names or other artifacts. -- Visual Regression Testing is recommended to test how different languages and text appear within a Component. diff --git a/app/[locale]/[[...path]]/page.tsx b/app/[locale]/[[...path]]/page.tsx deleted file mode 100644 index 2bb864c8ee455..0000000000000 --- a/app/[locale]/[[...path]]/page.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import { setContext, setTags } from '@sentry/nextjs'; -import { notFound } from 'next/navigation'; -import { unstable_setRequestLocale } from 'next-intl/server'; -import type { FC } from 'react'; - -import { setClientContext } from '@/client-context'; -import { MDXRenderer } from '@/components/mdxRenderer'; -import WithLayout from '@/components/withLayout'; -import { ENABLE_STATIC_EXPORT, VERCEL_REVALIDATE } from '@/next.constants.mjs'; -import { PAGE_VIEWPORT, DYNAMIC_ROUTES } from '@/next.dynamic.constants.mjs'; -import { dynamicRouter } from '@/next.dynamic.mjs'; -import { availableLocaleCodes, defaultLocale } from '@/next.locales.mjs'; -import { MatterProvider } from '@/providers/matterProvider'; - -type DynamicStaticPaths = { path: Array; locale: string }; -type DynamicParams = { params: DynamicStaticPaths }; - -// This is the default Viewport Metadata -// @see https://nextjs.org/docs/app/api-reference/functions/generate-viewport#generateviewport-function -export const generateViewport = async () => ({ ...PAGE_VIEWPORT }); - -// This generates each page's HTML Metadata -// @see https://nextjs.org/docs/app/api-reference/functions/generate-metadata -export const generateMetadata = async ({ params }: DynamicParams) => { - const { path = [], locale = defaultLocale.code } = params; - - const pathname = dynamicRouter.getPathname(path); - - return dynamicRouter.getPageMetadata(locale, pathname); -}; - -// Gets all mapped routes to the Next.js Routing Engine by Locale -const mapRoutesForLocale = async (locale: string) => { - const routesForLanguage = await dynamicRouter.getRoutesByLanguage(locale); - - return routesForLanguage.map(pathname => - dynamicRouter.mapPathToRoute(locale, pathname) - ); -}; - -// This provides all the possible paths that can be generated statically -// + provides all the paths that we support on the Node.js Website -export const generateStaticParams = async () => { - const paths: Array = []; - - // If static exports are enabled we need to compute all available routes - // And then append them to Next.js's Route Engine - if (ENABLE_STATIC_EXPORT) { - const allAvailableRoutes = await Promise.all( - availableLocaleCodes.map(mapRoutesForLocale) - ); - - paths.push(...allAvailableRoutes.flat()); - } - - return paths.sort(); -}; - -// This method parses the current pathname and does any sort of modifications needed on the route -// then it proceeds to retrieve the Markdown file and parse the MDX Content into a React Component -// finally it returns (if the locale and route are valid) the React Component with the relevant context -// and attached context providers for rendering the current page -const getPage: FC = async ({ params }) => { - const { path = [], locale = defaultLocale.code } = params; - - if (!availableLocaleCodes.includes(locale)) { - // Forces the current locale to be the Default Locale - unstable_setRequestLocale(defaultLocale.code); - - return notFound(); - } - - // Configures the current Locale to be the given Locale of the Request - unstable_setRequestLocale(locale); - - // Gets the current full pathname for a given path - const pathname = dynamicRouter.getPathname(path); - - const staticGeneratedLayout = DYNAMIC_ROUTES.get(pathname); - - // If the current patname is a statically generated route - // it means it does not have a Markdown file nor exists under the filesystem - // but it is a valid route with an assigned layout that should be rendered - if (staticGeneratedLayout !== undefined) { - // Decorate the Locale and current Pathname to Sentry - setTags({ pathname, locale }); - - // Metadata and shared Context to be available through the lifecycle of the page - const sharedContext = { pathname: `/${pathname}` }; - - // Defines a shared Server Context for the Client-Side - // That is shared for all pages under the dynamic router - setClientContext(sharedContext); - - // The Matter Provider allows Client-Side injection of the data - // to a shared React Client Provider even though the page is rendered - // within a server-side context - return ( - - - - ); - } - - // 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, - pathname - ); - - // Decorate the Locale and current Pathname to Sentry - setTags({ pathname, locale, filename }); - - if (source.length && filename.length) { - // This parses the source Markdown content and returns a React Component and - // relevant context from the Markdown File - const { MDXContent, 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, - pathname: `/${pathname}`, - readingTime, - filename, - }; - - // Add Additional relevant reproduction Context from MDX - setContext('MDX Provider', { - frontmatter, - headings, - readingTime, - }); - - // Defines a shared Server Context for the Client-Side - // That is shared for all pages under the dynamic router - setClientContext(sharedContext); - - // The Matter Provider allows Client-Side injection of the data - // to a shared React Client Provider even though the page is rendered - // within a server-side context - return ( - - - - - - ); - } - - return notFound(); -}; - -// In this case we want to catch-all possible pages even to this page. This ensures that we use our 404 -// and that all pages including existing ones are handled here and provide `next-intl` locale also -// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamicparams -export const dynamicParams = true; - -// Enforces that this route is used as static rendering -// Except whenever on the Development mode as we want instant-refresh when making changes -// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic -export const dynamic = 'force-static'; - -// Ensures that this endpoint is invalidated and re-executed every X minutes -// so that when new deployments happen, the data is refreshed -// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#revalidate -export const revalidate = VERCEL_REVALIDATE; - -export default getPage; diff --git a/app/[locale]/error.tsx b/app/[locale]/error.tsx deleted file mode 100644 index 96a35835e0b45..0000000000000 --- a/app/[locale]/error.tsx +++ /dev/null @@ -1,36 +0,0 @@ -'use client'; - -import { ArrowRightIcon } from '@heroicons/react/24/solid'; -import { captureException } from '@sentry/nextjs'; -import { useTranslations } from 'next-intl'; -import type { FC } from 'react'; - -import Button from '@/components/Common/Button'; -import CenteredLayout from '@/layouts/Centered'; - -const ErrorPage: FC<{ error: Error }> = ({ error }) => { - captureException(error); - const t = useTranslations(); - - return ( - -

- -
- 500 -

- {t('layouts.error.internalServerError.title')} -

-

- {t('layouts.error.internalServerError.description')} -

- -
- - ); -}; - -export default ErrorPage; diff --git a/app/[locale]/feed/[feed]/route.ts b/app/[locale]/feed/[feed]/route.ts deleted file mode 100644 index a77e9d9ccd3b4..0000000000000 --- a/app/[locale]/feed/[feed]/route.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { NextResponse } from 'next/server'; - -import provideWebsiteFeeds from '@/next-data/providers/websiteFeeds'; -import { VERCEL_REVALIDATE } from '@/next.constants.mjs'; -import { siteConfig } from '@/next.json.mjs'; -import { defaultLocale } from '@/next.locales.mjs'; - -// We only support fetching these pages from the /en/ locale code -const locale = defaultLocale.code; - -type StaticParams = { params: { feed: string; locale: string } }; - -// This is the Route Handler for the `GET` method which handles the request -// for the Node.js Website Blog Feeds (RSS) -// @see https://nextjs.org/docs/app/building-your-application/routing/router-handlers -export const GET = async (_: Request, { params }: StaticParams) => { - // Generate the Feed for the given feed type (blog, releases, etc) - const websiteFeed = provideWebsiteFeeds(params.feed); - - return new NextResponse(websiteFeed, { - headers: { 'Content-Type': 'application/xml' }, - status: websiteFeed ? 200 : 404, - }); -}; - -// This function generates the static paths that come from the dynamic segments -// `[locale]/feeds/[feed]` and returns an array of all available static paths -// This is used for ISR static validation and generation -export const generateStaticParams = async () => - siteConfig.rssFeeds.map(feed => ({ feed: feed.file, locale })); - -// In this case we want to catch-all possible requests. This is so that if a non defined feed is -// requested we can manually return a 404 response for it instead of having Next.js handle it -// and return our top level custom 404 html page instead -// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamicparams -export const dynamicParams = true; - -// Enforces that this route is cached and static as much as possible -// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic -export const dynamic = 'force-static'; - -// Ensures that this endpoint is invalidated and re-executed every X minutes -// so that when new deployments happen, the data is refreshed -// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#revalidate -export const revalidate = VERCEL_REVALIDATE; diff --git a/app/[locale]/layout.tsx b/app/[locale]/layout.tsx deleted file mode 100644 index 87d0f28dd1801..0000000000000 --- a/app/[locale]/layout.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { Analytics } from '@vercel/analytics/react'; -import { SpeedInsights } from '@vercel/speed-insights/next'; -import classNames from 'classnames'; -import { getLocale } from 'next-intl/server'; -import type { FC, PropsWithChildren } from 'react'; - -import BaseLayout from '@/layouts/Base'; -import { VERCEL_ENV } from '@/next.constants.mjs'; -import { IBM_PLEX_MONO, OPEN_SANS } from '@/next.fonts'; -import { availableLocalesMap, defaultLocale } from '@/next.locales.mjs'; -import { LocaleProvider } from '@/providers/localeProvider'; -import { ThemeProvider } from '@/providers/themeProvider'; - -import '@/styles/index.css'; - -const fontClasses = classNames(IBM_PLEX_MONO.variable, OPEN_SANS.variable); - -const RootLayout: FC = async ({ children }) => { - const locale = await getLocale(); - - const { langDir, hrefLang } = availableLocalesMap[locale] || defaultLocale; - - return ( - - - - - {children} - - - - ; + } + + return ( + , 'href'>)} + /> + ); +}; + +export default Link; diff --git a/apps/site/components/MDX/Calendar/Event/index.module.css b/apps/site/components/MDX/Calendar/Event/index.module.css new file mode 100644 index 0000000000000..f192532d77c24 --- /dev/null +++ b/apps/site/components/MDX/Calendar/Event/index.module.css @@ -0,0 +1,23 @@ +@reference "../../../../styles/index.css"; + +.event { + @apply flex + w-fit + flex-col + gap-1; + + .title { + @apply flex + flex-row + gap-2; + + span { + @apply text-sm + font-bold; + } + } + + a { + @apply text-sm; + } +} diff --git a/apps/site/components/MDX/Calendar/Event/index.tsx b/apps/site/components/MDX/Calendar/Event/index.tsx new file mode 100644 index 0000000000000..63f63441e49ff --- /dev/null +++ b/apps/site/components/MDX/Calendar/Event/index.tsx @@ -0,0 +1,44 @@ +import FormattedTime from '#site/components/Common/FormattedTime'; +import Link from '#site/components/Link'; +import { getZoomLink, isZoned } from '#site/components/MDX/Calendar/utils'; + +import type { CalendarEvent } from '#site/types'; +import type { FC } from 'react'; + +import styles from './index.module.css'; + +type EventProps = Pick< + CalendarEvent, + 'start' | 'end' | 'summary' | 'location' | 'description' +>; + +const Event: FC = ({ + start, + end, + description, + summary, + location, +}) => ( +
+
+ + + + - + + + + (UTC) +
+ + {summary} +
+); + +export default Event; diff --git a/components/MDX/Calendar/UpcomingMeetings.tsx b/apps/site/components/MDX/Calendar/UpcomingMeetings.tsx similarity index 77% rename from components/MDX/Calendar/UpcomingMeetings.tsx rename to apps/site/components/MDX/Calendar/UpcomingMeetings.tsx index cac02b5d71fc8..7a674286db712 100644 --- a/components/MDX/Calendar/UpcomingMeetings.tsx +++ b/apps/site/components/MDX/Calendar/UpcomingMeetings.tsx @@ -1,11 +1,11 @@ -import type { FC } from 'react'; +import FormattedTime from '#site/components/Common/FormattedTime'; +import Event from '#site/components/MDX/Calendar/Event'; +import { getZoomLink, isZoned } from '#site/components/MDX/Calendar/utils'; +import { CALENDAR_NODEJS_ID } from '#site/next.calendar.constants.mjs'; +import { getCalendarEvents } from '#site/next.calendar.mjs'; -import FormattedTime from '@/components/Common/FormattedTime'; -import Event from '@/components/MDX/Calendar/Event'; -import { getZoomLink, isZoned } from '@/components/MDX/Calendar/utils'; -import { CALENDAR_NODEJS_ID } from '@/next.calendar.constants.mjs'; -import { getCalendarEvents } from '@/next.calendar.mjs'; -import type { CalendarEvent } from '@/types'; +import type { CalendarEvent } from '#site/types'; +import type { FC } from 'react'; import styles from './calendar.module.css'; diff --git a/components/MDX/Calendar/calendar.module.css b/apps/site/components/MDX/Calendar/calendar.module.css similarity index 80% rename from components/MDX/Calendar/calendar.module.css rename to apps/site/components/MDX/Calendar/calendar.module.css index 52af750828ddf..ef0999b2b9d58 100644 --- a/components/MDX/Calendar/calendar.module.css +++ b/apps/site/components/MDX/Calendar/calendar.module.css @@ -1,3 +1,5 @@ +@reference "../../../styles/index.css"; + .events { @apply flex flex-col diff --git a/apps/site/components/MDX/Calendar/utils.ts b/apps/site/components/MDX/Calendar/utils.ts new file mode 100644 index 0000000000000..0c051431d969d --- /dev/null +++ b/apps/site/components/MDX/Calendar/utils.ts @@ -0,0 +1,10 @@ +import type { CalendarEvent, ZonedCalendarTime } from '#site/types'; + +export const isZoned = (d: object): d is ZonedCalendarTime => + 'dateTime' in d && 'timeZone' in d; + +export const getZoomLink = ( + event: Pick +) => + event.description?.match(/https:\/\/zoom.us\/j\/\d+/)?.[0] || + event.location?.match(/https:\/\/zoom.us\/j\/\d+/)?.[0]; diff --git a/apps/site/components/MDX/CodeBox/index.tsx b/apps/site/components/MDX/CodeBox/index.tsx new file mode 100644 index 0000000000000..4ea12905881aa --- /dev/null +++ b/apps/site/components/MDX/CodeBox/index.tsx @@ -0,0 +1,21 @@ +import { getLanguageDisplayName } from '@node-core/rehype-shiki'; + +import CodeBox from '#site/components/Common/CodeBox'; + +import type { FC, HTMLAttributes } from 'react'; + +const MDXCodeBox: FC> = ({ + children: code, + className, +}) => { + const matches = className?.match(/language-(?[a-zA-Z]+)/); + const language = matches?.groups?.language ?? ''; + + return ( + + {code} + + ); +}; + +export default MDXCodeBox; diff --git a/apps/site/components/MDX/Image/index.tsx b/apps/site/components/MDX/Image/index.tsx new file mode 100644 index 0000000000000..b0da9f581e232 --- /dev/null +++ b/apps/site/components/MDX/Image/index.tsx @@ -0,0 +1,27 @@ +import Image from 'next/image'; + +import type { ImageProps } from 'next/image'; +import type { FC } from 'react'; + +const MDXImage: FC = ({ width, height, alt, src, ...props }) => { + if (!width || !height) { + // Since `width` and `height` are not provided in the Markdown image format, + // we provide the height and width automatically. + // @see https://nextjs.org/docs/pages/building-your-application/optimizing/images + return ( + {alt} + ); + } + + return {alt}; +}; + +export default MDXImage; diff --git a/apps/site/components/Releases/MinorReleasesTable/index.module.css b/apps/site/components/Releases/MinorReleasesTable/index.module.css new file mode 100644 index 0000000000000..9fc65821e7ff4 --- /dev/null +++ b/apps/site/components/Releases/MinorReleasesTable/index.module.css @@ -0,0 +1,43 @@ +@reference "../../../styles/index.css"; + +.additionalLinks { + @apply flex + h-4 + items-center + gap-2; +} + +.items { + @apply flex + h-9 + gap-2; +} + +.scrollable { + @apply scrollbar-thin + flex + max-h-[29rem] + overflow-y-auto; + + table { + @apply ml:border-t-0 + border-t; + } + + th { + @apply ml:border-t + border-neutral-200 + dark:border-neutral-800; + } +} + +.header { + @apply top-0 + z-10 + border-t + bg-white + text-left + font-semibold + sm:sticky + dark:bg-neutral-950; +} diff --git a/apps/site/components/Releases/MinorReleasesTable/index.tsx b/apps/site/components/Releases/MinorReleasesTable/index.tsx new file mode 100644 index 0000000000000..342d2720f200d --- /dev/null +++ b/apps/site/components/Releases/MinorReleasesTable/index.tsx @@ -0,0 +1,86 @@ +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 LinkWithArrow from '#site/components/Common/LinkWithArrow'; +import Link from '#site/components/Link'; +import ReleaseOverviewItem from '#site/components/Releases/ReleaseOverview/ReleaseOverviewItem'; +import { BASE_CHANGELOG_URL } from '#site/next.constants.mjs'; +import { getNodeApiUrl } from '#site/util/url'; + +import type { MinorVersion } from '#site/types'; +import type { FC } from 'react'; + +import styles from './index.module.css'; + +type MinorReleasesTableProps = { + releases: Array; +}; + +const MinorReleasesTable: FC = ({ releases }) => { + const t = useTranslations(); + + return ( + + + + + + + + + + + + {releases.map(release => ( + + + + + + + + ))} + +
{t('components.minorReleasesTable.version')}{t('components.minorReleasesTable.nApiVersion')}{t('components.minorReleasesTable.npmVersion')}{t('components.minorReleasesTable.v8Version')}{t('components.minorReleasesTable.links')}
+ + v{release.version} + + + {release.modules && ( + + )} + + {release.npm && ( + + )} + + + +
+ + {t('components.minorReleasesTable.actions.docs')} + + + + {t('components.minorReleasesTable.actions.changelog')} + +
+
+ ); +}; + +export default MinorReleasesTable; diff --git a/apps/site/components/Releases/PreviousReleasesTable/TableBody.tsx b/apps/site/components/Releases/PreviousReleasesTable/TableBody.tsx new file mode 100644 index 0000000000000..f7c047506aadb --- /dev/null +++ b/apps/site/components/Releases/PreviousReleasesTable/TableBody.tsx @@ -0,0 +1,80 @@ +'use client'; + +import Badge from '@node-core/ui-components/Common/Badge'; +import { useTranslations } from 'next-intl'; +import { Fragment, useState } from 'react'; + +import FormattedTime from '#site/components/Common/FormattedTime'; +import LinkWithArrow from '#site/components/Common/LinkWithArrow'; +import Link from '#site/components/Link'; +import { STATUS_KIND_MAP } from '#site/next.constants.mjs'; + +import type { NodeRelease } from '#site/types'; +import type { FC } from 'react'; + +import ReleaseModal from '../ReleaseModal'; + +type PreviousReleasesTableBodyProps = { + releaseData: Array; +}; + +const PreviousReleasesTableBody: FC = ({ + releaseData, +}) => { + const t = useTranslations(); + + const [currentModal, setCurrentModal] = useState(); + + return ( + + {releaseData.map(release => ( + + + + + v{release.major} + + + + + {release.codename || '-'} + + + + + + + + + + + + + {release.status} + + + + + setCurrentModal(release.version)} + > + {t('components.downloadReleasesTable.details')} + + + + + open || setCurrentModal(undefined)} + /> + + ))} + + ); +}; + +export default PreviousReleasesTableBody; diff --git a/apps/site/components/Releases/PreviousReleasesTable/index.tsx b/apps/site/components/Releases/PreviousReleasesTable/index.tsx new file mode 100644 index 0000000000000..a018f0114732f --- /dev/null +++ b/apps/site/components/Releases/PreviousReleasesTable/index.tsx @@ -0,0 +1,32 @@ +import { getTranslations } from 'next-intl/server'; + +import provideReleaseData from '#site/next-data/providers/releaseData'; + +import type { FC } from 'react'; + +import PreviousReleasesTableBody from './TableBody'; + +const PreviousReleasesTable: FC = async () => { + const releaseData = await provideReleaseData(); + + const t = await getTranslations(); + + return ( + + + + + + + + + + + + + +
{t('components.downloadReleasesTable.version')}{t('components.downloadReleasesTable.codename')}{t('components.downloadReleasesTable.firstReleased')}{t('components.downloadReleasesTable.lastUpdated')}{t('components.downloadReleasesTable.status')}
+ ); +}; + +export default PreviousReleasesTable; diff --git a/apps/site/components/Releases/ReleaseModal.tsx b/apps/site/components/Releases/ReleaseModal.tsx new file mode 100644 index 0000000000000..25036a777c243 --- /dev/null +++ b/apps/site/components/Releases/ReleaseModal.tsx @@ -0,0 +1,44 @@ +import { Modal, Title, Content } from '@node-core/ui-components/Common/Modal'; +import { useTranslations } from 'next-intl'; + +import MinorReleasesTable from '#site/components/Releases/MinorReleasesTable'; +import ReleaseOverview from '#site/components/Releases/ReleaseOverview'; +import WithReleaseAlertBox from '#site/components/withReleaseAlertBox'; + +import type { NodeRelease } from '#site/types'; +import type { ComponentProps, FC } from 'react'; + +type ReleaseModalProps = ComponentProps & { + release: NodeRelease; +}; + +const ReleaseModal: FC = ({ release, ...props }) => { + const t = useTranslations(); + + const modalHeadingKey = release.codename + ? 'components.releaseModal.title' + : 'components.releaseModal.titleWithoutCodename'; + + const modalHeading = t(modalHeadingKey, { + version: release.major, + codename: release.codename ?? '', + }); + + return ( + + + + {modalHeading} + + + + +
{t('components.releaseModal.minorVersions')}
+ + +
+
+ ); +}; + +export default ReleaseModal; diff --git a/apps/site/components/Releases/ReleaseOverview/ReleaseOverviewItem/index.module.css b/apps/site/components/Releases/ReleaseOverview/ReleaseOverviewItem/index.module.css new file mode 100644 index 0000000000000..99e446cdb856d --- /dev/null +++ b/apps/site/components/Releases/ReleaseOverview/ReleaseOverviewItem/index.module.css @@ -0,0 +1,21 @@ +@reference "../../../../styles/index.css"; + +.item { + @apply flex + items-center + gap-2; + + h1 { + @apply text-sm + font-semibold; + } + + h2 { + @apply text-xs + font-normal; + } + + svg { + @apply size-4; + } +} diff --git a/apps/site/components/Releases/ReleaseOverview/ReleaseOverviewItem/index.tsx b/apps/site/components/Releases/ReleaseOverview/ReleaseOverviewItem/index.tsx new file mode 100644 index 0000000000000..36e5d44e092aa --- /dev/null +++ b/apps/site/components/Releases/ReleaseOverview/ReleaseOverviewItem/index.tsx @@ -0,0 +1,31 @@ +import classNames from 'classnames'; + +import type { FC, ReactNode, SVGProps } from 'react'; + +import styles from './index.module.css'; + +type ReleaseOverviewItemProps = { + Icon: FC>; + title: ReactNode; + subtitle?: ReactNode; + className?: string; +}; + +const ReleaseOverviewItem: FC = ({ + Icon, + title, + subtitle, + className, +}) => { + return ( +
+ +
+ {subtitle &&

{subtitle}

} +

{title}

+
+
+ ); +}; + +export default ReleaseOverviewItem; diff --git a/apps/site/components/Releases/ReleaseOverview/index.module.css b/apps/site/components/Releases/ReleaseOverview/index.module.css new file mode 100644 index 0000000000000..7ad4ef58f3f47 --- /dev/null +++ b/apps/site/components/Releases/ReleaseOverview/index.module.css @@ -0,0 +1,18 @@ +@reference "../../../styles/index.css"; + +.root { + @apply rounded + border + border-neutral-200 + p-4 + text-neutral-900 + dark:border-neutral-800 + dark:text-white; + + .container { + @apply grid + grid-cols-2 + gap-4 + lg:grid-cols-3; + } +} diff --git a/apps/site/components/Releases/ReleaseOverview/index.tsx b/apps/site/components/Releases/ReleaseOverview/index.tsx new file mode 100644 index 0000000000000..481314102ead3 --- /dev/null +++ b/apps/site/components/Releases/ReleaseOverview/index.tsx @@ -0,0 +1,73 @@ +import { + CalendarIcon, + ClockIcon, + CodeBracketSquareIcon, + Square3Stack3DIcon, +} from '@heroicons/react/24/outline'; +import NpmIcon from '@node-core/ui-components/Icons/PackageManager/Npm'; +import { useTranslations } from 'next-intl'; + +import FormattedTime from '#site/components/Common/FormattedTime'; + +import type { NodeRelease } from '#site/types'; +import type { FC } from 'react'; + +import ReleaseOverviewItem from './ReleaseOverviewItem'; + +import styles from './index.module.css'; + +type ReleaseOverviewProps = { + release: NodeRelease; +}; + +const ReleaseOverview: FC = ({ release }) => { + const t = useTranslations(); + + return ( +
+
+ } + subtitle={t('components.releaseOverview.firstReleased')} + /> + + } + subtitle={t('components.releaseOverview.lastUpdated')} + /> + + + + {release.modules && ( + + )} + + {release.npm && ( + + )} + + +
+
+ ); +}; + +export default ReleaseOverview; diff --git a/apps/site/components/withAvatarGroup.tsx b/apps/site/components/withAvatarGroup.tsx new file mode 100644 index 0000000000000..f9c3491dd8c92 --- /dev/null +++ b/apps/site/components/withAvatarGroup.tsx @@ -0,0 +1,34 @@ +'use client'; + +import AvatarGroup from '@node-core/ui-components/Common/AvatarGroup'; + +import Link from '#site/components/Link'; +import { getAuthors } from '#site/util/author'; + +import type { AuthorProps } from '#site/types'; +import type { ComponentProps, FC } from 'react'; + +type WithAvatarGroupProps = Omit< + ComponentProps, + 'avatars' | 'as' +> & + AuthorProps; + +const WithAvatarGroup: FC = ({ + usernames, + names, + clickable = true, + ...props +}) => ( + +); + +export default WithAvatarGroup; diff --git a/apps/site/components/withBadgeGroup.tsx b/apps/site/components/withBadgeGroup.tsx new file mode 100644 index 0000000000000..be4263437700c --- /dev/null +++ b/apps/site/components/withBadgeGroup.tsx @@ -0,0 +1,31 @@ +import BadgeGroup from '@node-core/ui-components/Common/BadgeGroup'; + +import Link from '#site/components/Link'; +import { siteConfig } from '#site/next.json.mjs'; +import { dateIsBetween } from '#site/util/date'; + +import type { FC } from 'react'; + +const WithBadgeGroup: FC<{ section: string }> = ({ section }) => { + const badge = siteConfig.websiteBadges[section]; + + if (badge && dateIsBetween(badge.startDate, badge.endDate)) { + return ( + + {badge.text} + + ); + } + + return null; +}; + +export default WithBadgeGroup; diff --git a/apps/site/components/withBanner.tsx b/apps/site/components/withBanner.tsx new file mode 100644 index 0000000000000..b8a186d01b23a --- /dev/null +++ b/apps/site/components/withBanner.tsx @@ -0,0 +1,57 @@ +'use client'; + +import { ArrowUpRightIcon } from '@heroicons/react/24/outline'; +import Banner from '@node-core/ui-components/Common/Banner'; +import { useTranslations } from 'next-intl'; +import { useEffect, useState } from 'react'; + +import Link from '#site/components/Link'; +import { siteConfig } from '#site/next.json.mjs'; +import { dateIsBetween } from '#site/util/date'; + +import type { FC } from 'react'; + +const STORAGE_KEY = 'banner-dismissal'; + +const WithBanner: FC<{ section: string }> = ({ section }) => { + const banner = siteConfig.websiteBanners[section]; + const t = useTranslations(); + + const [open, setOpen] = useState(false); + + useEffect(() => { + if (banner) { + // eslint-disable-next-line @eslint-react/set-state-in-effect + setOpen(localStorage.getItem(STORAGE_KEY) !== banner.text); + } + }, [banner]); + + if (banner && open && dateIsBetween(banner.startDate, banner.endDate)) { + const bannerType = banner.type || 'default'; + + const onClose = () => { + localStorage.setItem(STORAGE_KEY, banner.text); + setOpen(false); + }; + + return ( + + {banner.link ? ( + {banner.text} + ) : ( + banner.text + )} + {banner.link && } + + ); + } + + return null; +}; + +export default WithBanner; diff --git a/apps/site/components/withBlogCategories.tsx b/apps/site/components/withBlogCategories.tsx new file mode 100644 index 0000000000000..e4b58cebf793c --- /dev/null +++ b/apps/site/components/withBlogCategories.tsx @@ -0,0 +1,61 @@ +import { useTranslations } from 'next-intl'; + +import BlogPostCard from '#site/components/Blog/BlogPostCard'; +import LinkTabs from '#site/components/Common/LinkTabs'; +import Pagination from '#site/components/Common/Pagination'; +import { mapAuthorToCardAuthors } from '#site/util/author'; + +import type { BlogPostsRSC } from '#site/types'; +import type { ComponentProps, FC } from 'react'; + +type WithBlogCategoriesProps = { + categories: ComponentProps['tabs']; + blogData: BlogPostsRSC & { category: string; page: number }; +}; + +const mapPaginationPages = (category: string, pages: number) => + [...Array(pages).keys()].map(page => ({ + url: `/blog/${category}/page/${page + 1}`, + })); + +const WithBlogCategories: FC = ({ + categories, + blogData, +}) => { + const t = useTranslations(); + + return ( + <> + +
+ {blogData.posts.map(post => ( + + ))} +
+
+ +
+ +
+ + ); +}; + +export default WithBlogCategories; diff --git a/apps/site/components/withBlogCrossLinks.tsx b/apps/site/components/withBlogCrossLinks.tsx new file mode 100644 index 0000000000000..468100521dddc --- /dev/null +++ b/apps/site/components/withBlogCrossLinks.tsx @@ -0,0 +1,51 @@ +import { getClientContext } from '#site/client-context'; +import CrossLink from '#site/components/Common/CrossLink'; +import { getBlogData } from '#site/util/blog'; + +import type { BlogCategory } from '#site/types'; +import type { FC } from 'react'; + +const WithBlogCrossLinks: FC = () => { + const { pathname } = getClientContext(); + + // Extracts from the static URL the components used for the Blog Post slug + const [, , category, postname] = pathname.split('/') as [ + unknown, + unknown, + BlogCategory, + string, + ]; + + const { posts } = getBlogData(category); + + const currentItem = posts.findIndex( + ({ slug }) => slug === `/blog/${category}/${postname}` + ); + + const [previousCrossLink, nextCrossLink] = [ + posts[currentItem - 1], + posts[currentItem + 1], + ]; + + return ( +
+ {(previousCrossLink && ( + + )) ||
} + + {nextCrossLink && ( + + )} +
+ ); +}; + +export default WithBlogCrossLinks; diff --git a/apps/site/components/withBreadcrumbs.tsx b/apps/site/components/withBreadcrumbs.tsx new file mode 100644 index 0000000000000..753387ad35abd --- /dev/null +++ b/apps/site/components/withBreadcrumbs.tsx @@ -0,0 +1,76 @@ +'use client'; + +import Breadcrumbs from '@node-core/ui-components/Common/Breadcrumbs'; +import { useTranslations } from 'next-intl'; + +import Link from '#site/components/Link'; +import useClientContext from '#site/hooks/useClientContext'; +import useMediaQuery from '#site/hooks/useMediaQuery'; +import useSiteNavigation from '#site/hooks/useSiteNavigation'; +import { dashToCamelCase } from '#site/util/string'; + +import type { NavigationKeys } from '#site/types'; +import type { BreadcrumbLink } from '@node-core/ui-components/Common/Breadcrumbs'; +import type { FC } from 'react'; + +type WithBreadcrumbsProps = { + navKeys?: Array; +}; + +const WithBreadcrumbs: FC = ({ navKeys = [] }) => { + const { getSideNavigation } = useSiteNavigation(); + const t = useTranslations(); + const { pathname } = useClientContext(); + const isMobileScreen = useMediaQuery('(max-width: 639px)'); + + const maxLength = isMobileScreen ? 2 : 4; + + const getBreadcrumbs = () => { + const navigationTree = getSideNavigation(navKeys); + + const pathList = pathname + .split('/') + .filter(item => item !== '') + .map(dashToCamelCase); + + let currentNode = navigationTree; + + // Reduce the pathList to a breadcrumbs array by finding each path in the current navigation layer, + // updating the currentNode to the found node's items(next layer) for the next iteration. + return pathList.reduce((breadcrumbs, path, index) => { + const nodeWithCurrentPath = currentNode.find( + ([nodePath, entry]) => + // Checking link in cases where nodePath cannot = path. Like 'discoverJavaScriptTimers' + (nodePath === path || entry.link === pathname) && + // Skip checking child path if it is the last path since there is no more child item inside + (index === pathList.length - 1 || + entry.items.some( + ([childPath, entry]) => + childPath === pathList[index + 1] || entry.link === pathname + )) + ); + + if (nodeWithCurrentPath) { + const [, { label, link = '', items = [] }] = nodeWithCurrentPath; + + // Goes deeper on the tree of items if there are any. + currentNode = items; + + return label ? [...breadcrumbs, { label, href: link }] : breadcrumbs; + } + + return breadcrumbs; + }, [] as Array); + }; + + return ( + + ); +}; + +export default WithBreadcrumbs; diff --git a/apps/site/components/withDownloadArchive.tsx b/apps/site/components/withDownloadArchive.tsx new file mode 100644 index 0000000000000..ec9f47b37fa95 --- /dev/null +++ b/apps/site/components/withDownloadArchive.tsx @@ -0,0 +1,46 @@ +import { notFound } from 'next/navigation'; + +import { getClientContext } from '#site/client-context'; +import provideReleaseData from '#site/next-data/providers/releaseData'; +import { + buildReleaseArtifacts, + extractVersionFromPath, +} from '#site/util/download/archive'; + +import type { FC } from 'react'; + +type DownloadArchive = ReturnType; + +type WithDownloadArchiveProps = { + children: FC; +}; + +/** + * Higher-order component that extracts version from pathname, + * fetches release data, and provides download artifacts to child component + */ +const WithDownloadArchive: FC = async ({ + children: Component, +}) => { + const { pathname } = getClientContext(); + + // Extract version from pathname + const version = extractVersionFromPath(pathname); + + // Find the release data for the given version + const releaseData = await provideReleaseData(); + const release = releaseData.find(release => + // Match major version only (e.g., v22.x.x for release.major v22) + version.startsWith(`v${release.major}`) + )!; + + if (!release) { + return notFound(); + } + + const releaseArtifacts = buildReleaseArtifacts(release, version); + + return ; +}; + +export default WithDownloadArchive; diff --git a/apps/site/components/withDownloadSection.tsx b/apps/site/components/withDownloadSection.tsx new file mode 100644 index 0000000000000..373eebaafb290 --- /dev/null +++ b/apps/site/components/withDownloadSection.tsx @@ -0,0 +1,55 @@ +import { defaultLocale } from '@node-core/website-i18n'; +import { getLocale } from 'next-intl/server'; + +import { getClientContext } from '#site/client-context'; +import WithNodeRelease from '#site/components/withNodeRelease'; +import provideDownloadSnippets from '#site/next-data/providers/downloadSnippets'; +import { + ReleaseProvider, + ReleasesProvider, +} from '#site/providers/releaseProvider'; + +import type { NodeRelease } from '../types'; +import type { FC, PropsWithChildren } from 'react'; + +type WithDownloadSectionProps = PropsWithChildren<{ + releases: Array; +}>; + +const WithDownloadSection: FC = async ({ + releases, + children, +}) => { + const locale = await getLocale(); + + const snippets = await provideDownloadSnippets(); + + const localeSnippets = snippets.get(locale) ?? []; + + // By default the translated languages do not contain all the download snippets + // Hence we always merge any translated snippet with the fallbacks for missing snippets + const fallbackSnippets = snippets.get(defaultLocale.code) ?? []; + + const { pathname } = getClientContext(); + + // Some available translations do not have download snippets translated or have them partially translated + // This aims to merge the available translated snippets with the fallback snippets + const memoizedSnippets = fallbackSnippets + .filter(snippet => !localeSnippets.some(s => s.name === snippet.name)) + .concat(localeSnippets); + + // Decides which initial release to use based on the current pathname + const initialRelease = pathname.endsWith('/current') ? 'Current' : 'LTS'; + + return ( + + {({ release }) => ( + + {children} + + )} + + ); +}; + +export default WithDownloadSection; diff --git a/apps/site/components/withFooter.tsx b/apps/site/components/withFooter.tsx new file mode 100644 index 0000000000000..148aa7e65e08f --- /dev/null +++ b/apps/site/components/withFooter.tsx @@ -0,0 +1,69 @@ +import BadgeGroup from '@node-core/ui-components/Common/BadgeGroup'; +import Footer from '@node-core/ui-components/Containers/Footer'; +import { useTranslations } from 'next-intl'; + +import { getClientContext } from '#site/client-context'; +import Link from '#site/components/Link'; +import { siteNavigation } from '#site/next.json.mjs'; + +import type { FC } from 'react'; + +import WithLegal from './withLegal'; +import WithNodeRelease from './withNodeRelease'; + +const WithFooter: FC = () => { + const t = useTranslations(); + const { pathname } = getClientContext(); + + const { socialLinks, footerLinks } = siteNavigation; + + const navigation = { + socialLinks, + footerLinks: footerLinks.map(link => ({ + ...link, + translation: t(link.text), + })), + }; + + const primary = ( +
+ + {({ release }) => ( + + {t('components.containers.footer.releasePills.latestLTS')} + + )} + + + + {({ release }) => ( + + {t('components.containers.footer.releasePills.latestRelease')} + + )} + +
+ ); + + const legal = ; + + return ( +