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 0a78c7ae867ad..0000000000000 --- a/.eslintignore +++ /dev/null @@ -1,4 +0,0 @@ -**/*.min.js -static/legacy/ -external/ -build/ diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index a7d672df29d7a..0000000000000 --- a/.eslintrc +++ /dev/null @@ -1,52 +0,0 @@ -{ - "extends": [ - "eslint:recommended", - "standard", - "prettier" - ], - "plugins": [ - "prettier" - ], - "rules": { - "prettier/prettier": "error" - }, - "overrides": [ - { - "files": [ - "**/*.md" - ], - "plugins": [ - "markdown" - ], - "processor": "markdown/markdown" - }, - { - "files": [ - "**/*.md/*.js" - ], - "parserOptions": { - "ecmaVersion": "latest" - }, - "rules": { - "eqeqeq": "off", - "n/no-deprecated-api": "off", - "n/handle-callback-err": "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" - } - ], - "semi": ["error", "always"] - } - } - ] -} diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000000..7531feb70dd86 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,60 @@ +# 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 +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 + +# The following users DO NOT have write access, and their review is requested +# via a GitHub action. +apps/site/pages/en/learn/diagnostics @nodejs/diagnostics + +apps/site/pages/en/learn/getting-started/security-best-practices.md @nodejs/security-wg + +apps/site/pages/en/learn/manipulating-files @nodejs/fs + +apps/site/pages/en/learn/test-runner @nodejs/test_runner + +apps/site/pages/en/learn/typescript @nodejs/typescript + +apps/site/pages/en/about/partners.mdx @nodejs/marketing +apps/site/pages/en/about/branding.mdx @nodejs/marketing + +apps/site/pages/en/learn/getting-started/userland-migrations.md @nodejs/userland-migrations +apps/site/pages/en/blog/migrations @nodejs/userland-migrations diff --git a/.github/ISSUE_TEMPLATE/01-bug-report.yml b/.github/ISSUE_TEMPLATE/01-bug-report.yml index 4296d9ab9c8cd..281e5e0159968 100644 --- a/.github/ISSUE_TEMPLATE/01-bug-report.yml +++ b/.github/ISSUE_TEMPLATE/01-bug-report.yml @@ -1,50 +1,49 @@ -name: 🐛 Bug Report For Nodejs Website -description: Report an issue if something isn't working as expected 🤔. +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 wanting to report 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 + 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:" + 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? + label: 'Browser Name:' + description: What kind of browser are you using? placeholder: Chrome validations: required: true - type: input attributes: - label: "Browser Version:" + label: 'Browser Version:' description: What version of browser are you using? - placeholder: "103.0.5060.134" + placeholder: '103.0.5060.134' validations: required: true - type: input attributes: - label: "Operation System:" - description: - What kind of operation system are you using + 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" + placeholder: 'Windows 10, 21H2, 19044.1826' validations: required: true - type: textarea attributes: - label: "How to reproduce the issue:" + label: 'How to reproduce the issue:' placeholder: | - 1. What I did. - 2. What I expected to happen. + 1. What I did. + 2. What I expected to happen. 3. What I actually got. 4. If possible, images or videos are welcome. validations: diff --git a/.github/ISSUE_TEMPLATE/02-feature-request.yml b/.github/ISSUE_TEMPLATE/02-feature-request.yml index 4ba32b7ac8354..da59bf9dbc09b 100644 --- a/.github/ISSUE_TEMPLATE/02-feature-request.yml +++ b/.github/ISSUE_TEMPLATE/02-feature-request.yml @@ -1,5 +1,5 @@ -name: 🚀 Feature Request For Nodejs Website -description: I have a suggestion (and may want to implement it)! +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 @@ -8,11 +8,9 @@ body: 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. - We are also working on a relaunch at the moment, - so it might be a good idea to check out our plans there as well: https://github.com/nodejs/nodejs.dev/issues/. - type: textarea attributes: - label: "Enter your suggestions in details:" + label: 'Enter your suggestions in details:' placeholder: | 1. What I expected to happen. 2. Your reason (if possible, images or videos are welcome). 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.yml b/.github/ISSUE_TEMPLATE/03-i18n.yml deleted file mode 100644 index 6ad3d9daf672c..0000000000000 --- a/.github/ISSUE_TEMPLATE/03-i18n.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: 🔡 Internationalization and translations For Nodejs Website -description: Hello. Hola. Salut. Ciao. Здравствуйте. こんにちは. مرحباً -labels: [i18n] -body: - - type: markdown - attributes: - value: | - Please mention the relevant localization team in your issue, - it helps us having the right people take a look, or if you want - to create a new localization team. - - type: textarea - attributes: - label: "Enter your issue on localizations here:" - placeholder: | - 1. The name of the team (e.g: nodejs/node-cn). - 2. Any problems to report or you want to create it? - validations: - required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 60eef11252dd9..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? - url: https://github.com/nodejs/help/issues/ - about: File an issue in our help repo. - - name: 📗 Node.js API Docs + - 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: Please open an issue in the main Node.js repo. + 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: "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..fe08f8801de6a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,12 +1,100 @@ 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 + - twoslash + 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/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000000000..7002131771cce --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,89 @@ +# 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 + types: [opened, synchronize, reopened, ready_for_review] + merge_group: + +defaults: + run: + # This ensures that the working directory is the root of the repository + working-directory: ./ + +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: + - uses: nodejs/web-team/actions/setup-environment@9f3c83af227d721768d9dbb63009a47ed4f4282f + with: + pnpm: true + use-version-file: true + # We only want to install required production packages + install-flags: --prod + + - 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%" + + - uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 + 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: 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' + # See https://github.com/vercel/next.js/pull/81318 + TURBOPACK_STATS: ${{ matrix.os == 'ubuntu-latest' }} + + - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + if: matrix.os == 'ubuntu-latest' + with: + name: webpack-stats + path: apps/site/.next/server/webpack-stats.json + + - name: Build Next.js (Static Export) + # We want to generate a static build, as it is a requirement of our website. + run: node_modules/.bin/turbo deploy ${{ 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 + # TODO: The output of this is too large, and it crashes the GitHub Runner + NEXT_PUBLIC_STATIC_EXPORT_LOCALE: false # ${{ github.event_name == 'push' }} diff --git a/.github/workflows/bundle-compare.yml b/.github/workflows/bundle-compare.yml new file mode 100644 index 0000000000000..e2ca4bda57628 --- /dev/null +++ b/.github/workflows/bundle-compare.yml @@ -0,0 +1,70 @@ +name: Compare Bundle Size + +on: + workflow_run: + workflows: ['Build'] + types: [completed] + +permissions: + contents: read + actions: read + # To create the comment + pull-requests: write + +jobs: + compare: + name: Compare Bundle Stats + runs-on: ubuntu-latest + if: github.event.workflow_run.event == 'pull_request' + + steps: + - name: Harden Runner + uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 + with: + egress-policy: audit + + - name: Git Checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - name: Download Stats (HEAD) + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + with: + name: webpack-stats + path: head-stats + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Get Run ID from BASE + id: base-run + env: + WORKFLOW_ID: ${{ github.event.workflow_run.workflow_id }} + GH_TOKEN: ${{ github.token }} + run: | + ID=$(gh run list -c $GITHUB_SHA -w $WORKFLOW_ID -L 1 --json databaseId --jq ".[].databaseId") + echo "run_id=$ID" >> $GITHUB_OUTPUT + + - name: Download Stats (BASE) + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + with: + name: webpack-stats + path: base-stats + run-id: ${{ steps.base-run.outputs.run_id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Compare Bundle Size + id: compare-bundle-size + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + HEAD_STATS_PATH: ./head-stats/webpack-stats.json + BASE_STATS_PATH: ./base-stats/webpack-stats.json + with: + script: | + const { compare } = await import('${{github.workspace}}/apps/site/scripts/compare-size/index.mjs') + await compare({core}) + + - name: Add Comment to PR + uses: thollander/actions-comment-pull-request@e2c37e53a7d2227b61585343765f73a9ca57eda9 # v3.0.0 + with: + comment-tag: 'compare_bundle_size' + message: ${{ steps.compare-bundle-size.outputs.comment }} + pr-number: ${{ github.event.workflow_run.pull_requests[0].number }} diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml new file mode 100644 index 0000000000000..f51b214cedf87 --- /dev/null +++ b/.github/workflows/chromatic.yml @@ -0,0 +1,71 @@ +# 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: + - uses: nodejs/web-team/actions/setup-environment@9f3c83af227d721768d9dbb63009a47ed4f4282f + with: + pnpm: true + use-version-file: true + fetch-depth: 0 + + - name: Start Visual Regression Tests (Chromatic) + # This assigns the Environment Deployment for Storybook + id: chromatic-deploy + uses: chromaui/action@4c20b95e9d3209ecfdf9cd6aace6bbde71ba1694 # v13.3.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 5dada5c8f742c..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@v3 - - - name: Set up Node.js - uses: actions/setup-node@v3 - 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@v3 - - - name: Set up Node.js - uses: actions/setup-node@v3 - 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 index 93e877256c86b..ba18b4a8ec1c6 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,33 +1,22 @@ -name: "CodeQL" +name: 'CodeQL' on: push: - branches: - - main + branches: ['main'] pull_request: - branches: - - main + # The branches below must be a subset of the branches above + branches: ['main'] schedule: - - cron: "0 0 * * 0" - workflow_dispatch: + - cron: '0 0 * * 1' + +permissions: + contents: read jobs: analyze: name: Analyze - runs-on: ubuntu-latest + uses: nodejs/web-team/.github/workflows/codeql.yml@9f3c83af227d721768d9dbb63009a47ed4f4282f permissions: actions: read contents: read security-events: write - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: "javascript" - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/create-release-post.yml b/.github/workflows/create-release-post.yml new file mode 100644 index 0000000000000..fd3fa877a09cc --- /dev/null +++ b/.github/workflows/create-release-post.yml @@ -0,0 +1,59 @@ +# Security Notes +# Only selected Actions are allowed within this repository. Please refer to (https://github.com/nodejs/nodejs.org/settings/actions) +# for the full list of available actions. If you want to add a new one, please reach out a maintainer with Admin permissions. +# REVIEWERS, please always double-check security practices before merging a PR that contains Workflow changes!! +# AUTHORS, please only use actions with explicit SHA references, and avoid using `@master` or `@main` references or `@version` tags. + +name: Create Release Blog Post + +on: + workflow_dispatch: + inputs: + version: + description: The version to generate a blog post for. + type: string + required: true + +defaults: + run: + # This ensures that the working directory is the root of the repository + working-directory: ./ + +permissions: + contents: write + pull-requests: write + +jobs: + create-post: + runs-on: ubuntu-latest + + steps: + - uses: nodejs/web-team/actions/setup-environment@9f3c83af227d721768d9dbb63009a47ed4f4282f + with: + pnpm: true + use-version-file: true + + - run: node --run scripts:release-post -- "$VERSION" + working-directory: apps/site + id: release-post + env: + VERSION: ${{ inputs.version }} + + - name: Open pull request + uses: gr2m/create-or-update-pull-request-action@b65137ca591da0b9f43bad7b24df13050ea45d1b # v1.10.1 + # Creates a PR or update the Action's existing PR, or + # no-op if the base branch is already up-to-date. + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + update-pull-request-title-and-body: true + branch: release-${{ inputs.version }} + body: | + Creates a new blog post for ${{ inputs.version }} + + Check this workflow's logs at ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}. + commit-message: 'feat(blog): create post for ${{ inputs.version }}' + labels: fast-track + title: 'feat(blog): create post for ${{ inputs.version }}' + assignees: ${{ steps.release-post.outputs.author }} + draft: true diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000000000..3b0d4f38d0255 --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,18 @@ +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: + uses: nodejs/web-team/.github/workflows/dependency-review.yml@9f3c83af227d721768d9dbb63009a47ed4f4282f diff --git a/.github/workflows/lighthouse.yml b/.github/workflows/lighthouse.yml new file mode 100644 index 0000000000000..8d3cc737f5472 --- /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@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 + with: + egress-policy: audit + + - name: Git Checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + 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..5bd4c01adcb1b --- /dev/null +++ b/.github/workflows/lint-and-tests.yml @@ -0,0 +1,124 @@ +# 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 + types: [opened, synchronize, reopened, ready_for_review] + 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: + - uses: nodejs/web-team/actions/setup-environment@9f3c83af227d721768d9dbb63009a47ed4f4282f + with: + pnpm: true + use-version-file: true + + - name: Restore Lint Cache + uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 + 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: 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@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 + 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: + - uses: nodejs/web-team/actions/setup-environment@9f3c83af227d721768d9dbb63009a47ed4f4282f + with: + pnpm: true + use-version-file: true + + - name: Run Unit Tests + # We want to run Unit Tests in every circumstance, including Crowdin PRs and Dependabot PRs to ensure + # that changes to dependencies or translations don't break the Unit Tests + 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@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 + with: + files: ./apps/site/lcov.info,./packages/*/lcov.info + + - name: Upload test results to Codecov + if: ${{ !cancelled() && github.event_name != 'merge_group' }} + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 + with: + report_type: test_results + files: ./apps/site/junit.xml,./packages/*/junit.xml diff --git a/.github/workflows/notify-on-push.yml b/.github/workflows/notify-on-push.yml new file mode 100644 index 0000000000000..bcec8d1cd0751 --- /dev/null +++ b/.github/workflows/notify-on-push.yml @@ -0,0 +1,20 @@ +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: + - uses: nodejs/web-team/actions/notify-on-push@9f3c83af227d721768d9dbb63009a47ed4f4282f + with: + webhook: ${{ secrets.SLACK_WEBHOOK }} diff --git a/.github/workflows/playwright-cloudflare-open-next.yml b/.github/workflows/playwright-cloudflare-open-next.yml new file mode 100644 index 0000000000000..6741ad0ee7e83 --- /dev/null +++ b/.github/workflows/playwright-cloudflare-open-next.yml @@ -0,0 +1,65 @@ +# Security Notes +# Only selected Actions are allowed within this repository. Please refer to (https://github.com/nodejs/nodejs.org/settings/actions) +# for the full list of available actions. If you want to add a new one, please reach out a maintainer with Admin permissions. +# REVIEWERS, please always double-check security practices before merging a PR that contains Workflow changes!! +# AUTHORS, please only use actions with explicit SHA references, and avoid using `@master` or `@main` references or `@version` tags. + +name: Playwright Tests on Cloudflare Open-Next + +on: + push: + branches: + - main + pull_request: + branches: + - main + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + actions: read + +jobs: + playwright: + name: Playwright Tests + runs-on: ubuntu-latest + + steps: + - uses: nodejs/web-team/actions/setup-environment@9f3c83af227d721768d9dbb63009a47ed4f4282f + with: + pnpm: true + use-version-file: true + fetch-depth: 2 + + - name: Get Playwright version + id: playwright-version + working-directory: apps/site + run: echo "version=$(node_modules/.bin/playwright --version | awk '{print $2}')" >> $GITHUB_OUTPUT + + - name: Cache Playwright browsers + id: playwright-cache + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 + 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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + 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..ac28788100f8f --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,90 @@ +# 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: + - uses: nodejs/web-team/actions/setup-environment@9f3c83af227d721768d9dbb63009a47ed4f4282f + with: + pnpm: true + use-version-file: true + fetch-depth: 2 + + - name: Get Playwright version + id: playwright-version + working-directory: apps/site + run: echo "version=$(node_modules/.bin/playwright --version | awk '{print $2}')" >> $GITHUB_OUTPUT + + - name: Cache Playwright browsers + id: playwright-cache + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 + 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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + with: + name: playwright-report + path: apps/site/playwright-report/ diff --git a/.github/workflows/pnpm-updater.yml b/.github/workflows/pnpm-updater.yml new file mode 100644 index 0000000000000..129500ad1e2b7 --- /dev/null +++ b/.github/workflows/pnpm-updater.yml @@ -0,0 +1,60 @@ +# Security Notes +# Only selected Actions are allowed within this repository. Please refer to (https://github.com/nodejs/nodejs.org/settings/actions) +# for the full list of available actions. If you want to add a new one, please reach out a maintainer with Admin permissions. +# REVIEWERS, please always double-check security practices before merging a PR that contains Workflow changes!! +# AUTHORS, please only use actions with explicit SHA references, and avoid using `@master` or `@main` references or `@version` tags. + +name: PNPM updater + +on: + workflow_dispatch: + schedule: + - cron: '0 0 1 * *' + +permissions: + contents: write + pull-requests: write + +jobs: + update-pnpm: + runs-on: ubuntu-latest + + steps: + - name: Harden Runner + uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 + with: + egress-policy: audit + + - name: Git Checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - name: Setup pnpm + uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + + - name: Update pnpm and package.json + id: update-pnpm + run: | + echo "old=$(pnpm --version)" >> $GITHUB_OUTPUT + pnpm self-update + echo "new=$(pnpm --version)" >> $GITHUB_OUTPUT + cat <<< $(jq '.devEngines.packageManager.version = (.packageManager | split("@")[1])' package.json) > package.json + + - name: Open pull request + if: steps.update-pnpm.outputs.old != steps.update-pnpm.outputs.new + uses: gr2m/create-or-update-pull-request-action@b65137ca591da0b9f43bad7b24df13050ea45d1b # v1.10.1 + # Creates a PR or update the Action's existing PR, or + # no-op if the base branch is already up-to-date. + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + update-pull-request-title-and-body: true + branch: chore/update-pnpm + body: | + Updates pnpm from ${{ steps.update-pnpm.outputs.old }} to ${{ steps.update-pnpm.outputs.new }} + + cc @nodejs/web-infra + + Check this workflow's logs at ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}. + commit-message: 'meta: update pnpm from ${{ steps.update-pnpm.outputs.old }} to ${{ steps.update-pnpm.outputs.new }}' + title: 'meta: update pnpm from ${{ steps.update-pnpm.outputs.old }} to ${{ steps.update-pnpm.outputs.new }}' + draft: true diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml new file mode 100644 index 0000000000000..1b00bb45a1e99 --- /dev/null +++ b/.github/workflows/publish-packages.yml @@ -0,0 +1,135 @@ +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@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 + with: + egress-policy: audit + + - name: Verify commit authenticity + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Get commit data from GitHub API to verify its authenticity + COMMIT_DATA=$(gh api repos/${{ github.repository }}/commits/$COMMIT_SHA) + # Check if commit signature is verified (GPG signed) + VERIFIED=$(echo "$COMMIT_DATA" | jq -r '.commit.verification.verified') + # Check if commit was made through GitHub's web interface (merge queue) + COMMITTER=$(echo "$COMMIT_DATA" | jq -r '.commit.committer.email') + + # Security checks to ensure we only publish from verified and trusted sources + if [[ "$VERIFIED" != "true" ]]; then + echo "❌ Unverified commit! Aborting." + exit 1 + fi + + if [[ "$COMMITTER" != "noreply@github.com" ]]; then + echo "❌ Not merged with the merge queue! Aborting." + exit 1 + fi + + echo "✅ Commit is verified and trusted." + + - name: Checkout repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + 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 + # Skip if no packages need to be published + if: fromJson(needs.prepare-packages.outputs.matrix).package[0] != null + # 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: + - uses: nodejs/web-team/actions/setup-environment@9f3c83af227d721768d9dbb63009a47ed4f4282f + with: + pnpm: true + use-version-file: true + registry-url: 'https://registry.npmjs.org' + + - name: Publish + working-directory: packages/${{ matrix.package }} + run: | + # 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..5db76159159f0 --- /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@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 + 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/request-codeowner-review.yml b/.github/workflows/request-codeowner-review.yml new file mode 100644 index 0000000000000..f5a9147140789 --- /dev/null +++ b/.github/workflows/request-codeowner-review.yml @@ -0,0 +1,22 @@ +name: Request Codeowner Reviews + +on: + pull_request_target: + types: [opened] + +permissions: + contents: read + pull-requests: write + +jobs: + request-reviews: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - name: Request Codeowner Reviews + uses: pkgjs/request-codeowner-review@d39b61c5e1399655dce6287b3b119af93dee235a # v1.1.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + output-mode: comment diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 0000000000000..026761dc3e4fd --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,26 @@ +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 + +permissions: {} + +jobs: + analysis: + name: Scorecard analysis + uses: nodejs/web-team/.github/workflows/scorecard.yml@b62c434f5e530041c288a40280567849449e74be + 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 diff --git a/.github/workflows/sync-orama.yml b/.github/workflows/sync-orama.yml new file mode 100644 index 0000000000000..4e2b22d155dbd --- /dev/null +++ b/.github/workflows/sync-orama.yml @@ -0,0 +1,48 @@ +# 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: + workflow_dispatch: + 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: + - uses: nodejs/web-team/actions/setup-environment@9f3c83af227d721768d9dbb63009a47ed4f4282f + with: + pnpm: true + use-version-file: true + + - name: Sync Orama Cloud + working-directory: apps/site + run: node --run sync-orama + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NEW_ORAMA_PROJECT_ID: ${{ github.event_name == 'push' && secrets.NEW_ORAMA_PRODUCTION_PROJECT_ID || secrets.NEW_ORAMA_PROJECT_ID }} + NEW_ORAMA_PRIVATE_API_KEY: ${{ github.event_name == 'push' && secrets.NEW_ORAMA_PRODUCTION_PRIVATE_API_KEY || secrets.NEW_ORAMA_PRIVATE_API_KEY }} + NEW_ORAMA_DATASOURCE_ID: ${{ github.event_name == 'push' && secrets.NEW_ORAMA_PRODUCTION_DATASOURCE_ID || secrets.NEW_ORAMA_DATASOURCE_ID }} diff --git a/.github/workflows/tmp-cloudflare-open-next-deploy.yml b/.github/workflows/tmp-cloudflare-open-next-deploy.yml new file mode 100644 index 0000000000000..e19aa64223733 --- /dev/null +++ b/.github/workflows/tmp-cloudflare-open-next-deploy.yml @@ -0,0 +1,67 @@ +# Security Notes +# Only selected Actions are allowed within this repository. Please refer to (https://github.com/nodejs/nodejs.org/settings/actions) +# for the full list of available actions. If you want to add a new one, please reach out a maintainer with Admin permissions. +# REVIEWERS, please always double-check security practices before merging a PR that contains Workflow changes!! +# AUTHORS, please only use actions with explicit SHA references, and avoid using `@master` or `@main` references or `@version` tags. + +# Note: this is a temporary workflow for deploying the OpenNext version of the site (hosted on https://node-test.org) +# this version of the site is just used for testing purposes and ideally we want to keep it in sync with the +#  official website + +name: Deploy the Cloudflare OpenNext test version of the site (https://node-test.org) + +on: + workflow_dispatch: + push: + branches: + - main + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + deploy: + name: Deploy Open-Next site + runs-on: ubuntu-latest + + steps: + - name: Harden Runner + uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 + with: + egress-policy: audit + + - name: Git Checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - name: Set up pnpm + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 + + - name: Set up Node.js + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + with: + # We want to ensure that the Node.js version running here respects our supported versions + node-version-file: '.nvmrc' + cache: 'pnpm' + + - name: Install packages + run: pnpm install --frozen-lockfile + + - name: Build blog data + working-directory: apps/site + run: node --run build:blog-data + + - name: Build open-next site + working-directory: apps/site + run: node --run cloudflare:build:worker + + - name: Deploy open-next site + working-directory: apps/site + run: node --run cloudflare:deploy + env: + CF_WORKERS_SCRIPTS_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: fb4a2d0f103c6ff38854ac69eb709272 diff --git a/.github/workflows/translations-pr-lint.yml b/.github/workflows/translations-pr-lint.yml new file mode 100644 index 0000000000000..2f9529a86b96e --- /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@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 + with: + egress-policy: audit + + - uses: thollander/actions-comment-pull-request@e2c37e53a7d2227b61585343765f73a9ca57eda9 # v3.0.0 + with: + message: | + > [!NOTE]\ + > Your Pull Request seems to be updating **Translations** of the Node.js Website. + > + > Whilst we appreciate your intent; Any Translation update should be done through our [Crowdin Project](https://crowdin.com/project/nodejs-web). + > We recommend giving a read on our [Translation Guidelines](https://github.com/nodejs/nodejs.org/blob/main/docs/translation.md). + > + > Thank you! + comment-tag: use_crowdin diff --git a/.github/workflows/translations-sync.yml b/.github/workflows/translations-sync.yml new file mode 100644 index 0000000000000..d05becf5fc2f1 --- /dev/null +++ b/.github/workflows/translations-sync.yml @@ -0,0 +1,120 @@ +# 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@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + 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@60debf382ee245b21794321190ad0501db89d8c1 # v2.13.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: + - uses: nodejs/web-team/actions/setup-environment@9f3c83af227d721768d9dbb63009a47ed4f4282f + with: + pnpm: true + use-version-file: true + ref: ${{ env.BRANCH_NAME }} + token: ${{ secrets.CROWDIN_GITHUB_BOT_TOKEN }} + fetch-depth: 2 + + - name: Restore Lint Cache + uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 + 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: 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@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0 + 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@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 + 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..6fac126738fd9 --- /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@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + # see all the options at https://github.com/crowdin/github-action + - name: crowdin action + uses: crowdin/github-action@60debf382ee245b21794321190ad0501db89d8c1 # v2.13.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..3e5db9342a1f8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,54 @@ -# 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 -# OSX system files, the bane of our existence -.DS_Store -.AppleDouble -.LSOverride +# 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 -# Netlify -.netlify +# Cache Files +.eslintmdcache +.stylelintcache +.prettiercache + +# TypeScript +tsconfig.tsbuildinfo +dist/ + +# Cloudflare Build Output +apps/site/.open-next +apps/site/.wrangler + +## Playwright +test-results +playwright-report + +## MacOS Ignored Files +.DS_Store -.cache/ +## Other Files +.env 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..54c65116f15a6 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v24 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000000000..b0a2a81b9e2e2 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,48 @@ +# Commonly ignored Node.js files +node_modules + +# npm Compatibility +# https://github.com/nodejs/nodejs.org/discussions/5334#discussioncomment-12827850 +npm-debug.log +.npm +package-lock.json + +# Next.js Build Output +.next +build +next-env.d.ts + +# 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..374b3f715492a 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/web-team/blob/main/MEMBERS.md#nodejs-website-team-nodejsnodejs-website). 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/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..da65bb0d0e836 100644 --- a/README.md +++ b/README.md @@ -1,95 +1,128 @@ -# [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. + +Details regarding the deployment are only accessible to the maintainers of the Website Team due to certain limitations. -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. :) +The current integration is owned by the OpenJS Foundation and managed by the Website Team. -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. +
+ Legacy Deployment -### To translate content into other languages +The full setup is in minus secrets and certificates. -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 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. -Crowdin is an online tool that facilitates the user experience for the translator, here is more information: +
-Website translations are handled via [Crowdin](https://crowdin.com/project/nodejs-website). +## Node.js Binaries & API Docs -To help with localization, please read the [TRANSLATION](TRANSLATION.md) guide. +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/`. -## Layout +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. -* 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. +- [`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/`. -## Serve/Build Options +## Relevant Links -* `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. +[Code of Conduct][] -## Test Options +[Contribution Guidelines][] -Before submitting, you must pass all the unit tests and syntax checks by running the two commands below: +[Collaborator Guide][] -* `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. +[Governance Document][] -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 +[governance document]: https://github.com/nodejs/web-team/blob/main/GOVERNANCE.md +[figma design]: https://www.figma.com/file/a10cjjw3MzvRQMPT9FP3xz +[translation guidelines]: https://github.com/nodejs/nodejs.org/blob/main/docs/translation.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000000..d6a456a375a0f --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,20 @@ +# Security + +## Reporting a vulnerability to Node.js Website + +Please report security issues **privately** using the **GitHub Security Advisory** +workflow ([Security → “Report a vulnerability”](https://github.com/nodejs/nodejs.org/security/advisories/new)). + +Do **not** open a public GitHub issue for security problems. + +We aim to acknowledge reports within **7 business days**. +If you do **not** receive an acknowledgement within **7 business days**, +forward your report to **[tsc@nodejs.org](mailto:tsc@nodejs.org)**. + +## Disclosure & advisories + +Confirmed vulnerabilities will be published as a **GitHub Security Advisory** +(and assigned a CVE when applicable). Notices are also shared via: + +- Node.js blog advisories: [https://nodejs.org/blog/vulnerability/](https://nodejs.org/blog/vulnerability/) + when necessary. diff --git a/TRANSLATION.md b/TRANSLATION.md deleted file mode 100644 index 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..aecb38f3da63f --- /dev/null +++ b/apps/site/app/[locale]/[...path]/page.tsx @@ -0,0 +1,97 @@ +/** + * 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 { availableLocaleCodes, defaultLocale } from '@node-core/website-i18n'; +import { notFound } from 'next/navigation'; + +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 type { DynamicParams } from '#site/types'; +import type { FC } from 'react'; + +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..2a11e65becc46 --- /dev/null +++ b/apps/site/app/[locale]/blog/[...path]/page.tsx @@ -0,0 +1,82 @@ +import { defaultLocale } from '@node-core/website-i18n'; +import { notFound } from 'next/navigation'; + +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 type { DynamicParams } from '#site/types'; +import type { FC } from 'react'; + +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 = ({ params }: PageParams) => + basePage.generateMetadata({ params, prefix: 'blog' }); + +// 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..1cc23837ccdf1 --- /dev/null +++ b/apps/site/app/[locale]/download/archive/[version]/page.tsx @@ -0,0 +1,93 @@ +import { defaultLocale } from '@node-core/website-i18n'; +import { notFound, redirect } from 'next/navigation'; + +import provideReleaseData from '#site/next-data/providers/releaseData'; +import provideReleaseVersions from '#site/next-data/providers/releaseVersions'; +import { ENABLE_STATIC_EXPORT } from '#site/next.constants.mjs'; +import * as basePage from '#site/next.dynamic.page.mjs'; + +import type { DynamicParams } from '#site/types'; +import type { FC } from 'react'; + +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 []; + } + + const versions = await provideReleaseVersions(); + + return versions.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 = await provideReleaseData(); + + const release = releaseData.find(release => release.status === 'Current'); + + redirect(`/${locale}/download/archive/${release?.versionWithPrefix}`); + } + + const versions = await provideReleaseVersions(); + + // Verifies if the current route is a dynamic route + const isDynamicRoute = versions.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..0c4bf7f827ec8 --- /dev/null +++ b/apps/site/app/[locale]/error.tsx @@ -0,0 +1,30 @@ +'use client'; + +import { useTranslations } from 'next-intl'; + +import Button from '#site/components/Common/Button'; +import GlowingBackdropLayout from '#site/layouts/GlowingBackdrop'; + +import type { FC } from 'react'; + +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..c70c0b63f9b3d --- /dev/null +++ b/apps/site/app/[locale]/feed/[feed]/route.ts @@ -0,0 +1,45 @@ +import { defaultLocale } from '@node-core/website-i18n'; +import { NextResponse } from 'next/server'; + +import { siteConfig } from '#site/next.json.mjs'; +import { getFeeds } from '#site/util/feeds'; + +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 = getFeeds(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..5e1e440c5b974 --- /dev/null +++ b/apps/site/app/[locale]/layout.tsx @@ -0,0 +1,60 @@ +import { availableLocales, defaultLocale } from '@node-core/website-i18n'; +import { Analytics } from '@vercel/analytics/react'; +import { SpeedInsights } from '@vercel/speed-insights/next'; +import classNames from 'classnames'; +import { NextIntlClientProvider } from 'next-intl'; + +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 { ThemeProvider } from '#site/providers/themeProvider'; + +import type { FC, PropsWithChildren } from 'react'; + +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 } = + availableLocales.find(l => l.code === locale) || defaultLocale; + + return ( + + + + + {children} + + + + + ); +}; diff --git a/apps/site/components/Common/Searchbox/SearchItem/index.tsx b/apps/site/components/Common/Searchbox/SearchItem/index.tsx new file mode 100644 index 0000000000000..a69775b0a8076 --- /dev/null +++ b/apps/site/components/Common/Searchbox/SearchItem/index.tsx @@ -0,0 +1,36 @@ +import SearchHit from '@node-core/ui-components/Common/Search/Results/Hit'; +import Link from 'next/link'; +import { useLocale } from 'next-intl'; + +import type { Document } from '../DocumentLink'; +import type { LinkLike } from '@node-core/ui-components/types'; +import type { ComponentProps, FC } from 'react'; + +import { getDocumentHref, getFormattedPath } from './utils'; + +type SearchItemProps = Omit< + ComponentProps, + 'document' | 'as' +> & { + document: Document; +}; + +const SearchItem: FC = ({ document, ...props }) => { + const locale = useLocale(); + + return ( + + ); +}; + +export default SearchItem; diff --git a/apps/site/components/Common/Searchbox/SearchItem/utils.ts b/apps/site/components/Common/Searchbox/SearchItem/utils.ts new file mode 100644 index 0000000000000..ad4a9a4cc63bb --- /dev/null +++ b/apps/site/components/Common/Searchbox/SearchItem/utils.ts @@ -0,0 +1,18 @@ +import type { Document } from '../DocumentLink'; + +export const uppercaseFirst = (word: string) => + word.charAt(0).toUpperCase() + word.slice(1); + +export const getFormattedPath = (path: string, title: string) => + `${path + .replace(/#.+$/, '') + .split('/') + .map(element => element.replaceAll('-', ' ')) + .map(element => uppercaseFirst(element)) + .filter(Boolean) + .join(' > ')} — ${title}`; + +export const getDocumentHref = (document: Document, locale: string) => + document.siteSection?.toLowerCase() === 'docs' + ? `/${document.path}` + : `/${locale}/${document.path}`; diff --git a/apps/site/components/Common/Searchbox/SlidingChatPanel/index.module.css b/apps/site/components/Common/Searchbox/SlidingChatPanel/index.module.css new file mode 100644 index 0000000000000..4e9612bb55082 --- /dev/null +++ b/apps/site/components/Common/Searchbox/SlidingChatPanel/index.module.css @@ -0,0 +1,58 @@ +@reference "../../../../styles/index.css"; + +.slidingPanelCloseButton { + @apply absolute + top-2 + right-6 + z-20 + cursor-pointer + rounded-full + p-2 + text-neutral-700 + duration-300 + hover:bg-white/20 + focus:bg-white/20 + focus:outline-none + motion-safe:transition-colors + dark:text-white; + + svg { + @apply size-5; + } +} + +.slidingPanelContentWrapper { + @apply fixed + bottom-0 + left-0 + box-border + h-[95vh] + w-full + justify-self-center + overflow-hidden + rounded-lg + border + border-neutral-300 + bg-white + p-0 + text-white + duration-300 + motion-safe:ease-in-out + dark:border-neutral-900 + dark:bg-zinc-950; +} + +.slidingPanelInner { + @apply relative + mx-auto + flex + h-full + max-w-4xl + flex-col + justify-between + py-6; +} + +.slidingPanelBottom { + @apply relative; +} diff --git a/apps/site/components/Common/Searchbox/SlidingChatPanel/index.tsx b/apps/site/components/Common/Searchbox/SlidingChatPanel/index.tsx new file mode 100644 index 0000000000000..5e1d89fbe8c3f --- /dev/null +++ b/apps/site/components/Common/Searchbox/SlidingChatPanel/index.tsx @@ -0,0 +1,57 @@ +'use client'; + +import { XMarkIcon } from '@heroicons/react/24/solid'; +import ChatInput from '@node-core/ui-components/Common/Search/Chat/Input'; +import { SlidingPanel } from '@orama/ui/components'; +import { useTranslations } from 'next-intl'; + +import type { FC, PropsWithChildren } from 'react'; + +import { ChatInteractionsContainer } from '../ChatInteractions'; + +import styles from './index.module.css'; + +type SlidingChatPanelProps = PropsWithChildren<{ + open: boolean; + onClose: () => void; +}>; + +export const SlidingChatPanel: FC = ({ + open, + onClose, +}) => { + const t = useTranslations(); + + return ( + <> + + + + + + +
+ +
+ +
+
+
+
+ + ); +}; diff --git a/apps/site/components/Common/Searchbox/index.module.css b/apps/site/components/Common/Searchbox/index.module.css new file mode 100644 index 0000000000000..207d7ab262ae6 --- /dev/null +++ b/apps/site/components/Common/Searchbox/index.module.css @@ -0,0 +1,8 @@ +@reference "../../../styles/index.css"; + +.searchResultsContainer { + @apply flex + grow + flex-col + overflow-y-auto; +} diff --git a/apps/site/components/Common/Searchbox/index.tsx b/apps/site/components/Common/Searchbox/index.tsx new file mode 100644 index 0000000000000..340a1fec8673c --- /dev/null +++ b/apps/site/components/Common/Searchbox/index.tsx @@ -0,0 +1,68 @@ +'use client'; + +import ChatTrigger from '@node-core/ui-components/Common/Search/Chat/Trigger'; +import SearchModal from '@node-core/ui-components/Common/Search/Modal'; +import SearchResults from '@node-core/ui-components/Common/Search/Results'; +import SearchSuggestions from '@node-core/ui-components/Common/Search/Suggestions'; +import { useTranslations } from 'next-intl'; +import { useState } from 'react'; + +import { DEFAULT_ORAMA_QUERY_PARAMS } from '#site/next.constants.mjs'; + +import type { Document } from './DocumentLink'; +import type { FC } from 'react'; + +import { Footer } from './Footer'; +import { oramaClient } from './orama-client'; +import SearchItem from './SearchItem'; +import { SlidingChatPanel } from './SlidingChatPanel'; + +import styles from './index.module.css'; + +const Searchbox: FC = () => { + const t = useTranslations(); + const [mode, setMode] = useState<'chat' | 'search'>('search'); + + const sharedProps = { + searchParams: DEFAULT_ORAMA_QUERY_PARAMS, + tabIndex: mode === 'search' ? 0 : -1, + 'aria-hidden': mode === 'chat', + }; + + return ( + +
+ setMode('chat')}> + {t('components.search.chatButtonLabel')} + + ( + + )} + {...sharedProps} + > + setMode('chat')} + /> + +
+