diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index 4e0fdab245e1f9..a7641a3c04d73d 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -132,26 +132,6 @@ jobs: NODE=$(command -v node) make lint-md env: NODE_RELEASED_VERSIONS: ${{ steps.get-released-versions.outputs.NODE_RELEASED_VERSIONS }} - lint-nix: - if: github.event.pull_request.draft == false - runs-on: ubuntu-slim - steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - persist-credentials: false - sparse-checkout: '*.nix' - sparse-checkout-cone-mode: false - - uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6 - - name: Lint Nix files - run: | - nix-shell -I nixpkgs=./tools/nix/pkgs.nix -p 'nixfmt-tree' --run ' - treefmt --quiet --ci - ' && EXIT_CODE="$?" || EXIT_CODE="$?" - if [ "$EXIT_CODE" != "0" ] - then - git --no-pager diff || true - exit "$EXIT_CODE" - fi lint-py: if: github.event.pull_request.draft == false diff --git a/.github/workflows/nix-changes-comment.yml b/.github/workflows/nix-changes-comment.yml new file mode 100644 index 00000000000000..db96ff4a09caea --- /dev/null +++ b/.github/workflows/nix-changes-comment.yml @@ -0,0 +1,98 @@ +# This action requires the following secrets to be set on the repository: +# GH_USER_TOKEN: GitHub user token, to be used by ncu and to push changes +name: Comment on PR on Nix changes + +on: + workflow_run: + workflows: [Nix files edited] + types: [completed] + +permissions: {} + +jobs: + aggregate-results: + if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request' }} + name: Aggregate results + runs-on: ubuntu-slim + permissions: + pull-requests: write + steps: + - name: Download artefacts + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + run-id: ${{ github.event.workflow_run.id }} + # Using privileged token to be able to download file from a different workflow + github-token: ${{ secrets.GH_USER_TOKEN }} + + - name: Fetch PR data + id: pr-data + run: | + mv requisites-*/* . + ls + if grep -nvHE '^/nix/store/[0-9a-df-np-sv-z]{32}-[A-Za-z0-9?=._+-]+$' requisites-*.list; then + echo 'Unexpected line format in requisites list; refusing to process.' >&2 + exit 1 + fi + echo "NUMBER=$(grep -E '^[0-9]+$' pr_number.txt || true)" >> "$GITHUB_OUTPUT" + + - name: Aggregate results + if: ${{ steps.pr-data.outputs.NUMBER }} + run: | + set -x + > requisites-before.list + > requisites-after.list + TABLE=$( + echo '| Platform | Number of requisites | Proportion of new derivations |' + echo '| :-: | -: | -: |' + TOTAL_BEFORE=0 + TOTAL_AFTER=0 + TOTAL_CHANGED=0 + QUOTE='`' + for PLATFORM in x86_64-linux aarch64-linux x86_64-darwin aarch64-darwin; do + BEFORE=$(wc -l < "requisites-${PLATFORM}-before.list") + TOTAL_BEFORE=$(($TOTAL_BEFORE + $BEFORE)) + AFTER=$(wc -l < "requisites-${PLATFORM}-after.list") + TOTAL_AFTER=$(($TOTAL_AFTER + $AFTER)) + DIFF="_no changes_" + [ "$BEFORE" = "$AFTER" ] || DIFF="**$([ "$AFTER" < "$BEFORE" ] || echo '+')$(($AFTER - $BEFORE))**" + CHANGED=$(git diff --no-index "requisites-${PLATFORM}-before.list" "requisites-${PLATFORM}-after.list" | grep -c '^+[^+]') + TOTAL_CHANGED=$(($TOTAL_CHANGED + $CHANGED)) + PERCENT=100 + [ "$CHANGED" = "$AFTER" ] || { + PERMILLE=$((1000 * $CHANGED / $AFTER)) + PERCENT="$((PERMILLE / 10)).$((PERMILLE % 10))" + } + echo "| ${QUOTE}${PLATFORM}${QUOTE} | $BEFORE -> $AFTER => $DIFF | $CHANGED / $AFTER = $PERCENT% |" + awk '{ print $0 " ('"$PLATFORM"')" }' "requisites-${PLATFORM}-before.list" >> requisites-before.list + awk '{ print $0 " ('"$PLATFORM"')" }' "requisites-${PLATFORM}-after.list" >> requisites-after.list + done + + DIFF="_no changes_" + [ "$TOTAL_BEFORE" = "$TOTAL_AFTER" ] || DIFF="**$([ "$TOTAL_AFTER" < "$TOTAL_BEFORE" ] || echo '+')$(($TOTAL_AFTER - $TOTAL_BEFORE))**" + PERCENT=100 + [ "$TOTAL_CHANGED" = "$TOTAL_AFTER" ] || { + PERMILLE=$((1000 * $TOTAL_CHANGED / $TOTAL_AFTER)) + PERCENT="$((PERMILLE / 10)).$((PERMILLE % 10))" + } + echo "| **Total** | $TOTAL_BEFORE -> $TOTAL_AFTER => $DIFF | $TOTAL_CHANGED / $TOTAL_AFTER = $PERCENT% |" + ) + sort -k1.45 requisites-before.list > requisites-before.sort + sort -k1.45 requisites-after.list > requisites-after.sort + if git diff -U0 --no-color --no-index requisites-before.sort requisites-after.sort > requisites.diff; then + echo 'No changes detected.' + else + { + echo "$TABLE" + echo + echo '
Changelog' + echo + echo '```diff' + tail -n +5 requisites.diff + echo '```' + echo + echo '
' + } | gh pr comment -R "$GITHUB_REPOSITORY" "$PR_NUMBER" -F - + fi + env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ steps.pr-data.outputs.NUMBER }} diff --git a/.github/workflows/nix-changes.yml b/.github/workflows/nix-changes.yml new file mode 100644 index 00000000000000..ddf19260359027 --- /dev/null +++ b/.github/workflows/nix-changes.yml @@ -0,0 +1,140 @@ +# This action uses the following secrets: +# CACHIX_AUTH_TOKEN: Write access to nodejs.cachix.org – without it, the cache is read-only. +name: Nix files edited + +on: + push: + branches: + - main + - canary + - v[0-9]+.x-staging + - v[0-9]+.x + paths: + - '**.nix' + - .github/workflows/nix-changes.yml + pull_request: + paths: + - '**.nix' + - .github/workflows/nix-changes.yml + types: [opened, synchronize, reopened, ready_for_review] + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + diff: + if: github.event.pull_request.draft == false + strategy: + fail-fast: false + matrix: + include: + - runner: ubuntu-24.04 + system: x86_64-linux + - runner: ubuntu-24.04-arm + system: aarch64-linux + - runner: macos-15-intel + system: x86_64-darwin + - runner: macos-latest + system: aarch64-darwin + name: '${{ matrix.system }}: dependencies diff' + runs-on: ${{ matrix.runner }} + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 2 + persist-credentials: false + sparse-checkout: '*.nix' + sparse-checkout-cone-mode: false + + - uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6 + with: + extra_nix_config: sandbox = true + + - uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17 + with: + authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} + name: nodejs + + - name: Compute requisites after change + shell: bash # See https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference, we want the pipefail option. + run: | + nix-store --query --references "$( + nix-instantiate -I "nixpkgs=./tools/nix/pkgs.nix" shell.nix \ + --arg devTools " + (import ./tools/nix/devTools.nix {}) + ++ builtins.attrValues ( + { inherit (import {}) nixfmt-tree sccache; } + // import ./tools/nix/openssl-matrix.nix {} + )")" \ + | xargs nix-store --realise \ + | xargs nix-store --query --requisites \ + | sort -k1.45 \ + > requisites-${{ matrix.system }}-after.list + + - name: Compute requisites before change + shell: bash # See https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference, we want the pipefail option. + run: | + git reset HEAD^ --hard + nix-store --query --references "$( + nix-instantiate -I "nixpkgs=./tools/nix/pkgs.nix" shell.nix \ + --arg devTools " + (import ./tools/nix/devTools.nix {}) + ++ builtins.attrValues ( + { inherit (import {}) nixfmt-tree sccache; } + // import ./tools/nix/openssl-matrix.nix {} + )")" \ + | xargs nix-store --realise \ + | xargs nix-store --query --requisites \ + | sort -k1.45 \ + > requisites-${{ matrix.system }}-before.list + + - name: Output diff + run: | + { + if diff_output="$(git diff -U0 --no-color --no-index requisites-${{ matrix.system }}-before.list requisites-${{ matrix.system }}-after.list)"; then + echo 'No changes detected.' + else + echo '```diff' + echo "$diff_output" | tail -n +5 + echo '```' + fi + } >> "$GITHUB_STEP_SUMMARY" + + - name: Store PR number + if: ${{ github.event.pull_request && matrix.system == 'x86_64-linux' }} + run: echo "${{ github.event.pull_request.number }}" > pr_number.txt + + - name: Upload requisites lists + if: ${{ github.event.pull_request }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: requisites-${{ matrix.system }} + path: | + pr_number.* + requisites-${{ matrix.system }}-before.list + requisites-${{ matrix.system }}-after.list + + lint-nix: + if: github.event.pull_request.draft == false + runs-on: ubuntu-slim + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + sparse-checkout: '*.nix' + sparse-checkout-cone-mode: false + - uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6 + - name: Lint Nix files + run: | + nix-shell -I nixpkgs=./tools/nix/pkgs.nix -p 'nixfmt-tree' --run ' + treefmt --quiet --ci + ' && EXIT_CODE="$?" || EXIT_CODE="$?" + if [ "$EXIT_CODE" != "0" ] + then + git --no-pager diff || true + exit "$EXIT_CODE" + fi diff --git a/lib/internal/blob.js b/lib/internal/blob.js index ad84312683d010..68071a5f5645e7 100644 --- a/lib/internal/blob.js +++ b/lib/internal/blob.js @@ -122,7 +122,7 @@ function getSource(source, endings) { source = new Uint8Array(source); } else if (!isArrayBufferView(source)) { if (endings === 'native') - source = RegExpPrototypeSymbolReplace(/\n|\r\n/g, source, EOL); + source = RegExpPrototypeSymbolReplace(/\r\n|\r|\n/g, source, EOL); source = enc.encode(source); } diff --git a/lib/internal/mime.js b/lib/internal/mime.js index de691a745031e0..0c5bbc3325aaec 100644 --- a/lib/internal/mime.js +++ b/lib/internal/mime.js @@ -148,12 +148,13 @@ class MIMEParams { */ delete(name) { this.#parse(); - this.#data.delete(name); + this.#data.delete(toASCIILower(`${name}`)); } get(name) { this.#parse(); const data = this.#data; + name = toASCIILower(`${name}`); if (data.has(name)) { return data.get(name); } @@ -162,13 +163,13 @@ class MIMEParams { has(name) { this.#parse(); - return this.#data.has(name); + return this.#data.has(toASCIILower(`${name}`)); } set(name, value) { this.#parse(); const data = this.#data; - name = `${name}`; + name = toASCIILower(`${name}`); value = `${value}`; const invalidNameIndex = SafeStringPrototypeSearch(name, NOT_HTTP_TOKEN_CODE_POINT); if (name.length === 0 || invalidNameIndex !== -1) { diff --git a/test/parallel/test-blob.js b/test/parallel/test-blob.js index cbe7a42ada467f..e20ebbb86404bc 100644 --- a/test/parallel/test-blob.js +++ b/test/parallel/test-blob.js @@ -407,6 +407,24 @@ assert.throws(() => new Blob({}), { const b = new Blob(['hello\n'], { endings: 'native' }); assert.strictEqual(b.size, EOL.length + 5); + // The WHATWG "convert line endings to native" algorithm normalizes every + // standalone "\r", "\n", and "\r\n" sequence to the native line ending. + // Refs: https://w3c.github.io/FileAPI/#convert-line-endings-to-native + (async () => { + const cases = [ + ['a\rb', `a${EOL}b`], + ['a\nb', `a${EOL}b`], + ['a\r\nb', `a${EOL}b`], + ['a\r\rb', `a${EOL}${EOL}b`], + ['a\n\rb', `a${EOL}${EOL}b`], + ['\r\n\r', `${EOL}${EOL}`], + ]; + for (const [input, expected] of cases) { + const blob = new Blob([input], { endings: 'native' }); + assert.strictEqual(await blob.text(), expected); + } + })().then(common.mustCall()); + [1, {}, 'foo'].forEach((endings) => { assert.throws(() => new Blob([], { endings }), { code: 'ERR_INVALID_ARG_VALUE', diff --git a/test/parallel/test-mime-api.js b/test/parallel/test-mime-api.js index 30272e5aa64df5..dffead31850b47 100644 --- a/test/parallel/test-mime-api.js +++ b/test/parallel/test-mime-api.js @@ -158,3 +158,30 @@ assert.throws(() => params.set(`x${NOT_HTTP_TOKEN_CODE_POINT}`, 'x'), /parameter assert.throws(() => params.set('x', `${NOT_HTTP_QUOTED_STRING_CODE_POINT};`), /parameter value/i); assert.throws(() => params.set('x', `${NOT_HTTP_QUOTED_STRING_CODE_POINT}x`), /parameter value/i); assert.throws(() => params.set('x', `x${NOT_HTTP_QUOTED_STRING_CODE_POINT}`), /parameter value/i); + +// MIME parameter names are case-insensitive. The parser lower-cases them, so +// the MIMEParams accessors must lower-case their argument too; otherwise a +// parsed parameter is unreachable by the casing the caller actually used and +// `set()` can create case-colliding duplicate parameters. +// Refs: https://mimesniff.spec.whatwg.org/ (parameter names are ASCII-lowercased) +{ + const mime = new MIMEType('text/plain;Charset=value'); + assert.strictEqual(mime.params.get('Charset'), 'value'); + assert.strictEqual(mime.params.get('charset'), 'value'); + assert.strictEqual(mime.params.get('CHARSET'), 'value'); + assert.strictEqual(mime.params.has('Charset'), true); + assert.strictEqual(`${mime.params}`, 'charset=value'); + + // `set()` with a mixed-case name overwrites the same parameter rather than + // adding a second, case-colliding one. + const params = new MIMEType('text/plain').params; + params.set('Foo', 'a'); + params.set('foo', 'b'); + assert.deepStrictEqual([...params], [['foo', 'b']]); + assert.strictEqual(`${params}`, 'foo=b'); + + // `delete()` is case-insensitive as well. + params.delete('FOO'); + assert.strictEqual(params.has('foo'), false); + assert.deepStrictEqual([...params], []); +} diff --git a/tools/dep_updaters/update-nixpkgs-pin.sh b/tools/dep_updaters/update-nixpkgs-pin.sh index eb5fde1526ab0c..a025676e20a7b5 100755 --- a/tools/dep_updaters/update-nixpkgs-pin.sh +++ b/tools/dep_updaters/update-nixpkgs-pin.sh @@ -1,10 +1,11 @@ #!/bin/sh set -ex # Shell script to update Nixpkgs pin in the source tree to the most recent -# version on the unstable channel. +# version on the unstable channel, and the 26.05 one for Intel Mac support. BASE_DIR=$(cd "$(dirname "$0")/../.." && pwd) NIXPKGS_PIN_FILE="$BASE_DIR/tools/nix/pkgs.nix" +NIXPKGS_COMPAT_PIN_FILE="$BASE_DIR/tools/nix/pkgs-26.05.nix" OPENSSL_MATRIX_FILE="$BASE_DIR/tools/nix/openssl-matrix.nix" NIXPKGS_REPO=$(grep 'repo =' "$NIXPKGS_PIN_FILE" | awk -F'"' '{ print $2 }') @@ -19,12 +20,27 @@ NEW_VERSION=$(echo "$NEW_UPSTREAM_SHA1" | head -c 35) compare_dependency_version "nixpkgs-unstable" "$CURRENT_VERSION_SHA1" "$NEW_UPSTREAM_SHA1" -CURRENT_TARBALL_HASH=$(grep 'sha256 =' "$NIXPKGS_PIN_FILE" | awk -F'"' '{ print $2 }') -NEW_TARBALL_HASH=$(nix-prefetch-url --unpack "$NIXPKGS_REPO/archive/$NEW_UPSTREAM_SHA1.tar.gz") +update_pkgs_file() { + PIN_FILE=$1 + PREVIOUS_SHA1=$2 + UPSTREAM_SHA1=$3 -TMP_FILE=$(mktemp) -sed "s/$CURRENT_VERSION_SHA1/$NEW_UPSTREAM_SHA1/;s/$CURRENT_TARBALL_HASH/$NEW_TARBALL_HASH/" "$NIXPKGS_PIN_FILE" > "$TMP_FILE" -mv "$TMP_FILE" "$NIXPKGS_PIN_FILE" + CURRENT_TARBALL_HASH=$(grep 'sha256 =' "$PIN_FILE" | awk -F'"' '{ print $2 }') + NEW_TARBALL_HASH=$(nix-prefetch-url --unpack "$NIXPKGS_REPO/archive/$UPSTREAM_SHA1.tar.gz") + + TMP_FILE=$(mktemp) + sed "s/$PREVIOUS_SHA1/$UPSTREAM_SHA1/;s/$CURRENT_TARBALL_HASH/$NEW_TARBALL_HASH/" "$PIN_FILE" > "$TMP_FILE" + mv "$TMP_FILE" "$PIN_FILE" +} + +update_pkgs_file "$NIXPKGS_PIN_FILE" "$CURRENT_VERSION_SHA1" "$NEW_UPSTREAM_SHA1" + +# Unstable channel no longer supports Intel architecture for macOS. We can use the 26.05 channel +# to keep testing on that platform for a little longer. +# TODO: remove this when 26.05 is EOL (end of 2026) +COMPAT_VERSION_SHA1=$(grep 'rev =' "$NIXPKGS_COMPAT_PIN_FILE" | awk -F'"' '{ print $2 }') +COMPAT_UPSTREAM_SHA1=$(git ls-remote "$NIXPKGS_REPO.git" nixpkgs-26.05-darwin | awk '{print $1}') +update_pkgs_file "$NIXPKGS_COMPAT_PIN_FILE" "$COMPAT_VERSION_SHA1" "$COMPAT_UPSTREAM_SHA1" nix-instantiate -I "nixpkgs=$NIXPKGS_PIN_FILE" --eval --strict --json -E " let diff --git a/tools/nix/pkgs-26.05.nix b/tools/nix/pkgs-26.05.nix new file mode 100644 index 00000000000000..cbee28de68e2f4 --- /dev/null +++ b/tools/nix/pkgs-26.05.nix @@ -0,0 +1,13 @@ +arg: +let + repo = "https://github.com/NixOS/nixpkgs"; + rev = "fad15a0c9ebf6432dcba932743decbf8905cb024"; + nixpkgs = import (builtins.fetchTarball { + url = "${repo}/archive/${rev}.tar.gz"; + sha256 = "0q845gkz18nz463cwmkhvficl3jpncdvvnx8xlrj5gdp4c285rcn"; + }) arg; +in +# Unstable channel no longer supports Intel architecture for macOS. We can use the 26.05 channel +# to keep testing on that platform for a little longer. +# TODO: remove this file when 26.05 is EOL (end of 2026) +nixpkgs diff --git a/tools/nix/pkgs.nix b/tools/nix/pkgs.nix index 09678d12da7fc5..62d48ecc98c776 100644 --- a/tools/nix/pkgs.nix +++ b/tools/nix/pkgs.nix @@ -7,4 +7,7 @@ let sha256 = "0ffi2k8hllbgqw9vvdfaxw88qz53gl7myll4lwri6ynq5l0lnvbc"; }) arg; in -nixpkgs +# Unstable channel no longer supports Intel architecture for macOS. We can use the 26.05 channel +# to keep testing on that platform for a little longer. +# TODO: remove this when 26.05 is EOL (end of 2026) +if builtins.currentSystem == "x86_64-darwin" then (import ./pkgs-26.05.nix arg) else nixpkgs