diff --git a/.browserslistrc b/.browserslistrc deleted file mode 100644 index ea5c29134f6fc..0000000000000 --- a/.browserslistrc +++ /dev/null @@ -1,4 +0,0 @@ -# https://github.com/browserslist/browserslist#readme - -defaults -Explorer >= 10 diff --git a/.editorconfig b/.editorconfig index e85c3fc5982fe..5c165c38584da 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,13 +1,10 @@ -# https://editorconfig.org/ - root = true [*] indent_style = space indent_size = 2 end_of_line = lf -insert_final_newline = true +charset = utf-8 trim_trailing_whitespace = true - -[*.svg] -insert_final_newline = false +insert_final_newline = true +max_line_length = 80 diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index dd6fec1664e68..0000000000000 --- a/.eslintignore +++ /dev/null @@ -1,6 +0,0 @@ -**/*.min.js -static/legacy/ -external/ -build/ -# Top level await isn't supported till ESLint 8 -locale/en/blog/release/v17.0.0.md diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 6d4d41151c94b..0000000000000 --- a/.eslintrc +++ /dev/null @@ -1,46 +0,0 @@ -{ - "extends": [ - "eslint:recommended", - "semistandard", - "prettier" - ], - "plugins": [ - "prettier" - ], - "rules": { - "prettier/prettier": "error" - }, - "overrides": [ - { - "files": [ - "**/*.md" - ], - "plugins": [ - "markdown" - ], - "processor": "markdown/markdown" - }, - { - "files": [ - "**/*.md/*.js" - ], - "rules": { - "eqeqeq": "off", - "no-const-assign": "off", - "no-undef": "off", - "no-unused-expressions": "off", - "no-unused-vars": "off", - "node/handle-callback-err": "off", - "node/no-deprecated-api": "off", - "prefer-const": "off", - "prettier/prettier": [ - "error", - { - "singleQuote": true, - "trailingComma": "none" - } - ] - } - } - ] -} diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000000..2e8ac23f5b9dc --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,47 @@ +# 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.ts @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 +apps/site/wrangler.jsonc @nodejs/web-infra +apps/site/open-next.config.ts @nodejs/web-infra +apps/site/redirects.json @nodejs/web-infra + +# Critical Documents +LICENSE @nodejs/tsc +GOVERNANCE.md @nodejs/tsc +CONTRIBUTING.md @nodejs/nodejs-website @nodejs/web-infra +docs @nodejs/nodejs-website @nodejs/web-infra +SECURITY.md @nodejs/security-wg + +# Node.js Release Blog Posts +apps/site/pages/en/blog/release @nodejs/releasers +apps/site/pages/en/blog/announcements @nodejs/releasers + +# Specific content +apps/site/pages/en/learn/getting-started/security-best-practices.md @nodejs/security-wg +apps/site/pages/en/learn/typescript @nodejs/typescript diff --git a/.github/ISSUE_TEMPLATE/01-bug-report.yml b/.github/ISSUE_TEMPLATE/01-bug-report.yml new file mode 100644 index 0000000000000..281e5e0159968 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/01-bug-report.yml @@ -0,0 +1,50 @@ +name: Report a Technical/Visual Issue on the Node.js Website +description: 'Is something not working as expected? Did you encounter a glitch or a bug with the Website?' +labels: [bug] +body: + - type: markdown + attributes: + value: | + Thanks for reporting an issue you've found on the nodejs.org website. + Please fill in the template below. If unsure about something, just do as best + as you're able. If you are reporting a visual glitch, it will be much easier + for us to fix it when you attach a screenshot as well. + - type: input + attributes: + label: 'URL:' + description: The URL of the page you are reporting an issue on. + placeholder: https://nodejs.org/en/ + validations: + required: true + - type: input + attributes: + label: 'Browser Name:' + description: What kind of browser are you using? + placeholder: Chrome + validations: + required: true + - type: input + attributes: + label: 'Browser Version:' + description: What version of browser are you using? + placeholder: '103.0.5060.134' + validations: + required: true + - type: input + attributes: + label: 'Operating System:' + description: What kind of operation system are you using + (Write it in full, with version number)? + placeholder: 'Windows 10, 21H2, 19044.1826' + validations: + required: true + - type: textarea + attributes: + label: 'How to reproduce the issue:' + placeholder: | + 1. What I did. + 2. What I expected to happen. + 3. What I actually got. + 4. If possible, images or videos are welcome. + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/01-bug_report.md b/.github/ISSUE_TEMPLATE/01-bug_report.md deleted file mode 100755 index 1456ea7a0d53a..0000000000000 --- a/.github/ISSUE_TEMPLATE/01-bug_report.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: 🐛 Bug Report -about: If something isn't working as expected 🤔. ---- - - - -- **URL**: -- **Browser version**: -- **Operating system**: - - diff --git a/.github/ISSUE_TEMPLATE/02-feature-request.yml b/.github/ISSUE_TEMPLATE/02-feature-request.yml new file mode 100644 index 0000000000000..da59bf9dbc09b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/02-feature-request.yml @@ -0,0 +1,19 @@ +name: Suggest a new feature or improvement for the Node.js Website +description: 'Do you have an idea or a suggestion and you want to share?' +labels: [feature request] +body: + - type: markdown + attributes: + value: | + You have an idea how to improve the site? That's awesome! + Before submitting, please have a look at the existing issues if there's already + something related to your suggestion. + - type: textarea + attributes: + label: 'Enter your suggestions in details:' + placeholder: | + 1. What I expected to happen. + 2. Your reason (if possible, images or videos are welcome). + 3. What I plan to do (Optional but better). + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/02-feature_request.md b/.github/ISSUE_TEMPLATE/02-feature_request.md deleted file mode 100755 index ea9ee3e081cf3..0000000000000 --- a/.github/ISSUE_TEMPLATE/02-feature_request.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: 🚀 Feature Request -about: I have a suggestion (and may want to implement it 🙂)! ---- - - diff --git a/.github/ISSUE_TEMPLATE/03-article-issue.yml b/.github/ISSUE_TEMPLATE/03-article-issue.yml new file mode 100644 index 0000000000000..a30127b2af336 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/03-article-issue.yml @@ -0,0 +1,28 @@ +name: Report an issue on a Learn article +description: 'Found an error or something unclear in one of our Learn articles? Let us know!' +labels: [bug, content, learn] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to help improve the Node.js learning experience! + Before submitting, please check if there's already an existing issue that matches yours. + + - type: input + attributes: + label: 'Affected URL' + placeholder: 'https://nodejs.org/en/learn/some-article' + description: 'Please include the full URL of the article where the issue exists.' + validations: + required: true + + - type: textarea + attributes: + label: 'Describe the issue in detail:' + placeholder: | + 1. What is wrong or unclear? + 2. What did you expect to see? + 3. Any suggestions or corrections? + description: 'Tell us what you noticed and how we can improve the article.' + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/03-i18n.md b/.github/ISSUE_TEMPLATE/03-i18n.md deleted file mode 100644 index a3e83b70fe8d6..0000000000000 --- a/.github/ISSUE_TEMPLATE/03-i18n.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: 🔡 Internationalization and translations -about: Hello. Hola. Salut. Ciao. Здравствуйте. こんにちは. ---- - - diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 87170b0f95f28..ce6f540b49da4 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,17 @@ -blank_issues_enabled: false +blank_issues_enabled: true contact_links: - - name: ⁉️ Need help with Node.js? + - 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?' + - name: Need help with Node.js? url: https://github.com/nodejs/help/issues/ - about: File an issue in our help repo. - - name: 📗 Node.js API Docs - url: https://github.com/nodejs/nodejs.org/issues/new/choose - about: Please open an issue in the main Node.js repo, prefixed with "doc". + about: "Struggling with Node.js? You're not sure how to code?" diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000000..fc641b4b78268 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,32 @@ + + +## Description + + + +## Validation + + + +## Related Issues + + + +### Check List + + + +- [ ] 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 `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 774051b2278c7..2a776fb78dafa 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,12 +1,99 @@ version: 2 updates: - package-ecosystem: github-actions - directory: "/" + directory: '/' schedule: - interval: weekly + interval: monthly + labels: + - 'github_actions:pull-request' + commit-message: + prefix: meta + cooldown: + default-days: 3 open-pull-requests-limit: 10 + - package-ecosystem: npm - directory: "/" + directory: '/' + versioning-strategy: increase schedule: - interval: weekly + interval: monthly + labels: + - 'github_actions:pull-request' + commit-message: + prefix: meta + cooldown: + default-days: 3 + groups: + lint: + patterns: + - '@eslint/*' + - '@typescript-eslint/*' + - acorn + - eslint + - eslint-* + - lint-staged + - prettier + - prettier-* + - stylelint + - stylelint-* + - typescript-eslint + - unified + exclude-patterns: + - 'eslint-plugin-storybook' + - 'prettier-plugin-tailwindcss' + mdx: + patterns: + - '@vcarl/remark-headings' + - '@shikijs/*' + - '@mdx-js/*' + - hast-util-* + - rehype-* + - remark-* + - shiki + - sval + - unist-util-* + - vfile + - vfile-* + - reading-time + orama: + patterns: + - '@orama/*' + - '@oramacloud/*' + radix: + patterns: + - '@radix-ui/*' + react: + patterns: + - 'react' + - 'react-dom' + - '@types/react' + 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: + - 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/scripts/report-inactive-collaborators.mjs b/.github/scripts/report-inactive-collaborators.mjs new file mode 100644 index 0000000000000..a1b306dba9786 --- /dev/null +++ b/.github/scripts/report-inactive-collaborators.mjs @@ -0,0 +1,131 @@ +import { readFile } from 'node:fs/promises'; + +const CONFIG = { + GOVERNANCE_FILE: 'GOVERNANCE.md', + CURRENT_MEMBERS_HEADER: '#### Current Members', + INACTIVE_MONTHS: 12, + ISSUE_TITLE: 'Inactive Collaborator Report', + ISSUE_LABELS: ['meta', 'inactive-collaborator-report'], +}; + +// Get date N months ago in YYYY-MM-DD format +const getDateMonthsAgo = (months = CONFIG.INACTIVE_MONTHS) => { + const date = new Date(); + date.setMonth(date.getMonth() - months); + return date.toISOString().split('T')[0]; +}; + +// Check if there's already an open issue +async function hasOpenIssue(github, context) { + const { owner, repo } = context.repo; + const { data: issues } = await github.rest.issues.listForRepo({ + owner, + repo, + state: 'open', + labels: CONFIG.ISSUE_LABELS[1], + per_page: 1, + }); + + return issues.length > 0; +} + +// Parse collaborator usernames from governance file +async function parseCollaborators() { + const content = await readFile(CONFIG.GOVERNANCE_FILE, 'utf8'); + const lines = content.split('\n'); + const collaborators = []; + + const startIndex = + lines.findIndex(l => l.startsWith(CONFIG.CURRENT_MEMBERS_HEADER)) + 1; + if (startIndex <= 0) return collaborators; + + for (let i = startIndex; i < lines.length; i++) { + const line = lines[i]; + if (line.startsWith('#')) break; + + const match = line.match(/^\s*-\s*\[([^\]]+)\]/); + if (match) collaborators.push(match[1]); + } + + return collaborators; +} + +// Check if users have been active since cutoff date +async function getInactiveUsers(github, usernames, repo, cutoffDate) { + const inactiveUsers = []; + + for (const username of usernames) { + // Check commits + const { data: commits } = await github.rest.search.commits({ + q: `author:${username} repo:${repo} committer-date:>=${cutoffDate}`, + per_page: 1, + }); + + // Check issues and PRs + const { data: issues } = await github.rest.search.issuesAndPullRequests({ + q: `involves:${username} repo:${repo} updated:>=${cutoffDate}`, + per_page: 1, + }); + + // User is inactive if they have no commits AND no issues/PRs + if (commits.total_count === 0 && issues.total_count === 0) { + inactiveUsers.push(username); + } + } + + return inactiveUsers; +} + +// Generate report for inactive members +function formatReport(inactiveMembers, cutoffDate) { + if (!inactiveMembers.length) return null; + + const today = getDateMonthsAgo(0); + return `# Inactive Collaborators Report + +Last updated: ${today} +Checking for inactivity since: ${cutoffDate} + +## Inactive Collaborators (${inactiveMembers.length}) + +| Login | +| ----- | +${inactiveMembers.map(m => `| @${m} |`).join('\n')} + +## What happens next? + +@nodejs/nodejs-website should review this list and contact inactive collaborators to confirm their continued interest in participating in the project.`; +} + +async function createIssue(github, context, report) { + if (!report) return; + + const { owner, repo } = context.repo; + await github.rest.issues.create({ + owner, + repo, + title: CONFIG.ISSUE_TITLE, + body: report, + labels: CONFIG.ISSUE_LABELS, + }); +} + +export default async function (github, context) { + // Check for existing open issue first - exit early if one exists + if (await hasOpenIssue(github, context)) { + return; + } + + const cutoffDate = getDateMonthsAgo(); + const collaborators = await parseCollaborators(); + + const inactiveMembers = await getInactiveUsers( + github, + collaborators, + `${context.repo.owner}/${context.repo.repo}`, + cutoffDate + ); + const report = formatReport(inactiveMembers, cutoffDate); + + await createIssue(github, context, report); +} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000000000..a0efcf15db46b --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,85 @@ +# 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: Build + +on: + push: + branches: + - main + pull_request: + branches: + - main + merge_group: + +defaults: + run: + # This ensures that the working directory is the root of the repository + working-directory: ./ + +permissions: + contents: read + actions: read + +env: + # See https://turbo.build/repo/docs/reference/command-line-reference/run#--cache-dir + TURBO_ARGS: --cache-dir=.turbo/cache + +jobs: + build: + name: Build on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + + steps: + - name: Harden Runner + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + with: + egress-policy: audit + + - name: Use GNU tar instead BSD tar + # This ensures that we use GNU `tar` which is more efficient for extracting caches's + if: matrix.os == 'windows-latest' + shell: cmd + run: echo C:\Program Files\Git\usr\bin>>"%GITHUB_PATH%" + + - name: Git Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + 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: Set up pnpm + uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + + - name: Set up Node.js + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.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 + # We only want to install required production packages + run: pnpm install --prod --frozen-lockfile + + - name: Build Next.js + # We want a ISR build on CI to ensure that regular Next.js builds work as expected. + run: node_modules/.bin/turbo build ${{ env.TURBO_ARGS }} + 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 + NEXT_PUBLIC_STATIC_EXPORT_LOCALE: ${{ github.event_name == 'push' }} diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml new file mode 100644 index 0000000000000..46fc4f70b723f --- /dev/null +++ b/.github/workflows/chromatic.yml @@ -0,0 +1,92 @@ +# 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_target: + branches: + - main + paths: + - packages/ui-components/** + - .github/workflows/chromatic.yml + types: + - labeled + workflow_dispatch: + +defaults: + run: + # This ensures that the working directory is the root of the repository + working-directory: ./ + +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_target' || + ( + github.event.label.name == 'github_actions: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: + - name: Harden Runner + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + with: + egress-policy: audit + + - name: Git Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + # Provides the Pull Request commit SHA or the GitHub merge group ref + ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || 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 pnpm + uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + + - name: Set up Node.js + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.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: Start Visual Regression Tests (Chromatic) + # This assigns the Environment Deployment for Storybook + id: chromatic-deploy + uses: chromaui/action@d0795df816d05c4a89c80295303970fddd247cce # v13.1.4 + with: + workingDir: packages/ui-components + buildScriptName: storybook:build + projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} + exitOnceUploaded: true + onlyChanged: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index cba25c8dad301..0000000000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: CI - -on: - push: - branches: - - main - pull_request: - workflow_dispatch: - -env: - FORCE_COLOR: 2 - -jobs: - lint: - name: Lint - runs-on: ubuntu-latest - - steps: - - name: Clone repository - uses: actions/checkout@v2 - - - name: Set up Node.js - uses: actions/setup-node@v2 - with: - node-version: "lts/*" - cache: npm - - - name: Install npm dependencies - run: npm ci - - - name: Lint - run: | - echo "::add-matcher::.github/workflows/remark-lint-problem-matcher.json" - npm run test:lint - - test: - name: Node on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, windows-latest] - - steps: - - name: Clone repository - uses: actions/checkout@v2 - - - name: Set up Node.js - uses: actions/setup-node@v2 - with: - node-version: "lts/*" - cache: npm - - - run: java -version - - - name: Install npm dependencies - run: npm ci - - - name: Build - run: npm run build - - - name: Run unit tests - run: npm run test:unit - - - name: Run HTML validator - run: npm run test:html - - - name: Run linkinator - uses: JustinBeckwith/linkinator-action@v1 - with: - linksToSkip: "^(?!http://localhost)" - paths: en - recurse: true - serverRoot: build - verbosity: error diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000000000..8c6a19218518d --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,78 @@ +# 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: + push: + branches: ['main'] + pull_request: + # The branches below must be a subset of the branches above + branches: ['main'] + schedule: + - cron: '0 0 * * 1' + +permissions: + contents: read + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + 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@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + with: + egress-policy: audit + + - name: Checkout repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 + 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@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 + + # ℹ️ 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@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 + with: + category: '/language:${{matrix.language}}' diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000000000..108576735552b --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,37 @@ +# 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: + 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@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + with: + egress-policy: audit + + - name: Git Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - name: Review Dependencies + uses: actions/dependency-review-action@56339e523c0409420f6c2c9a2f4292bbb3c07dd3 # v4.8.0 diff --git a/.github/workflows/find-inactive-collaborators.yml b/.github/workflows/find-inactive-collaborators.yml new file mode 100644 index 0000000000000..38aa7b7ea86ce --- /dev/null +++ b/.github/workflows/find-inactive-collaborators.yml @@ -0,0 +1,32 @@ +name: Find inactive collaborators + +on: + schedule: + - cron: '0 0 1 * *' # Runs at 00:00 UTC on the 1st day of every month + + workflow_dispatch: + +permissions: + contents: read + issues: write + +jobs: + find: + if: github.repository == 'nodejs/nodejs.org' + runs-on: ubuntu-latest + + steps: + - name: Harden Runner + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + with: + egress-policy: audit + + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - name: Report inactive collaborators + id: inactive + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + script: | + const { default: report } = await import("${{github.workspace}}/.github/scripts/report-inactive-collaborators.mjs"); + report(github, context); diff --git a/.github/workflows/lighthouse.yml b/.github/workflows/lighthouse.yml new file mode 100644 index 0000000000000..879b36a2fefdc --- /dev/null +++ b/.github/workflows/lighthouse.yml @@ -0,0 +1,129 @@ +# 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 +# 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: Lighthouse + +on: + pull_request_target: + branches: + - main + types: + - labeled + +defaults: + run: + # This ensures that the working directory is the root of the repository + working-directory: ./ + +permissions: + contents: read + actions: read + # This permission is required by `thollander/actions-comment-pull-request` + pull-requests: write + +jobs: + get-vercel-preview: + # We want to skip our lighthouse analysis on Dependabot PRs + if: | + startsWith(github.event.pull_request.head.ref, 'dependabot/') == false && + github.event.label.name == 'github_actions:pull-request' + name: Get Vercel Preview + runs-on: ubuntu-latest + outputs: + deployment_found: ${{ steps.set_outputs.outputs.deployment_found }} + url: ${{ steps.set_outputs.outputs.url }} + steps: + - name: Capture Vercel Preview + id: check_deployment + uses: patrickedqvist/wait-for-vercel-preview@06c79330064b0e6ef7a2574603b62d3c98789125 # v1.3.2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + max_timeout: 300 # timeout after 5 minutes + check_interval: 10 # check every 10 seconds + continue-on-error: true + - name: Set Outputs + if: always() + id: set_outputs + run: | + if [[ -z "${{ steps.check_deployment.outputs.url }}" ]]; then + echo "deployment_found=false" >> $GITHUB_OUTPUT + else + echo "deployment_found=true" >> $GITHUB_OUTPUT + echo "url=${{ steps.check_deployment.outputs.url }}" >> $GITHUB_OUTPUT + fi + + lighthouse-ci: + needs: get-vercel-preview + if: needs.get-vercel-preview.outputs.deployment_found == 'true' + name: Lighthouse Report + runs-on: ubuntu-latest + + steps: + - name: Harden Runner + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + with: + egress-policy: audit + + - name: Git Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + # Provides the Pull Request commit SHA or the GitHub merge group ref + ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || github.ref }} + + - name: Add Comment to PR + # Signal that a lighthouse run is about to start + uses: thollander/actions-comment-pull-request@e2c37e53a7d2227b61585343765f73a9ca57eda9 # v3.0.0 + with: + message: | + Running Lighthouse audit... + # Used later to edit the existing comment + comment-tag: 'lighthouse_audit' + + - name: Audit Preview URL with Lighthouse + # Conduct the lighthouse audit + id: lighthouse_audit + uses: treosh/lighthouse-ci-action@fcd65974f7c4c2bf0ee9d09b84d2489183c29726 # v12.6.1 + with: + # Defines the settings and assertions to audit + configPath: './.lighthouserc.json' + # These URLS capture critical pages / site functionality. + urls: | + ${{ needs.get-vercel-preview.outputs.url }}/en + ${{ needs.get-vercel-preview.outputs.url }}/en/about + ${{ needs.get-vercel-preview.outputs.url }}/en/about/previous-releases + ${{ needs.get-vercel-preview.outputs.url }}/en/download + ${{ needs.get-vercel-preview.outputs.url }}/en/download/archive/current + ${{ needs.get-vercel-preview.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@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.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: ${{ needs.get-vercel-preview.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}}/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@e2c37e53a7d2227b61585343765f73a9ca57eda9 # v3.0.0 + with: + # Reference the previously created comment + comment-tag: 'lighthouse_audit' + message: | + ${{ steps.format_lighthouse_score.outputs.comment }} diff --git a/.github/workflows/lint-and-tests.yml b/.github/workflows/lint-and-tests.yml new file mode 100644 index 0000000000000..8ce2a9cfdba8c --- /dev/null +++ b/.github/workflows/lint-and-tests.yml @@ -0,0 +1,154 @@ +# 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: 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: + branches: + - main + merge_group: + +# 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 + +env: + # See https://turbo.build/repo/docs/reference/command-line-reference/run#--cache-dir + TURBO_ARGS: --cache-dir=.turbo/cache + +jobs: + lint: + name: Quality checks + runs-on: ubuntu-latest + + steps: + - name: Harden Runner + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + with: + egress-policy: audit + + - name: Git Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - name: Restore Lint Cache + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: | + .turbo/cache + .eslintmdcache + .stylelintcache + .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('.turbo/cache/**') }} + restore-keys: | + cache-lint-${{ hashFiles('pnpm-lock.yaml') }}- + cache-lint- + + - name: Set up pnpm + uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + + - name: Set up Node.js + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.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: 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' && github.event.pull_request.head.ref != 'chore/crowdin') + run: node_modules/.bin/turbo lint lint:types prettier ${{ env.TURBO_ARGS }} + + - name: Save Lint Cache + # We only want to save caches on `push` events or `pull_request_target` events + # and if it is a `pull_request_target` event, we want to avoid saving the cache if the PR comes from Dependabot + # or if it comes from an automated Crowdin Pull Request + # The reason we save caches on `push` is because caches creates on `main` (default) branches can be reused within + # other Pull Requests and PRs coming from forks + if: | + github.event_name == 'push' || + (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@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: | + .turbo/cache + .eslintmdcache + .stylelintcache + .prettiercache + key: cache-lint-${{ hashFiles('pnpm-lock.yaml') }}-${{ hashFiles('.turbo/cache/**') }} + + tests: + name: Tests + runs-on: ubuntu-latest + + steps: + - name: Harden Runner + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + with: + egress-policy: audit + + - name: Git Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - name: Set up pnpm + uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + + - name: Set up Node.js + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.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: 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 + run: node --run test:ci -- ${{ env.TURBO_ARGS }} + + - name: Upload test coverage to Codecov + if: ${{ !cancelled() && github.event_name != 'merge_group' }} + uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 + with: + files: ./apps/site/lcov.info,./packages/*/lcov.info + + - name: Upload test results to Codecov + if: ${{ !cancelled() && github.event_name != 'merge_group' }} + uses: codecov/test-results-action@47f89e9acb64b76debcd5ea40642d25a4adced9f # v1.1.1 + with: + 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..9fd8ad4647cfa --- /dev/null +++ b/.github/workflows/notify-on-push.yml @@ -0,0 +1,30 @@ +on: + push: + branches: + - main + +name: Notify on Push +permissions: + contents: read + +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: + - name: Slack Notification + uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # 2.3.3 + env: + SLACK_COLOR: '#DE512A' + SLACK_ICON: https://github.com/nodejs.png?size=48 + SLACK_TITLE: ${{ github.actor }} directly pushed to ${{ github.ref }} + SLACK_MESSAGE: | + A commit was directly pushed to by + + Before: + After: + SLACK_USERNAME: nodejs-bot + SLACK_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..da1fe8727626c --- /dev/null +++ b/.github/workflows/playwright-cloudflare-open-next.yml @@ -0,0 +1,82 @@ +# 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: + - name: Harden Runner + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + with: + egress-policy: audit + + - name: Git Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + fetch-depth: 2 + + - name: Set up pnpm + uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + + - name: Set up Node.js + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.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: 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@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.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_RUN_CLOUDFLARE_PREVIEW: true + PLAYWRIGHT_BASE_URL: http://127.0.0.1:8787 + + - name: Upload Playwright test results + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + 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..2037071b38792 --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,107 @@ +# 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: + get-vercel-preview: + name: Get Vercel Preview + runs-on: ubuntu-latest + outputs: + deployment_found: ${{ steps.set_outputs.outputs.deployment_found }} + url: ${{ steps.set_outputs.outputs.url }} + steps: + - name: Capture Vercel Preview + id: check_deployment + uses: patrickedqvist/wait-for-vercel-preview@06c79330064b0e6ef7a2574603b62d3c98789125 # v1.3.2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + max_timeout: 300 # timeout after 5 minutes + check_interval: 10 # check every 10 seconds + continue-on-error: true + - name: Set Outputs + if: always() + id: set_outputs + run: | + if [[ -z "${{ steps.check_deployment.outputs.url }}" ]]; then + echo "deployment_found=false" >> $GITHUB_OUTPUT + else + echo "deployment_found=true" >> $GITHUB_OUTPUT + echo "url=${{ steps.check_deployment.outputs.url }}" >> $GITHUB_OUTPUT + fi + + playwright: + needs: get-vercel-preview + if: needs.get-vercel-preview.outputs.deployment_found == 'true' + name: Playwright Tests + runs-on: ubuntu-latest + + steps: + - name: Harden Runner + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + with: + egress-policy: audit + + - name: Git Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + fetch-depth: 2 + + - name: Set up pnpm + uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + + - name: Set up Node.js + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.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: 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@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.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: ${{ needs.get-vercel-preview.outputs.url }} + + - name: Upload Playwright test results + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: playwright-report + path: apps/site/playwright-report/ diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml new file mode 100644 index 0000000000000..e299b31b9f63c --- /dev/null +++ b/.github/workflows/publish-packages.yml @@ -0,0 +1,153 @@ +name: Publish Packages + +# This workflow publishes packages to npm when changes are merged to main branch or when manually triggered. + +on: + push: + paths: + - 'packages/**' + # For security reasons, this should never be set to anything but `main` + branches: [main] + workflow_dispatch: + inputs: + package: + description: 'Specific package to publish (leave empty for all packages)' + required: false + type: string + +permissions: + contents: read + # For npm OIDC (https://docs.npmjs.com/trusted-publishers) + id-token: write + +env: + COMMIT_SHA: ${{ github.sha }} + +jobs: + prepare-packages: + runs-on: ubuntu-latest + outputs: + # Output the matrix of packages to publish for use in the publish job + matrix: ${{ steps.generate-matrix.outputs.matrix }} + steps: + - name: Harden Runner + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + 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." + + - name: Checkout repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + fetch-depth: 2 # Need at least 2 commits to detect changes between commits + + - name: Generate package matrix + id: generate-matrix + env: + PACKAGE: ${{ github.event.inputs.package }} + EVENT_NAME: ${{ github.event_name }} + run: | + if [ -n "$PACKAGE" ]; then + # If a specific package is requested via workflow_dispatch, just publish that one + echo "matrix={\"package\":[\"$PACKAGE\"]}" >> $GITHUB_OUTPUT + else + CHANGED_PACKAGES=() + for pkg in $(ls -d packages/*); do + PKG_NAME=$(basename "$pkg") + PKG_JSON="$pkg/package.json" + + # Determine if the package has changed (or include all on manual trigger) + if [ "$EVENT_NAME" == "workflow_dispatch" ] || ! git diff --quiet $COMMIT_SHA~1 $COMMIT_SHA -- "$pkg/"; then + OLD_VERSION=$(git show $COMMIT_SHA~1:$PKG_JSON | jq -r '.version') + NEW_VERSION=$(jq -r '.version' "$PKG_JSON") + if [ "$OLD_VERSION" != "$NEW_VERSION" ]; then + CHANGED_PACKAGES+=("$PKG_NAME") + fi + fi + done + + # Format the output for GitHub Actions matrix using jq + PACKAGES_JSON=$(jq -n '$ARGS.positional' --args "${CHANGED_PACKAGES[@]}" -c) + echo "matrix={\"package\":$PACKAGES_JSON}" >> $GITHUB_OUTPUT + fi + + publish: + needs: prepare-packages + runs-on: ubuntu-latest + # Use the dynamic matrix from prepare-packages job to create parallel jobs for each package + strategy: + matrix: ${{ fromJson(needs.prepare-packages.outputs.matrix) }} + fail-fast: false # Continue publishing other packages even if one fails + steps: + - name: Harden Runner + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + with: + egress-policy: audit + + - name: Checkout repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - name: Set up pnpm + uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + + - name: Setup Node.js + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 + with: + # Don't use caching here as we never install dependencies in this workflow + node-version-file: '.nvmrc' + registry-url: 'https://registry.npmjs.org' + + - name: Re-install npm + # TODO: OIDC requires npm >=11.5.1. + # Until Node.js v24 is LTS (with npm 11 as the default), we need to bump. + run: npm install -g npm@11 + + - name: Publish + working-directory: packages/${{ matrix.package }} + run: | + # Install deps + pnpm install --frozen-lockfile + + # Check if a custom publish script exists in package.json + if jq -e '.scripts.release' package.json > /dev/null; then + pnpm run release + else + pnpm publish --access public --no-git-checks + fi + + - name: Notify on Manual Release + if: ${{ github.event_name == 'workflow_dispatch' }} + uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # 2.3.3 + env: + SLACK_COLOR: '#43853D' + SLACK_ICON: https://github.com/nodejs.png?size=48 + SLACK_TITLE: ':rocket: Package Published: ${{ matrix.package }}' + SLACK_MESSAGE: | + :package: *Package*: `${{ matrix.package }}` () + :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 new file mode 100644 index 0000000000000..9e1dd866ecfaa --- /dev/null +++ b/.github/workflows/pull-request-label.yml @@ -0,0 +1,41 @@ +# 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 CI Label + +on: + pull_request_target: + branches: + - main + types: + - labeled + +defaults: + run: + # 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 + +jobs: + # This Job removes the `github_actions:pull-request` label after it got applied + # which allows people with write access to the repository to easily reapply the label if they need to trigger + # this Workflow again + remove_pull_request_label: + name: Remove Pull Request Label + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + with: + egress-policy: audit + + - name: Remove GitHub Actions Label + uses: actions-ecosystem/action-remove-labels@2ce5d41b4b6aa8503e285553f75ed56e0a40bae0 # v1.3.0 + with: + labels: github_actions:pull-request diff --git a/.github/workflows/remark-lint-problem-matcher.json b/.github/workflows/remark-lint-problem-matcher.json deleted file mode 100644 index cfb281310a9a0..0000000000000 --- a/.github/workflows/remark-lint-problem-matcher.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "problemMatcher": [ - { - "owner": "remark-lint", - "pattern": [ - { - "regexp": "^(?:\\x1b\\[\\d+m)*(.+?)(?:\\x1b\\[\\d+m)*$", - "file": 1 - }, - { - "regexp": "^\\s+(?:\\d+:\\d+-)?(\\d+):(\\d+)\\s+\\S*(error|warning|info)\\S*\\s+(.+)\\s+(\\S+)\\s+(?:\\S+)$", - "line": 1, - "column": 2, - "severity": 3, - "message": 4, - "code": 5, - "loop": true - } - ] - } - ] -} diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 0000000000000..f42839e4f1d05 --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,64 @@ +# 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 + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection + branch_protection_rule: + # To guarantee that the Maintained check is occasionally updated. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained + schedule: + - cron: '20 7 * * 2' + push: + branches: + - main + +# Declare default permissions as read only. +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + # Needed to upload the results to code-scanning dashboard. + security-events: write + # Needed to publish results and get a badge (see publish_results below). + id-token: write + contents: read + actions: read + + steps: + - name: Harden Runner + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + with: + egress-policy: audit + + - name: Git Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + + - name: Run Scorecard Analysis + uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 + 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@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + 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@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 + with: + sarif_file: results.sarif diff --git a/.github/workflows/sync-orama.yml b/.github/workflows/sync-orama.yml new file mode 100644 index 0000000000000..e85e6e68ff743 --- /dev/null +++ b/.github/workflows/sync-orama.yml @@ -0,0 +1,59 @@ +# 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 +# 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: Sync Orama Cloud + +on: + push: + branches: + - main + pull_request_target: + branches: + - main + types: + - labeled + +permissions: + contents: read + +jobs: + sync-orama-cloud: + name: Sync Orama Cloud + runs-on: ubuntu-latest + + # This Job should run either on non-`pull_request_target` 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 occasions. As this allows us to be certain the that builds are passing + if: github.event_name != 'pull_request_target' || github.event.label.name == 'github_actions:pull-request' + + steps: + - name: Git Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || github.ref }} + + - name: Set up pnpm + uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + + - name: Set up Node.js + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.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: Sync Orama Cloud + working-directory: apps/site + run: node --run sync-orama + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ORAMA_INDEX_ID: ${{ github.event_name == 'push' && secrets.ORAMA_PRODUCTION_INDEX_ID || secrets.ORAMA_INDEX_ID }} + ORAMA_SECRET_KEY: ${{ github.event_name == 'push' && secrets.ORAMA_PRODUCTION_SECRET_KEY || secrets.ORAMA_SECRET_KEY }} diff --git a/.github/workflows/translations-pr-lint.yml b/.github/workflows/translations-pr-lint.yml new file mode 100644 index 0000000000000..176b440ebe09b --- /dev/null +++ b/.github/workflows/translations-pr-lint.yml @@ -0,0 +1,59 @@ +# 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: + actions: read + +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@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + 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-sync.yml b/.github/workflows/translations-sync.yml new file mode 100644 index 0000000000000..5cd9066070782 --- /dev/null +++ b/.github/workflows/translations-sync.yml @@ -0,0 +1,137 @@ +# 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: + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + token: ${{ secrets.CROWDIN_GITHUB_BOT_TOKEN }} + + # see all the options at https://github.com/crowdin/github-action + - name: Crowdin PR + uses: crowdin/github-action@0749939f635900a2521aa6aac7a3766642b2dc71 # v2.11.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: '[automated]: crowdin sync' + pull_request_body: 'New Crowdin translations from the [Node.js Crowdin project](https://crowdin.com/project/nodejs-web)' + commit_message: 'chore: synced 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: + needs: synchronize-with-crowdin + 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@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + with: + egress-policy: audit + + - name: Git Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + ref: ${{ env.BRANCH_NAME }} + token: ${{ secrets.CROWDIN_GITHUB_BOT_TOKEN }} + fetch-depth: 2 + + - name: Restore Lint Cache + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.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: Set up pnpm + uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + + - name: Set up Node.js + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.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: Patch version if the files changed + working-directory: packages/i18n + run: | + CHANGED_FILES=$(git diff --name-only HEAD^1 HEAD) + if [ -n "$CHANGED_FILES" ]; then + pnpm version patch --no-git-tag-version + 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@778341af668090896ca464160c2def5d1d1a3eb0 # v6.0.1 + with: + commit_options: '--no-verify --signoff' + commit_message: 'chore: automated format of translated files' + branch: ${{ env.BRANCH_NAME }} + + - name: Save Lint Cache + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.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..4316ce0698b7e --- /dev/null +++ b/.github/workflows/translations-upload.yml @@ -0,0 +1,41 @@ +# 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 + +jobs: + upload-to-crowdin: + runs-on: ubuntu-latest + + steps: + - name: Harden Runner + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + # see all the options at https://github.com/crowdin/github-action + - name: crowdin action + uses: crowdin/github-action@0749939f635900a2521aa6aac7a3766642b2dc71 # v2.11.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/.gitignore b/.gitignore index c648057343c34..6dbefc3ed5566 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,51 @@ -# Generated HTML and other static files -build/ # Commonly ignored Node.js files -node_modules/ +node_modules +.env.* + +# npm Compatibility +# https://github.com/nodejs/nodejs.org/discussions/5334#discussioncomment-12827850 npm-debug.log -.npm/ +.npm +package-lock.json + +# Next.js Build Output +apps/site/.next +apps/site/build +apps/site/public/blog-data.json + +# Test Runner +junit.xml +lcov.info + +# Distributed Files +dist + +# Storybook +storybook-static +build-storybook.log +.nyc_output + +# Vercel Files +.vercel +.turbo +cache + +# Cache Files +.eslintmdcache +.stylelintcache +.prettiercache + +# TypeScript +tsconfig.tsbuildinfo +dist/ -# OSX system files, the bane of our existence -.DS_Store -.AppleDouble -.LSOverride +# Cloudflare Build Output +apps/site/.open-next +apps/site/.wrangler -# Netlify -.netlify +## Playwright +test-results +playwright-report -.cache/ +## MacOS Ignored Files +.DS_Store \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000000000..5f31f933a76bf --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,7 @@ +# lint and format staged files + +node --run lint:staged + +# verify typescript fully + +node --run lint:types diff --git a/.lighthouserc.json b/.lighthouserc.json new file mode 100644 index 0000000000000..be61cf8385d95 --- /dev/null +++ b/.lighthouserc.json @@ -0,0 +1,19 @@ +{ + "ci": { + "collect": { + "numberOfRuns": 1, + "settings": { + "preset": "desktop", + "skipAudits": ["is-crawlable"] + } + }, + "assert": { + "assertions": { + "categories:performance": ["warn", { "minScore": 0.9 }], + "categories:accessibility": ["warn", { "minScore": 0.9 }], + "categories:best-practices": ["warn", { "minScore": 0.9 }], + "categories:seo": ["warn", { "minScore": 0.9 }] + } + } + } +} diff --git a/.lintstagedrc.json b/.lintstagedrc.json new file mode 100644 index 0000000000000..91a0e156e4ab9 --- /dev/null +++ b/.lintstagedrc.json @@ -0,0 +1,3 @@ +{ + "**/*.{js,mjs,ts,tsx,md,mdx,json.yml}": ["prettier --check --write"] +} diff --git a/.mailmap b/.mailmap deleted file mode 100644 index ce9ed4e2b3ee1..0000000000000 --- a/.mailmap +++ /dev/null @@ -1 +0,0 @@ -Mary Marchini diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000000000..517f38666b4bd --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v22.14.0 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000000000..ae7d4df21b609 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,47 @@ +# 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 + +# Test Runner +junit.xml +lcov.info + +# Storybook +storybook-static +build-storybook.log +.nyc_output + +# Vercel Files +.vercel +.turbo +cache + +# Cache Files +.eslintmdcache +.stylelintcache +.prettiercache + +# TypeScript +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 0fbddb05993d9..c69d55a94d950 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,13 +1,13 @@ { - "overrides": [ - { - "files":[ - "**/*.js" - ], - "options": { - "singleQuote": true, - "trailingComma": "none" - } - } - ] + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": true, + "jsxSingleQuote": false, + "trailingComma": "es5", + "bracketSpacing": true, + "bracketSameLine": false, + "arrowParens": "avoid", + "plugins": ["prettier-plugin-tailwindcss"], + "tailwindPreserveWhitespace": true } diff --git a/.remarkignore b/.remarkignore deleted file mode 100644 index 2b569d27b58d7..0000000000000 --- a/.remarkignore +++ /dev/null @@ -1,4 +0,0 @@ -# We don't need to check all the md files under 'test/scripts' -# Because they are for test ONLY - -tests/scripts/ diff --git a/.remarkrc b/.remarkrc deleted file mode 100644 index 89bb6a54f34e8..0000000000000 --- a/.remarkrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "plugins": [ - "remark-frontmatter", - "remark-preset-lint-node", - ["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-undefined-references", false], - ["remark-lint-prohibited-strings", false], - ["remark-preset-lint-node/remark-lint-nodejs-links.js", false] - ] -} diff --git a/.stylelintignore b/.stylelintignore deleted file mode 100644 index 5021b0283305d..0000000000000 --- a/.stylelintignore +++ /dev/null @@ -1,2 +0,0 @@ -/build/ -**/vendor/ diff --git a/.stylelintrc b/.stylelintrc deleted file mode 100644 index 7d578a7d42f52..0000000000000 --- a/.stylelintrc +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": [ - "stylelint-config-twbs-bootstrap" - ], - "rules": { - "declaration-no-important": null, - "order/properties-order": null, - "selector-max-id": 1, - "selector-max-type": null, - "selector-no-qualifying-type": null - } -} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000000000..2d83c6ee3edef --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + "recommendations": [ + "esbenp.prettier-vscode", + "bradlc.vscode-tailwindcss", + "stylelint.vscode-stylelint", + "unifiedjs.vscode-mdx", + "dbaeumer.vscode-eslint", + "editorconfig.editorconfig" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000000..f45e0fc02c2f8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "css.validate": false, + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "javascript.updateImportsOnFileMove.enabled": "always", + "typescript.updateImportsOnFileMove.enabled": "always", + "typescript.tsdk": "node_modules/typescript/lib" +} diff --git a/CODEOWNERS b/CODEOWNERS deleted file mode 100644 index 4463b482983fd..0000000000000 --- a/CODEOWNERS +++ /dev/null @@ -1,18 +0,0 @@ -# Localization teams -/locale/ar/ @nodejs/nodejs-ar -/locale/ca/ # No Catalan team -/locale/de/ @nodejs/nodejs-de -/locale/es/ @nodejs/nodejs-es -/locale/fa/ @nodejs/nodejs-fa -/locale/fr/ @nodejs/nodejs-fr -/locale/gl/ # No Galacian team -/locale/it/ @nodejs/nodejs-it -/locale/ja/ @nodejs/nodejs-ja -/locale/ko/ @nodejs/nodejs-ko -/locale/pt-br/ @nodejs/nodejs-pt -/locale/ro/ @nodejs/nodejs-ro -/locale/ru/ @nodejs/nodejs-ru -/locale/tr/ @nodejs/nodejs-tr -/locale/uk/ @nodejs/nodejs-uk -/locale/zh-cn/ @nodejs/nodejs-cn -/locale/zh-tw/ @nodejs/nodejs-tw diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000000..8fff30e0a6cca --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,4 @@ +# 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 a88b692b9c26c..0000000000000 --- a/COLLABORATOR_GUIDE.md +++ /dev/null @@ -1,202 +0,0 @@ -# Node.js Collaborator Guide - -* [Issues and Pull Requests](#issues-and-pull-requests) -* [Accepting Modifications](#accepting-modifications) - * [Involving the Website Group](#involving-the-website-group) -* [Developer's Certificate of Origin 1.1](#developers-certificate-of-origin-11) -* [Code of Conduct](#code-of-conduct) -* [Code editing](#code-editing) - * [Adding new pages](#adding-new-pages) - * [Create the page content](#create-the-page-content) - * [Update locale site.json to add link attributes](#update-locale-sitejson-to-add-link-attributes) - * [Update the layout to add a link](#update-the-layout-to-add-a-link) - * [Translating pages](#translating-pages) - -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 for the future of the Node.js project. Where this is -unclear, the issue should be left open for several days to allow for -additional discussion. Where this does not yield input from Node.js -Collaborators or additional evidence that the issue has relevance, the -issue may be closed. Remember that issues can always be re-opened if -necessary. - -## Accepting Modifications - -All modifications to the Node.js code and documentation should be -performed via GitHub pull requests. Only the `Website` group -can merge their own work and should do so with great care. - -All pull requests must be reviewed and accepted by a Collaborator with -sufficient expertise who is able to 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. - -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. - -Before landing pull requests, sufficient time should be left for input -from other Collaborators. Leave at least 48 hours during the week and -72 hours over weekends to account for international time differences -and work schedules. Trivial changes (e.g. those which fix minor bugs -or improve performance without affecting API or causing other -wide-reaching impact) may be landed after a shorter delay. Any press -release can land with no time constraints as long as the copy is -properly formatted, it is not the responsibility of the website group -to review the copy itself. - -Where there is no disagreement amongst Collaborators, a pull request -may be landed given appropriate review. Where there is discussion -amongst Collaborators, consensus should be sought if possible. The -lack of consensus may indicate the need to elevate discussion to the -Website Group for resolution (see below). - -All bugfixes require a test case which demonstrates the defect. The -test should *fail* before the change, and *pass* after the change. - -All pull requests that modify executable code should be subjected to -continuous integration tests on the -[project CI server](https://ci.nodejs.org/). - -### Involving the Website Group - -Collaborators may opt to elevate pull requests or issues to the group for -discussion by mentioning `@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 consensus amongst the Collaborators who are - actively participating in the discussion. - -The Website group should serve as the final arbiter where required. - -## Developer's Certificate of Origin 1.1 - -By making a contribution to this project, I certify that: - -* (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. - -## Code of Conduct - -This Code of Conduct is adapted from [Rust's wonderful -CoC](https://github.com/rust-lang/rust/wiki/Note-development-policy#conduct). - -* We are committed to providing a friendly, safe and welcoming - environment for all, regardless of gender, sexual orientation, - disability, ethnicity, religion, or similar personal characteristic. -* Please avoid using overtly sexual nicknames or other nicknames that - might detract from a friendly, safe and welcoming environment for - all. -* Please be kind and courteous. There's no need to be mean or rude. -* Respect that people have differences of opinion and that every - design or implementation choice carries a trade-off and numerous - costs. There is seldom a right answer. -* Please keep unstructured critique to a minimum. If you have solid - ideas you want to experiment with, make a fork and see how it works. -* We will exclude you from interaction if you insult, demean or harass - anyone. That is not welcome behavior. We interpret the term - "harassment" as including the definition in the [Citizen Code of - Conduct](http://citizencodeofconduct.org/); if you have any lack of - clarity about what might be included in that concept, please read - their definition. In particular, we don't tolerate behavior that - excludes people in socially marginalized groups. -* Private harassment is also unacceptable. No matter who you are, if - you feel you have been or are being harassed or made uncomfortable - by a community member, please contact one of the channel ops or any - of the TC members immediately with a capture (log, photo, email) of - the harassment if possible. Whether you're a regular contributor or - a newcomer, we care about making this community a safe place for you - and we've got your back. -* Likewise any spamming, trolling, flaming, baiting or other - attention-stealing behavior is not welcome. -* Avoid the use of personal pronouns in code comments or - documentation. There is no need to address persons when explaining - code (e.g. "When the developer") - -## Code editing - -### Adding new pages - -1. Create new page content including the layout, title and copy. -2. Update `/locale/en/site.json` to provide page link attributes. -3. Update the relevant `/layout` to add a link to the new page. - -#### Create the page content - -Create a new markdown file in `/local/en`. As specified in the -[README.md](./README.md#layout), initial development happens in English. - -At the top of the markdown file, set a page the title and layout. - -```markdown ---- -title: Events -layout: contribute.hbs ---- - -[Event copy goes here] -``` - -#### Update locale site.json to add link attributes - -Open `local/en/site.json` and find the appropriate page structure. -Add a new object defining the link attributes. - -```json -"event": { - "link": "get-involved/events", - "text": "Events" -} -``` - -#### Update the layout to add a link - -Using the example layout, open `/layouts/contribute.hbs` and add your new -link to the markup. It's essential to update the handlebars paths to site.json. - -```handlebars -{{site.locale}}/{{site.getinvolved.events.link}} -``` - -### Translating pages - -See [TRANSLATION.md](./TRANSLATION.md) for the website translation policy. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6c1fa597134e0..10594a75cb4a8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,62 +1,143 @@ -# Node.js Community Contributing Guide 1.0 +# Node.js Website Contributing Guide + +## Table of Contents + +- [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) + +--- + +Thank you for your interest in contributing to the Node.js Website! This guide will help you get started with contributing to our project. + +## Quick Start + +New to contributing? Start here: + +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 ## Code of Conduct -The Code of Conduct explains the _bare minimum_ behavior -expectations the Node Foundation requires of its contributors. -[Please read it before participating](https://github.com/nodejs/admin/blob/HEAD/CODE_OF_CONDUCT.md). +Before contributing, please read and follow our [Code of Conduct](https://github.com/nodejs/node/blob/HEAD/CODE_OF_CONDUCT.md). + +### Developer's Certificate of Origin 1.1 + +``` +By contributing to this project, I certify that: + +- (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. + +``` + +## Ways to Contribute + +### For All Contributors + +- **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 learn 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 + +### 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 + +## Development Workflow + +### 1. Set Up Your Environment + +```bash +# Fork and clone the repository +git clone https://github.com/YOUR_USERNAME/nodejs.org.git +cd nodejs.org + +# Install dependencies +pnpm install --frozen-lockfile + +# Start development server +node --run dev +``` + +For detailed setup instructions, see [Getting Started](./docs/getting-started.md). + +### 2. Make Your Changes + +- **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) -## Vocabulary +### 3. Test Your Changes -* A **Contributor** is any individual creating or commenting on an issue or pull request. -* A **Collaborator** is a subset of contributors who have been given write access to the repository. +```bash +# Format and lint code +node --run format -## Logging Issues +# Run tests +node --run test -Log an issue for any question or problem you might have. When in doubt, log an issue. -Any additional policies about what to include will be provided in the responses. The only -exception is security disclosures which should be sent privately. +# Test build +node --run build +``` -Collaborators may direct you to another repository, ask for additional clarifications, and -add appropriate metadata before the issue is addressed. +### 4. Submit Your Contribution -Please be courteous, respectful, and every participant is expected to follow the -project's Code of Conduct. +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) -## Contributions +## Documentation Structure -Any change to resources in this repository must be through pull requests. +Our documentation is organized in the [`docs/`](./docs/) directory, so check out it's **[README](./docs/README.md)** for navigation. -No pull request can be merged without being reviewed. +## Getting Help -For non-trivial contributions, pull requests should sit for at least 36 hours to ensure that -contributors in other timezones have time to review. Consideration should also be given to -weekends and other holiday periods to ensure active collaborators all have reasonable time to -become involved in the discussion and review process if they wish. +- **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/) -The default for each contribution is that it is accepted once no collaborator has an objection. -During review collaborators may also request that a specific contributor who is most versed in a -particular area gives a "LGTM" before the PR can be merged. There is no additional "sign off" -process for contributions to land. Once all issues brought by collaborators are addressed it can -be landed by any collaborator. +## Project Maintainers -In the case of an objection being raised in a pull request by another collaborator, all involved -collaborators should seek to arrive at a consensus by way of addressing concerns being expressed -by discussion, compromise on the proposed change, or withdrawal of the proposed change. +This project is maintained by the [Node.js Website Team](https://github.com/nodejs/nodejs.org#readme). For questions about governance or high-level project direction, you can: -If a contribution is controversial and collaborators cannot agree about how to -get it landed or if it should be landed, then it should be escalated to the -[Node.js Technical Steering Committee](https://github.com/nodejs/tsc). Only a -small minority of issues are brought to the Technical Steering Committee for -resolution. Discussion and compromise among collaborators is the default -resolution mechanism. +- 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 -## Becoming a Collaborator +## License -Individuals with non-trivial contributions may be added as Collaborators. -Collaborators are expected to follow this policy and continue to send pull -requests and go through proper review. +By contributing to this project, you agree that your contributions will be licensed under the project's [MIT License](./LICENSE). -## How to contribute +--- -Please read more at [How to contribute](/README.md#contributing) +**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/GOVERNANCE.md b/GOVERNANCE.md new file mode 100644 index 0000000000000..555217dc82a23 --- /dev/null +++ b/GOVERNANCE.md @@ -0,0 +1,90 @@ +# 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. + +### TSC Oversight + +Any website change that expresses a position about a global event or group of people requires explicit +[TSC](https://github.com/nodejs/TSC/blob/main/TSC-Charter.md#section-4-responsibilities-of-the-tsc) +approval. This can be obtained by pinging `@nodejs/tsc` and receive no objections after seven days, +or by sending an email to `tsc@iojs.org` and receive at least one approval and no objections after seven days. + +### 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 [Contributing Guidelines](https://github.com/nodejs/nodejs.org/blob/main/CONTRIBUTING.md#becoming-a-collaborator) within this repository. After a passed nomination, members should submit a PR to add themselves to the list of current members, shown below. + +#### Current Members + +- [araujogui](https://github.com/araujogui) - **Guilherme Araújo** (he/him) + +- [AugustinMauroy](https://github.com/AugustinMauroy) - **Augustin Mauroy** (he/him) + +- [avivkeller](https://github.com/avivkeller) - **Aviv Keller** (he/him) + +- [aymen94](https://github.com/aymen94) - **Aymen Naghmouchi** + +- [benhalverson](https://github.com/benhalverson) - **Ben Halverson** (he/him) + +- [bjohansebas](https://github.com/bjohansebas) - **Sebastian Beltran** + +- [bmuenzenmeyer](https://github.com/bmuenzenmeyer) - **Brian Muenzenmeyer** (he/him) + +- [bnb](https://github.com/bnb) - **Tierney Cyren** (they/them) + +- [canerakdas](https://github.com/canerakdas) - **Caner Akdas** + +- [dario-piotrowicz](https://github.com/dario-piotrowicz) - **Dario Piotrowicz** + +- [Harkunwar](https://github.com/Harkunwar) - **Harkunwar Kochar** (he/him) + +- [HinataKah0](https://github.com/HinataKah0) - **HinataKah0** (he/him) + +- [manishprivet](https://github.com/manishprivet) - **Manish Kumar** (he/him) + +- [mikeesto](https://github.com/mikeesto) - **Michael Esteban** (he/him) + +- [ovflowd](https://github.com/ovflowd) - **Claudio Wunder** (they/them) + +- [SEWeiTung](https://github.com/SEWeiTung) - **Wei Tung** + +- [shanpriyan](https://github.com/shanpriyan) - **Shanmughapriyan S** + +### 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/LICENSE b/LICENSE index e2e6f65bc114f..896a53b840d2b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,5 @@ -The original contents of the nodejs.org repo are licensed for use as follows: +MIT License -""" Copyright Node.js Website WG contributors. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy @@ -20,4 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -""" diff --git a/README.md b/README.md index de55448c67f50..aa076088a5427 100644 --- a/README.md +++ b/README.md @@ -1,95 +1,125 @@ -# [nodejs.org](https://nodejs.org/) - -[![CI Status](https://github.com/nodejs/nodejs.org/actions/workflows/ci.yml/badge.svg)](https://github.com/nodejs/nodejs.org/actions/workflows/ci.yml?query=branch%3Amain) -[![MIT Licensed](https://img.shields.io/badge/license-MIT-blue)](LICENSE) -[![Crowdin](https://badges.crowdin.net/nodejs-website/localized.svg)](https://crowdin.com/project/nodejs-website) +

