From 0d41076ae2c5b426df5f9f9f562c9458274b2657 Mon Sep 17 00:00:00 2001 From: Aviv Keller Date: Fri, 22 Aug 2025 13:02:47 -0400 Subject: [PATCH 1/4] feat(nextjs): upgrade to 15.5, and refresh config (#8096) * feat(nextjs): upgrade to 15.5, and refresh config * fixup! --- apps/site/eslint.config.js | 2 +- apps/site/next-env.d.ts | 1 + apps/site/next.config.mjs | 11 +-- apps/site/package.json | 6 +- pnpm-lock.yaml | 136 ++++++++++++++++++------------------- 5 files changed, 76 insertions(+), 80 deletions(-) diff --git a/apps/site/eslint.config.js b/apps/site/eslint.config.js index dd5bea57b1387..61ca4ccb1d6ec 100644 --- a/apps/site/eslint.config.js +++ b/apps/site/eslint.config.js @@ -20,7 +20,7 @@ const compatConfig = compat.config({ export default tseslint.config( ...baseConfig, - { ignores: ['pages/en/blog/**/*.{md,mdx}/**', 'public'] }, + { ignores: ['pages/en/blog/**/*.{md,mdx}/**', 'public', 'next-env.d.ts'] }, { extends: [ react.configs.flat['jsx-runtime'], diff --git a/apps/site/next-env.d.ts b/apps/site/next-env.d.ts index 3cd7048ed9471..36a4fe488ad02 100644 --- a/apps/site/next-env.d.ts +++ b/apps/site/next-env.d.ts @@ -1,6 +1,7 @@ /// /// /// +/// // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/site/next.config.mjs b/apps/site/next.config.mjs index bb7ad9b4e52fc..d162a178f4335 100644 --- a/apps/site/next.config.mjs +++ b/apps/site/next.config.mjs @@ -7,8 +7,6 @@ import { redirects, rewrites } from './next.rewrites.mjs'; /** @type {import('next').NextConfig} */ const nextConfig = { allowedDevOrigins: ['10.1.1.232'], - // We don't use trailing slashes on URLs from the Node.js Website - trailingSlash: false, // We don't want to redirect with trailing slashes skipTrailingSlashRedirect: true, // We allow the BASE_PATH to be overridden in case that the Website @@ -54,7 +52,7 @@ const nextConfig = { ], }, // On static export builds we want the output directory to be "build" - distDir: ENABLE_STATIC_EXPORT ? 'build' : '.next', + distDir: ENABLE_STATIC_EXPORT ? 'build' : undefined, // On static export builds we want to enable the export feature output: ENABLE_STATIC_EXPORT ? 'export' : undefined, // This configures all the Next.js rewrites, which are used for rewriting internal URLs into other internal Endpoints @@ -65,10 +63,13 @@ const nextConfig = { // We don't want to run Type Checking on Production Builds // as we already check it on the CI within each Pull Request typescript: { ignoreBuildErrors: true }, + // Enable statically typed links + // @see https://nextjs.org/docs/app/api-reference/config/typescript#statically-typed-links + typedRoutes: true, // We don't want to run ESLint Checking on Production Builds // as we already check it on the CI within each Pull Request - // we also configure ESLint to run its lint checking on all files (next lint) - eslint: { dirs: ['.'], ignoreDuringBuilds: true }, + // we also configure ESLint to run its lint checking on all files + eslint: { ignoreDuringBuilds: true }, experimental: { // Ensure that server-side code is also minified serverMinification: true, diff --git a/apps/site/package.json b/apps/site/package.json index e08cc2a3adc0e..343abb7f669cb 100644 --- a/apps/site/package.json +++ b/apps/site/package.json @@ -60,7 +60,7 @@ "feed": "~5.1.0", "github-slugger": "~2.0.0", "gray-matter": "~4.0.3", - "next": "15.4.4", + "next": "15.5.0", "next-intl": "~4.3.4", "next-themes": "~0.4.6", "postcss-calc": "~10.1.1", @@ -81,13 +81,13 @@ "@eslint/compat": "~1.3.1", "@eslint/eslintrc": "~3.3.1", "@flarelabs-net/wrangler-build-time-fs-assets-polyfilling": "^0.0.1", - "@next/eslint-plugin-next": "15.4.4", + "@next/eslint-plugin-next": "15.5.0", "@opennextjs/cloudflare": "^1.6.4", "@playwright/test": "^1.54.1", "@testing-library/user-event": "~14.6.1", "@types/mdx": "^2.0.13", "@types/semver": "~7.7.0", - "eslint-config-next": "15.4.4", + "eslint-config-next": "15.5.0", "eslint-import-resolver-typescript": "~4.4.4", "eslint-plugin-mdx": "~3.6.2", "eslint-plugin-react": "~7.37.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d5177123f1b73..3419a7fbb1242 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -128,13 +128,13 @@ importers: version: 0.1.0 '@vercel/analytics': specifier: ~1.5.0 - version: 1.5.0(next@15.4.4(@opentelemetry/api@1.9.0)(@playwright/test@1.54.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1) + version: 1.5.0(next@15.5.0(@opentelemetry/api@1.9.0)(@playwright/test@1.54.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1) '@vercel/otel': specifier: ~1.13.0 version: 1.13.0(@opentelemetry/api-logs@0.203.0)(@opentelemetry/api@1.9.0)(@opentelemetry/instrumentation@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-logs@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-metrics@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0)) '@vercel/speed-insights': specifier: ~1.2.0 - version: 1.2.0(next@15.4.4(@opentelemetry/api@1.9.0)(@playwright/test@1.54.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1) + version: 1.2.0(next@15.5.0(@opentelemetry/api@1.9.0)(@playwright/test@1.54.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1) classnames: specifier: 'catalog:' version: 2.5.1 @@ -151,17 +151,17 @@ importers: specifier: ~4.0.3 version: 4.0.3 next: - specifier: 15.4.4 - version: 15.4.4(@opentelemetry/api@1.9.0)(@playwright/test@1.54.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: 15.5.0 + version: 15.5.0(@opentelemetry/api@1.9.0)(@playwright/test@1.54.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) next-intl: specifier: ~4.3.4 - version: 4.3.4(next@15.4.4(@opentelemetry/api@1.9.0)(@playwright/test@1.54.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(typescript@5.8.3) + version: 4.3.4(next@15.5.0(@opentelemetry/api@1.9.0)(@playwright/test@1.54.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(typescript@5.8.3) next-themes: specifier: ~0.4.6 version: 0.4.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1) postcss-calc: specifier: ~10.1.1 - version: 10.1.1(postcss@8.5.6) + version: 10.1.1(postcss@8.5.3) react: specifier: 'catalog:' version: 19.1.1 @@ -209,8 +209,8 @@ importers: specifier: ^0.0.1 version: 0.0.1 '@next/eslint-plugin-next': - specifier: 15.4.4 - version: 15.4.4 + specifier: 15.5.0 + version: 15.5.0 '@opennextjs/cloudflare': specifier: ^1.6.4 version: 1.6.4(wrangler@4.26.1) @@ -227,8 +227,8 @@ importers: specifier: ~7.7.0 version: 7.7.0 eslint-config-next: - specifier: 15.4.4 - version: 15.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.38.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3) + specifier: 15.5.0 + version: 15.5.0(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.38.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3) eslint-import-resolver-typescript: specifier: ~4.4.4 version: 4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.38.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-import@2.32.0)(eslint@9.32.0(jiti@2.4.2)) @@ -1890,56 +1890,56 @@ packages: '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} - '@next/env@15.4.4': - resolution: {integrity: sha512-SJKOOkULKENyHSYXE5+KiFU6itcIb6wSBjgM92meK0HVKpo94dNOLZVdLLuS7/BxImROkGoPsjR4EnuDucqiiA==} + '@next/env@15.5.0': + resolution: {integrity: sha512-sDaprBAfzCQiOgo2pO+LhnV0Wt2wBgartjrr+dpcTORYVnnXD0gwhHhiiyIih9hQbq+JnbqH4odgcFWhqCGidw==} - '@next/eslint-plugin-next@15.4.4': - resolution: {integrity: sha512-1FDsyN//ai3Jd97SEd7scw5h1yLdzDACGOPRofr2GD3sEFsBylEEoL0MHSerd4n2dq9Zm/mFMqi4+NRMOreOKA==} + '@next/eslint-plugin-next@15.5.0': + resolution: {integrity: sha512-+k83U/fST66eQBjTltX2T9qUYd43ntAe+NZ5qeZVTQyTiFiHvTLtkpLKug4AnZAtuI/lwz5tl/4QDJymjVkybg==} - '@next/swc-darwin-arm64@15.4.4': - resolution: {integrity: sha512-eVG55dnGwfUuG+TtnUCt+mEJ+8TGgul6nHEvdb8HEH7dmJIFYOCApAaFrIrxwtEq2Cdf+0m5sG1Np8cNpw9EAw==} + '@next/swc-darwin-arm64@15.5.0': + resolution: {integrity: sha512-v7Jj9iqC6enxIRBIScD/o0lH7QKvSxq2LM8UTyqJi+S2w2QzhMYjven4vgu/RzgsdtdbpkyCxBTzHl/gN5rTRg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@15.4.4': - resolution: {integrity: sha512-zqG+/8apsu49CltEj4NAmCGZvHcZbOOOsNoTVeIXphYWIbE4l6A/vuQHyqll0flU2o3dmYCXsBW5FmbrGDgljQ==} + '@next/swc-darwin-x64@15.5.0': + resolution: {integrity: sha512-s2Nk6ec+pmYmAb/utawuURy7uvyYKDk+TRE5aqLRsdnj3AhwC9IKUBmhfnLmY/+P+DnwqpeXEFIKe9tlG0p6CA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@15.4.4': - resolution: {integrity: sha512-LRD4l2lq4R+2QCHBQVC0wjxxkLlALGJCwigaJ5FSRSqnje+MRKHljQNZgDCaKUZQzO/TXxlmUdkZP/X3KNGZaw==} + '@next/swc-linux-arm64-gnu@15.5.0': + resolution: {integrity: sha512-mGlPJMZReU4yP5fSHjOxiTYvZmwPSWn/eF/dcg21pwfmiUCKS1amFvf1F1RkLHPIMPfocxLViNWFvkvDB14Isg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@15.4.4': - resolution: {integrity: sha512-LsGUCTvuZ0690fFWerA4lnQvjkYg9gHo12A3wiPUR4kCxbx/d+SlwmonuTH2SWZI+RVGA9VL3N0S03WTYv6bYg==} + '@next/swc-linux-arm64-musl@15.5.0': + resolution: {integrity: sha512-biWqIOE17OW/6S34t1X8K/3vb1+svp5ji5QQT/IKR+VfM3B7GvlCwmz5XtlEan2ukOUf9tj2vJJBffaGH4fGRw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@15.4.4': - resolution: {integrity: sha512-aOy5yNRpLL3wNiJVkFYl6w22hdREERNjvegE6vvtix8LHRdsTHhWTpgvcYdCK7AIDCQW5ATmzr9XkPHvSoAnvg==} + '@next/swc-linux-x64-gnu@15.5.0': + resolution: {integrity: sha512-zPisT+obYypM/l6EZ0yRkK3LEuoZqHaSoYKj+5jiD9ESHwdr6QhnabnNxYkdy34uCigNlWIaCbjFmQ8FY5AlxA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@15.4.4': - resolution: {integrity: sha512-FL7OAn4UkR8hKQRGBmlHiHinzOb07tsfARdGh7v0Z0jEJ3sz8/7L5bR23ble9E6DZMabSStqlATHlSxv1fuzAg==} + '@next/swc-linux-x64-musl@15.5.0': + resolution: {integrity: sha512-+t3+7GoU9IYmk+N+FHKBNFdahaReoAktdOpXHFIPOU1ixxtdge26NgQEEkJkCw2dHT9UwwK5zw4mAsURw4E8jA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@15.4.4': - resolution: {integrity: sha512-eEdNW/TXwjYhOulQh0pffTMMItWVwKCQpbziSBmgBNFZIIRn2GTXrhrewevs8wP8KXWYMx8Z+mNU0X+AfvtrRg==} + '@next/swc-win32-arm64-msvc@15.5.0': + resolution: {integrity: sha512-d8MrXKh0A+c9DLiy1BUFwtg3Hu90Lucj3k6iKTUdPOv42Ve2UiIG8HYi3UAb8kFVluXxEfdpCoPPCSODk5fDcw==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@15.4.4': - resolution: {integrity: sha512-SE5pYNbn/xZKMy1RE3pAs+4xD32OI4rY6mzJa4XUkp/ItZY+OMjIgilskmErt8ls/fVJ+Ihopi2QIeW6O3TrMw==} + '@next/swc-win32-x64-msvc@15.5.0': + resolution: {integrity: sha512-Fe1tGHxOWEyQjmygWkkXSwhFcTJuimrNu52JEuwItrKJVV4iRjbWp9I7zZjwqtiNnQmxoEvoisn8wueFLrNpvQ==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -4696,8 +4696,8 @@ packages: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} - eslint-config-next@15.4.4: - resolution: {integrity: sha512-sK/lWLUVF5om18O5w76Jt3F8uzu/LP5mVa6TprCMWkjWHUmByq80iHGHcdH7k1dLiJlj+DRIWf98d5piwRsSuA==} + eslint-config-next@15.5.0: + resolution: {integrity: sha512-Yl4hlOdBqstAuHnlBfx2RimBzWQwysM2SJNu5EzYVa2qS2ItPs7lgxL0sJJDudEx5ZZHfWPZ/6U8+FtDFWs7/w==} peerDependencies: eslint: ^7.23.0 || ^8.0.0 || ^9.0.0 typescript: '>=3.3.1' @@ -6280,8 +6280,8 @@ packages: react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc - next@15.4.4: - resolution: {integrity: sha512-kNcubvJjOL9yUOfwtZF3HfDhuhp+kVD+FM2A6Tyua1eI/xfmY4r/8ZS913MMz+oWKDlbps/dQOWdDricuIkXLw==} + next@15.5.0: + resolution: {integrity: sha512-N1lp9Hatw3a9XLt0307lGB4uTKsXDhyOKQo7uYMzX4i0nF/c27grcGXkLdb7VcT8QPYLBa8ouIyEoUQJ2OyeNQ==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: @@ -10138,34 +10138,34 @@ snapshots: '@tybys/wasm-util': 0.10.0 optional: true - '@next/env@15.4.4': {} + '@next/env@15.5.0': {} - '@next/eslint-plugin-next@15.4.4': + '@next/eslint-plugin-next@15.5.0': dependencies: fast-glob: 3.3.1 - '@next/swc-darwin-arm64@15.4.4': + '@next/swc-darwin-arm64@15.5.0': optional: true - '@next/swc-darwin-x64@15.4.4': + '@next/swc-darwin-x64@15.5.0': optional: true - '@next/swc-linux-arm64-gnu@15.4.4': + '@next/swc-linux-arm64-gnu@15.5.0': optional: true - '@next/swc-linux-arm64-musl@15.4.4': + '@next/swc-linux-arm64-musl@15.5.0': optional: true - '@next/swc-linux-x64-gnu@15.4.4': + '@next/swc-linux-x64-gnu@15.5.0': optional: true - '@next/swc-linux-x64-musl@15.4.4': + '@next/swc-linux-x64-musl@15.5.0': optional: true - '@next/swc-win32-arm64-msvc@15.4.4': + '@next/swc-win32-arm64-msvc@15.5.0': optional: true - '@next/swc-win32-x64-msvc@15.4.4': + '@next/swc-win32-x64-msvc@15.5.0': optional: true '@noble/ciphers@1.3.0': {} @@ -12202,9 +12202,9 @@ snapshots: mdast-util-to-string: 3.2.0 unist-util-visit: 4.1.2 - '@vercel/analytics@1.5.0(next@15.4.4(@opentelemetry/api@1.9.0)(@playwright/test@1.54.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)': + '@vercel/analytics@1.5.0(next@15.5.0(@opentelemetry/api@1.9.0)(@playwright/test@1.54.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)': optionalDependencies: - next: 15.4.4(@opentelemetry/api@1.9.0)(@playwright/test@1.54.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + next: 15.5.0(@opentelemetry/api@1.9.0)(@playwright/test@1.54.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react: 19.1.1 '@vercel/otel@1.13.0(@opentelemetry/api-logs@0.203.0)(@opentelemetry/api@1.9.0)(@opentelemetry/instrumentation@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-logs@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-metrics@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))': @@ -12217,9 +12217,9 @@ snapshots: '@opentelemetry/sdk-metrics': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) - '@vercel/speed-insights@1.2.0(next@15.4.4(@opentelemetry/api@1.9.0)(@playwright/test@1.54.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)': + '@vercel/speed-insights@1.2.0(next@15.5.0(@opentelemetry/api@1.9.0)(@playwright/test@1.54.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)': optionalDependencies: - next: 15.4.4(@opentelemetry/api@1.9.0)(@playwright/test@1.54.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + next: 15.5.0(@opentelemetry/api@1.9.0)(@playwright/test@1.54.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react: 19.1.1 '@vitest/expect@3.2.4': @@ -13303,9 +13303,9 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-config-next@15.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.38.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3): + eslint-config-next@15.5.0(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.38.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3): dependencies: - '@next/eslint-plugin-next': 15.4.4 + '@next/eslint-plugin-next': 15.5.0 '@rushstack/eslint-patch': 1.12.0 '@typescript-eslint/eslint-plugin': 8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3) '@typescript-eslint/parser': 8.38.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3) @@ -13392,14 +13392,14 @@ snapshots: - bluebird - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.32.0(jiti@2.4.2)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.32.0(jiti@2.4.2)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.38.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3) eslint: 9.32.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.38.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-import@2.32.0)(eslint@9.32.0(jiti@2.4.2)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.38.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-import@2.32.0)(eslint@9.32.0(jiti@2.4.2)) transitivePeerDependencies: - supports-color @@ -13432,7 +13432,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.32.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.32.0(jiti@2.4.2)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.32.0(jiti@2.4.2)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -15381,11 +15381,11 @@ snapshots: neo-async@2.6.2: {} - next-intl@4.3.4(next@15.4.4(@opentelemetry/api@1.9.0)(@playwright/test@1.54.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(typescript@5.8.3): + next-intl@4.3.4(next@15.5.0(@opentelemetry/api@1.9.0)(@playwright/test@1.54.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(typescript@5.8.3): dependencies: '@formatjs/intl-localematcher': 0.5.10 negotiator: 1.0.0 - next: 15.4.4(@opentelemetry/api@1.9.0)(@playwright/test@1.54.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + next: 15.5.0(@opentelemetry/api@1.9.0)(@playwright/test@1.54.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react: 19.1.1 use-intl: 4.3.4(react@19.1.1) optionalDependencies: @@ -15396,9 +15396,9 @@ snapshots: react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - next@15.4.4(@opentelemetry/api@1.9.0)(@playwright/test@1.54.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + next@15.5.0(@opentelemetry/api@1.9.0)(@playwright/test@1.54.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: - '@next/env': 15.4.4 + '@next/env': 15.5.0 '@swc/helpers': 0.5.15 caniuse-lite: 1.0.30001727 postcss: 8.4.31 @@ -15406,14 +15406,14 @@ snapshots: react-dom: 19.1.1(react@19.1.1) styled-jsx: 5.1.6(react@19.1.1) optionalDependencies: - '@next/swc-darwin-arm64': 15.4.4 - '@next/swc-darwin-x64': 15.4.4 - '@next/swc-linux-arm64-gnu': 15.4.4 - '@next/swc-linux-arm64-musl': 15.4.4 - '@next/swc-linux-x64-gnu': 15.4.4 - '@next/swc-linux-x64-musl': 15.4.4 - '@next/swc-win32-arm64-msvc': 15.4.4 - '@next/swc-win32-x64-msvc': 15.4.4 + '@next/swc-darwin-arm64': 15.5.0 + '@next/swc-darwin-x64': 15.5.0 + '@next/swc-linux-arm64-gnu': 15.5.0 + '@next/swc-linux-arm64-musl': 15.5.0 + '@next/swc-linux-x64-gnu': 15.5.0 + '@next/swc-linux-x64-musl': 15.5.0 + '@next/swc-win32-arm64-msvc': 15.5.0 + '@next/swc-win32-x64-msvc': 15.5.0 '@opentelemetry/api': 1.9.0 '@playwright/test': 1.54.1 sharp: 0.34.3 @@ -15724,12 +15724,6 @@ snapshots: postcss-selector-parser: 7.1.0 postcss-value-parser: 4.2.0 - postcss-calc@10.1.1(postcss@8.5.6): - dependencies: - postcss: 8.5.6 - postcss-selector-parser: 7.1.0 - postcss-value-parser: 4.2.0 - postcss-cli@11.0.1(jiti@2.4.2)(postcss@8.5.3)(tsx@4.20.3): dependencies: chokidar: 3.6.0 From 5be40991bfee3d608a3d781269eecd55e5a6a86a Mon Sep 17 00:00:00 2001 From: Aviv Keller Date: Fri, 22 Aug 2025 13:03:00 -0400 Subject: [PATCH 2/4] fix(ui-components): throw on failed publish (#8093) Signed-off-by: Aviv Keller --- packages/ui-components/scripts/publish.mjs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/ui-components/scripts/publish.mjs b/packages/ui-components/scripts/publish.mjs index 5f75ef47febaa..095b0d07ce48c 100644 --- a/packages/ui-components/scripts/publish.mjs +++ b/packages/ui-components/scripts/publish.mjs @@ -18,7 +18,13 @@ await writeFile( ); // Now, publish the generated `dist` folder -spawnSync('pnpm', ['publish', '--no-git-checks'], { +const { status, error } = spawnSync('pnpm', ['publish', '--no-git-checks'], { cwd: 'dist', stdio: 'inherit', }); + +if (error) { + throw error; +} + +process.exitCode = status; From ef868539a9ef294fa353e312a7893e38ae15bb50 Mon Sep 17 00:00:00 2001 From: Aviv Keller Date: Fri, 22 Aug 2025 16:56:07 -0400 Subject: [PATCH 3/4] feat(remark-lint): add (#8057) * feat(remark-lint): add 1 * fixup! * fixup! * fixup! --- .github/workflows/publish-packages.yml | 26 +- apps/site/.remarkrc.json | 20 +- apps/site/package.json | 13 +- .../en/learn/modules/how-to-use-streams.md | 1 + .../en/learn/modules/publishing-a-package.mdx | 10 +- .../pages/id/about/security-reporting.mdx | 2 +- packages/remark-lint/.lintstagedrc.json | 4 + packages/remark-lint/README.md | 123 +++++++++ packages/remark-lint/eslint.config.js | 17 ++ packages/remark-lint/package.json | 59 +++++ packages/remark-lint/src/api.mjs | 67 +++++ packages/remark-lint/src/index.mjs | 76 ++++++ .../duplicate-stability-nodes.test.mjs | 59 +++++ .../__tests__/hashed-self-references.test.mjs | 52 ++++ .../__tests__/ordered-references.test.mjs | 65 +++++ .../__tests__/required-metadata.test.mjs | 50 ++++ .../remark-lint/src/rules/__tests__/utils.mjs | 46 ++++ .../__tests__/yaml/ordered-yaml-keys.test.mjs | 62 +++++ .../__tests__/yaml/validate-changes.test.mjs | 147 +++++++++++ .../__tests__/yaml/validate-versions.test.mjs | 77 ++++++ .../src/rules/duplicate-stability-nodes.mjs | 57 ++++ .../src/rules/hashed-self-reference.mjs | 45 ++++ .../src/rules/ordered-references.mjs | 36 +++ .../src/rules/required-metadata.mjs | 46 ++++ packages/remark-lint/src/rules/yaml/index.mjs | 55 ++++ .../src/rules/yaml/ordered-yaml-keys.mjs | 58 +++++ .../src/rules/yaml/validate-changes.mjs | 93 +++++++ .../src/rules/yaml/validate-versions.mjs | 100 +++++++ packages/remark-lint/turbo.json | 15 ++ pnpm-lock.yaml | 246 +++++++++++------- 30 files changed, 1589 insertions(+), 138 deletions(-) create mode 100644 packages/remark-lint/.lintstagedrc.json create mode 100644 packages/remark-lint/README.md create mode 100644 packages/remark-lint/eslint.config.js create mode 100644 packages/remark-lint/package.json create mode 100644 packages/remark-lint/src/api.mjs create mode 100644 packages/remark-lint/src/index.mjs create mode 100644 packages/remark-lint/src/rules/__tests__/duplicate-stability-nodes.test.mjs create mode 100644 packages/remark-lint/src/rules/__tests__/hashed-self-references.test.mjs create mode 100644 packages/remark-lint/src/rules/__tests__/ordered-references.test.mjs create mode 100644 packages/remark-lint/src/rules/__tests__/required-metadata.test.mjs create mode 100644 packages/remark-lint/src/rules/__tests__/utils.mjs create mode 100644 packages/remark-lint/src/rules/__tests__/yaml/ordered-yaml-keys.test.mjs create mode 100644 packages/remark-lint/src/rules/__tests__/yaml/validate-changes.test.mjs create mode 100644 packages/remark-lint/src/rules/__tests__/yaml/validate-versions.test.mjs create mode 100644 packages/remark-lint/src/rules/duplicate-stability-nodes.mjs create mode 100644 packages/remark-lint/src/rules/hashed-self-reference.mjs create mode 100644 packages/remark-lint/src/rules/ordered-references.mjs create mode 100644 packages/remark-lint/src/rules/required-metadata.mjs create mode 100644 packages/remark-lint/src/rules/yaml/index.mjs create mode 100644 packages/remark-lint/src/rules/yaml/ordered-yaml-keys.mjs create mode 100644 packages/remark-lint/src/rules/yaml/validate-changes.mjs create mode 100644 packages/remark-lint/src/rules/yaml/validate-versions.mjs create mode 100644 packages/remark-lint/turbo.json diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml index 3d35a9fe01da7..6c6f7e32a7f28 100644 --- a/.github/workflows/publish-packages.yml +++ b/.github/workflows/publish-packages.yml @@ -74,13 +74,25 @@ jobs: # If a specific package is requested via workflow_dispatch, just publish that one echo "matrix={\"package\":[\"$PACKAGE\"]}" >> $GITHUB_OUTPUT else - # Otherwise, identify all packages with changes since the last commit CHANGED_PACKAGES=() for pkg in $(ls -d packages/*); do PKG_NAME=$(basename "$pkg") - # For manual runs, include all packages. For automatic runs, only include packages with changes + 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 - CHANGED_PACKAGES+=("$PKG_NAME") + HAS_VERSION=$(jq 'has("version")' "$PKG_JSON") + if [ "$HAS_VERSION" == "false" ]; then + # Include packages without version field + CHANGED_PACKAGES+=("$PKG_NAME") + else + # For packages with version field, include only if version changed + 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 fi done @@ -125,8 +137,12 @@ jobs: run: | # Install deps pnpm install --frozen-lockfile - # Create a unique version using the commit SHA as a prerelease identifier - npm version --no-git-tag-version 1.0.1-$COMMIT_SHA + + HAS_VERSION=$(jq 'has("version")' package.json) + if [ "$HAS_VERSION" == "false" ]; then + # Only bump version if package has no version field + npm version --no-git-tag-version 1.0.1-$COMMIT_SHA + fi # Check if a custom publish script exists in package.json if jq -e '.scripts.publish' package.json > /dev/null; then diff --git a/apps/site/.remarkrc.json b/apps/site/.remarkrc.json index 40370dea6d613..71f00f776d25e 100644 --- a/apps/site/.remarkrc.json +++ b/apps/site/.remarkrc.json @@ -1,21 +1,3 @@ { - "settings": { - "bullet": "-", - "resourceLink": true - }, - "plugins": [ - "remark-frontmatter", - "remark-preset-lint-node", - ["remark-gfm", false], - ["remark-lint-fenced-code-flag", false], - ["remark-lint-first-heading-level", false], - ["remark-lint-maximum-line-length", false], - ["remark-lint-no-file-name-articles", false], - ["remark-lint-no-literal-urls", false], - ["remark-lint-no-unused-definitions", false], - ["remark-lint-no-undefined-references", false], - ["remark-lint-prohibited-strings", false], - ["remark-lint-unordered-list-marker-style", "-"], - ["remark-preset-lint-node/remark-lint-nodejs-links.js", false] - ] + "plugins": ["remark-frontmatter", "@node-core/remark-lint"] } diff --git a/apps/site/package.json b/apps/site/package.json index 343abb7f669cb..7174e6dbe56f6 100644 --- a/apps/site/package.json +++ b/apps/site/package.json @@ -82,6 +82,7 @@ "@eslint/eslintrc": "~3.3.1", "@flarelabs-net/wrangler-build-time-fs-assets-polyfilling": "^0.0.1", "@next/eslint-plugin-next": "15.5.0", + "@node-core/remark-lint": "workspace:*", "@opennextjs/cloudflare": "^1.6.4", "@playwright/test": "^1.54.1", "@testing-library/user-event": "~14.6.1", @@ -95,17 +96,7 @@ "global-jsdom": "^26.0.0", "handlebars": "4.7.8", "jsdom": "^26.0.0", - "remark-frontmatter": "5.0.0", - "remark-lint-fenced-code-flag": "^4.2.0", - "remark-lint-first-heading-level": "^4.0.1", - "remark-lint-maximum-line-length": "^4.1.1", - "remark-lint-no-file-name-articles": "^3.0.1", - "remark-lint-no-literal-urls": "^4.0.1", - "remark-lint-no-undefined-references": "^5.0.2", - "remark-lint-no-unused-definitions": "^4.0.2", - "remark-lint-prohibited-strings": "^4.0.0", - "remark-lint-unordered-list-marker-style": "^4.0.1", - "remark-preset-lint-node": "5.1.2", + "remark-frontmatter": "^5.0.0", "stylelint": "16.23.0", "stylelint-config-standard": "39.0.0", "stylelint-order": "7.0.0", diff --git a/apps/site/pages/en/learn/modules/how-to-use-streams.md b/apps/site/pages/en/learn/modules/how-to-use-streams.md index a98b48bf15a98..f046574e21ef8 100644 --- a/apps/site/pages/en/learn/modules/how-to-use-streams.md +++ b/apps/site/pages/en/learn/modules/how-to-use-streams.md @@ -815,6 +815,7 @@ This work is derived from content published by [Matteo Collina][] in [Platformat [`on('close')`]: https://nodejs.org/api/stream.html#event-close_1 [`on('error')`]: https://nodejs.org/api/stream.html#event-error_1 [`.read()`]: https://nodejs.org/docs/latest/api/stream.html#stream_readable_read_size +[`_read()`]: https://nodejs.org/api/stream.html#readable_readsize [`.write()`]: https://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback [`_write`]: https://nodejs.org/api/stream.html#writable_writechunk-encoding-callback [`.end()`]: https://nodejs.org/api/stream.html#writableendchunk-encoding-callback diff --git a/apps/site/pages/en/learn/modules/publishing-a-package.mdx b/apps/site/pages/en/learn/modules/publishing-a-package.mdx index 9d5299d89a7c0..98e1b4423aeba 100644 --- a/apps/site/pages/en/learn/modules/publishing-a-package.mdx +++ b/apps/site/pages/en/learn/modules/publishing-a-package.mdx @@ -6,7 +6,7 @@ authors: JakobJingleheimer # Publishing a package -All the provided `package.json` configurations (not specifically marked “does not work”) work in Node.js 12.22.x (v12 latest, the oldest supported line) and 17.2.0 [current latest at the time][^1], and for grins, with webpack 5.53.0 and 5.63.0 respectively. These are available: [JakobJingleheimer/nodejs-module-config-examples](https://github.com/JakobJingleheimer/nodejs-module-config-examples). +All the provided `package.json` configurations (not specifically marked “does not work”) work in Node.js 12.22.x (v12 latest, the oldest supported line) and 17.2.0 current latest at the time[^1], and for grins, with webpack 5.53.0 and 5.63.0 respectively. These are available: [JakobJingleheimer/nodejs-module-config-examples](https://github.com/JakobJingleheimer/nodejs-module-config-examples). For curious cats, [How did we get here](#how-did-we-get-here) and [Down the rabbit-hole](#down-the-rabbit-hole) provide background and deeper explanations. @@ -337,7 +337,7 @@ We're not in Kansas anymore, Toto. The configurations (there are 2 options) are nearly the same as [ESM source and both CJS & ESM distribution](#esm-source-and-both-cjs-amp-esm-distribution), just exclude `packageJson.exports.import`. -💡 Using [`"type": "module"`][^2] paired with the `.cjs` file extension (for commonjs files) yields best results. For more information on why, see [Down the rabbit-hole](#down-the-rabbit-hole) and [Gotchas](#gotchas) below. +💡 Using `"type": "module"`[^2] paired with the `.cjs` file extension (for commonjs files) yields best results. For more information on why, see [Down the rabbit-hole](#down-the-rabbit-hole) and [Gotchas](#gotchas) below. **Working example**: [esm-with-cjs-distro](https://github.com/JakobJingleheimer/nodejs-module-config-examples/tree/main/packages/esm/cjs-distro) @@ -386,7 +386,7 @@ If your files explicitly _all_ use `.cjs` and/or `.mjs` file extensions (none us } ``` -💡 Using [`"type": "module"`][^2] paired with the `.cjs` file extension (for commonjs files) yields best results. For more information on why, see [Down the rabbit-hole](#down-the-rabbit-hole) and [Gotchas](#gotchas) below. +💡 Using `"type": "module"`[^2] paired with the `.cjs` file extension (for commonjs files) yields best results. For more information on why, see [Down the rabbit-hole](#down-the-rabbit-hole) and [Gotchas](#gotchas) below. #### Publish a CJS distribution with an ESM wrapper @@ -426,7 +426,7 @@ This is also almost identical to the [CJS source and dual distribution using an } ``` -💡 Using [`"type": "module"`][^2] paired with the `.cjs` file extension (for commonjs files) yields best results. For more information on why, see [Down the rabbit-hole](#down-the-rabbit-hole) and [Gotchas](#gotchas) below. +💡 Using `"type": "module"`[^2] paired with the `.cjs` file extension (for commonjs files) yields best results. For more information on why, see [Down the rabbit-hole](#down-the-rabbit-hole) and [Gotchas](#gotchas) below. #### Publish both full CJS & ESM distributions @@ -498,7 +498,7 @@ Alternatively, you can use `"default"` and `"node"` keys, which are less counter } ``` -💡 Using [`"type": "module"`][^2] paired with the `.cjs` file extension (for commonjs files) yields best results. For more information on why, see [Down the rabbit-hole](#down-the-rabbit-hole) and [Gotchas](#gotchas) below. +💡 Using `"type": "module"`[^2] paired with the `.cjs` file extension (for commonjs files) yields best results. For more information on why, see [Down the rabbit-hole](#down-the-rabbit-hole) and [Gotchas](#gotchas) below. ##### Use the `.mjs` (or equivalent) file extension for all source code files diff --git a/apps/site/pages/id/about/security-reporting.mdx b/apps/site/pages/id/about/security-reporting.mdx index 396acfce25eb6..307463d9aeb0a 100644 --- a/apps/site/pages/id/about/security-reporting.mdx +++ b/apps/site/pages/id/about/security-reporting.mdx @@ -46,7 +46,7 @@ Pemberitahuan keamanan akan didistribusikan melalui metode berikut. ## Komentar tentang kebijakan ini -Jika Anda memiliki saran tentang bagaimana proses ini dapat ditingkatkan, silakan kirimkan [permintaan penarikan](https://github.com/nodejs/nodejs.org) atau [ajukan masalah (https://github.com/nodejs/security -wg/issues/new) untuk didiskusikan. +Jika Anda memiliki saran tentang bagaimana proses ini dapat ditingkatkan, silakan kirimkan [permintaan penarikan](https://github.com/nodejs/nodejs.org) atau [ajukan masalah](https://github.com/nodejs/security-wg/issues/new) untuk didiskusikan. ## Praktik Terbaik OpenSSF diff --git a/packages/remark-lint/.lintstagedrc.json b/packages/remark-lint/.lintstagedrc.json new file mode 100644 index 0000000000000..1d1673ffabe4d --- /dev/null +++ b/packages/remark-lint/.lintstagedrc.json @@ -0,0 +1,4 @@ +{ + "**/*.{js,mjs,ts,tsx,md,mdx}": ["prettier --check --write", "eslint --fix"], + "**/*.{json,yml}": ["prettier --check --write"] +} diff --git a/packages/remark-lint/README.md b/packages/remark-lint/README.md new file mode 100644 index 0000000000000..3097a32207fa7 --- /dev/null +++ b/packages/remark-lint/README.md @@ -0,0 +1,123 @@ +# `@node-core/remark-lint` + +A [`remark-lint`](https://github.com/remarkjs/remark-lint) plugin with configurations tailored to the documentation and contribution standards of the [Node.js GitHub Organization](https://github.com/nodejs). + +## Installation + +```bash +npm install --save-dev @node-core/remark-lint +``` + +## Usage + +Add the plugin to your `.remarkrc` or `remark.config.js`: + +```json +{ + "plugins": ["@node-core/remark-lint"] +} +``` + +Run remark to lint your Markdown files: + +```bash +npx remark . --frail +``` + +## Configuration + +### Released Versions + +Some rules, such as `node-core:yaml-comments`, validate version references against known released Node.js versions. You can provide these using the `releasedVersions` option: + +```json +{ + "plugins": [ + [ + "@node-core/remark-lint", + { + "releasedVersions": ["v18.0.0", "v18.1.0", "v18.2.0", "v20.0.0"] + } + ] + ] +} +``` + +For Node.js projects, these versions can be automatically generated [using `list-released-versions-from-changelogs.mjs`](https://github.com/nodejs/node/blob/main/tools/lint-md/list-released-versions-from-changelogs.mjs). + +If not specified, version-related rules will accept any valid SemVer format. + +## Rules + +### `node-core:duplicate-stability-nodes` + +Prevents redundant stability markers in nested sections. + +**Not allowed:** + +```markdown +# Parent Section + +> Stability: 2 - Stable + +## Child Section + +> Stability: 2 - Stable +``` + +### `node-core:hashed-self-reference` + +Ensures self-references use fragment-only links. + +**Allowed:** + +```markdown +See the [Introduction](#introduction) section. +``` + +**Not allowed:** + +```markdown +See the [Introduction](document.md#introduction) section. +``` + +### `node-core:ordered-references` + +Enforces alphabetical sorting of reference-style link definitions. + +**Allowed:** + +```markdown +[api]: https://example.com/api +[docs]: https://example.com/docs +[info]: https://example.com/info +``` + +### `node-core:required-metadata` + +Requires essential metadata for documentation: + +- `llm_description`: A description for Large Language Models (can be inferred from first paragraph) +- `introduced_in`: API introduction version + +Metadata can be provided in comments: + +```markdown + +``` + +### `node-core:yaml-comments` + +Enforces structure and content of YAML comment blocks: + +- `added`: An array of valid version strings +- `napiVersion`: The N-API version +- `deprecated`: An array of valid version strings +- `removed`: An array of valid version strings +- `changes`: An array of: + - `pr-url`: Pull request URL + - `commit`: Commit hash (only required for security fixes) + - `version`: Valid version string + - `description`: Change description + +All version references must be valid SemVer, or match the provided `releasedVersions`. diff --git a/packages/remark-lint/eslint.config.js b/packages/remark-lint/eslint.config.js new file mode 100644 index 0000000000000..205088e6ad6ae --- /dev/null +++ b/packages/remark-lint/eslint.config.js @@ -0,0 +1,17 @@ +import globals from 'globals'; + +import baseConfig from '../../eslint.config.js'; + +export default [ + ...baseConfig, + { + languageOptions: { + globals: globals.nodeBuiltin, + parserOptions: { + // Allow nullish syntax (i.e. "?." or "??"), + // and top-level await + ecmaVersion: 'latest', + }, + }, + }, +]; diff --git a/packages/remark-lint/package.json b/packages/remark-lint/package.json new file mode 100644 index 0000000000000..72753be7fce5e --- /dev/null +++ b/packages/remark-lint/package.json @@ -0,0 +1,59 @@ +{ + "name": "@node-core/remark-lint", + "type": "module", + "version": "1.0.0", + "exports": { + ".": "./src/index.mjs", + "./api": "./src/api.mjs" + }, + "scripts": { + "lint": "node --run lint:js", + "lint:fix": "node --run lint:js:fix", + "lint:js": "eslint \"**/*.mjs\"", + "lint:js:fix": "node --run lint:js -- --fix", + "test": "node --run test:unit", + "test:unit": "cross-env NODE_NO_WARNINGS=1 node --experimental-test-coverage --test \"**/*.test.mjs\"" + }, + "dependencies": { + "remark-gfm": "^4.0.1", + "remark-lint-blockquote-indentation": "^4.0.1", + "remark-lint-checkbox-character-style": "^5.0.1", + "remark-lint-checkbox-content-indent": "^5.0.1", + "remark-lint-code-block-style": "^4.0.1", + "remark-lint-definition-spacing": "^4.0.1", + "remark-lint-fenced-code-flag": "^4.2.0", + "remark-lint-fenced-code-marker": "^4.0.1", + "remark-lint-final-definition": "^4.0.2", + "remark-lint-heading-style": "^4.0.1", + "remark-lint-maximum-line-length": "^4.1.1", + "remark-lint-no-consecutive-blank-lines": "^5.0.1", + "remark-lint-no-file-name-consecutive-dashes": "^3.0.1", + "remark-lint-no-file-name-outer-dashes": "^3.0.1", + "remark-lint-no-heading-indent": "^5.0.1", + "remark-lint-no-literal-urls": "^4.0.1", + "remark-lint-no-multiple-toplevel-headings": "^4.0.1", + "remark-lint-no-shell-dollars": "^4.0.1", + "remark-lint-no-table-indentation": "^5.0.1", + "remark-lint-no-tabs": "^4.0.1", + "remark-lint-no-trailing-spaces": "^3.0.2", + "remark-lint-no-unused-definitions": "^4.0.2", + "remark-lint-prohibited-strings": "^4.0.0", + "remark-lint-rule-style": "^4.0.1", + "remark-lint-strong-marker": "^4.0.1", + "remark-lint-table-cell-padding": "^5.1.1", + "remark-lint-table-pipes": "^5.0.1", + "remark-lint-unordered-list-marker-style": "^4.0.1", + "remark-preset-lint-recommended": "^7.0.1", + "semver": "^7.7.2", + "unified-lint-rule": "^3.0.1", + "unist-util-visit": "^5.0.0", + "yaml": "^2.8.1" + }, + "devDependencies": { + "cross-env": "catalog:", + "dedent": "^1.6.0", + "globals": "^16.3.0", + "remark-parse": "^11.0.0", + "unified": "^11.0.5" + } +} diff --git a/packages/remark-lint/src/api.mjs b/packages/remark-lint/src/api.mjs new file mode 100644 index 0000000000000..962ad03e715a6 --- /dev/null +++ b/packages/remark-lint/src/api.mjs @@ -0,0 +1,67 @@ +import remarkLintFencedCodeFlag from 'remark-lint-fenced-code-flag'; +import remarkLintMaximumLineLength from 'remark-lint-maximum-line-length'; +import remarkLintNoUnusedDefinitions from 'remark-lint-no-unused-definitions'; +import remarkLintProhibitedStrings from 'remark-lint-prohibited-strings'; +import remarkLintUnorderedListMarkerStyle from 'remark-lint-unordered-list-marker-style'; + +import basePreset from './index.mjs'; +import duplicateStabilityNodes from './rules/duplicate-stability-nodes.mjs'; +import hashedSelfReference from './rules/hashed-self-reference.mjs'; +import orderedReferences from './rules/ordered-references.mjs'; +import requiredMetadata from './rules/required-metadata.mjs'; +import yamlComments from './rules/yaml/index.mjs'; + +/** + * @typedef {Object} Options + * @property {Array} releasedVersions The released versions, for validating the YAML + */ + +/** + * @param {Options} options + */ +export default (options = {}) => ({ + settings: { + ...basePreset.settings, + bullet: '*', + }, + plugins: [ + ...basePreset.plugins, + + // Internal Rules + ...[ + duplicateStabilityNodes, + yamlComments, + hashedSelfReference, + orderedReferences, + requiredMetadata, + ].map(plugin => [plugin, options]), + + // External Rules + remarkLintNoUnusedDefinitions, + [remarkLintFencedCodeFlag, { allowEmpty: false }], + [remarkLintMaximumLineLength, 120], + [remarkLintUnorderedListMarkerStyle, '*'], + [ + remarkLintProhibitedStrings, + [ + { yes: 'End-of-Life' }, + { no: 'filesystem', yes: 'file system' }, + { yes: 'GitHub' }, + { no: 'hostname', yes: 'host name' }, + { yes: 'JavaScript' }, + { no: '[Ll]ong[ -][Tt]erm [Ss]upport', yes: 'Long Term Support' }, + { no: 'Node', yes: 'Node.js', ignoreNextTo: '-API' }, + { yes: 'Node.js' }, + { no: 'Node[Jj][Ss]', yes: 'Node.js' }, + { no: "Node\\.js's?", yes: 'the Node.js' }, + { no: '[Nn]ote that', yes: '' }, + { yes: 'RFC' }, + { no: '[Rr][Ff][Cc]\\d+', yes: 'RFC ' }, + { yes: 'TypeScript' }, + { yes: 'Unix' }, + { yes: 'Valgrind' }, + { yes: 'V8' }, + ], + ], + ], +}); diff --git a/packages/remark-lint/src/index.mjs b/packages/remark-lint/src/index.mjs new file mode 100644 index 0000000000000..bad1d1d8cc956 --- /dev/null +++ b/packages/remark-lint/src/index.mjs @@ -0,0 +1,76 @@ +import remarkGfm from 'remark-gfm'; +import remarkLintBlockquoteIndentation from 'remark-lint-blockquote-indentation'; +import remarkLintCheckboxCharacterStyle from 'remark-lint-checkbox-character-style'; +import remarkLintCheckboxContentIndent from 'remark-lint-checkbox-content-indent'; +import remarkLintCodeBlockStyle from 'remark-lint-code-block-style'; +import remarkLintDefinitionSpacing from 'remark-lint-definition-spacing'; +import remarkLintFencedCodeMarker from 'remark-lint-fenced-code-marker'; +import remarkLintFinalDefinition from 'remark-lint-final-definition'; +import remarkLintHeadingStyle from 'remark-lint-heading-style'; +import remarkLintNoConsecutiveBlankLines from 'remark-lint-no-consecutive-blank-lines'; +import remarkLintNoFileNameConsecutiveDashes from 'remark-lint-no-file-name-consecutive-dashes'; +import remarkLintNofileNameOuterDashes from 'remark-lint-no-file-name-outer-dashes'; +import remarkLintNoHeadingIndent from 'remark-lint-no-heading-indent'; +import remarkLintNoLiteralURLs from 'remark-lint-no-literal-urls'; +import remarkLintNoMultipleToplevelHeadings from 'remark-lint-no-multiple-toplevel-headings'; +import remarkLintNoShellDollars from 'remark-lint-no-shell-dollars'; +import remarkLintNoTableIndentation from 'remark-lint-no-table-indentation'; +import remarkLintNoTabs from 'remark-lint-no-tabs'; +import remarkLintNoTrailingSpaces from 'remark-lint-no-trailing-spaces'; +import remarkLintNoUnusedDefinitions from 'remark-lint-no-unused-definitions'; +import remarkLintRuleStyle from 'remark-lint-rule-style'; +import remarkLintStrongMarker from 'remark-lint-strong-marker'; +import remarkLintTableCellPadding from 'remark-lint-table-cell-padding'; +import remarkLintTablePipes from 'remark-lint-table-pipes'; +import remarkPresetLintRecommended from 'remark-preset-lint-recommended'; + +export default { + settings: { + tightDefinitions: true, + emphasis: '_', + bullet: '-', + rule: '-', + }, + plugins: [ + // Enable GitHub Flavored Markdown + remarkGfm, + + // Base plugins + remarkPresetLintRecommended, + + // Blockquote and list rules + [remarkLintBlockquoteIndentation, 2], + [remarkLintCheckboxCharacterStyle, { checked: 'x', unchecked: ' ' }], + remarkLintCheckboxContentIndent, + + // Code and formatting rules + [remarkLintCodeBlockStyle, 'fenced'], + [remarkLintFencedCodeMarker, '`'], + [remarkLintRuleStyle, '---'], + [remarkLintStrongMarker, '*'], + + // File and filename rules + remarkLintNoFileNameConsecutiveDashes, + remarkLintNofileNameOuterDashes, + + // Heading and link rules + remarkLintFinalDefinition, + [remarkLintNoUnusedDefinitions, false], + [remarkLintNoLiteralURLs, false], + [remarkLintHeadingStyle, 'atx'], + remarkLintNoHeadingIndent, + remarkLintNoMultipleToplevelHeadings, + + // Layout and spacing rules + remarkLintDefinitionSpacing, + remarkLintNoConsecutiveBlankLines, + remarkLintNoTableIndentation, + remarkLintNoTabs, + remarkLintNoTrailingSpaces, + [remarkLintTableCellPadding, 'padded'], + remarkLintTablePipes, + + // Shell and console rules + remarkLintNoShellDollars, + ], +}; diff --git a/packages/remark-lint/src/rules/__tests__/duplicate-stability-nodes.test.mjs b/packages/remark-lint/src/rules/__tests__/duplicate-stability-nodes.test.mjs new file mode 100644 index 0000000000000..1cb1816f2195f --- /dev/null +++ b/packages/remark-lint/src/rules/__tests__/duplicate-stability-nodes.test.mjs @@ -0,0 +1,59 @@ +import { describe, it } from 'node:test'; + +import dedent from 'dedent'; + +import duplicateStabilityNodes from '../duplicate-stability-nodes.mjs'; +import { testRule } from './utils.mjs'; + +const testCases = [ + { + name: 'no stability nodes', + input: dedent` + # Heading 1 + + > No stability here. + `, + expected: [], + }, + { + name: 'single stability blockquote', + input: dedent` + # Heading 1 + + > Stability: 1.0 + `, + expected: [], + }, + { + name: 'different stabilities under different headings', + input: dedent` + # Heading 1 + + > Stability: 1.0 + + ## Heading 2 + + > Stability: 2.0 + `, + expected: [], + }, + { + name: 'duplicate stability at deeper heading triggers message', + input: dedent` + # Heading 1 + + > Stability: 1.0 + + ## Heading 2 + + > Stability: 1.0 + `, + expected: ['Duplicate stability node'], + }, +]; + +describe('duplicate-stability-nodes', () => { + for (const { name, input, expected } of testCases) { + it(name, () => testRule(duplicateStabilityNodes, input, expected)); + } +}); diff --git a/packages/remark-lint/src/rules/__tests__/hashed-self-references.test.mjs b/packages/remark-lint/src/rules/__tests__/hashed-self-references.test.mjs new file mode 100644 index 0000000000000..70a884ca6be45 --- /dev/null +++ b/packages/remark-lint/src/rules/__tests__/hashed-self-references.test.mjs @@ -0,0 +1,52 @@ +import { describe, it } from 'node:test'; + +import hashedSelfReference from '../hashed-self-reference.mjs'; +import { testRule } from './utils.mjs'; + +const testCases = [ + { + name: 'ignores links to other paths', + input: '[external](./other.md)', + path: 'docs/current.md', + expected: [], + }, + { + name: 'accepts hashed self-reference', + input: '[header](#my-heading)', + path: 'docs/current.md', + expected: [], + }, + { + name: 'reports self-reference with fragment', + input: '[bad link](./current.md#section)', + path: 'docs/current.md', + expected: [ + 'Self-reference must start with hash (expected "#section", got "./current.md#section")', + ], + }, + { + name: 'reports self-reference to path only', + input: '[bad link](./current.md)', + path: 'docs/current.md', + expected: [ + 'Self-reference must start with hash (expected "#", got "./current.md")', + ], + }, + { + name: 'ignores external links', + input: '[nodejs](https://nodejs.org)', + path: 'docs/current.md', + expected: [], + }, +]; + +describe('hashed-self-references', () => { + for (const { name, input, expected, ...options } of testCases) { + it(name, () => + testRule(hashedSelfReference, input, expected, { + ...options, + cwd: process.cwd(), + }) + ); + } +}); diff --git a/packages/remark-lint/src/rules/__tests__/ordered-references.test.mjs b/packages/remark-lint/src/rules/__tests__/ordered-references.test.mjs new file mode 100644 index 0000000000000..0205fc13730d2 --- /dev/null +++ b/packages/remark-lint/src/rules/__tests__/ordered-references.test.mjs @@ -0,0 +1,65 @@ +import { describe, it } from 'node:test'; + +import dedent from 'dedent'; + +import orderedReferences from '../ordered-references.mjs'; +import { testRule } from './utils.mjs'; + +const testCases = [ + { + name: 'no definitions', + input: 'Just some text.', + expected: [], + }, + { + name: 'single definition', + input: '[foo]: https://example.com', + expected: [], + }, + { + name: 'ordered references', + input: dedent` + [alpha]: https://example.com/a + [beta]: https://example.com/b + [charlie]: https://example.com/c + `, + expected: [], + }, + { + name: 'unordered references (simple case)', + input: dedent` + [beta]: https://example.com/b + [alpha]: https://example.com/a + `, + expected: ['Unordered reference ("alpha" should be before "beta")'], + }, + { + name: 'unordered references (deep nesting)', + input: dedent` + [foo]: https://example.com/z + + > Note: + + > [bar]: https://example.com/a + `, + expected: ['Unordered reference ("bar" should be before "foo")'], + }, + { + name: 'multiple unordered references', + input: dedent` + [zulu]: https://z.com + [yankee]: https://y.com + [alpha]: https://a.com + `, + expected: [ + 'Unordered reference ("yankee" should be before "zulu")', + 'Unordered reference ("alpha" should be before "yankee")', + ], + }, +]; + +describe('hashed-self-references', () => { + for (const { name, input, expected } of testCases) { + it(name, () => testRule(orderedReferences, input, expected)); + } +}); diff --git a/packages/remark-lint/src/rules/__tests__/required-metadata.test.mjs b/packages/remark-lint/src/rules/__tests__/required-metadata.test.mjs new file mode 100644 index 0000000000000..fe03986719d8a --- /dev/null +++ b/packages/remark-lint/src/rules/__tests__/required-metadata.test.mjs @@ -0,0 +1,50 @@ +import { describe, it } from 'node:test'; + +import dedent from 'dedent'; + +import requiredMetadata from '../required-metadata.mjs'; +import { testRule } from './utils.mjs'; + +const testCases = [ + { + name: 'both metadata comments present', + input: dedent` + + + `, + expected: [], + }, + { + name: 'missing introduced_in', + input: '', + expected: ['Missing "introduced_in" metadata'], + }, + { + name: 'missing llm_description, but paragraph exists', + input: dedent` + + + This is a short description for LLMs. + `, + expected: [], + }, + { + name: 'missing both metadata entries', + input: '', + expected: [ + 'Missing "introduced_in" metadata', + 'Missing "llm_description" metadata', + ], + }, + { + name: 'only paragraph, no comments at all', + input: 'This is just a paragraph, nothing else.', + expected: ['Missing "introduced_in" metadata'], + }, +]; + +describe('duplicate-stability-nodes', () => { + for (const { name, input, expected } of testCases) { + it(name, () => testRule(requiredMetadata, input, expected)); + } +}); diff --git a/packages/remark-lint/src/rules/__tests__/utils.mjs b/packages/remark-lint/src/rules/__tests__/utils.mjs new file mode 100644 index 0000000000000..ab0e373bac801 --- /dev/null +++ b/packages/remark-lint/src/rules/__tests__/utils.mjs @@ -0,0 +1,46 @@ +import assert from 'node:assert/strict'; +import { mock } from 'node:test'; + +import remarkParse from 'remark-parse'; +import { unified } from 'unified'; + +/** + * Tests a markdown rule against a markdown string + */ +export const testRule = (rule, markdown, expected, vfileOptions = {}) => { + // Parse the markdown once + const tree = unified().use(remarkParse).parse(markdown); + + // Create a mock vfile + const vfile = { + ...vfileOptions, + message: mock.fn(), + messages: [], + }; + + // Execute the rule + rule()(tree, vfile, () => {}); + + // Assert that the expected messages were reported + assert.deepEqual( + vfile.message.mock.calls.map(call => call.arguments[0]), + expected + ); +}; + +/** + * Tests a YAML rule against a YAML string + */ +export function testYamlRule(rule, input, options = {}, expected) { + // Create a mock reporter + const report = mock.fn(); + + // Execute the rule + rule(input, report, options); + + // Assert that the expected messages were reported + assert.deepEqual( + report.mock.calls.flatMap(call => call.arguments), + expected + ); +} diff --git a/packages/remark-lint/src/rules/__tests__/yaml/ordered-yaml-keys.test.mjs b/packages/remark-lint/src/rules/__tests__/yaml/ordered-yaml-keys.test.mjs new file mode 100644 index 0000000000000..44d7d131f708a --- /dev/null +++ b/packages/remark-lint/src/rules/__tests__/yaml/ordered-yaml-keys.test.mjs @@ -0,0 +1,62 @@ +import { describe, it } from 'node:test'; + +import validateKeys from '../../yaml/ordered-yaml-keys.mjs'; +import { testYamlRule } from '../utils.mjs'; + +const testCases = [ + { + name: 'does not report when keys are valid and in correct order', + input: { + added: 'v1.0.0', + napiVersion: 3, + deprecated: 'v2.0.0', + removed: 'v3.0.0', + changes: [], + }, + expected: [], + }, + { + name: 'reports invalid keys', + input: { + added: 'v1.0.0', + unexpected: true, + oops: false, + }, + expected: ['Invalid key(s) found: unexpected, oops'], + }, + { + name: 'reports out-of-order keys', + input: { + removed: 'v3.0.0', + added: 'v1.0.0', + }, + expected: [ + 'Key "added" is out of order. Expected order: added, napiVersion, deprecated, removed, changes', + ], + }, + { + name: 'reports both invalid and out-of-order keys (first invalid)', + input: { + foo: true, + deprecated: 'v2.0.0', + added: 'v1.0.0', + }, + expected: [ + 'Invalid key(s) found: foo', + 'Key "added" is out of order. Expected order: added, napiVersion, deprecated, removed, changes', + ], + }, + { + name: 'does not report on empty object', + input: {}, + expected: [], + }, +]; + +describe('ordered-yaml-keys', () => { + for (const { name, input, options, expected } of testCases) { + it(name, () => { + testYamlRule(validateKeys, input, options, expected); + }); + } +}); diff --git a/packages/remark-lint/src/rules/__tests__/yaml/validate-changes.test.mjs b/packages/remark-lint/src/rules/__tests__/yaml/validate-changes.test.mjs new file mode 100644 index 0000000000000..03d7b690c4ef3 --- /dev/null +++ b/packages/remark-lint/src/rules/__tests__/yaml/validate-changes.test.mjs @@ -0,0 +1,147 @@ +import { describe, it } from 'node:test'; + +import validateChanges from '../../yaml/validate-changes.mjs'; +import { testYamlRule } from '../utils.mjs'; + +const testCases = [ + { + name: 'valid change entry', + input: { + changes: [ + { + version: 'v2.0.0', + 'pr-url': 'https://github.com/nodejs/node/pull/12345', + description: 'This is a valid change.', + }, + ], + }, + expected: [], + }, + { + name: '"changes" is not an array', + input: { + changes: {}, + }, + expected: ['"changes" must be an Array'], + }, + { + name: 'invalid PR URL', + input: { + changes: [ + { + version: 'v1.0.0', + 'pr-url': 'ftp://example.com/invalid', + description: 'Something happened.', + }, + ], + }, + expected: [ + 'In "changes[0]": "ftp://example.com/invalid" is not a valid PR URL.', + ], + }, + { + name: 'invalid key in change', + input: { + changes: [ + { + version: 'v1.0.0', + 'pr-url': 'https://github.com/nodejs/node/pull/123', + description: 'Change made.', + extra: true, + }, + ], + }, + expected: ['In "changes[0]": Invalid key(s) found: extra'], + }, + { + name: 'invalid version in change', + input: { + changes: [ + { + version: 'foo', + 'pr-url': 'https://github.com/nodejs/node/pull/123', + description: 'Change made.', + }, + ], + }, + expected: ['In "changes[0].version": foo is invalid'], + }, + { + name: 'unsorted versions in change', + input: { + changes: [ + { + version: ['v1.0.0', 'v2.0.0'], + 'pr-url': 'https://github.com/nodejs/node/pull/123', + description: 'Out of order.', + }, + ], + }, + expected: [ + 'In "changes[0].version": Versions are unsorted (should be in descending order)', + ], + }, + { + name: 'empty description', + input: { + changes: [ + { + version: 'v1.0.0', + 'pr-url': 'https://github.com/nodejs/node/pull/123', + description: '', + }, + ], + }, + expected: ['In "changes[0]": Description cannot be empty'], + }, + { + name: 'description missing period', + input: { + changes: [ + { + version: 'v1.0.0', + 'pr-url': 'https://github.com/nodejs/node/pull/123', + description: 'Missing period', + }, + ], + }, + expected: ['In "changes[0]": Description must end with a "."'], + }, + { + name: 'valid security-related change (private PR, valid commit)', + input: { + changes: [ + { + version: 'v1.0.0', + 'pr-url': 'https://github.com/nodejs-private/node-private/pull/999', + // A valid commit _must_ be forty characters + commit: '0'.repeat(40), + description: 'Security fix.', + }, + ], + }, + expected: [], + }, + { + name: 'security-related change with invalid commit', + input: { + changes: [ + { + version: 'v1.0.0', + 'pr-url': 'https://github.com/nodejs-private/node-private/pull/999', + commit: 'invalid_commit', + description: 'Security fix.', + }, + ], + }, + expected: ['In "changes[0]": Invalid commit: "invalid_commit"'], + }, +]; + +describe('validate-changes', () => { + for (const { name, input, options, expected } of testCases) { + it(name, () => { + testYamlRule(validateChanges, input, options, expected); + }); + } +}); diff --git a/packages/remark-lint/src/rules/__tests__/yaml/validate-versions.test.mjs b/packages/remark-lint/src/rules/__tests__/yaml/validate-versions.test.mjs new file mode 100644 index 0000000000000..76718be916166 --- /dev/null +++ b/packages/remark-lint/src/rules/__tests__/yaml/validate-versions.test.mjs @@ -0,0 +1,77 @@ +import { describe, it } from 'node:test'; + +import validateVersionRule from '../../yaml/validate-versions.mjs'; +import { testYamlRule } from '../utils.mjs'; + +const testCases = [ + { + name: 'does not report on valid, sorted versions', + input: { + added: ['v2.0.0', 'v1.0.0'], + removed: 'v3.0.0', + }, + expected: [], + }, + { + name: 'reports invalid version format', + input: { + added: 'foo', + }, + expected: ['In "added": foo is invalid'], + }, + { + name: 'reports unsorted versions', + input: { + deprecated: ['v1.0.0', 'v2.0.0'], + }, + expected: [ + 'In "deprecated": Versions are unsorted (should be in descending order)', + ], + }, + { + name: 'handles REPLACEME correctly when alone', + input: { + added: 'REPLACEME', + }, + expected: [], + }, + { + name: 'reports REPLACEME when part of multi-entry array', + input: { + removed: ['REPLACEME', 'v1.0.0'], + }, + expected: ['In "removed": REPLACEME is invalid'], + }, + { + name: 'ignores ancient hardcoded versions (e.g. 0.1.0)', + input: { + added: 'v0.1.0', + }, + expected: [], + }, + { + name: 'supports specifying custom versions', + input: { + added: 'vCUSTOM', + }, + options: { + releasedVersions: 'CUSTOM', + }, + expected: [], + }, + { + name: 'does not report when key is missing', + input: { + napiVersion: 4, + }, + expected: [], + }, +]; + +describe('validate-version', () => { + for (const { name, input, options, expected } of testCases) { + it(name, () => { + testYamlRule(validateVersionRule, input, options, expected); + }); + } +}); diff --git a/packages/remark-lint/src/rules/duplicate-stability-nodes.mjs b/packages/remark-lint/src/rules/duplicate-stability-nodes.mjs new file mode 100644 index 0000000000000..0f41b039d145d --- /dev/null +++ b/packages/remark-lint/src/rules/duplicate-stability-nodes.mjs @@ -0,0 +1,57 @@ +import { lintRule } from 'unified-lint-rule'; +import { visit } from 'unist-util-visit'; + +// TODO(@avivkeller): This is re-used from doc-kit +// Regex to match "Stability: " in blockquotes +const STABILITY = /Stability: ([0-5](?:\.[0-3])?)/; + +/** + * Finds and reports duplicate stability nodes + * @type {import('unified-lint-rule').Rule} + */ +const duplicateStabilityNodes = (tree, vfile) => { + let currentDepth = 0; + let currentStability = -1; + let currentHeaderDepth = 0; + + visit(tree, node => { + // Update the current heading depth whenever a heading node is encountered + if (node.type === 'heading') { + currentHeaderDepth = node.depth; + } + + // Look for blockquotes which may contain stability indicators + if (node.type === 'blockquote') { + // Assume the first child is a paragraph + const paragraph = node.children?.[0]; + // And the first child of that paragraph is text + const text = paragraph?.children?.[0]; + + // Ensure structure is paragraph > text + if (paragraph?.type === 'paragraph' && text?.type === 'text') { + // Try to match "Stability: X" + const match = text.value.match(STABILITY); + if (match) { + const stability = parseFloat(match[1]); + // If the heading got deeper, and stability is valid and matches previous, report a duplicate + if ( + currentHeaderDepth > currentDepth && + stability >= 0 && + stability === currentStability + ) { + vfile.message('Duplicate stability node', node); + } else { + // Otherwise, record this stability and heading depth + currentDepth = currentHeaderDepth; + currentStability = stability; + } + } + } + } + }); +}; + +export default lintRule( + 'node-core:duplicate-stability-nodes', + duplicateStabilityNodes +); diff --git a/packages/remark-lint/src/rules/hashed-self-reference.mjs b/packages/remark-lint/src/rules/hashed-self-reference.mjs new file mode 100644 index 0000000000000..789c41cab3dd4 --- /dev/null +++ b/packages/remark-lint/src/rules/hashed-self-reference.mjs @@ -0,0 +1,45 @@ +import path from 'node:path'; +import { pathToFileURL } from 'node:url'; + +import { lintRule } from 'unified-lint-rule'; + +const getLinksRecursively = function* (node) { + if (node.url) { + yield node; + } + + const { children = [] } = node; + + for (const child of children) { + yield* getLinksRecursively(child); + } +}; + +/** + * Ensures that all self-references begin with `#` + * @type {import('unified-lint-rule').Rule} + */ +const hashedSelfReference = (tree, vfile) => { + const currentFileURL = pathToFileURL( + path.isAbsolute(vfile.path) ? vfile.path : path.join(vfile.cwd, vfile.path) + ); + + for (const node of getLinksRecursively(tree)) { + const { url } = node; + + if (!url || url[0] === '#') continue; + + const targetURL = new URL(url, currentFileURL); + + if (targetURL.pathname === currentFileURL.pathname) { + const expected = url.includes('#') ? url.slice(url.indexOf('#')) : '#'; + + vfile.message( + `Self-reference must start with hash (expected "${expected}", got "${url}")`, + node + ); + } + } +}; + +export default lintRule('node-core:hashed-self-reference', hashedSelfReference); diff --git a/packages/remark-lint/src/rules/ordered-references.mjs b/packages/remark-lint/src/rules/ordered-references.mjs new file mode 100644 index 0000000000000..a6366fbdb93f2 --- /dev/null +++ b/packages/remark-lint/src/rules/ordered-references.mjs @@ -0,0 +1,36 @@ +import { lintRule } from 'unified-lint-rule'; + +const getDefinitionsRecursively = function* (node) { + if (node.type === 'definition') { + yield node; + } + + const { children = [] } = node; + + for (const child of children) { + yield* getDefinitionsRecursively(child); + } +}; + +/** + * Ensures references are alphabetical. + * @type {import('unified-lint-rule').Rule} + */ +const orderedReferences = (tree, vfile) => { + let previousLabel; + + for (const node of getDefinitionsRecursively(tree)) { + const { label } = node; + + if (previousLabel && previousLabel > label) { + vfile.message( + `Unordered reference ("${label}" should be before "${previousLabel}")`, + node + ); + } + + previousLabel = label; + } +}; + +export default lintRule('node-core:ordered-references', orderedReferences); diff --git a/packages/remark-lint/src/rules/required-metadata.mjs b/packages/remark-lint/src/rules/required-metadata.mjs new file mode 100644 index 0000000000000..218a44881ec38 --- /dev/null +++ b/packages/remark-lint/src/rules/required-metadata.mjs @@ -0,0 +1,46 @@ +import { lintRule } from 'unified-lint-rule'; +import { visit } from 'unist-util-visit'; + +// Define the required metadata keys that must be present in the Markdown content +const REQUIRED_KEYS = ['introduced_in', 'llm_description']; +const METADATA_CHECKS = REQUIRED_KEYS.map( + key => new RegExp(``) +); + +/** + * Ensures all needed metadata exists + * @type {import('unified-lint-rule').Rule} + */ +const hasRequiredMetadata = (tree, vfile) => { + const foundKeys = new Set(); + let hasParagraph = false; + + visit(tree, ['html', 'paragraph'], node => { + if (node.type === 'html') { + // Check if the HTML node contains any of the required metadata comments + METADATA_CHECKS.forEach((regex, i) => { + if (regex.test(node.value)) { + foundKeys.add(REQUIRED_KEYS[i]); + } + }); + } else { + // Mark that a paragraph exists in the document + hasParagraph = true; + } + }); + + REQUIRED_KEYS.forEach(key => { + if (foundKeys.has(key)) { + return; + } + + // Allow llm_description to be provided as a first paragraph + if (key === 'llm_description' && hasParagraph) { + return; + } + + vfile.message(`Missing "${key}" metadata`, tree); + }); +}; + +export default lintRule('node-core:required-metadata', hasRequiredMetadata); diff --git a/packages/remark-lint/src/rules/yaml/index.mjs b/packages/remark-lint/src/rules/yaml/index.mjs new file mode 100644 index 0000000000000..b1ff0884dcde3 --- /dev/null +++ b/packages/remark-lint/src/rules/yaml/index.mjs @@ -0,0 +1,55 @@ +import { lintRule } from 'unified-lint-rule'; +import { visit } from 'unist-util-visit'; +import { parse } from 'yaml'; + +import orderedYamlKeys from './ordered-yaml-keys.mjs'; +import validateChanges from './validate-changes.mjs'; +import validateVersions from './validate-versions.mjs'; + +const YAML_HTML_COMMENT_RE = /^'; +const RULES = [orderedYamlKeys, validateVersions, validateChanges]; + +/** + * @callback YAMLRule + * @param {Record} yaml - The YAML object to validate. + * @param {(message: string) => void} report - Reporting function + * @param {import('../../api.mjs').Options} options - The options. + */ + +/** + * Determine if a node is a YAML-bearing HTML comment. + * @param {import('unist').Node} node + */ +const isYamlHtmlComment = node => + node.type === 'html' && YAML_HTML_COMMENT_RE.test(node.value); + +/** + * Lints YAML embedded inside HTML comments in Markdown AST. + * @type {import('unified-lint-rule').Rule} + */ +const yamlComments = (tree, vfile, options) => { + visit(tree, isYamlHtmlComment, node => { + const trimmed = node.value.trim(); + + // Consistency check for ""', node); + return; + } + + // "#" comments out the first line ("" + const parsed = parse(`#${trimmed.slice(0, -HTML_COMMENT_CLOSE.length)}`); + + const report = (...args) => vfile.message(...args, node); + + RULES.forEach(rule => rule(parsed, report, options)); + }); +}; + +export default lintRule('node-core:yaml-comments', yamlComments); diff --git a/packages/remark-lint/src/rules/yaml/ordered-yaml-keys.mjs b/packages/remark-lint/src/rules/yaml/ordered-yaml-keys.mjs new file mode 100644 index 0000000000000..9126a4b0959df --- /dev/null +++ b/packages/remark-lint/src/rules/yaml/ordered-yaml-keys.mjs @@ -0,0 +1,58 @@ +/** + * Default allowed keys and their required order at the top level. + * Order matters for validation. + */ +export const DEFAULT_VALID_KEYS = [ + 'added', + 'napiVersion', + 'deprecated', + 'removed', + 'changes', +]; + +/** + * Validate that: + * - Only valid keys are present + * - Keys appear in the expected order (relative order respected) + * + * @type {import('./index.mjs').YAMLRule} + * @param {readonly string[]} [validKeys=DEFAULT_VALID_KEYS] - Allowed keys in the expected order. + * @param {string} [prefix=''] - Message prefix for context. + */ +export default function orderedYamlKeys( + yaml, + report, + _, + validKeys = DEFAULT_VALID_KEYS, + prefix = '' +) { + if (!yaml || typeof yaml !== 'object' || Array.isArray(yaml)) return; + + const keys = Object.keys(yaml); + + // Check for invalid keys + const invalidKeys = keys.filter(key => !validKeys.includes(key)); + if (invalidKeys.length > 0) { + report(`${prefix}Invalid key(s) found: ${invalidKeys.join(', ')}`); + } + + // Check key order + let lastIndex = -1; + for (const key of keys) { + const index = validKeys.indexOf(key); + + if (index === -1) { + // Non-validated keys are ignored for ordering, since + // they were already reported as invalid above + continue; + } + + if (index < lastIndex) { + report( + `${prefix}Key "${key}" is out of order. Expected order: ${validKeys.join(', ')}` + ); + break; + } + lastIndex = index; + } +} diff --git a/packages/remark-lint/src/rules/yaml/validate-changes.mjs b/packages/remark-lint/src/rules/yaml/validate-changes.mjs new file mode 100644 index 0000000000000..c22215ac72b5e --- /dev/null +++ b/packages/remark-lint/src/rules/yaml/validate-changes.mjs @@ -0,0 +1,93 @@ +import orderedYamlKeys from './ordered-yaml-keys.mjs'; +import { validateVersion } from './validate-versions.mjs'; + +const CHANGE_VALID_KEYS = ['version', 'pr-url', 'description']; +const VALID_PR_URL_RE = + /^https:\/\/github\.com\/nodejs(?:-private)?\/node(?:-private)?\/pull\/\d+$/; +const PRIVATE_PR_STARTER = + 'https://github.com/nodejs-private/node-private/pull/'; +const COMMIT_SHA_RE = /^[0-9a-f]{40}$/i; + +/** + * A change is security-related if it references a PR in the private Node.js repo, + * with a valid commit. + * @param {Record} change + * @returns {boolean} + */ +const isSecurityRelated = change => + typeof change?.['pr-url'] === 'string' && + change['pr-url'].startsWith(PRIVATE_PR_STARTER) && + typeof change?.['commit'] === 'string'; + +/** + * Anything below v1.0 is older than this format. + * @param {Record} change + * @returns {boolean} + */ +const isAncient = change => + typeof change?.version === 'string' && change.version.startsWith('v0.'); + +/** + * Validate the "changes" array within the YAML object. + * @type {import('./index.mjs').YAMLRule} + */ +export default function validateChanges({ changes }, report, options) { + if (changes === undefined) { + // Nothing to validate + return; + } + + if (!Array.isArray(changes)) { + report('"changes" must be an Array'); + return; + } + + changes.forEach((change, index) => { + const prefix = `In "changes[${index}]": `; + + if (!change || typeof change !== 'object' || Array.isArray(change)) { + report(`${prefix}Item must be an object`); + } + + // Security-related validations + if (isSecurityRelated(change)) { + const commit = change.commit; + + if (!COMMIT_SHA_RE.test(commit)) { + report(`${prefix}Invalid commit: "${commit}"`); + } + + // Remove the "commit" key so we can validate keys like normal. + delete change.commit; + } + + // For non-ancient entries, validate PR URL, keys, and description presence + if (!isAncient(change)) { + const prUrl = change['pr-url']; + + if (!VALID_PR_URL_RE.test(prUrl)) { + report(`${prefix}"${prUrl}" is not a valid PR URL.`); + } + + // Key validation + orderedYamlKeys(change, report, options, CHANGE_VALID_KEYS, prefix); + } + + // Version validation + validateVersion( + change.version, + report, + options, + `changes[${index}].version` + ); + + // Description validation + if (typeof change.description !== 'string') { + report(`${prefix}Description must be a string`); + } else if (change.description.trim().length === 0) { + report(`${prefix}Description cannot be empty`); + } else if (!change.description.endsWith('.')) { + report(`${prefix}Description must end with a "."`); + } + }); +} diff --git a/packages/remark-lint/src/rules/yaml/validate-versions.mjs b/packages/remark-lint/src/rules/yaml/validate-versions.mjs new file mode 100644 index 0000000000000..d4dbb2cf8d9fa --- /dev/null +++ b/packages/remark-lint/src/rules/yaml/validate-versions.mjs @@ -0,0 +1,100 @@ +import { valid, parse, gt } from 'semver'; + +const MAX_SAFE_SEMVER = parse( + `${Number.MAX_SAFE_INTEGER}.${Number.MAX_SAFE_INTEGER}.${Number.MAX_SAFE_INTEGER}` +); +const VERSION_KEYS = ['added', 'removed', 'deprecated']; + +/** + * Checks if a version is a placeholder that should be ignored in validation + * @param {string} version - The version string to check + * @param {number} totalVersions - Total number of versions in the array + */ +const isPlaceholder = (version, totalVersions) => + version === 'REPLACEME' && totalVersions === 1; + +/** + * Checks if a version should be ignored in validation (e.g., very old versions) + * @param {string} version - The version string to check + */ +const isIgnoredVersion = version => { + const parsed = parse(version); + return parsed?.major === 0 && parsed.minor < 2; +}; + +/** + * Determines if a version string is valid according to project rules + * @param {string} version - The version string to validate + * @param {number} totalVersions - Total number of versions in the array + * @param {Array} releasedVersions - The released versions + */ +const isValidVersion = (version, totalVersions, releasedVersions) => { + // Special cases that bypass normal validation + if (isPlaceholder(version, totalVersions) || isIgnoredVersion(version)) { + return true; + } + + // Check against known released versions if available + if (releasedVersions.length > 0) { + return releasedVersions.includes(version.replace(/^v/, '')); + } + + // Fall back to semver validation + return Boolean(valid(version)); +}; + +/** + * Converts a version string to a comparable object + * @param {string} version - The version string to convert + */ +const getComparableVersion = version => + version === 'REPLACEME' ? MAX_SAFE_SEMVER : parse(version); + +/** + * Validates a single version field in the YAML + * @type {import('./index.mjs').YAMLRule} + * @param {string} key + */ +export const validateVersion = ( + input, + report, + { releasedVersions = [] }, + key +) => { + const versions = Array.isArray(input) ? input : [input]; + const totalVersions = versions.length; + + // Validate each version individually + versions.forEach(version => { + if (!isValidVersion(version, totalVersions, releasedVersions)) { + report(`In "${key}": ${version} is invalid`); + } + }); + + // Check if versions are sorted in descending order + for (let i = 1; i < totalVersions; i++) { + const prev = getComparableVersion(versions[i - 1]); + const curr = getComparableVersion(versions[i]); + + if (gt(curr, prev)) { + report( + `In "${key}": Versions are unsorted (should be in descending order)` + ); + break; + } + } +}; + +/** + * Validates version fields in a YAML document + * @type {import('./index.mjs').YAMLRule} + */ +const validateVersions = (yaml, report, options) => { + VERSION_KEYS.forEach(key => { + if (yaml[key]) { + validateVersion(yaml[key], report, options, key); + } + }); +}; + +export default validateVersions; diff --git a/packages/remark-lint/turbo.json b/packages/remark-lint/turbo.json new file mode 100644 index 0000000000000..7a34fa4ca9091 --- /dev/null +++ b/packages/remark-lint/turbo.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "lint:js": { + "inputs": ["src/**/*.mjs"] + }, + "lint:fix": { + "cache": false + }, + "test:unit": { + "inputs": ["src/**/*.mjs"] + } + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3419a7fbb1242..e7634528d75b1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -211,6 +211,9 @@ importers: '@next/eslint-plugin-next': specifier: 15.5.0 version: 15.5.0 + '@node-core/remark-lint': + specifier: workspace:* + version: link:../../packages/remark-lint '@opennextjs/cloudflare': specifier: ^1.6.4 version: 1.6.4(wrangler@4.26.1) @@ -251,38 +254,8 @@ importers: specifier: ^26.0.0 version: 26.1.0 remark-frontmatter: - specifier: 5.0.0 + specifier: ^5.0.0 version: 5.0.0 - remark-lint-fenced-code-flag: - specifier: ^4.2.0 - version: 4.2.0 - remark-lint-first-heading-level: - specifier: ^4.0.1 - version: 4.0.1 - remark-lint-maximum-line-length: - specifier: ^4.1.1 - version: 4.1.1 - remark-lint-no-file-name-articles: - specifier: ^3.0.1 - version: 3.0.1 - remark-lint-no-literal-urls: - specifier: ^4.0.1 - version: 4.0.1 - remark-lint-no-undefined-references: - specifier: ^5.0.2 - version: 5.0.2 - remark-lint-no-unused-definitions: - specifier: ^4.0.2 - version: 4.0.2 - remark-lint-prohibited-strings: - specifier: ^4.0.0 - version: 4.0.0 - remark-lint-unordered-list-marker-style: - specifier: ^4.0.1 - version: 4.0.1 - remark-preset-lint-node: - specifier: 5.1.2 - version: 5.1.2 stylelint: specifier: 16.23.0 version: 16.23.0(typescript@5.8.3) @@ -351,6 +324,124 @@ importers: specifier: 'catalog:' version: 10.0.0 + packages/remark-lint: + dependencies: + remark-gfm: + specifier: ^4.0.1 + version: 4.0.1 + remark-lint-blockquote-indentation: + specifier: ^4.0.1 + version: 4.0.1 + remark-lint-checkbox-character-style: + specifier: ^5.0.1 + version: 5.0.1 + remark-lint-checkbox-content-indent: + specifier: ^5.0.1 + version: 5.0.1 + remark-lint-code-block-style: + specifier: ^4.0.1 + version: 4.0.1 + remark-lint-definition-spacing: + specifier: ^4.0.1 + version: 4.0.1 + remark-lint-fenced-code-flag: + specifier: ^4.2.0 + version: 4.2.0 + remark-lint-fenced-code-marker: + specifier: ^4.0.1 + version: 4.0.1 + remark-lint-final-definition: + specifier: ^4.0.2 + version: 4.0.2 + remark-lint-heading-style: + specifier: ^4.0.1 + version: 4.0.1 + remark-lint-maximum-line-length: + specifier: ^4.1.1 + version: 4.1.1 + remark-lint-no-consecutive-blank-lines: + specifier: ^5.0.1 + version: 5.0.1 + remark-lint-no-file-name-consecutive-dashes: + specifier: ^3.0.1 + version: 3.0.1 + remark-lint-no-file-name-outer-dashes: + specifier: ^3.0.1 + version: 3.0.1 + remark-lint-no-heading-indent: + specifier: ^5.0.1 + version: 5.0.1 + remark-lint-no-literal-urls: + specifier: ^4.0.1 + version: 4.0.1 + remark-lint-no-multiple-toplevel-headings: + specifier: ^4.0.1 + version: 4.0.1 + remark-lint-no-shell-dollars: + specifier: ^4.0.1 + version: 4.0.1 + remark-lint-no-table-indentation: + specifier: ^5.0.1 + version: 5.0.1 + remark-lint-no-tabs: + specifier: ^4.0.1 + version: 4.0.1 + remark-lint-no-trailing-spaces: + specifier: ^3.0.2 + version: 3.0.2 + remark-lint-no-unused-definitions: + specifier: ^4.0.2 + version: 4.0.2 + remark-lint-prohibited-strings: + specifier: ^4.0.0 + version: 4.0.0 + remark-lint-rule-style: + specifier: ^4.0.1 + version: 4.0.1 + remark-lint-strong-marker: + specifier: ^4.0.1 + version: 4.0.1 + remark-lint-table-cell-padding: + specifier: ^5.1.1 + version: 5.1.1 + remark-lint-table-pipes: + specifier: ^5.0.1 + version: 5.0.1 + remark-lint-unordered-list-marker-style: + specifier: ^4.0.1 + version: 4.0.1 + remark-preset-lint-recommended: + specifier: ^7.0.1 + version: 7.0.1 + semver: + specifier: ^7.7.2 + version: 7.7.2 + unified-lint-rule: + specifier: ^3.0.1 + version: 3.0.1 + unist-util-visit: + specifier: ^5.0.0 + version: 5.0.0 + yaml: + specifier: ^2.8.1 + version: 2.8.1 + devDependencies: + cross-env: + specifier: 'catalog:' + version: 10.0.0 + dedent: + specifier: ^1.6.0 + version: 1.6.0 + globals: + specifier: ^16.3.0 + version: 16.3.0 + remark-parse: + specifier: ^11.0.0 + version: 11.0.0 + unified: + specifier: ^11.0.5 + version: 11.0.5 + packages/ui-components: dependencies: '@heroicons/react': @@ -4427,6 +4518,14 @@ packages: babel-plugin-macros: optional: true + dedent@1.6.0: + resolution: {integrity: sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} @@ -5205,6 +5304,10 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} + globals@16.3.0: + resolution: {integrity: sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==} + engines: {node: '>=18'} + globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} @@ -7016,9 +7119,6 @@ packages: remark-lint-final-newline@3.0.1: resolution: {integrity: sha512-q5diKHD6BMbzqWqgvYPOB8AJgLrMzEMBAprNXjcpKoZ/uCRqly+gxjco+qVUMtMWSd+P+KXZZEqoa7Y6QiOudw==} - remark-lint-first-heading-level@4.0.1: - resolution: {integrity: sha512-ZqH476wQU2rk3L2X1Ef/FsdDZJsSkMqTkEjKyeac/hxnwDZ8ZLYYMmm4UKTgVZTtqFUkNYzgGEPAFXtrppHbJA==} - remark-lint-hard-break-spaces@4.1.1: resolution: {integrity: sha512-AKDPDt39fvmr3yk38OKZEWJxxCOOUBE+96AsBfs+ExS5LW6oLa9041X5ahFDQHvHGzdoremEIaaElursaPEkNg==} @@ -7043,9 +7143,6 @@ packages: remark-lint-no-duplicate-definitions@4.0.1: resolution: {integrity: sha512-Ek+A/xDkv5Nn+BXCFmf+uOrFSajCHj6CjhsHjtROgVUeEPj726yYekDBoDRA0Y3+z+U30AsJoHgf/9Jj1IFSug==} - remark-lint-no-file-name-articles@3.0.1: - resolution: {integrity: sha512-h31ZDDJV2T6g9WLBrXg1CJ1m8M170O/tlDPAEPGCa/rxwKvMcfum4yicaot0ZKbUZ1uEPjVSUPDeo3sU0zciCQ==} - remark-lint-no-file-name-consecutive-dashes@3.0.1: resolution: {integrity: sha512-qGJRZ81sowEjv1dBodbHZ29pDZbrFpxiQQ6gBvkkHkkoYPekdnr8iUxmV38HcqH8+JNW1O4ELr+m71AA9/34Mw==} @@ -7121,10 +7218,6 @@ packages: remark-parse@11.0.0: resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} - remark-preset-lint-node@5.1.2: - resolution: {integrity: sha512-ukBPfLqD05AomGL+Z3tbmBCKTaEM+9Dv8Pn0r/0vok8F95Z0wj/AY70cFhm038ID1vKBD07anky11dvigDAHlw==} - engines: {node: '>=18.0.0'} - remark-preset-lint-recommended@7.0.1: resolution: {integrity: sha512-j1CY5u48PtZl872BQ40uWSQMT3R4gXKp0FUgevMu5gW7hFMtvaCiDq+BfhzeR8XKKiW9nIMZGfIMZHostz5X4g==} @@ -8237,6 +8330,11 @@ packages: engines: {node: '>= 14.6'} hasBin: true + yaml@2.8.1: + resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} + engines: {node: '>= 14.6'} + hasBin: true + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -10283,7 +10381,7 @@ snapshots: express: 5.0.1 path-to-regexp: 6.3.0 urlpattern-polyfill: 10.1.0 - yaml: 2.8.0 + yaml: 2.8.1 transitivePeerDependencies: - aws-crt - supports-color @@ -12912,6 +13010,8 @@ snapshots: dedent@1.5.3: {} + dedent@1.6.0: {} + deep-eql@5.0.2: {} deep-is@0.1.4: {} @@ -14014,6 +14114,8 @@ snapshots: globals@14.0.0: {} + globals@16.3.0: {} + globalthis@1.0.4: dependencies: define-properties: 1.2.1 @@ -15745,7 +15847,7 @@ snapshots: postcss-load-config@5.1.0(jiti@2.4.2)(postcss@8.5.3)(tsx@4.20.3): dependencies: lilconfig: 3.1.3 - yaml: 2.8.0 + yaml: 2.8.1 optionalDependencies: jiti: 2.4.2 postcss: 8.5.3 @@ -16196,6 +16298,7 @@ snapshots: '@types/mdast': 4.0.4 quotation: 2.0.3 unified-lint-rule: 3.0.1 + optional: true remark-lint-final-definition@4.0.2: dependencies: @@ -16217,15 +16320,6 @@ snapshots: unified-lint-rule: 3.0.1 vfile-location: 5.0.3 - remark-lint-first-heading-level@4.0.1: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-mdx: 3.0.0 - unified-lint-rule: 3.0.1 - unist-util-visit-parents: 6.0.1 - transitivePeerDependencies: - - supports-color - remark-lint-hard-break-spaces@4.1.1: dependencies: '@types/mdast': 4.0.4 @@ -16306,11 +16400,6 @@ snapshots: unist-util-visit-parents: 6.0.1 vfile-message: 4.0.2 - remark-lint-no-file-name-articles@3.0.1: - dependencies: - '@types/mdast': 4.0.4 - unified-lint-rule: 3.0.1 - remark-lint-no-file-name-consecutive-dashes@3.0.1: dependencies: '@types/mdast': 4.0.4 @@ -16517,45 +16606,6 @@ snapshots: transitivePeerDependencies: - supports-color - remark-preset-lint-node@5.1.2: - dependencies: - js-yaml: 4.1.0 - remark-gfm: 4.0.1 - remark-lint-blockquote-indentation: 4.0.1 - remark-lint-checkbox-character-style: 5.0.1 - remark-lint-checkbox-content-indent: 5.0.1 - remark-lint-code-block-style: 4.0.1 - remark-lint-definition-spacing: 4.0.1 - remark-lint-fenced-code-flag: 4.2.0 - remark-lint-fenced-code-marker: 4.0.1 - remark-lint-file-extension: 3.0.1 - remark-lint-final-definition: 4.0.2 - remark-lint-first-heading-level: 4.0.1 - remark-lint-heading-style: 4.0.1 - remark-lint-maximum-line-length: 4.1.1 - remark-lint-no-consecutive-blank-lines: 5.0.1 - remark-lint-no-file-name-articles: 3.0.1 - remark-lint-no-file-name-consecutive-dashes: 3.0.1 - remark-lint-no-file-name-outer-dashes: 3.0.1 - remark-lint-no-heading-indent: 5.0.1 - remark-lint-no-multiple-toplevel-headings: 4.0.1 - remark-lint-no-shell-dollars: 4.0.1 - remark-lint-no-table-indentation: 5.0.1 - remark-lint-no-tabs: 4.0.1 - remark-lint-no-trailing-spaces: 3.0.2 - remark-lint-prohibited-strings: 4.0.0 - remark-lint-rule-style: 4.0.1 - remark-lint-strong-marker: 4.0.1 - remark-lint-table-cell-padding: 5.1.1 - remark-lint-table-pipes: 5.0.1 - remark-lint-unordered-list-marker-style: 4.0.1 - remark-preset-lint-recommended: 7.0.1 - semver: 7.7.2 - unified-lint-rule: 3.0.1 - unist-util-visit: 5.0.0 - transitivePeerDependencies: - - supports-color - remark-preset-lint-recommended@7.0.1: dependencies: remark-lint: 10.0.1 @@ -17494,7 +17544,7 @@ snapshots: vfile-message: 4.0.2 vfile-reporter: 8.1.1 vfile-statistics: 3.0.0 - yaml: 2.8.0 + yaml: 2.8.1 transitivePeerDependencies: - bluebird - supports-color @@ -17991,6 +18041,8 @@ snapshots: yaml@2.8.0: {} + yaml@2.8.1: {} + yargs-parser@21.1.1: {} yargs-parser@22.0.0: {} From a9e90a21ab27f50923640b6bff5e9c648071df48 Mon Sep 17 00:00:00 2001 From: Aviv Keller Date: Fri, 22 Aug 2025 17:41:58 -0400 Subject: [PATCH 4/4] fix(id-token): explicitly set repository.url (#8104) * fix(id-token): explicitly set repository.url * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Aviv Keller * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Aviv Keller * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Aviv Keller * Update packages/i18n/package.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Aviv Keller * fixup! --------- Signed-off-by: Aviv Keller Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/i18n/package.json | 5 +++++ packages/rehype-shiki/package.json | 5 +++++ packages/remark-lint/package.json | 5 +++++ packages/ui-components/package.json | 5 +++++ 4 files changed, 20 insertions(+) diff --git a/packages/i18n/package.json b/packages/i18n/package.json index ee8c742919d47..e99deeabdaf7f 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -10,6 +10,11 @@ ], ".": "./src/index.mjs" }, + "repository": { + "type": "git", + "url": "https://github.com/nodejs/nodejs.org", + "directory": "packages/i18n" + }, "main": "./src/index.mjs", "module": "./src/index.mjs", "scripts": { diff --git a/packages/rehype-shiki/package.json b/packages/rehype-shiki/package.json index 631308016f4de..62edd03c52ae2 100644 --- a/packages/rehype-shiki/package.json +++ b/packages/rehype-shiki/package.json @@ -5,6 +5,11 @@ ".": "./src/index.mjs", "./*": "./src/*.mjs" }, + "repository": { + "type": "git", + "url": "https://github.com/nodejs/nodejs.org", + "directory": "packages/rehype-shiki" + }, "scripts": { "lint": "node --run lint:js", "lint:fix": "node --run lint:js:fix", diff --git a/packages/remark-lint/package.json b/packages/remark-lint/package.json index 72753be7fce5e..a607b9e085217 100644 --- a/packages/remark-lint/package.json +++ b/packages/remark-lint/package.json @@ -6,6 +6,11 @@ ".": "./src/index.mjs", "./api": "./src/api.mjs" }, + "repository": { + "type": "git", + "url": "https://github.com/nodejs/nodejs.org", + "directory": "packages/remark-lint" + }, "scripts": { "lint": "node --run lint:js", "lint:fix": "node --run lint:js:fix", diff --git a/packages/ui-components/package.json b/packages/ui-components/package.json index 21eb5e99bcbe0..4267399460f16 100644 --- a/packages/ui-components/package.json +++ b/packages/ui-components/package.json @@ -10,6 +10,11 @@ "./src/*/index.ts" ] }, + "repository": { + "type": "git", + "url": "https://github.com/nodejs/nodejs.org", + "directory": "packages/ui-components" + }, "scripts": { "compile:ts": "tsc", "compile:css": "postcss --dir dist --base src \"src/**/*.module.css\" src/styles/index.css",