+
+ + + + + + +

+ +

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

+ +

+ + MIT License + + + Crowdin Badge + + + + + Powered by Vercel + + +
+ Build and Analysis Checks + + nodejs.org scorecard badge + +
+
+

## What is this repo? -[nodejs.org](https://nodejs.org/) by the [OpenJS Foundation](https://openjsf.org/) builds on the merged community's past website projects to form a self-publishing, community-managed version of the previous site. +[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. -On a technical level, inspiration has been taken from the `iojs.org` repo while design and content has been migrated from the old [nodejs.org repo](https://github.com/nodejs/nodejs.org-archive). These technical changes have helped to facilitate community involvement and empower the foundation's internationalization communities to provide alternative website content in other languages. +```bash +pnpm install --frozen-lockfile +pnpm dev -This repo's issues section has become the primary home for the Website WG's coordination efforts (meeting planning, minute approval, etc). +# listening at localhost:3000 +``` ## Contributing -There are two ways to contribute to this project. The first is **submitting new features or fixing bugs** and the second is **translating content to other languages**. +This project adopts the Node.js [Code of Conduct][]. -In both cases the workflow is different, please check how it is done in each case. +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. -### To submit a new feature or a bugfix +> \[!IMPORTANT]\ +> Please read our [Translation Guidelines][] before contributing to Translation and Localization of the Website -Please contribute! There are plenty of [good first issues](https://github.com/nodejs/nodejs.org/labels/good%20first%20issue) to work on. To get started, you have to [fork](https://github.com/nodejs/nodejs.org/fork) this repo to your own GitHub account first. Then open up a terminal on your machine and enter the following commands: +> \[!NOTE]\ +> We recommend a read of all Relevant Links below before doing code changes; Including Dependency changes, Content changes, and Code changes. -```bash -git clone https://github.com//nodejs.org.git -cd nodejs.org -npm install -npm start -``` +### Deployment + +The Website is automatically deployed to [Vercel](https://vercel.com) through its GitHub App integration when new pushes happen on the `main` branch. -This will start the development server on `http://localhost:8080/en/`. This page should reload automatically when you make changes to the code, but no code is perfect, so sometimes you may need to restart it. :) +Details regarding the deployment are only accessible to the maintainers of the Website Team due to certain limitations. -If you want to submit a new feature or a bugfix, the best way is to create the changes in a separate branch, e.g.: `git checkout -b feature/mycoolfeature`. This will make it easier for you to submit a pull request and get your feature merged. +The current integration is owned by the OpenJS Foundation and managed by the Website Team. -### To translate content into other languages +
+ Legacy Deployment -If you want to help translate to other languages or improve existing translations, it isn't necessary to work from GitHub. You can and should do it through Crowdin, this is the correct workflow. +The full setup is in minus secrets and certificates. -Crowdin is an online tool that facilitates the user experience for the translator, here is more information: +The webhook is set up on GitHub for this project and talks to a small Node server on the host, which does the work. See the [github-webhook](https://github.com/rvagg/github-webhook) package for this. -Website translations are handled via [Crowdin](https://crowdin.com/project/nodejs-website). +
-To help with localization, please read the [TRANSLATION](TRANSLATION.md) guide. +## Node.js Binaries & API Docs -## Layout +This repository does not contain the codebase or related infrastructure that serves `https://nodejs.org/api/`, `https://nodejs.org/docs/` or `https://nodejs.org/dist/`. -* Page templates are in `/layouts` -* Global styles are in `/layouts/css` -* Global static files are in `/static` -* All content is in `/locale` - * Initial development usually happens in English: `/locale/en` - * `/locale/{{locale}}/site.json` is where global localization information lives. - * All content is in Markdown and is per locale. - * The top of each Markdown file is a block of YAML for page specific localization information that is passed to various templates. - * The bulk of the Markdown content for each page is referenced as `{{{content}}}` in the corresponding template. +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. -## Serve/Build Options +- [`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/`. -* `DEFAULT_LOCALE={{locale}} node build.js` builds all the translated files present in the locale folder (will display 404 status code if file is not present), the static/css folder for all the Sass files, as well as copy the rest of the static assets to their subfolder in the build directory. -* `DEFAULT_LOCALE={{locale}} node build.js --preserveLocale` the same as `node build.js` but it will add the pages present in the English locale that are missing instead of throwing 404 status code. -* `DEFAULT_LOCALE={{locale}} npm run serve` builds only the files present in the specified locale folder (will display 404 status code if file is not present), then start the default website (`http://localhost:${port}/${mainLocale}`). Here `{port}` is 8080, `{mainLocale}` is `en` or the first specified language. -* `DEFAULT_LOCALE={{locale}} npm run serve -- --preserveLocale` the same as `npm run serve ` but it will add the pages present in the English locale that are missing. -* `npm run serve` builds all the current languages and returns 404 when a file is not present in the current locale, then start the default website (`http://localhost:${port}/${mainLocale}`). Here `{port}` is 8080, `{mainLocale}` is `en` in default. -* `npm run serve -- --preserveLocale` the same as `npm run serve` but it will add the pages present in the English locale that are missing instead of throwing 404 status code. +## Relevant Links -## Test Options +[Code of Conduct][] -Before submitting, you must pass all the unit tests and syntax checks by running the two commands below: +[Contribution Guidelines][] -* `npm-run-all test:lint test:unit` run all the unit test cases in `tests` folder, as well as check syntax with eslint. -* `npm-run-all --parallel test:lint:*` run all the syntax checks for `js`, `md` and other related files. +[Collaborator Guide][] -There're also two syntax check commands for you: -* `npm run test:lint:js -- --fix` try to automatically fix some formations for all the js files. -* `npm run test:lint:stylelint -- --fix` try to automatically fix some formations for all the css/scss files. +[Figma Design][] -## Notice +[Content vs Code][] -* Multiple locales can be built by using comma-separated values in the `DEFAULT_LOCALE` variable: `DEFAULT_LOCALE=en,es,it`. -* For other options, see `package.json`. +[Dependency Pinning][] -## Deployment +[Translation Guidelines][] -Full setup is in minus secrets and certificates. The webhook is setup on GitHub for this project and talks to a small Node server on the host which does the work. See the [github-webhook](https://github.com/rvagg/github-webhook) package for this. +[Status Page](https://status.nodejs.org/) of the Node.js web infrastructure. -## Content vs. Code +## Thanks -The Website Working Group is primarily concerned with the code and overall structure of the website. +- Thanks to all contributors and collaborators that make this project possible. +- Thanks to [Chromatic](https://www.chromatic.com/) for providing the visual testing platform that helps us review UI changes and catch visual regressions. +- Thanks to [Vercel](https://www.vercel.com/) for providing the infrastructure that serves and powers the Node.js Website +- Thanks to [Cloudflare](https://cloudflare.com) for providing the infrastructure that serves Node.js's Website, Node.js's CDN and more. + - 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. -The content of the website comes from a variety of working groups (Evangelism, Core, i18n, etc). -The Website WG defers to these WGs on matters of content and routinely adds collaborators from these -working groups as they add and improve content on the website. In other words, the Website WG is not -an *editorial* Working Group except when no other Working Group has taken responsibility for a -content area. +[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/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 +[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 7007778270e08..0000000000000 --- a/TRANSLATION.md +++ /dev/null @@ -1,76 +0,0 @@ -# Node.js Website Translation Policy - -Node.js is a global platform and so this site has many translations. The translation of the site into -languages other than English is handled by the localization working group of the language in question. If you -would like to contribute to the translation of nodejs.org, please refer to the following process: - -> Since Apr, 1 2020 translation process moved to [Crowdin](https://crowdin.com/project/nodejs-website) - -## Get started - -1. Open [nodejs-website](https://crowdin.com/project/nodejs-website) Crowdin project -2. Find your locale and start translation. Find more details in [guide for volunteer translators](https://support.crowdin.com/for-volunteer-translators/) - -All translated and approved content will be pushed to this repo automatically. You don't need to create any PRs with translation. Just keep localization process on Crowdin. - -Original source can be found in [/locale/en](https://github.com/nodejs/nodejs.org/tree/main/locale/en). If you find any problem with original source, please create a PR with changes directly to `/locale/en`. Crowdin automatically pull all updates within 24 hours. - -### Can't find my locale on Crowdin - -Please create a [new issue](https://github.com/nodejs/nodejs.org/issues/new?template=03-i18n.md) in this repo. Crowdin managers team would be happy to add new languages. - -## Localization groups - -An existing localization group is not required to start translation. You can contribute on Crowdin without it. Think about groups like a basement to communicate with other translators of your locale. - -Contact your appropriate localization group, and discuss with them the best possible way to contribute. A list of the localization groups can be found below. - -* [`nodejs-ar`](https://github.com/nodejs/nodejs-ar) Arabic Community -* [`nodejs-bg`](https://github.com/nodejs/nodejs-bg) Bulgarian Community -* [`nodejs-bn`](https://github.com/nodejs/nodejs-bn) Bengali Community -* [`nodejs-zh-CN`](https://github.com/nodejs/nodejs-zh-CN) Chinese Community -* [`nodejs-cs`](https://github.com/nodejs/nodejs-cs) Czech Community -* [`nodejs-da`](https://github.com/nodejs/nodejs-da) Danish Community -* [`nodejs-de`](https://github.com/nodejs/nodejs-de) German Community -* [`nodejs-el`](https://github.com/nodejs/nodejs-el) Greek Community -* [`nodejs-es`](https://github.com/nodejs/nodejs-es) Spanish Community -* [`nodejs-fa`](https://github.com/nodejs/nodejs-fa) Persian Community -* [`nodejs-fi`](https://github.com/nodejs/nodejs-fi) Finnish Community -* [`nodejs-fr`](https://github.com/nodejs/nodejs-fr) French Community -* [`nodejs-he`](https://github.com/nodejs/nodejs-he) Hebrew Community -* [`nodejs-hi`](https://github.com/nodejs/nodejs-hi) Hindi Community -* [`nodejs-hu`](https://github.com/nodejs/nodejs-hu) Hungarian Community -* [`nodejs-id`](https://github.com/nodejs/nodejs-id) Indonesian Community -* [`nodejs-it`](https://github.com/nodejs/nodejs-it) Italian Community -* [`nodejs-ja`](https://github.com/nodejs/nodejs-ja) Japanese Community -* [`nodejs-ka`](https://github.com/nodejs/nodejs-ka) Georgian Community -* [`nodejs-ko`](https://github.com/nodejs/nodejs-ko) Korean Community -* [`nodejs-mk`](https://github.com/nodejs/nodejs-mk) Macedonian Community -* [`nodejs-ms`](https://github.com/nodejs/nodejs-ms) Malaysian Community -* [`nodejs-nl`](https://github.com/nodejs/nodejs-nl) Dutch Community -* [`nodejs-no`](https://github.com/nodejs/nodejs-no) Norwegian Community -* [`nodejs-pl`](https://github.com/nodejs/nodejs-pl) Polish Community -* [`nodejs-pt`](https://github.com/nodejs/nodejs-pt) Portuguese Community -* [`nodejs-ro`](https://github.com/nodejs/nodejs-ro) Romanian Community -* [`nodejs-ru`](https://github.com/nodejs/nodejs-ru) Russian Community -* [`nodejs-sv`](https://github.com/nodejs/nodejs-sv) Swedish Community -* [`nodejs-ta`](https://github.com/nodejs/nodejs-ta) Tamil Community -* [`nodejs-tr`](https://github.com/nodejs/nodejs-tr) Turkish Community -* [`nodejs-zh-TW`](https://github.com/nodejs/nodejs-zh-TW) Taiwanese Community -* [`nodejs-uk`](https://github.com/nodejs/nodejs-uk) Ukrainian Community -* [`nodejs-vi`](https://github.com/nodejs/nodejs-vi) Vietnamese Community - -### Group for my locale does not exist - -If you can't find group for your locale: - -1. Translate 1000 strings or more on Crowdin for your locale -2. Find at least one more translator for your locale -3. Create a [new issue](https://github.com/nodejs/nodejs.org/issues/new?template=03-i18n.md) in this repo requesting the creation of a group for your locale - -### Group for my locale is archived - -If you find the group for your locale is archived: - -1. Try to contact members of the group by creating a [new issue](https://github.com/nodejs/nodejs.org/issues/new?template=03-i18n.md) in this repo. Include a mention of the group so members get notified of the issue. -2. If there is no response from members in 7 days and if you have already done 1000 strings or more on Crowdin for your locale, open an issue in https://github.com/nodejs/admin requesting the repository be unarchived. diff --git a/apps/site/.lintstagedrc.json b/apps/site/.lintstagedrc.json new file mode 100644 index 0000000000000..a1bc170c02b46 --- /dev/null +++ b/apps/site/.lintstagedrc.json @@ -0,0 +1,5 @@ +{ + "**/*.{js,mjs,ts,tsx,md,mdx}": ["prettier --check --write", "eslint --fix"], + "**/*.css": ["stylelint --allow-empty-input --fix", "prettier --write"], + "**/*.{json,yml}": ["prettier --check --write"] +} diff --git a/apps/site/.postcssrc.json b/apps/site/.postcssrc.json new file mode 100644 index 0000000000000..d750bab9d9e52 --- /dev/null +++ b/apps/site/.postcssrc.json @@ -0,0 +1,6 @@ +{ + "plugins": { + "postcss-calc": {}, + "@tailwindcss/postcss": {} + } +} diff --git a/apps/site/.remarkrc.json b/apps/site/.remarkrc.json new file mode 100644 index 0000000000000..71f00f776d25e --- /dev/null +++ b/apps/site/.remarkrc.json @@ -0,0 +1,3 @@ +{ + "plugins": ["remark-frontmatter", "@node-core/remark-lint"] +} diff --git a/apps/site/.stylelintignore b/apps/site/.stylelintignore new file mode 100644 index 0000000000000..bb4f97f4cd44d --- /dev/null +++ b/apps/site/.stylelintignore @@ -0,0 +1,23 @@ +# Next.js files +.next +.turbo +.swc +build + +# Public Folder +public + +# Test Runner +junit.xml +lcov.info + +# Old Styles +styles/old + +# Cloudflare Build Output +.open-next +.wrangler + +# Playwright +test-results +playwright-report diff --git a/apps/site/.stylelintrc.mjs b/apps/site/.stylelintrc.mjs new file mode 100644 index 0000000000000..c618c7b3dca9e --- /dev/null +++ b/apps/site/.stylelintrc.mjs @@ -0,0 +1,54 @@ +// 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', + 'reference', + 'utility', + 'theme', + 'custom-variant', + 'screen', + 'source', + 'tailwind', + 'variants', +]; + +// 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', + '@node-core/ui-components/stylelint/one-utility-class-per-line.mjs', + ], + 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', + // Allow the `@apply` at rule as its part of Tailwind + 'at-rule-no-deprecated': [true, { ignoreAtRules: CUSTOM_AT_RULES }], + 'nodejs/one-utility-class-per-line': true, + }, +}; diff --git a/apps/site/app/[locale]/[...path]/page.tsx b/apps/site/app/[locale]/[...path]/page.tsx new file mode 100644 index 0000000000000..3c75be3786632 --- /dev/null +++ b/apps/site/app/[locale]/[...path]/page.tsx @@ -0,0 +1,96 @@ +/** + * This file extends on the `page.tsx` file, which is the default file that is used to render + * the entry points for each locale and then also reused within the [...path] route to render the + * and contains all logic for rendering our dynamic and static routes within the Node.js Website. + * + * Note: that each `page.tsx` should have its own `generateStaticParams` to prevent clash of + * dynamic params, which will lead on static export errors and other sort of issues. + */ + +import { notFound } from 'next/navigation'; +import type { FC } from 'react'; + +import { ENABLE_STATIC_EXPORT } from '#site/next.constants.mjs'; +import { ENABLE_STATIC_EXPORT_LOCALE } from '#site/next.constants.mjs'; +import { dynamicRouter } from '#site/next.dynamic.mjs'; +import * as basePage from '#site/next.dynamic.page.mjs'; +import { availableLocaleCodes, defaultLocale } from '#site/next.locales.mjs'; +import type { DynamicParams } from '#site/types'; + +type PageParams = DynamicParams<{ path: Array }>; + +// This is the default Viewport Metadata +// @see https://nextjs.org/docs/app/api-reference/functions/generate-viewport#generateviewport-function +export const generateViewport = basePage.generateViewport; + +// This generates each page's HTML Metadata +// @see https://nextjs.org/docs/app/api-reference/functions/generate-metadata +export const generateMetadata = basePage.generateMetadata; + +// Generates all possible static paths based on the locales and environment configuration +// - Returns an empty array if static export is disabled (`ENABLE_STATIC_EXPORT` is false) +// - If `ENABLE_STATIC_EXPORT_LOCALE` is true, generates paths for all available locales +// - Otherwise, generates paths only for the default locale +// @see https://nextjs.org/docs/app/api-reference/functions/generate-static-params +export const generateStaticParams = async () => { + // Return an empty array if static export is disabled + if (!ENABLE_STATIC_EXPORT) { + return []; + } + + const routes = await dynamicRouter.getAllRoutes(); + + // Helper function to fetch and map routes for a specific locale + const getRoutesForLocale = async (l: string) => + routes.map(pathname => dynamicRouter.mapPathToRoute(l, pathname)); + + // Determine which locales to include in the static export + const locales = ENABLE_STATIC_EXPORT_LOCALE + ? availableLocaleCodes + : [defaultLocale.code]; + + // Generates all possible routes for all available locales + const routesWithLocales = await Promise.all(locales.map(getRoutesForLocale)); + + return routesWithLocales.flat().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 props => { + const { path, locale: routeLocale } = await props.params; + + // Gets the current full pathname for a given path + const [locale, pathname] = basePage.getLocaleAndPath(path, routeLocale); + + // Gets the Markdown content and context + const [content, context] = await basePage.getMarkdownContext({ + locale, + pathname, + }); + + // If we have a filename and layout then we have a page + if (context.filename && context.frontmatter.layout) { + return basePage.renderPage({ + content, + layout: context.frontmatter.layout, + context, + }); + } + + return notFound(); +}; + +// 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 = 300; + +export default getPage; diff --git a/apps/site/app/[locale]/blog/[...path]/page.tsx b/apps/site/app/[locale]/blog/[...path]/page.tsx new file mode 100644 index 0000000000000..2e3546994465f --- /dev/null +++ b/apps/site/app/[locale]/blog/[...path]/page.tsx @@ -0,0 +1,80 @@ +import { notFound } from 'next/navigation'; +import type { FC } from 'react'; + +import { ENABLE_STATIC_EXPORT } from '#site/next.constants.mjs'; +import { BLOG_DYNAMIC_ROUTES } from '#site/next.dynamic.constants.mjs'; +import * as basePage from '#site/next.dynamic.page.mjs'; +import { defaultLocale } from '#site/next.locales.mjs'; +import type { DynamicParams } from '#site/types'; + +type PageParams = DynamicParams<{ path: Array }>; + +// This is the default Viewport Metadata +// @see https://nextjs.org/docs/app/api-reference/functions/generate-viewport#generateviewport-function +export const generateViewport = basePage.generateViewport; + +// This generates each page's HTML Metadata +// @see https://nextjs.org/docs/app/api-reference/functions/generate-metadata +export const generateMetadata = basePage.generateMetadata; + +// Generates all possible static paths based on the locales and environment configuration +// - Returns an empty array if static export is disabled (`ENABLE_STATIC_EXPORT` is false) +// - If `ENABLE_STATIC_EXPORT_LOCALE` is true, generates paths for all available locales +// - Otherwise, generates paths only for the default locale +// @see https://nextjs.org/docs/app/api-reference/functions/generate-static-params +export const generateStaticParams = async () => { + // Return an empty array if static export is disabled + if (!ENABLE_STATIC_EXPORT) { + return []; + } + + return BLOG_DYNAMIC_ROUTES.map(pathname => ({ + locale: defaultLocale.code, + path: pathname.split('/'), + })); +}; + +// 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 props => { + const { path, locale: routeLocale } = await props.params; + + // Gets the current full pathname for a given path + const [locale, pathname] = basePage.getLocaleAndPath(path, routeLocale); + + // Verifies if the current route is a dynamic route + const isDynamicRoute = BLOG_DYNAMIC_ROUTES.some(r => r.includes(pathname)); + + // Gets the Markdown content and context for Blog pages + // otherwise this is likely a blog-category or a blog post + const [content, context] = await basePage.getMarkdownContext({ + locale, + pathname: `blog/${pathname}`, + }); + + // If this isn't a valid dynamic route for blog post or there's no markdown file + // for this, then we fail as not found as there's nothing we can do. + if (isDynamicRoute || context.filename) { + return basePage.renderPage({ + content, + layout: context.frontmatter.layout ?? 'blog-category', + context: { ...context, pathname: `/blog/${pathname}` }, + }); + } + + return notFound(); +}; + +// 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 = 300; + +export default getPage; diff --git a/apps/site/app/[locale]/download/archive/[version]/page.tsx b/apps/site/app/[locale]/download/archive/[version]/page.tsx new file mode 100644 index 0000000000000..f8b627977bab3 --- /dev/null +++ b/apps/site/app/[locale]/download/archive/[version]/page.tsx @@ -0,0 +1,88 @@ +import { notFound, redirect } from 'next/navigation'; +import type { FC } from 'react'; + +import provideReleaseData from '#site/next-data/providers/releaseData'; +import { ENABLE_STATIC_EXPORT } from '#site/next.constants.mjs'; +import { ARCHIVE_DYNAMIC_ROUTES } from '#site/next.dynamic.constants.mjs'; +import * as basePage from '#site/next.dynamic.page.mjs'; +import { defaultLocale } from '#site/next.locales.mjs'; +import type { DynamicParams } from '#site/types'; + +type PageParams = DynamicParams<{ version: string }>; + +// This is the default Viewport Metadata +// @see https://nextjs.org/docs/app/api-reference/functions/generate-viewport#generateviewport-function +export const generateViewport = basePage.generateViewport; + +// This generates each page's HTML Metadata +// @see https://nextjs.org/docs/app/api-reference/functions/generate-metadata +export const generateMetadata = basePage.generateMetadata; + +// Generates all possible static paths based on the locales and environment configuration +// - Returns an empty array if static export is disabled (`ENABLE_STATIC_EXPORT` is false) +// - If `ENABLE_STATIC_EXPORT_LOCALE` is true, generates paths for all available locales +// - Otherwise, generates paths only for the default locale +// @see https://nextjs.org/docs/app/api-reference/functions/generate-static-params +export const generateStaticParams = async () => { + // Return an empty array if static export is disabled + if (!ENABLE_STATIC_EXPORT) { + return []; + } + + return ARCHIVE_DYNAMIC_ROUTES.map(version => ({ + locale: defaultLocale.code, + version, + })); +}; + +// 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 props => { + const { version, locale: routeLocale } = await props.params; + + // Gets the current full pathname for a given path + const [locale, pathname] = basePage.getLocaleAndPath(version, routeLocale); + + if (version === 'current') { + const releaseData = provideReleaseData(); + + const release = releaseData.find(release => release.status === 'Current'); + + redirect(`/${locale}/download/archive/${release?.versionWithPrefix}`); + } + + // Verifies if the current route is a dynamic route + const isDynamicRoute = ARCHIVE_DYNAMIC_ROUTES.some(r => r.includes(pathname)); + + // Gets the Markdown content and context for Download Archive pages + const [content, context] = await basePage.getMarkdownContext({ + locale, + pathname: 'download/archive', + }); + + // If this isn't a valid dynamic route for archive version or there's no markdown + // file for this, then we fail as not found as there's nothing we can do. + if (isDynamicRoute && context.filename) { + return basePage.renderPage({ + content, + layout: context.frontmatter.layout!, + context: { ...context, pathname: `/download/archive/${pathname}` }, + }); + } + + return notFound(); +}; + +// 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 = 300; + +export default getPage; diff --git a/apps/site/app/[locale]/error.tsx b/apps/site/app/[locale]/error.tsx new file mode 100644 index 0000000000000..c3f2232662fd4 --- /dev/null +++ b/apps/site/app/[locale]/error.tsx @@ -0,0 +1,30 @@ +'use client'; + +import { ArrowRightIcon } from '@heroicons/react/24/solid'; +import { useTranslations } from 'next-intl'; +import type { FC } from 'react'; + +import Button from '#site/components/Common/Button'; +import GlowingBackdropLayout from '#site/layouts/GlowingBackdrop'; + +const ErrorPage: FC<{ error: Error }> = () => { + const t = useTranslations(); + + return ( + + 500 +

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

+

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

+ +
+ ); +}; + +export default ErrorPage; diff --git a/apps/site/app/[locale]/feed/[feed]/route.ts b/apps/site/app/[locale]/feed/[feed]/route.ts new file mode 100644 index 0000000000000..a5abc2981a17b --- /dev/null +++ b/apps/site/app/[locale]/feed/[feed]/route.ts @@ -0,0 +1,45 @@ +import { NextResponse } from 'next/server'; + +import provideWebsiteFeeds from '#site/next-data/providers/websiteFeeds'; +import { siteConfig } from '#site/next.json.mjs'; +import { defaultLocale } from '#site/next.locales.mjs'; + +type DynamicStaticPaths = { locale: string; feed: string }; +type StaticParams = { params: Promise }; + +// 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, props: StaticParams) => { + const params = await props.params; + + // 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 !== undefined ? 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 => ({ + locale: defaultLocale.code, + feed: feed.file, + })); + +// Enforces that only the paths from `generateStaticParams` are allowed, giving 404 on the contrary +// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamicparams +export const dynamicParams = false; + +// 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 = 300; diff --git a/apps/site/app/[locale]/layout.tsx b/apps/site/app/[locale]/layout.tsx new file mode 100644 index 0000000000000..3faa14079216a --- /dev/null +++ b/apps/site/app/[locale]/layout.tsx @@ -0,0 +1,58 @@ +import { Analytics } from '@vercel/analytics/react'; +import { SpeedInsights } from '@vercel/speed-insights/next'; +import classNames from 'classnames'; +import { NextIntlClientProvider } from 'next-intl'; +import type { FC, PropsWithChildren } from 'react'; + +import BaseLayout from '#site/layouts/Base'; +import { VERCEL_ENV } from '#site/next.constants.mjs'; +import { IBM_PLEX_MONO, OPEN_SANS } from '#site/next.fonts'; +import { availableLocalesMap, defaultLocale } from '#site/next.locales.mjs'; +import { ThemeProvider } from '#site/providers/themeProvider'; + +import '#site/styles/index.css'; + +const fontClasses = classNames(IBM_PLEX_MONO.variable, OPEN_SANS.variable); + +type RootLayoutProps = PropsWithChildren<{ + params: Promise<{ locale: string }>; +}>; + +const RootLayout: FC = async ({ children, params }) => { + const { locale } = await params; + + const { langDir, hrefLang } = availableLocalesMap[locale] || defaultLocale; + + return ( + + + + + {children} + + + + ; + } + + return ; +}; + +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..542cb494b6bb7 --- /dev/null +++ b/apps/site/components/MDX/Calendar/Event/index.tsx @@ -0,0 +1,44 @@ +import type { FC } from 'react'; + +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 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/apps/site/components/MDX/Calendar/UpcomingMeetings.tsx b/apps/site/components/MDX/Calendar/UpcomingMeetings.tsx new file mode 100644 index 0000000000000..743db02817ab0 --- /dev/null +++ b/apps/site/components/MDX/Calendar/UpcomingMeetings.tsx @@ -0,0 +1,54 @@ +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 type { CalendarEvent } from '#site/types'; + +import styles from './calendar.module.css'; + +type GroupedEntries = Record>; + +const UpcomingMeetings: FC = async () => { + const events = await getCalendarEvents(CALENDAR_NODEJS_ID); + + const groupedEntries = events.filter(getZoomLink).reduce((acc, event) => { + const startDate = new Date( + isZoned(event.start) ? event.start.dateTime : event.start.date + ); + + const datePerDay = startDate.toDateString(); + + acc[datePerDay] = acc[datePerDay] || []; + acc[datePerDay].push(event); + + return acc; + }, {} as GroupedEntries); + + const sortedGroupedEntries = Object.entries(groupedEntries).sort( + ([dateA], [dateB]) => new Date(dateA).getTime() - new Date(dateB).getTime() + ); + + return sortedGroupedEntries.map(([date, entries]) => ( +
+

+ +

+ + {entries.map(({ id, start, end, summary, location, description }) => ( + + ))} +
+ )); +}; + +export default UpcomingMeetings; diff --git a/apps/site/components/MDX/Calendar/calendar.module.css b/apps/site/components/MDX/Calendar/calendar.module.css new file mode 100644 index 0000000000000..ef0999b2b9d58 --- /dev/null +++ b/apps/site/components/MDX/Calendar/calendar.module.css @@ -0,0 +1,19 @@ +@reference "../../../styles/index.css"; + +.events { + @apply flex + flex-col + gap-2; + + h4 { + @apply text-xl + font-bold; + } +} + +.summits { + @apply flex + flex-col + gap-3 + md:flex-row; +} 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..ef0b977a1a995 --- /dev/null +++ b/apps/site/components/MDX/CodeBox/index.tsx @@ -0,0 +1,27 @@ +import { getLanguageDisplayName } from '@node-core/rehype-shiki'; +import type { FC, PropsWithChildren } from 'react'; + +import CodeBox from '#site/components/Common/CodeBox'; + +type CodeBoxProps = { className?: string; showCopyButton?: string }; + +const MDXCodeBox: FC> = ({ + children: code, + className, + showCopyButton, +}) => { + 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..e2e39f1727213 --- /dev/null +++ b/apps/site/components/MDX/Image/index.tsx @@ -0,0 +1,26 @@ +import type { ImageProps } from 'next/image'; +import Image 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..0f1d714fc3842 --- /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 xs:border-t-0 + border-t; + } + + th { + @apply xs: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..1c8662f492389 --- /dev/null +++ b/apps/site/components/Releases/MinorReleasesTable/index.tsx @@ -0,0 +1,85 @@ +import { CodeBracketSquareIcon } from '@heroicons/react/24/outline'; +import Separator from '@node-core/ui-components/Common/Separator'; +import NpmIcon from '@node-core/ui-components/Icons/PackageManager/Npm'; +import { useTranslations } from 'next-intl'; +import type { FC } from 'react'; + +import 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 type { MinorVersion } from '#site/types'; +import { getNodeApiUrl } from '#site/util/url'; + +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.tsx b/apps/site/components/Releases/PreviousReleasesTable.tsx new file mode 100644 index 0000000000000..58f7846f80fd2 --- /dev/null +++ b/apps/site/components/Releases/PreviousReleasesTable.tsx @@ -0,0 +1,98 @@ +'use client'; + +import Badge from '@node-core/ui-components/Common/Badge'; +import { useTranslations } from 'next-intl'; +import type { FC } from 'react'; +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 provideReleaseData from '#site/next-data/providers/releaseData'; + +import ReleaseModal from './ReleaseModal'; + +const BADGE_KIND_MAP = { + 'End-of-life': 'warning', + 'Maintenance LTS': 'neutral', + 'Active LTS': 'info', + Current: 'default', + Pending: 'default', +} as const; + +const PreviousReleasesTable: FC = () => { + const releaseData = provideReleaseData(); + + const t = useTranslations(); + + const [currentModal, setCurrentModal] = useState(); + + return ( + + + + + + + + + + + + + + {releaseData.map(release => ( + + + + + + + + + + + + + + + + open || setCurrentModal(undefined)} + /> + + ))} + +
{t('components.downloadReleasesTable.version')}{t('components.downloadReleasesTable.codename')}{t('components.downloadReleasesTable.firstReleased')}{t('components.downloadReleasesTable.lastUpdated')}{t('components.downloadReleasesTable.status')}
+ + v{release.major} + + + {release.codename || '-'} + + + + + + + {release.status} + {release.status === 'End-of-life' ? ' (EoL)' : ''} + + + setCurrentModal(release.version)} + > + {t('components.downloadReleasesTable.details')} + +
+ ); +}; + +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..c20ea30624158 --- /dev/null +++ b/apps/site/components/Releases/ReleaseModal.tsx @@ -0,0 +1,43 @@ +import { Modal, Title, Content } from '@node-core/ui-components/Common/Modal'; +import { useTranslations } from 'next-intl'; +import type { ComponentProps, FC } from 'react'; + +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'; + +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..935d04d660ce5 --- /dev/null +++ b/apps/site/components/Releases/ReleaseOverview/ReleaseOverviewItem/index.tsx @@ -0,0 +1,30 @@ +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..47f6026bdcef5 --- /dev/null +++ b/apps/site/components/Releases/ReleaseOverview/index.tsx @@ -0,0 +1,71 @@ +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 type { FC } from 'react'; + +import FormattedTime from '#site/components/Common/FormattedTime'; +import type { NodeRelease } from '#site/types'; + +import styles from './index.module.css'; +import ReleaseOverviewItem from './ReleaseOverviewItem'; + +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..a18c597b86b3d --- /dev/null +++ b/apps/site/components/withAvatarGroup.tsx @@ -0,0 +1,33 @@ +'use client'; + +import AvatarGroup from '@node-core/ui-components/Common/AvatarGroup'; +import type { ComponentProps, FC } from 'react'; + +import Link from '#site/components/Link'; +import type { AuthorProps } from '#site/types'; +import { getAuthors } from '#site/util/author'; + +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..678ab8404bd93 --- /dev/null +++ b/apps/site/components/withBadgeGroup.tsx @@ -0,0 +1,30 @@ +import BadgeGroup from '@node-core/ui-components/Common/BadgeGroup'; +import type { FC } from 'react'; + +import Link from '#site/components/Link'; +import { siteConfig } from '#site/next.json.mjs'; +import { dateIsBetween } from '#site/util/date'; + +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..169ddd5598f4f --- /dev/null +++ b/apps/site/components/withBanner.tsx @@ -0,0 +1,28 @@ +import { ArrowUpRightIcon } from '@heroicons/react/24/outline'; +import Banner from '@node-core/ui-components/Common/Banner'; +import type { FC } from 'react'; + +import Link from '#site/components/Link'; +import { siteConfig } from '#site/next.json.mjs'; +import { dateIsBetween } from '#site/util/date'; + +const WithBanner: FC<{ section: string }> = ({ section }) => { + const banner = siteConfig.websiteBanners[section]; + + if (banner && dateIsBetween(banner.startDate, banner.endDate)) { + 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..d007d1673893c --- /dev/null +++ b/apps/site/components/withBlogCategories.tsx @@ -0,0 +1,60 @@ +import { useTranslations } from 'next-intl'; +import type { ComponentProps, FC } from 'react'; + +import BlogPostCard from '#site/components/Blog/BlogPostCard'; +import LinkTabs from '#site/components/Common/LinkTabs'; +import Pagination from '#site/components/Common/Pagination'; +import type { BlogPostsRSC } from '#site/types'; +import { mapAuthorToCardAuthors } from '#site/util/author'; + +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..c128c919dfb03 --- /dev/null +++ b/apps/site/components/withBlogCrossLinks.tsx @@ -0,0 +1,51 @@ +import type { FC } from 'react'; + +import { getClientContext } from '#site/client-context'; +import CrossLink from '#site/components/Common/CrossLink'; +import getBlogData from '#site/next-data/blogData'; +import type { BlogCategory } from '#site/types'; + +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..2d342a2b6803a --- /dev/null +++ b/apps/site/components/withBreadcrumbs.tsx @@ -0,0 +1,77 @@ +'use client'; + +import type { BreadcrumbLink } from '@node-core/ui-components/Common/Breadcrumbs'; +import Breadcrumbs from '@node-core/ui-components/Common/Breadcrumbs'; +import { useTranslations } from 'next-intl'; +import type { FC } from 'react'; + +import Link from '#site/components/Link'; +import { + useClientContext, + useMediaQuery, + useSiteNavigation, +} from '#site/hooks'; +import type { NavigationKeys } from '#site/types'; +import { dashToCamelCase } from '#site/util/string'; + +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..460150ead3948 --- /dev/null +++ b/apps/site/components/withDownloadArchive.tsx @@ -0,0 +1,45 @@ +import { notFound } from 'next/navigation'; +import type { FC } from 'react'; + +import { getClientContext } from '#site/client-context'; +import provideReleaseData from '#site/next-data/providers/releaseData'; +import { + buildReleaseArtifacts, + extractVersionFromPath, +} from '#site/util/download/archive'; + +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 = 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..34d76d09d6991 --- /dev/null +++ b/apps/site/components/withDownloadSection.tsx @@ -0,0 +1,47 @@ +import { useLocale } from 'next-intl'; +import type { FC, PropsWithChildren } from 'react'; + +import { getClientContext } from '#site/client-context'; +import WithNodeRelease from '#site/components/withNodeRelease'; +import provideDownloadSnippets from '#site/next-data/providers/downloadSnippets'; +import provideReleaseData from '#site/next-data/providers/releaseData'; +import { defaultLocale } from '#site/next.locales.mjs'; +import { + ReleaseProvider, + ReleasesProvider, +} from '#site/providers/releaseProvider'; + +// 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 = provideDownloadSnippets(defaultLocale.code); + +const WithDownloadSection: FC = ({ children }) => { + const locale = useLocale(); + const releases = provideReleaseData(); + + const snippets = provideDownloadSnippets(locale); + 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 => !snippets.some(s => s.name === snippet.name)) + .concat(snippets); + + // Decides which initial release to use based on the current pathname + const initialRelease = pathname.endsWith('/current') + ? 'Current' + : 'Active 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..b334621adff20 --- /dev/null +++ b/apps/site/components/withFooter.tsx @@ -0,0 +1,62 @@ +import BadgeGroup from '@node-core/ui-components/Common/BadgeGroup'; +import Footer from '@node-core/ui-components/Containers/Footer'; +import { useTranslations } from 'next-intl'; +import type { FC } from 'react'; + +import { getClientContext } from '#site/client-context'; +import Link from '#site/components/Link'; +import { siteNavigation } from '#site/next.json.mjs'; + +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, text: t(link.text) })), + }; + + const primary = ( +
+ + {({ release }) => ( + + {t('components.containers.footer.releasePills.latestLTS')} + + )} + + + + {({ release }) => ( + + {t('components.containers.footer.releasePills.latestRelease')} + + )} + +
+ ); + + return ( +