diff --git a/.github/actions/materialize/action.yml b/.github/actions/materialize/action.yml
index 0ef4aef685..e7c3f2abf1 100644
--- a/.github/actions/materialize/action.yml
+++ b/.github/actions/materialize/action.yml
@@ -33,7 +33,7 @@ runs:
set -euo pipefail
UP="$RUNNER_TEMP/upstream-$BASE_TAG"
if [ ! -d "$UP/.git" ]; then
- git clone --depth 1 --branch "$BASE_TAG" https://github.com/nodejs/node.git "$UP"
+ scripts/clone-upstream.sh https://github.com/nodejs/node.git "$BASE_TAG" "$UP"
fi
# file:// so git treats the shallow cache as a proper transport.
UPSTREAM_REPO="file://$UP" scripts/prepare.sh "$RUNNER_TEMP/matsrc"
diff --git a/.github/actions/upload-test-log/action.yml b/.github/actions/upload-test-log/action.yml
new file mode 100644
index 0000000000..6415551702
--- /dev/null
+++ b/.github/actions/upload-test-log/action.yml
@@ -0,0 +1,46 @@
+name: Upload diagnostic test logs
+description: >
+ Upload test logs with one retry for transient artifact-service failures.
+ Logs are best effort; the test step remains the required pass/fail gate.
+inputs:
+ name:
+ description: Artifact name
+ required: true
+ path:
+ description: Log paths, one per line
+ required: true
+runs:
+ using: composite
+ steps:
+ - name: Upload logs
+ id: upload
+ continue-on-error: true
+ uses: actions/upload-artifact@v7
+ with:
+ name: ${{ inputs.name }}
+ path: ${{ inputs.path }}
+ if-no-files-found: ignore
+
+ - name: Wait before retrying the artifact service
+ if: always() && steps.upload.outcome == 'failure'
+ shell: bash
+ run: sleep 15
+
+ - name: Retry log upload
+ id: retry
+ if: always() && steps.upload.outcome == 'failure'
+ continue-on-error: true
+ uses: actions/upload-artifact@v7
+ with:
+ # A failed response can still leave an artifact on the service. A
+ # distinct name avoids conflicts without deleting the first upload.
+ name: ${{ inputs.name }}-retry
+ path: ${{ inputs.path }}
+ if-no-files-found: ignore
+
+ - name: Report unavailable log artifact
+ if: always() && steps.upload.outcome == 'failure' && steps.retry.outcome == 'failure'
+ shell: bash
+ run: |
+ echo '::warning::Test log upload failed twice; use the live job log and test summary. The test verdict is unchanged.'
+ echo 'Test log artifact unavailable after two upload attempts. See the live job log and the test summary above.' >> "$GITHUB_STEP_SUMMARY"
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index f1a8c7494b..3fb332f429 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -70,8 +70,8 @@ permissions:
jobs:
package-preview:
- if: github.ref == 'refs/heads/recipe-v26'
- needs: [ci-required, curated-tests-android, curated-tests-ios]
+ if: github.ref == 'refs/heads/recipe-v26' || (github.event_name == 'pull_request' && needs.upgrade-check.outputs.upgrade == 'true')
+ needs: [release-check, upgrade-check, ci-required, curated-tests-android, curated-tests-ios]
runs-on: ubuntu-24.04
permissions:
contents: read
@@ -86,7 +86,7 @@ jobs:
run: python3 scripts/package-preview.py preview-input preview-packages
- uses: actions/upload-artifact@v7
with:
- name: nodejs-mobile-26.10.0-0-preview
+ name: nodejs-mobile-${{ needs.release-check.outputs.version }}-preview
path: preview-packages
retention-days: 90
if-no-files-found: error
@@ -94,9 +94,10 @@ jobs:
# Flavor matrices are inline expressions, not a helper job (a full VM boot
# to echo two constants earns nothing). Android always runs both flavors
# (ubuntu+KVM is 1x-billed and plentiful); iOS drops to full-only on
- # pull_request because hosted macOS concurrency is capped far below Linux
+ # ordinary pull_request because hosted macOS concurrency is capped far below Linux
# and two PRs running the complete iOS matrix could queue the release
- # pipeline behind them. What the lite iOS leg uniquely catches —
+ # pipeline behind them. Upstream upgrades build and smoke both iOS flavors.
+ # What the lite iOS leg uniquely catches —
# lite-specific configure/link breakage — is still caught at compile time
# on the merge run. Pushes and releases run both. See docs/TESTING.md,
# "Flavors, and what's required to merge".
@@ -319,11 +320,11 @@ jobs:
path: out_android
build-ios:
- needs: release-check
+ needs: [release-check, upgrade-check]
strategy:
fail-fast: false
matrix:
- flavor: ${{ github.event_name == 'pull_request' && fromJSON('["full"]') || fromJSON('["full","lite"]') }}
+ flavor: ${{ github.event_name == 'pull_request' && needs.upgrade-check.outputs.upgrade != 'true' && fromJSON('["full"]') || fromJSON('["full","lite"]') }}
config:
# x86_64 (Intel Mac) simulator support was dropped for v24 — Intel
# Macs are EOL.
@@ -567,9 +568,9 @@ jobs:
strategy:
fail-fast: false
matrix:
- flavor: ${{ github.event_name == 'pull_request' && fromJSON('["full"]') || fromJSON('["full","lite"]') }}
+ flavor: ${{ github.event_name == 'pull_request' && needs.upgrade-check.outputs.upgrade != 'true' && fromJSON('["full"]') || fromJSON('["full","lite"]') }}
runs-on: macos-26
- needs: [build-ios]
+ needs: [build-ios, upgrade-check]
env:
NODEJS_MOBILE_FLAVOR: ${{ matrix.flavor }}
FLAVOR_SUFFIX: ${{ matrix.flavor == 'lite' && '-lite' || '' }}
@@ -696,11 +697,11 @@ jobs:
# Boot smoke for iOS: install the testnode app on a booted simulator and run a
# console.log via the freshly built NodeMobile.xcframework.
smoke-ios:
- needs: [combine-ios]
+ needs: [combine-ios, upgrade-check]
strategy:
fail-fast: false
matrix:
- flavor: ${{ github.event_name == 'pull_request' && fromJSON('["full"]') || fromJSON('["full","lite"]') }}
+ flavor: ${{ github.event_name == 'pull_request' && needs.upgrade-check.outputs.upgrade != 'true' && fromJSON('["full"]') || fromJSON('["full","lite"]') }}
runs-on: macos-26
env:
NODEJS_MOBILE_FLAVOR: ${{ matrix.flavor }}
@@ -1223,6 +1224,8 @@ jobs:
fetch-depth: 1
- name: Check the pinned upstream base is on this fork
+ env:
+ GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
# || true: grep exits 1 on a comments-only file, which under pipefail
@@ -1230,6 +1233,14 @@ jobs:
BASE=$(grep -Ev '^\s*(#|$)' upstream-base.txt | head -n1 | tr -d '[:space:]' || true)
[ -n "$BASE" ] || { echo "::error::no base in upstream-base.txt"; exit 1; }
+ if [[ "$BASE" =~ ^[0-9a-f]{40}$ ]]; then
+ WANT=$(gh api "repos/nodejs/node/commits/$BASE" --jq .sha)
+ [ "$WANT" = "$BASE" ] || { echo "::error::upstream snapshot SHA mismatch"; exit 1; }
+ echo "Verified upstream PR snapshot $BASE; no release ancestry ref is required."
+ # release-control.py rejects Cut release/publication for SHA bases.
+ exit 0
+ fi
+
# Each major keeps an independent upstream ancestry ref.
MAJOR=${BASE#v}; MAJOR=${MAJOR%%.*}
BASE_REF=upstream-base
diff --git a/.github/workflows/full-device-suite.yml b/.github/workflows/full-device-suite.yml
index dad84ec6e7..af1b36707d 100644
--- a/.github/workflows/full-device-suite.yml
+++ b/.github/workflows/full-device-suite.yml
@@ -1,6 +1,6 @@
# The full device suite: everything test/parallel + test/sequential that
-# .status does not skip, on an Android emulator and an iOS simulator, in 4
-# round-robin shards per platform (test.py --run=n,4).
+# .status does not skip, on an Android emulator and an iOS simulator, in
+# round-robin shards (16 Android, 4 iOS).
#
# The curated gate (build.yml) is the fast PR allow-list; this job is the
# complement: a test upstream adds is picked up automatically, and excluding
@@ -97,10 +97,15 @@ jobs:
if: needs.precheck.outputs.skip != 'true' && inputs.platform != 'ios'
strategy:
fail-fast: false
- # Keep in step with the ,4 in --run below: a matrix of a different
- # length would drop or double-run tests rather than fail.
+ # Two Node 26.11 runs lost runners after ~470-560 app launches, with
+ # QEMU hangs and no failing test. Shorter emulator sessions retain all
+ # test coverage; bound concurrency to the previous four Android jobs.
+ max-parallel: 4
matrix:
- shard: [0, 1, 2, 3]
+ shard: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
+ env:
+ # The denominator and matrix length are checked by the integrity gate.
+ ANDROID_SUITE_SHARDS: 16
runs-on: ubuntu-24.04
timeout-minutes: 90
steps:
@@ -149,25 +154,30 @@ jobs:
api-level: ${{ env.EMULATOR_API_LEVEL }}
target: default
arch: x86_64
+ ram-size: 2048M
+ heap-size: 256M
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
script: |
adb wait-for-device
./tools/mobile-test/android/prepare-android-test.sh
- bash -o pipefail -c "./tools/test.py -j 1 --flaky-tests=skip --timeout=300 --arch android --run=${{ matrix.shard }},4 parallel sequential 2>&1 | tee shard.log"
+ ./tools/mobile-test/android/run-suite-shard.sh ${{ matrix.shard }} "$ANDROID_SUITE_SHARDS"
- name: Summarize shard ${{ matrix.shard }}
if: always()
run: |
./tools/mobile-test/summarize-suite-run.sh shard.log "android shard ${{ matrix.shard }}"
- - uses: actions/upload-artifact@v7
+ # Log transport must not change the device-test verdict. Binary uploads
+ # in Build remain required; only these diagnostic logs are best effort.
+ - uses: ./.github/actions/upload-test-log
if: always()
with:
name: full-suite-android-shard-${{ matrix.shard }}
- path: shard.log
- if-no-files-found: ignore
+ path: |
+ shard.log
+ shard-resources.log
ios:
needs: precheck
@@ -225,12 +235,11 @@ jobs:
run: |
./tools/mobile-test/summarize-suite-run.sh shard.log "ios shard ${{ matrix.shard }}"
- - uses: actions/upload-artifact@v7
+ - uses: ./.github/actions/upload-test-log
if: always()
with:
name: full-suite-ios-shard-${{ matrix.shard }}
path: shard.log
- if-no-files-found: ignore
- name: Shutdown simulator
if: always()
diff --git a/.github/workflows/verify-patches.yml b/.github/workflows/verify-patches.yml
index a14779cafc..4e00376a02 100644
--- a/.github/workflows/verify-patches.yml
+++ b/.github/workflows/verify-patches.yml
@@ -84,6 +84,9 @@ jobs:
- name: Reconstruct + verify
run: scripts/prepare.sh out
+ - name: Check immutable upstream clones and cache transport
+ run: python3 scripts/test-upstream-clone.py
+
- uses: actions/setup-node@v6
with:
node-version: '24'
@@ -107,6 +110,9 @@ jobs:
- name: Check mobile verdict isolation and final output
run: python3 scripts/test-mobile-verdicts.py out
+ - name: Check Android full-suite coverage and failure propagation
+ run: python3 scripts/test-android-suite.py out
+
- name: Check manual prerelease version selection and guards
run: python3 scripts/test-release-control.py
@@ -286,7 +292,7 @@ jobs:
run: |
set -euo pipefail
BASE=$(grep -Ev '^\s*(#|$)' upstream-base.txt | head -n1 | tr -d '[:space:]')
- git clone --depth 1 --branch "$BASE" https://github.com/nodejs/node.git src
+ scripts/clone-upstream.sh https://github.com/nodejs/node.git "$BASE" src
git -C src config user.name ci
git -C src config user.email ci@invalid
while IFS= read -r p; do
diff --git a/README.md b/README.md
index ea96237e63..f33e5725c4 100644
--- a/README.md
+++ b/README.md
@@ -9,12 +9,16 @@ in-app, with npm modules and N-API native addons.
| Android | `libnode.so` + headers | `arm64-v8a`, `armeabi-v7a`, `x86_64` (16 KB page size) |
| iOS | `NodeMobile.xcframework` | `arm64` device, `arm64` simulator |
-Current line: **Node.js 26** (`v26.10.0`, prerelease line).
+Current line: **Node.js 26** (`v26.11.0`, upstream PR preview).
+This checkout pins [official release proposal #66546](https://github.com/nodejs/node/pull/66546)
+at `49072a0be4c7410982557aebf5b5c924fe7db597`, before its merge/tag.
+Builds are CI previews; [provenance and validation](docs/UPSTREAM-26.11.md)
+record how to move to the official tag after release.
The stable Node 24 line continues independently on [`recipe`](https://github.com/fogtape/nodejs-mobile/tree/recipe).
See [maintained version lines](docs/VERSION-LINES.md) for branch selection,
build instructions and download locations. `process.version` reports the
upstream version unchanged, so version-parsing tools keep working; the
-mobile build is identified by `process.versions.mobile` (e.g. `"26.10.0-0"`).
+mobile build is identified by `process.versions.mobile` (e.g. `"26.11.0-0"`).
## Get it
@@ -53,13 +57,13 @@ for building one — about 1.5 MB instead of a 1 GB fork:
| | |
|---|---|
-| `upstream-base.txt` | the pinned upstream release tag (`v26.10.0`) |
+| `upstream-base.txt` | the pinned upstream PR commit (see [26.11 provenance](docs/UPSTREAM-26.11.md)) |
| `patches/` | per-concern patches to upstream files, plus `series` (apply order) and `files.map` (which patch owns which file) |
| `mobile-src/` | files that have no upstream counterpart — build scripts, the iOS framework project, test apps and harness |
| `expected-tree.txt` | the git tree hash the reconstruction must produce |
| `scripts/` | `prepare.sh` (recipe → source tree) and `regenerate-patches.py` (source tree → recipe) |
-`scripts/prepare.sh` clones upstream at the pinned tag, applies the patch
+`scripts/prepare.sh` clones upstream at the pinned tag or commit, applies the patch
series, overlays `mobile-src/`, and verifies the result hashes to
`expected-tree.txt` — so what CI builds, what a release tag contains, and
what was reviewed here are provably the same bytes.
diff --git a/docs/BUILDING.md b/docs/BUILDING.md
index 5b3b585d55..38a3d95b75 100644
--- a/docs/BUILDING.md
+++ b/docs/BUILDING.md
@@ -1,6 +1,8 @@
# Build Instructions
-These instructions describe the Node 26 line (`recipe-v26`). For Node 24,
+These instructions describe the Node 26 line. This checkout is the pinned
+26.11 upstream PR preview on `upgrade/node26.11-mobile-lite`;
+see [provenance](UPSTREAM-26.11.md). For Node 24,
use the `recipe` branch and its toolchain instructions. See
[VERSION-LINES.md](VERSION-LINES.md) for the maintenance policy and the
Node 26 FFI/Temporal configuration.
@@ -86,7 +88,7 @@ sdkmanager "ndk;29.0.14206865"
This repository holds the recipe, not the source. Generate a tree from it:
```sh
-git clone -b recipe-v26 https://github.com/fogtape/nodejs-mobile
+git clone -b upgrade/node26.11-mobile-lite https://github.com/fogtape/nodejs-mobile
cd nodejs-mobile && scripts/prepare.sh && cd out
```
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index d5312d2bac..3998a1932d 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -6,6 +6,7 @@
+26.11.0-0 (upstream PR preview)
26.10.0-0
26.1.0-0
24.21.0-0
@@ -41,8 +42,36 @@
|
+
+## Unreleased, Version 26.11.0-0 (upstream PR preview)
+
+- Pin official Node.js release proposal [#66546](https://github.com/nodejs/node/pull/66546)
+ at `49072a0be4c7410982557aebf5b5c924fe7db597` and rebase the mobile patch series.
+ This source upgrade does not arm publication.
+- Include the proposal's HdrHistogram 0.12.0, NSS 3.129 certificate source,
+ WebCrypto/source-map fixtures and Alpine support documentation updates.
+- Split the complete Android device suite into 16 shorter sessions, with at
+ most four running concurrently. Release the Gradle JVM after APK assembly
+ and publish resource diagnostics while tests run to investigate runner
+ shutdowns without excluding additional tests.
+- Exclude the watch-mode CLI subprocess tests on mobile, matching the
+ existing child-Node limitation; in-process debugger and filesystem-watch
+ tests keep their current coverage. Retry diagnostic log uploads once and
+ report artifact-service failures separately from the device-test verdict.
+- Carry forward the danmu lite ICU data profile, all web encoding converters,
+ Chinese collation and Unicode normalization. Lite omits Temporal, FFI, Amaro,
+ Inspector, SQLite/Web Storage, SEA and native object-print helpers; Android
+ retains JIT/native WASM and 64-bit pointer compression. Keep the lite runtime
+ strip helper and dynamic-export checks.
+- Preserve upstream's new Temporal zoneinfo generator alongside the mobile
+ Rust host/target actions. Adapt the z/OS build refactor without losing iOS guards.
+- Triage new subprocess tests on both platforms, keep in-process bench/negative-zero
+ assertions, update the renamed VFS exclusion and add 11 in-process device regressions.
+- Support SHA-pinned materialization in local builds and CI. Block release
+ preparation/publication until an official release tag is pinned and validated.
+
-## 2026-10-01, Version 26.10.0-0 (Current)
+## 2026-10-01, Version 26.10.0-0
- Upgrade the independent Node 26 line to official Node.js 26.10.0.
- Enable Temporal in Android/iOS full builds using Rust 1.86.0, with separate
diff --git a/docs/DANMU-LITE.md b/docs/DANMU-LITE.md
index 49b409025d..40fcef80f5 100644
--- a/docs/DANMU-LITE.md
+++ b/docs/DANMU-LITE.md
@@ -29,7 +29,8 @@ cannot be inferred from today's imports.
before/after trimming and lets icupkg validate package dependencies. A unique
intermediate package name prevents an installed host's full ICU from hiding
missing resources. The final conventional data entry point is preserved.
-With the pinned ICU 78.3, data goes from 33,105,536 to 11,024,064 bytes. This
+With the Node 26.11 proposal’s bundled ICU data, the production helper
+reduces 33,107,952 to 11,026,480 bytes (22,081,472 bytes removed). This
measures data only, not a complete newly compiled libnode or compressed ZIP.
## Other lite subtractions
diff --git a/docs/TESTING.md b/docs/TESTING.md
index 8fe2c140ec..71b10ecf69 100644
--- a/docs/TESTING.md
+++ b/docs/TESTING.md
@@ -70,7 +70,7 @@ it through `tools/test.py`.
| `build.yml` → `build-*` / `combine-*` | ubuntu / macos | PR · push `recipe` | the cross-compile actually succeeds — the only check that compiles target code |
| `build.yml` → `smoke-{android,ios}` (+ the NAPI symbol assert in `combine-android`) | ubuntu+KVM / macos | PR · push `recipe` | **boot smoke**: the exact shipping artifact boots and runs JS; NAPI symbols in `.dynsym` |
| `build.yml` → `curated-tests-android` / `curated-tests-ios` | ubuntu+KVM / macos | PR · push `recipe` · releases | **curated device tests**: the curated subset + crc-native addon load on an x86_64 emulator and arm64 simulator |
-| `full-device-suite.yml` (also `build.yml` → `full-suite-android` / `full-suite-ios` on releases and on PRs that bump `upstream-base.txt`) | ubuntu+KVM / macos | nightly 03:00 UTC · dispatch · releases · upstream-bump PRs | **full device suite**: the whole non-`.status`-skipped `test/parallel` + `test/sequential` suite on both platforms, 4 round-robin shards each (`test.py --run=n,4`). The curated gate covers what someone chose; this covers everything else, so a test upstream adds tomorrow is picked up without anyone noticing it exists. On a PR that moves the pinned tag it also gates `ci-required` (via `full-suite-gate`): an upgrade carries whatever tests upstream added, and an allow-list gate cannot see them |
+| `full-device-suite.yml` (also `build.yml` → `full-suite-android` / `full-suite-ios` on releases and on PRs that bump `upstream-base.txt`) | ubuntu+KVM / macos | nightly 03:00 UTC · dispatch · releases · upstream-bump PRs | **full device suite**: the whole non-`.status`-skipped `test/parallel` + `test/sequential` suite on both platforms, 16 Android / 4 iOS round-robin shards (`test.py --run=n,16` / `--run=n,4`). The curated gate covers what someone chose; this covers everything else, so a test upstream adds tomorrow is picked up without anyone noticing it exists. On a PR that moves the pinned tag it also gates `ci-required` (via `full-suite-gate`): an upgrade carries whatever tests upstream added, and an allow-list gate cannot see them |
| `build.yml` → `real-device-smoke-android` / `real-device-smoke-ios` | ubuntu / macos-15 + BrowserStack | releases (untagged version of record; required to publish) · dispatch | **real-device smoke**: boot + crc-native addon load on physical devices — Android arm64 (Pixel 9, 16 KB pages) via Espresso and iPhone via XCUITest. The iOS leg runs **once per flavor** (`ios_flavors`, both by default): a simulator is a macOS process and is not subject to the address-space limits a real iOS process is, so simulator-only coverage cannot tell you a flavor boots on hardware. Android is still full-only. Needs `BROWSERSTACK_USER`/`BROWSERSTACK_PW` secrets. |
Every job first **materializes** the source tree from the recipe branch
@@ -139,7 +139,7 @@ for a PR and broad enough to be trusted:
"did this change break something we already care about".
- **The full device suite** — `full-device-suite.yml`. Everything
`parallel.status` and `sequential.status` do not skip — a few thousand
- tests per platform, split four ways. It answers "what is true on a device
+ tests per platform, split into 16 Android shards (four concurrent) and four iOS shards. It answers "what is true on a device
that we have not looked at", which is the larger question: the curated gate
covers about 6% of the runnable suite. `sequential`'s mobile skips are
measured, not assumed — every non-structural skip covers a test that spawns
@@ -164,6 +164,9 @@ per-PR because it costs about eight device-hours per run, which the PR loop
cannot absorb; the nightly covers drift the rest of the time. Each shard
writes a summary (counts, plus the failing names, with hangs and crashes
counted separately — they mean different things) and uploads its log.
+Diagnostic log uploads retry once after a service failure, then warn if the
+artifact is still unavailable. The live job output and test summary remain
+available; test failures and required binary-artifact uploads still fail CI.
### The curated subset
diff --git a/docs/UPGRADING.md b/docs/UPGRADING.md
index 8950c7169d..ca62a20868 100644
--- a/docs/UPGRADING.md
+++ b/docs/UPGRADING.md
@@ -11,6 +11,20 @@ the patches are files, and the upgrade is an ordinary reviewable PR.
Expect a handful of small conflicts resolvable in minutes — plus,
occasionally, one that only the compile catches (see the warning below).
+## Preview an upstream release proposal
+
+A full 40-character commit SHA in `upstream-base.txt` is supported by
+`scripts/clone-upstream.sh`, including the materialization cache and per-patch
+CI configuration checks. Pin the exact reviewed proposal commit, never a
+moving PR branch. `prepare.sh` records a local `refs/nodejs-mobile/upstream-base`
+ref for the in-tree test audit; it creates no upstream release tag.
+
+PR snapshots can run normal builds and produce CI preview packages. Cut release
+and prerelease publication reject a SHA baseline. After upstream releases,
+compare the proposal commit with the official tag, rebase/regenerate as needed,
+update the tree checksum, and complete the build/device gates before release
+preparation. The [26.11 preview](UPSTREAM-26.11.md) records the current pin.
+
## Re-base the series
```sh
diff --git a/docs/UPSTREAM-26.11.md b/docs/UPSTREAM-26.11.md
new file mode 100644
index 0000000000..9dd1023ec2
--- /dev/null
+++ b/docs/UPSTREAM-26.11.md
@@ -0,0 +1,159 @@
+# Node 26.11 upstream PR preview
+
+This recipe previews official Node.js release proposal
+[#66546](https://github.com/nodejs/node/pull/66546), titled
+`2026-10-07, Version 26.11.0 (Current)`. At preparation time on 2026-10-06,
+it was open and ready for review, with no official `v26.11.0` tag.
+
+`upstream-base.txt` pins the proposal's exact commit:
+`49072a0be4c7410982557aebf5b5c924fe7db597`.
+The upstream version header reports `v26.11.0`; the mobile header reports
+`26.11.0-0`. These identifiers do not mean the official release or a mobile
+release has been published. The immutable commit and expected source-tree
+checksum identify this preview even if the proposal branch changes.
+
+The refreshed pin includes HdrHistogram 0.12.0, NSS 3.129 certificate source
+data, WebCrypto WPT and test426 fixture updates, plus the Alpine support
+documentation added after the initial `7f68d75ee7` preview. The certificate
+source update does not change `src/node_root_certs.h`. V8, ICU, Temporal,
+FFI and the existing mobile/lite configuration are unchanged by this refresh.
+
+## Build and inspect
+
+Use `upgrade/node26.11-mobile-lite` for this preview. The Node 24 `recipe`
+line and maintained Node 26.10 release assets remain independent.
+
+```sh
+git clone -b upgrade/node26.11-mobile-lite https://github.com/fogtape/nodejs-mobile.git
+cd nodejs-mobile
+scripts/prepare.sh
+```
+
+For platform builds, follow [BUILDING.md](BUILDING.md). Reconstruction,
+the CI source cache and per-patch configure checks all accept the full SHA;
+no local release tag is fabricated. The test audit uses a local Git ref
+recording the upstream commit, outside the source tree.
+
+## Flavor contract
+
+Upstream upgrade PRs build and boot-smoke both iOS flavors, and preview
+package names use the recorded mobile version. SHA baselines are verified
+against the official repository without requiring a release ancestry branch.
+
+Full retains complete ICU data, Temporal and FFI, with the existing mobile
+Rust/libffi integration. The 26.11 rebase also preserves upstream's new
+Temporal zoneinfo source generator and the iOS platform guards.
+
+Lite carries forward the reviewed [danmu contract](DANMU-LITE.md): the ICU
+API with all Node web encoding converters, selected English/Chinese/Cantonese/
+Japanese/Korean locale families, Unicode normalization/IDNA, time zones and
+segmentation. Temporal, FFI, Amaro, Inspector, SQLite/Web Storage, SEA and
+native object-print helpers remain disabled. Android retains JIT/native
+WebAssembly and pointer compression on 64-bit targets. iOS retains jitless
+V8 and polywasm without pointer compression. Android lite strips non-runtime
+symbols only after confirming the dynamic exports remain unchanged.
+
+The actual 26.11 ICU profile generation retained its required resources and
+passed package dependency validation: **33,107,952 → 11,026,480 bytes**.
+The 22,081,472-byte reduction measures ICU data, not a compiled libnode or ZIP.
+
+## Validation
+
+- A fresh shallow fetch from `nodejs/node` applied all 22 regenerated patches
+ directly; regeneration from that fresh tree preserved every recipe byte.
+ The current recipe reconstructs tree
+ `366de32e0018c646ea07608448b694716229b35b` after the upstream refresh and Android test correction
+ described below.
+- 39 local regression tests passed: immutable SHA/tag/cache/CI baseline checks (5),
+ flavor/ICU contracts (5), Rust/libffi toolchain actions (5), embedded verdict
+ isolation (6), real shared-library symbol stripping (1), release control (17).
+- Six additional Android shard regressions passed, including exact coverage
+ of all 4,125 runnable cases in 16 shards of 257–258 tests, and a failing
+ test process retaining its nonzero exit status through logging and cleanup.
+ The actual Node status parser verifies watch-mode CLI exclusions on both
+ mobile platforms while retaining in-process debugger/filesystem tests and
+ leaving the two CLI tests runnable on desktop Linux.
+ HdrHistogram 0.12.0 compiled on Termux arm64 and passed recording,
+ percentile, bounds, extreme-value and four-thread atomic-recording probes.
+- Recipe ownership, YAML/shell/JavaScript syntax, all 219 curated test entries,
+ zero upstream workflows and the test-edit audit passed.
+- All ten full GYP graphs generated successfully: Android arm/arm64/x64 and
+ iOS arm64 device/simulator, each in full/lite. Config assertions confirmed
+ full ICU, the Temporal/FFI split and Android-only 64-bit lite pointer
+ compression. This is graph validation using Termux Clang and Rust version
+ stubs; it does not compile with the Linux NDK or Xcode toolchains.
+- Eleven new in-process Buffer, HTTP, crypto, stream and compression tests
+ were added to the curated device gate. The new subprocess exclusions
+ (11 parallel tests and one sequential test per platform) are inferred from
+ their code; the full device suite must confirm them. The renamed VFS load
+ exclusion was updated. Existing bench/negative-zero tests retain their
+ in-process assertions and skip only the child startup checks.
+
+[Build run 37410879823](https://github.com/fogtape/nodejs-mobile/actions/runs/37410879823)
+completed all ten Android/iOS full/lite compilations, both platforms' full/lite
+boot smokes, Android full/lite curated tests and iOS full curated tests
+successfully. Recipe reconstruction and per-patch configure checks also passed.
+
+The Android full-suite attempt was incomplete: shards 0, 1 and 2 received
+`The runner has received a shutdown signal` before completing or uploading
+their summaries. Shards 1 and 2 also exposed missing debugger CLI exclusions:
+`test-debugger-backtrace`, `test-debugger-exec` and
+`test-debugger-restart-message` launch `process.execPath` through
+`test/common/debugger.js`. In an Android app this starts `app_process`, whose
+child aborted with `Error changing dalvik-cache ownership : Permission denied`.
+The recipe now excludes those three tests and the same subprocess-dependent
+`test-debugger-low-level` and `test-debugger-repeat-last` on Android. The
+in-process `test-debugger-run-restart-init` and `test-debugger-wait-for-debugger`
+remain runnable. Node's actual status-file parser verified that distinction.
+
+These exclusions correct test applicability; they do not explain the runner
+shutdowns. The next [Build run 37441663775](https://github.com/fogtape/nodejs-mobile/actions/runs/37441663775)
+again passed all ten builds, boot smokes and curated gates. Android shards
+1, 2 and 3 then lost their runners with 514, 559 and 470 passed tests and
+zero failed tests; QEMU reported unresponsive threads in two of those logs.
+All four iOS full-suite shards passed. No new test exclusion is justified by
+the Android failures.
+
+The Android full suite now uses 16 deterministic shards, with four jobs at
+most running concurrently, to limit the number of app relaunches per emulator.
+APK assembly uses Gradle's `--no-daemon --max-workers=2`, so its JVM exits
+before the sweep. The AVD has explicit 2048 MB RAM / 256 MB Java heap settings.
+The shard wrapper records host/guest memory, disk and process statistics at
+startup, every minute and exit, in both the live job output and an artifact.
+Its pipefail exit status retains actual test failures. The integrity gate
+checks the workflow denominator and uses Node's actual test selector to verify
+that every runnable parallel/sequential test appears exactly once.
+
+The runner logs do not prove an out-of-memory cause. These changes reduce
+session length and resource contention and provide evidence for a future
+failure. A complete device-suite rerun is still required before merge.
+Final artifact size measurements are separate from the ICU data measurement.
+
+The [refreshed Build run 37473151347](https://github.com/fogtape/nodejs-mobile/actions/runs/37473151347)
+passed all ten builds, boot smokes and curated gates. All 16 Android shards
+finished without a runner shutdown; 14 passed, while shards 8 and 9 exposed
+two further CLI tests, `test-watch-mode-inspect` and `test-watch-mode`. Their
+independent child Node attempts failed with dalvik-cache permission errors
+and SIGABRT. Android now excludes those two specific tests. iOS already
+excluded `test-watch-mode`; `test-watch-mode-inspect` now has the same
+child-process exclusion because `NodeInstance` uses a separate executable.
+The simulator permitted that spawn, which a shipped iOS app cannot use.
+
+All four iOS shards passed their test sweeps. Shard 0's 1,015 passing tests
+were followed by `Failed to CreateArtifact: Unable to make request: ENOTFOUND`
+from the diagnostic log upload, causing the job and full-suite gate to fail.
+The log action now retries once after 15 seconds, using a distinct retry name
+to avoid a partial-upload collision. If both attempts fail, it emits a warning
+and a job-summary note; the live test log and preceding summary remain
+available. Only diagnostic log uploads are best effort: test execution and
+binary-artifact uploads remain required. These corrections require another
+complete device-suite run.
+
+## Move to the official release
+
+After upstream publishes `v26.11.0`, compare its commit against this pin,
+review any additional changes, update `upstream-base.txt` to the official tag,
+regenerate the recipe and checksum, and rerun the platform/device gates.
+The SHA baseline is deliberately rejected by Cut release and manual
+prerelease publication. This upgrade does not include a release-ready marker.
+Follow [RELEASING.md](RELEASING.md) after the official-tag upgrade is reviewed.
diff --git a/docs/VERSION-LINES.md b/docs/VERSION-LINES.md
index 4dbe019875..fd7da16b38 100644
--- a/docs/VERSION-LINES.md
+++ b/docs/VERSION-LINES.md
@@ -8,6 +8,11 @@ versioned together on that branch.
| --- | --- | --- | --- |
| `recipe` | Node.js 24.21.0 | Stable Node 24 maintenance and releases | Android and iOS, full and lite |
| `recipe-v26` | Node.js 26.10.0 | Independent Node 26 maintenance and prereleases | Android and iOS, full and lite |
+| `upgrade/node26.11-mobile-lite` | Node.js 26.11.0 proposal at a pinned SHA | Preview of upstream PR #66546 with the danmu lite profile | Android and iOS, full and lite |
+
+For the 26.11 preview, clone `upgrade/node26.11-mobile-lite`; it pins an immutable
+commit, and release preparation is disabled until the official release tag is
+reviewed and pinned. See [26.11 provenance](UPSTREAM-26.11.md).
The authoritative base is always `upstream-base.txt` on the selected branch.
Updating Node 26 does not change the Node 24 recipe or replace its published
@@ -42,7 +47,7 @@ partition and expected source-tree checksum.
The Node 26 line uses Android NDK r29 (`29.0.14206865`) and Clang 19 for
Linux host tools. Node 24 keeps its existing NDK r27d configuration.
-Node 26.10 full builds enable V8 Temporal and the experimental `node:ffi`
+This Node 26.11 preview’s full builds enable V8 Temporal and the experimental `node:ffi`
module on Android and iOS, with complete ICU data (`--with-intl=full-icu`)
for named time zones. small-ICU cannot load Temporal timezone information
in this upstream version. The build requires Rust **1.86.0** and its standard
@@ -89,7 +94,7 @@ run URL with the downloaded files; do not mix platform slices or flavors
from different runs. Versioned preview packages additionally contain
`BUILD-INFO.json` and are accompanied by `SHA256SUMS`.
-`process.version` reports `v26.10.0`; `process.versions.mobile` identifies
+This preview reports `process.version` as `v26.11.0`; `process.versions.mobile` identifies
the mobile build revision. A CI preview is not a published release. **Cut release** also supports
`recipe-v26`; its reviewed PR arms a specific version; after merge, manually run **Build**
with `operation=prerelease` and that exact mobile version. Push/PR builds do
diff --git a/expected-tree.txt b/expected-tree.txt
index 21b09459d8..1547f16ce6 100644
--- a/expected-tree.txt
+++ b/expected-tree.txt
@@ -14,4 +14,4 @@
# run prepare.sh (or regenerate-patches.py, which prints the hash) and
# update the value below in the same commit. If you forget, CI fails and
# prints the hash it got.
-cf1e359f446d47683db958b3f70a66ad52ecbb61
+366de32e0018c646ea07608448b694716229b35b
diff --git a/mobile-src/src/node_mobile_version.h b/mobile-src/src/node_mobile_version.h
index 4260606965..4dd80bfb8f 100644
--- a/mobile-src/src/node_mobile_version.h
+++ b/mobile-src/src/node_mobile_version.h
@@ -4,7 +4,7 @@
#include "node_version.h"
#define NODE_MOBILE_MAJOR_VERSION 26
-#define NODE_MOBILE_MINOR_VERSION 10
+#define NODE_MOBILE_MINOR_VERSION 11
#define NODE_MOBILE_PATCH_VERSION 0
// Mobile rebuild revision of the same upstream Node version: the -R in the
diff --git a/mobile-src/tools/mobile-test/android/prepare-android-test.sh b/mobile-src/tools/mobile-test/android/prepare-android-test.sh
index 121188c075..14c9b75f8c 100755
--- a/mobile-src/tools/mobile-test/android/prepare-android-test.sh
+++ b/mobile-src/tools/mobile-test/android/prepare-android-test.sh
@@ -23,8 +23,9 @@ unlink "$NODEJS_BASE_DIR/test/fixtures/wasi/subdir/loop2"
unlink "$NODEJS_BASE_DIR/test/fixtures/wasi/subdir/outside.txt"
set -e
-# Build the Android test app
-( cd "$TEST_APP_BASE_DIR" && ./gradlew assembleDebug )
+# Build once without retaining a Gradle JVM during the emulator test sweep.
+# The daemon can reserve 1.5 GB after APK assembly, alongside QEMU and the app.
+( cd "$TEST_APP_BASE_DIR" && ./gradlew --no-daemon --max-workers=2 assembleDebug )
# Copy the Android proxy to the target directory.
cp "$SCRIPT_BASE_DIR/node-android-proxy.sh" "$TEST_PROXY_TARGETDIR/node"
diff --git a/mobile-src/tools/mobile-test/android/run-suite-shard.sh b/mobile-src/tools/mobile-test/android/run-suite-shard.sh
new file mode 100755
index 0000000000..6057a83fec
--- /dev/null
+++ b/mobile-src/tools/mobile-test/android/run-suite-shard.sh
@@ -0,0 +1,54 @@
+#!/bin/bash
+# Keep the test verdict authoritative and publish runner resource diagnostics
+# while the suite is running: a runner shutdown cannot upload post-step logs.
+set -euo pipefail
+
+SHARD="${1:?usage: run-suite-shard.sh }"
+SHARDS="${2:?usage: run-suite-shard.sh }"
+if ! [[ "$SHARD" =~ ^[0-9]+$ && "$SHARDS" =~ ^[1-9][0-9]*$ ]] ||
+ (( SHARD >= SHARDS )); then
+ echo "invalid Android shard $SHARD/$SHARDS" >&2
+ exit 2
+fi
+
+ROOT="$(cd "$(dirname "$0")/../../.." && pwd)"
+cd "$ROOT"
+
+resource_snapshot() {
+ echo "Android shard $SHARD/$SHARDS resources at $(date -u +%FT%TZ)"
+ free -m || true
+ df -h . "${ANDROID_AVD_HOME:-${HOME}/.android/avd}" 2>/dev/null || true
+ ps -eo pid,comm,rss --sort=-rss | head -n 12 || true
+ for metric in /proc/pressure/memory /sys/fs/cgroup/memory.events; do
+ if [ -r "$metric" ]; then
+ echo "$metric"
+ cat "$metric" || true
+ fi
+ done
+ timeout 5 adb shell 'cat /proc/meminfo; df -h /data' 2>/dev/null || true
+}
+
+report_resources() {
+ resource_snapshot | tee -a shard-resources.log
+}
+
+MONITOR_PID=""
+cleanup() {
+ if [ -n "$MONITOR_PID" ]; then
+ # Stop both the loop and its pending sleep/adb query.
+ pkill -P "$MONITOR_PID" 2>/dev/null || true
+ kill "$MONITOR_PID" 2>/dev/null || true
+ wait "$MONITOR_PID" 2>/dev/null || true
+ fi
+ report_resources
+}
+trap cleanup EXIT
+
+report_resources
+( while sleep 60; do report_resources; done ) &
+MONITOR_PID=$!
+
+# pipefail preserves test.py's failure; metrics and tee cannot turn a failed
+# test into a successful job. Each deterministic shard runs once, no retries.
+./tools/test.py -j 1 --flaky-tests=skip --timeout=300 --arch android \
+ "--run=$SHARD,$SHARDS" parallel sequential 2>&1 | tee shard.log
diff --git a/mobile-src/tools/mobile-test/audit-test-edits.sh b/mobile-src/tools/mobile-test/audit-test-edits.sh
index 2924db7e25..0145caff43 100755
--- a/mobile-src/tools/mobile-test/audit-test-edits.sh
+++ b/mobile-src/tools/mobile-test/audit-test-edits.sh
@@ -18,6 +18,9 @@ SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
REPO_ROOT=$(git -C "${SCRIPT_DIR}" rev-parse --show-toplevel)
cd "${REPO_ROOT}"
+# prepare.sh records the exact upstream commit as local git metadata, so
+# unreleased PR snapshots can be audited without a fabricated release tag.
+# For older materializations, derive the tag from src/node_version.h.
# The upstream base is derived from the tree itself: src/node_version.h is
# upstream's own version header and is not patched by this project, so it
# states exactly which nodejs/node release this tree was built from. That
@@ -28,6 +31,9 @@ MA=$(grep -oE '#define NODE_MAJOR_VERSION [0-9]+' src/node_version.h | awk '{pri
MI=$(grep -oE '#define NODE_MINOR_VERSION [0-9]+' src/node_version.h | awk '{print $3}')
PA=$(grep -oE '#define NODE_PATCH_VERSION [0-9]+' src/node_version.h | awk '{print $3}')
BASE="v${MA}.${MI}.${PA}"
+if git rev-parse --verify --quiet 'refs/nodejs-mobile/upstream-base^{commit}' >/dev/null; then
+ BASE=refs/nodejs-mobile/upstream-base
+fi
if ! git rev-parse --verify --quiet "${BASE}^{commit}" >/dev/null; then
echo "error: upstream base '${BASE}' (from src/node_version.h) is not a commit here." >&2
echo " This audit needs the upstream tag present, as it is in a tree" >&2
diff --git a/mobile-src/tools/mobile-test/curated-device-tests.txt b/mobile-src/tools/mobile-test/curated-device-tests.txt
index 4a8b86bf63..f067800772 100644
--- a/mobile-src/tools/mobile-test/curated-device-tests.txt
+++ b/mobile-src/tools/mobile-test/curated-device-tests.txt
@@ -218,3 +218,16 @@ parallel/test-zlib-convenience-methods
parallel/test-zlib-destroy-pipe
parallel/test-zlib-flush-drain
parallel/test-zlib-invalid-arg-value-brotli-compress
+
+# Node 26.11 in-process buffer, HTTP, crypto, stream and compression regressions.
+parallel/test-buffer-islatin1
+parallel/test-buffer-stringlength
+parallel/test-http-is-valid-header
+parallel/test-http-parser-cached-callbacks
+parallel/test-http-server-raw-header-lookup
+parallel/test-crypto-randomfill-properties
+parallel/test-whatwg-readablestream-async-iterator-shape
+parallel/test-zlib-decode-strings
+parallel/test-zlib-reset-incomplete-output
+parallel/test-zlib-brotli-reset
+parallel/test-zlib-zstd-reset-incomplete-frame
diff --git a/patches/build-Android-iOS-dest-os-and-toolchain-configure.py.patch b/patches/build-Android-iOS-dest-os-and-toolchain-configure.py.patch
index ee3d17e9fa..d5358b0de8 100644
--- a/patches/build-Android-iOS-dest-os-and-toolchain-configure.py.patch
+++ b/patches/build-Android-iOS-dest-os-and-toolchain-configure.py.patch
@@ -144,10 +144,10 @@ index 5cea0393f4..c76ec94c8f 100644
- os.system("./configure --dest-cpu=" + DEST_CPU + " --dest-os=android --openssl-no-asm --cross-compiling")
+ sys.exit(os.system("./configure --dest-cpu=" + DEST_CPU + " --dest-os=android --openssl-no-asm --with-intl=" + intl + extra_flags + " --cross-compiling --shared") >> 8)
diff --git a/configure.py b/configure.py
-index 8b3332a461..fa5dc65bd1 100755
+index acda6a16c9..c44509883d 100755
--- a/configure.py
+++ b/configure.py
-@@ -1303,6 +1303,11 @@ parser.add_argument('-C',
+@@ -1304,6 +1304,11 @@ parser.add_argument('-C',
default=None,
help=argparse.SUPPRESS)
@@ -159,7 +159,7 @@ index 8b3332a461..fa5dc65bd1 100755
parser.add_argument('--clang-cl',
action='store',
dest='clang_cl',
-@@ -1899,6 +1904,9 @@ def configure_node_cctest_sources(o):
+@@ -1901,6 +1906,9 @@ def configure_node_cctest_sources(o):
SearchFiles('test/cctest', 'h')
def configure_node(o):
diff --git a/patches/build-mobile-Temporal-Rust-toolsets-and-libffi-iOS-c.patch b/patches/build-mobile-Temporal-Rust-toolsets-and-libffi-iOS-c.patch
index 7f6a8a4be3..3b7967d715 100644
--- a/patches/build-mobile-Temporal-Rust-toolsets-and-libffi-iOS-c.patch
+++ b/patches/build-mobile-Temporal-Rust-toolsets-and-libffi-iOS-c.patch
@@ -5,26 +5,29 @@ Subject: [PATCH] build: mobile Temporal Rust toolsets and libffi iOS callbacks
Separate native host tools from Android/iOS Rust archives and use libffi's
precompiled executable trampoline table for iOS callbacks. Full builds
-retain ICU and enable Temporal; lite keeps its existing no-ICU policy.
+retain complete ICU and enable Temporal; lite retains the danmu ICU profile
+and disables Temporal and FFI. Preserve upstream's Temporal zoneinfo generator.
Signed-off-by: fogtape
Assisted-by: Codex
---
- deps/crates/crates.gyp | 215 +++++++++++++-------------------
+ deps/crates/crates.gyp | 261 ++++++++++++--------------------
deps/libffi/generate-headers.py | 19 ++-
- deps/libffi/libffi.gyp | 70 ++++++++---
- 3 files changed, 153 insertions(+), 151 deletions(-)
+ deps/libffi/libffi.gyp | 70 ++++++---
+ 3 files changed, 166 insertions(+), 184 deletions(-)
diff --git a/deps/crates/crates.gyp b/deps/crates/crates.gyp
-index 0720ee6dcf..9ebf172222 100644
+index 216b0cff51..12c1718b6d 100644
--- a/deps/crates/crates.gyp
+++ b/deps/crates/crates.gyp
-@@ -1,129 +1,86 @@
+@@ -1,162 +1,99 @@
-{
- 'variables': {
- 'cargo%': 'cargo',
- 'cargo_vendor_dir': './vendor',
- 'temporal_capi_dir': 'temporal_capi-v0_2',
+- 'zoneinfo64_dir': 'zoneinfo64-v0_3',
+- 'temporal_zoneinfo64_data_output': '<(SHARED_INTERMEDIATE_DIR)/src/builtins/builtins-temporal-zoneinfo64-data.cc',
- 'cargo_rust_target%': '',
- },
- 'conditions': [
@@ -147,6 +150,37 @@ index 0720ee6dcf..9ebf172222 100644
- ],
- },
- },
+- {
+- # Bakes zoneinfo64.res into a C++ source for Temporal when ICU is not
+- # available to load it at runtime.
+- 'target_name': 'temporal_zoneinfo64_data',
+- 'type': 'none',
+- 'toolsets': ['host', 'target'],
+- 'hard_dependency': 1,
+- 'direct_dependent_settings': {
+- 'sources': [
+- '<(temporal_zoneinfo64_data_output)',
+- ],
+- },
+- 'actions': [
+- {
+- 'action_name': 'make_temporal_zoneinfo_cpp',
+- 'inputs': [
+- '../v8/tools/include-file-as-bytes.py',
+- '<(cargo_vendor_dir)/<(zoneinfo64_dir)/src/data/zoneinfo64.res',
+- ],
+- 'outputs': [
+- '<(temporal_zoneinfo64_data_output)',
+- ],
+- 'action': [
+- '<(python)',
+- '<@(_inputs)',
+- '<@(_outputs)',
+- 'zoneinfo64_static_data',
+- ],
+- },
+- ],
+- },
- ]
-}
+# Mobile Rust actions select their triple per toolset; desktop builds retain
@@ -154,12 +188,13 @@ index 0720ee6dcf..9ebf172222 100644
+{'variables': {'cargo%': 'cargo',
+ 'cargo_vendor_dir': './vendor',
+ 'temporal_capi_dir': 'temporal_capi-v0_2',
-+ 'cargo_rust_target%': ''},
++ 'cargo_rust_target%': '',
++ 'zoneinfo64_dir': 'zoneinfo64-v0_3',
++ 'temporal_zoneinfo64_data_output': '<(SHARED_INTERMEDIATE_DIR)/src/builtins/builtins-temporal-zoneinfo64-data.cc'},
+ 'conditions': [['build_type == "Release"',
+ {'variables': {'cargo_build_flags': ['--release']},
+ 'conditions': [['cargo_rust_target!=""',
-+ {'variables': {'cargo_build_flags': ['--target',
-+ '<(cargo_rust_target)']}}],
++ {'variables': {'cargo_build_flags': ['--target', '<(cargo_rust_target)']}}],
+ ['OS=="win"',
+ {'variables': {'node_crates_libpath': '<(SHARED_INTERMEDIATE_DIR)/$(Platform)/release/node_crates.lib'}},
+ {'variables': {'node_crates_libpath': '<(SHARED_INTERMEDIATE_DIR)/<(cargo_rust_target)/release/<(STATIC_LIB_PREFIX)node_crates<(STATIC_LIB_SUFFIX)'}}]]},
@@ -173,8 +208,7 @@ index 0720ee6dcf..9ebf172222 100644
+ 'hard_dependency': 1,
+ 'sources': ['Cargo.toml', 'Cargo.lock', 'src/lib.rs'],
+ 'link_settings': {'libraries': ['<(node_crates_libpath)'],
-+ 'conditions': [['OS=="win"',
-+ {'libraries': ['-lntdll', '-luserenv']}]]},
++ 'conditions': [['OS=="win"', {'libraries': ['-lntdll', '-luserenv']}]]},
+ 'conditions': [['OS in "android ios"',
+ {'link_settings': {'libraries!': ['<(node_crates_libpath)'],
+ 'libraries': ['<(SHARED_INTERMEDIATE_DIR)/mobile-rust/<(_toolset)/libnode_crates.a'],
@@ -234,7 +268,20 @@ index 0720ee6dcf..9ebf172222 100644
+ 'toolsets': ['host', 'target'],
+ 'sources': [],
+ 'dependencies': ['node_crates'],
-+ 'direct_dependent_settings': {'include_dirs': ['<(cargo_vendor_dir)/<(temporal_capi_dir)/bindings/cpp']}}]}
++ 'direct_dependent_settings': {'include_dirs': ['<(cargo_vendor_dir)/<(temporal_capi_dir)/bindings/cpp']}},
++ {'target_name': 'temporal_zoneinfo64_data',
++ 'type': 'none',
++ 'toolsets': ['host', 'target'],
++ 'hard_dependency': 1,
++ 'direct_dependent_settings': {'sources': ['<(temporal_zoneinfo64_data_output)']},
++ 'actions': [{'action_name': 'make_temporal_zoneinfo_cpp',
++ 'inputs': ['../v8/tools/include-file-as-bytes.py',
++ '<(cargo_vendor_dir)/<(zoneinfo64_dir)/src/data/zoneinfo64.res'],
++ 'outputs': ['<(temporal_zoneinfo64_data_output)'],
++ 'action': ['<(python)',
++ '<@(_inputs)',
++ '<@(_outputs)',
++ 'zoneinfo64_static_data']}]}]}
diff --git a/deps/libffi/generate-headers.py b/deps/libffi/generate-headers.py
index fb58edf17b..5369c1e194 100644
--- a/deps/libffi/generate-headers.py
diff --git a/patches/build-mobile-platform-settings-in-common.gypi.patch b/patches/build-mobile-platform-settings-in-common.gypi.patch
index cddaa197ca..6e9e0f6483 100644
--- a/patches/build-mobile-platform-settings-in-common.gypi.patch
+++ b/patches/build-mobile-platform-settings-in-common.gypi.patch
@@ -23,10 +23,10 @@ Co-authored-by: João Matos
1 file changed, 118 insertions(+), 7 deletions(-)
diff --git a/common.gypi b/common.gypi
-index 71712d3084..a38421b5a5 100644
+index de80a68f49..fd3394b5cb 100644
--- a/common.gypi
+++ b/common.gypi
-@@ -110,7 +110,7 @@
+@@ -111,7 +111,7 @@
'obj_dir%': '<(PRODUCT_DIR)/obj.target',
'v8_base': '<(PRODUCT_DIR)/obj.target/tools/v8_gypfiles/libv8_snapshot.a',
}],
@@ -35,7 +35,7 @@ index 71712d3084..a38421b5a5 100644
'obj_dir%': '<(PRODUCT_DIR)/obj.target',
'v8_base': '<(PRODUCT_DIR)/libv8_snapshot.a',
}],
-@@ -221,6 +221,23 @@
+@@ -219,11 +219,28 @@
}],
],
}],
@@ -59,34 +59,31 @@ index 71712d3084..a38421b5a5 100644
['OS=="solaris"', {
# pull in V8's postmortem metadata
'ldflags': [ '-Wl,-z,allextract' ]
-@@ -229,7 +246,7 @@
- # increase performance, number from experimentation
- 'cflags': [ '-qINLINE=::150:100000' ]
}],
-- ['OS!="mac" and OS!="win" and OS!="zos"', {
-+ ['OS!="mac" and OS!="ios" and OS!="win" and OS!="zos"', {
+- ['OS!="mac" and OS!="win"', {
++ ['OS!="mac" and OS!="ios" and OS!="win"', {
# -fno-omit-frame-pointer is necessary for the --perf_basic_prof
# flag to work correctly. perf(1) gets confused about JS stack
# frames otherwise, even with --call-graph dwarf.
-@@ -455,7 +472,7 @@
+@@ -477,7 +494,7 @@
[ 'target_arch=="arm64"', {
'msvs_configuration_platform': 'arm64',
}],
-- ['asan == 1 and OS != "mac" and OS != "zos"', {
-+ ['asan == 1 and OS != "mac" and OS != "ios" and OS != "zos"', {
+- ['asan == 1 and OS != "mac"', {
++ ['asan == 1 and OS != "mac" and OS != "ios"', {
'cflags+': [
'-fno-omit-frame-pointer',
'-fsanitize=address',
-@@ -483,7 +500,7 @@
+@@ -505,7 +522,7 @@
}],
],
}],
-- ['ubsan == 1 and OS != "mac" and OS != "zos"', {
-+ ['ubsan == 1 and OS != "mac" and OS != "ios" and OS != "zos"', {
+- ['ubsan == 1 and OS != "mac"', {
++ ['ubsan == 1 and OS != "mac" and OS != "ios"', {
'cflags+': [
'-fno-omit-frame-pointer',
'-fsanitize=undefined',
-@@ -492,7 +509,7 @@
+@@ -514,7 +531,7 @@
'cflags!': [ '-fno-omit-frame-pointer' ],
'ldflags': [ '-fsanitize=undefined' ],
}],
@@ -95,7 +92,7 @@ index 71712d3084..a38421b5a5 100644
'xcode_settings': {
'OTHER_CFLAGS+': [
'-fno-omit-frame-pointer',
-@@ -591,11 +608,13 @@
+@@ -616,11 +633,13 @@
}],
['_toolset=="host"', {
'conditions': [
@@ -111,7 +108,7 @@ index 71712d3084..a38421b5a5 100644
'cflags': [ '-m64' ],
'ldflags': [ '-m64' ],
}],
-@@ -777,6 +796,98 @@
+@@ -802,6 +821,98 @@
}],
],
}],
diff --git a/patches/build-mobile-targets-in-node.gyp-node.gypi.patch b/patches/build-mobile-targets-in-node.gyp-node.gypi.patch
index 51c4f1854c..2f72b2f2e5 100644
--- a/patches/build-mobile-targets-in-node.gyp-node.gypi.patch
+++ b/patches/build-mobile-targets-in-node.gyp-node.gypi.patch
@@ -27,7 +27,7 @@ Assisted-by: Codex
2 files changed, 33 insertions(+), 7 deletions(-)
diff --git a/node.gyp b/node.gyp
-index 2cf1e62b56..010117fb97 100644
+index 587df5cace..c1d6e6735f 100644
--- a/node.gyp
+++ b/node.gyp
@@ -77,6 +77,11 @@
@@ -42,7 +42,7 @@ index 2cf1e62b56..010117fb97 100644
'<@(node_builtin_shareable_builtins)',
],
'node_sources': [
-@@ -583,6 +588,11 @@
+@@ -580,6 +585,11 @@
['OS=="linux" and clang==1', {
'libraries': ['-latomic'],
}],
@@ -54,7 +54,7 @@ index 2cf1e62b56..010117fb97 100644
],
},
-@@ -1434,8 +1444,9 @@
+@@ -1427,8 +1437,9 @@
['OS=="solaris"', {
'ldflags': [ '-I<(SHARED_INTERMEDIATE_DIR)' ]
}],
@@ -66,7 +66,7 @@ index 2cf1e62b56..010117fb97 100644
'type': 'none',
}],
[ 'node_shared=="true"', {
-@@ -1659,7 +1670,8 @@
+@@ -1652,7 +1663,8 @@
[ 'node_shared_libuv=="false"', {
'dependencies': [ 'deps/uv/uv.gyp:libuv#host' ],
}],
@@ -77,7 +77,7 @@ index 2cf1e62b56..010117fb97 100644
}],
[ 'debug_node=="true"', {
diff --git a/node.gypi b/node.gypi
-index b382784e61..75d619dbeb 100644
+index 9eb972adc4..67d8100fa5 100644
--- a/node.gypi
+++ b/node.gypi
@@ -280,6 +280,11 @@
@@ -120,9 +120,9 @@ index b382784e61..75d619dbeb 100644
# -force_load or --whole-archive are not applicable for
# the static library
[ 'force_load=="true"', {
-@@ -487,5 +496,10 @@
- }, {
- 'defines': [ 'HAVE_QUIC=0' ]
+@@ -484,5 +493,10 @@
+ }],
+ ],
}],
+ [ 'OS=="android" or OS=="ios"', {
+ 'defines': [
diff --git a/patches/build-v8-mobile-host-target-settings-in-v8-gypfiles.patch b/patches/build-v8-mobile-host-target-settings-in-v8-gypfiles.patch
index 1f9a9ab2eb..ff2436e85f 100644
--- a/patches/build-v8-mobile-host-target-settings-in-v8-gypfiles.patch
+++ b/patches/build-v8-mobile-host-target-settings-in-v8-gypfiles.patch
@@ -46,7 +46,7 @@ index 3676848df8..61af0a4044 100644
'configurations': {
'Debug': {
diff --git a/tools/v8_gypfiles/v8.gyp b/tools/v8_gypfiles/v8.gyp
-index 093be6e539..664542b0d6 100644
+index fd8394b370..caba744b0a 100644
--- a/tools/v8_gypfiles/v8.gyp
+++ b/tools/v8_gypfiles/v8.gyp
@@ -906,6 +906,15 @@
@@ -112,7 +112,7 @@ index 093be6e539..664542b0d6 100644
['v8_enable_turbofan==1', {
'dependencies': ['v8_compiler_sources'],
}, {
-@@ -1263,7 +1274,7 @@
+@@ -1268,7 +1279,7 @@
'conditions': [
['v8_enable_webassembly==1', {
'conditions': [
@@ -121,7 +121,7 @@ index 093be6e539..664542b0d6 100644
'sources': [
'<(V8_ROOT)/src/trap-handler/handler-inside-posix.cc',
'<(V8_ROOT)/src/trap-handler/handler-outside-posix.cc',
-@@ -1406,7 +1417,8 @@
+@@ -1411,7 +1422,8 @@
# Platforms that don't have Compare-And-Swap (CAS) support need to link atomic library
# to implement atomic memory access.
# Clang needs it for some atomic operations (https://clang.llvm.org/docs/Toolchain.html#atomics-library).
@@ -131,7 +131,7 @@ index 093be6e539..664542b0d6 100644
'link_settings': {
'libraries': ['-latomic', ],
},
-@@ -1560,6 +1572,8 @@
+@@ -1565,6 +1577,8 @@
'<(V8_ROOT)/src/base/platform/platform-posix.h',
'<(V8_ROOT)/src/base/platform/platform-posix-time.cc',
'<(V8_ROOT)/src/base/platform/platform-posix-time.h',
@@ -140,7 +140,7 @@ index 093be6e539..664542b0d6 100644
],
'link_settings': {
'target_conditions': [
-@@ -1572,9 +1586,18 @@
+@@ -1577,9 +1591,18 @@
},
'target_conditions': [
['_toolset=="host"', {
@@ -162,7 +162,7 @@ index 093be6e539..664542b0d6 100644
],
}, {
'sources': [
-@@ -2183,12 +2206,14 @@
+@@ -2188,12 +2211,14 @@
],
}, { # 'OS!="win"'
'conditions': [
@@ -179,7 +179,7 @@ index 093be6e539..664542b0d6 100644
'sources': [
'<(V8_ROOT)/src/heap/base/asm/ia32/push_registers_asm.cc',
],
-@@ -2406,6 +2431,46 @@
+@@ -2411,6 +2436,46 @@
},
}, # postmortem-metadata
@@ -226,7 +226,7 @@ index 093be6e539..664542b0d6 100644
{
'target_name': 'v8_zlib',
'type': 'static_library',
-@@ -2420,6 +2485,10 @@
+@@ -2425,6 +2490,10 @@
}]
]
}],
diff --git a/patches/ci-remove-upstream-only-workflows-and-config.patch b/patches/ci-remove-upstream-only-workflows-and-config.patch
index 87b5d00790..b30c929ea4 100644
--- a/patches/ci-remove-upstream-only-workflows-and-config.patch
+++ b/patches/ci-remove-upstream-only-workflows-and-config.patch
@@ -22,12 +22,12 @@ is read only from the default branch and is left untouched.
---
.github/ISSUE_TEMPLATE/config.yml | 14 -
.github/SUPPORT.md | 27 --
- .github/label-pr-config.yml | 224 -----------
+ .github/label-pr-config.yml | 236 -----------
.github/workflows/author-ready-conflicts.yml | 48 ---
- .github/workflows/auto-start-ci.yml | 80 ----
- .github/workflows/benchmark.yml | 279 --------------
- .github/workflows/build-shared.yml | 96 -----
- .github/workflows/build-tarball.yml | 132 -------
+ .github/workflows/auto-start-ci.yml | 93 -----
+ .github/workflows/benchmark.yml | 271 -------------
+ .github/workflows/build-shared.yml | 93 -----
+ .github/workflows/build-tarball.yml | 137 -------
.github/workflows/close-stalled.yml | 36 --
.github/workflows/codeql.yml | 44 ---
.github/workflows/comment-labeled.yml | 57 ---
@@ -36,7 +36,7 @@ is read only from the default branch and is left untouched.
.github/workflows/contributor-guidance.yml | 141 -------
.../workflows/coverage-linux-without-intl.yml | 90 -----
.github/workflows/coverage-linux.yml | 90 -----
- .github/workflows/coverage-windows.yml | 106 ------
+ .github/workflows/coverage-windows.yml | 106 -----
.github/workflows/create-release-proposal.yml | 81 ----
.github/workflows/daily-wpt-fyi.yml | 163 --------
.github/workflows/daily.yml | 30 --
@@ -47,11 +47,11 @@ is read only from the default branch and is left untouched.
.github/workflows/label-pr.yml | 18 -
.github/workflows/license-builder.yml | 38 --
.github/workflows/lint-release-proposal.yml | 119 ------
- .github/workflows/linters.yml | 255 -------------
+ .github/workflows/linters.yml | 278 -------------
.github/workflows/major-release.yml | 49 ---
.github/workflows/nix-changes-comment.yml | 98 -----
- .github/workflows/nix-changes.yml | 122 ------
- .github/workflows/notify-on-push.yml | 64 ----
+ .github/workflows/nix-changes.yml | 128 ------
+ .github/workflows/notify-on-push.yml | 64 ---
.github/workflows/notify-on-review-wanted.yml | 45 ---
.github/workflows/post-release.yml | 33 --
.../remark-lint-problem-matcher.json | 22 --
@@ -59,17 +59,17 @@ is read only from the default branch and is left untouched.
.github/workflows/stale.yml | 51 ---
.github/workflows/stress-test.yml | 114 ------
.github/workflows/test-internet.yml | 70 ----
- .github/workflows/test-linux-perfetto.yml | 70 ----
+ .github/workflows/test-linux-perfetto.yml | 73 ----
.github/workflows/test-linux-quic.yml | 80 ----
.github/workflows/test-linux.yml | 100 -----
.github/workflows/test-macos.yml | 142 -------
- .github/workflows/test-shared.yml | 274 --------------
+ .github/workflows/test-shared.yml | 273 -------------
.github/workflows/timezone-update.yml | 74 ----
- .github/workflows/tools.yml | 355 ------------------
- .github/workflows/update-openssl.yml | 61 ---
+ .github/workflows/tools.yml | 365 ------------------
+ .github/workflows/update-openssl.yml | 69 ----
.github/workflows/update-v8.yml | 57 ---
.github/workflows/update-wpt.yml | 95 -----
- 49 files changed, 4690 deletions(-)
+ 49 files changed, 4758 deletions(-)
delete mode 100644 .github/ISSUE_TEMPLATE/config.yml
delete mode 100644 .github/SUPPORT.md
delete mode 100644 .github/label-pr-config.yml
@@ -175,10 +175,10 @@ index 4613512bf2..0000000000
-your expectations.
diff --git a/.github/label-pr-config.yml b/.github/label-pr-config.yml
deleted file mode 100644
-index 8b36066473..0000000000
+index f30b6b8a5e..0000000000
--- a/.github/label-pr-config.yml
+++ /dev/null
-@@ -1,224 +0,0 @@
+@@ -1,236 +0,0 @@
-## Order of entries in this map *does* matter for the resolved labels
-## earlier entries override later entries
-subSystemLabels:
@@ -196,6 +196,7 @@ index 8b36066473..0000000000
- /^src\/uv\./: c++, libuv
- /^src\/(?:connect(?:ion)?|pipe|tcp)_/: c++, net
- /^src\/node_os/: c++, os
+- /^src\/permission\//: c++, permission
- /^src\/(?:node_main|signal_)/: c++, process
- /^src\/timer[_s]/: c++, timers
- /^src\/node_root_certs/: c++, tls
@@ -283,6 +284,7 @@ index 8b36066473..0000000000
- /^lib\/test.js$/: test_runner
- /^lib\/v8.js$/: v8 module
- /^lib\/internal\/url\.js$/: whatwg-url
+- /^lib\/internal\/process\/permission\.js$/: permission
- /^lib\/internal\/modules\/esm/: esm
- /^lib\/internal\/modules/: module
- /^lib\/internal\/source_map/: source maps
@@ -297,6 +299,13 @@ index 8b36066473..0000000000
-
-exlusiveLabels:
- # More specific tests
+- /^test\/(?:parallel|sequential)\/test-(?:[^/]+-)?permission(?:-|\.)/: test, permission
+- /^test\/ffi\/test-ffi-permissions?(?:-|\.)/: test, permission
+- /^test\/addons\/no-addons\/permission\.js$/: test, addons, permission
+- /^test\/es-module\/test-[^/]*-permission(?:-|\.)/: test, esm, permission
+- /^test\/module-hooks\/test-[^/]*-permission(?:-|\.)/: test, module, loaders, permission
+- /^test\/fixtures\/permission\//: test, permission
+- /^test\/fixtures\/rc\/permission-namespace\.json$/: test, permission
- /^test\/addons\//: test, addons
- /^test\/debugger/: test, debugger
- /^test\/doctool\//: test, doc, tools
@@ -331,6 +340,7 @@ index 8b36066473..0000000000
- # test_runner documentation
- /^doc\/api\/test.md$/: doc, test_runner
- # Automatically tag JS subsystem-specific API doc changes
+- /^doc\/api\/permissions\.md$/: doc, permission
- /^doc\/api\/(\w+)\.md$/: doc, $1
- # Add deprecations label to PRs that affect doc/api/deprecations.md
- /^doc\/api\/deprecations.md$/: doc, deprecations
@@ -343,6 +353,7 @@ index 8b36066473..0000000000
- /^doc\//: doc
-
- # More specific benchmarks
+- /^benchmark\/fs\/readfile-permission-enabled\.js$/: benchmark, fs, permission
- /^benchmark\/buffers\//: benchmark, buffer
- /^benchmark\/es\//: benchmark, v8 engine
- /^benchmark\/_http/: benchmark, http
@@ -379,6 +390,7 @@ index 8b36066473..0000000000
- - os
- - path
- - perf_hooks
+- - permission
- - process
- - punycode
- - querystring
@@ -459,13 +471,16 @@ index f447c6b80a..0000000000
- done
diff --git a/.github/workflows/auto-start-ci.yml b/.github/workflows/auto-start-ci.yml
deleted file mode 100644
-index dc827d62ba..0000000000
+index 85f3e92f2f..0000000000
--- a/.github/workflows/auto-start-ci.yml
+++ /dev/null
-@@ -1,80 +0,0 @@
+@@ -1,93 +0,0 @@
-# This action uses the following secrets:
-# JENKINS_USER: GitHub user whose Jenkins token is defined below
--# JENKINS_TOKEN: Jenkins token, to be used to start CI
+-# JENKINS_TOKEN: Jenkins token, to be used to start or resume CI
+-# Optional repository variables:
+-# AUTO_START_CI_BATCH_SIZE: maximum PRs to process per run (default: 5)
+-# AUTO_START_CI_MAX_WORKLOAD: pause at this many running or queued PR jobs (default: 15)
-name: Auto Start CI
-
-on:
@@ -495,16 +510,25 @@ index dc827d62ba..0000000000
- steps:
- - name: Get Pull Requests
- id: get_prs_for_ci
-- run: >
-- echo "numbers=$(gh pr list \
+- # Explicit bash enables pipefail so jq cannot hide a failed gh query.
+- shell: bash
+- run: |
+- if ! [[ "$BATCH_SIZE" =~ ^[1-9][0-9]*$ ]]; then
+- echo '::error::AUTO_START_CI_BATCH_SIZE must be a strictly positive integer'
+- exit 1
+- fi
+- # Filter before selecting the batch; UNKNOWN mergeability is retried later.
+- numbers=$(gh pr list \
- --repo "$GITHUB_REPOSITORY" \
-- --label 'request-ci' \
-- --json 'number' \
-- --search 'review:approved' \
-- -t '{{ range . }}{{ .number }} {{ end }}' \
-- --limit 5)" >> "$GITHUB_OUTPUT"
+- --json 'number,mergeable' \
+- --search 'review:approved label:request-ci,resume-ci' \
+- --limit 100 \
+- | jq --argjson limit "$BATCH_SIZE" \
+- '[.[] | select(.mergeable == "MERGEABLE") | .number] | .[:$limit] | map(tostring) | join(" ")' -r)
+- echo "numbers=$numbers" >> "$GITHUB_OUTPUT"
- env:
- GH_TOKEN: ${{ github.token }}
+- BATCH_SIZE: ${{ vars.AUTO_START_CI_BATCH_SIZE || '5' }}
- start-ci:
- permissions:
- checks: read
@@ -535,7 +559,7 @@ index dc827d62ba..0000000000
- GH_TOKEN: ${{ github.token }}
- JENKINS_TOKEN: ${{ secrets.JENKINS_TOKEN }}
-
-- - name: Start the CI
+- - name: Start or resume the CI
- run: |
- read -r -a numbers <<< "$PULL_REQUESTS"
- curl -fsSL "https://github.com/${GITHUB_REPOSITORY}/raw/${GITHUB_SHA}/tools/actions/start-ci.sh" \
@@ -543,12 +567,13 @@ index dc827d62ba..0000000000
- env:
- GH_TOKEN: ${{ github.token }}
- PULL_REQUESTS: ${{ needs.get-prs-for-ci.outputs.numbers }}
+- MAX_WORKLOAD: ${{ vars.AUTO_START_CI_MAX_WORKLOAD || '15' }}
diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml
deleted file mode 100644
-index 92bab7856b..0000000000
+index 0e901de78d..0000000000
--- a/.github/workflows/benchmark.yml
+++ /dev/null
-@@ -1,279 +0,0 @@
+@@ -1,271 +0,0 @@
-name: Benchmark
-
-on:
@@ -672,15 +697,11 @@ index 92bab7856b..0000000000
- --pure --keep TAR_DIR --keep FLAKY_TESTS \
- --keep SCCACHE_GHA_ENABLED --keep SCCACHE_GHA_RW_MODE \
- --keep ACTIONS_CACHE_SERVICE_V2 --keep ACTIONS_RESULTS_URL --keep ACTIONS_RUNTIME_TOKEN \
-- --arg useSeparateDerivationForV8 true \
- --arg withPerfetto ${{ matrix.perfetto || false }} \
-- --arg loadJSBuiltinsDynamically false \
- --arg ccache '(import {}).sccache' \
-- --arg devTools '[]' \
-- --arg benchmarkTools '[]' \
- --run '
- make build-ci -j4 V=1 && sccache --stop-server
-- '
+- ' tools/nix/ci-shell.nix
- mv out/Release/node base_node
-
- - name: Checkout the merge commit
@@ -692,15 +713,11 @@ index 92bab7856b..0000000000
- nix-shell \
- -I nixpkgs=./tools/nix/pkgs.nix \
- --pure --keep SCCACHE_LOCAL_RW_MODE \
-- --arg useSeparateDerivationForV8 true \
- --arg withPerfetto ${{ matrix.perfetto || false }} \
-- --arg loadJSBuiltinsDynamically false \
- --arg ccache '(import {}).sccache' \
-- --arg devTools '[]' \
-- --arg benchmarkTools '[]' \
- --run '
- make build-ci -j4 V=1
-- '
+- ' tools/nix/ci-shell.nix
- env:
- SCCACHE_LOCAL_RW_MODE: READ_ONLY
-
@@ -783,7 +800,7 @@ index 92bab7856b..0000000000
- nix-shell \
- -I nixpkgs=./tools/nix/pkgs.nix \
- --pure \
-- -E '(import {}).mkShell { buildInputs = import ./tools/nix/benchmarkTools.nix { withHttpBenchmarkDeps = false; }; }' \
+- -E '(import {}).mkShellNoCC { buildInputs = import ./tools/nix/benchmarkTools.nix { withHttpBenchmarkDeps = false; }; }' \
- --run '
- export LC_ALL=C.UTF-8
- echo "> [!WARNING] "
@@ -830,10 +847,10 @@ index 92bab7856b..0000000000
- PR_ID: ${{ inputs.pr_id }}
diff --git a/.github/workflows/build-shared.yml b/.github/workflows/build-shared.yml
deleted file mode 100644
-index 7d77c13e56..0000000000
+index 73f7ec6afb..0000000000
--- a/.github/workflows/build-shared.yml
+++ /dev/null
-@@ -1,96 +0,0 @@
+@@ -1,93 +0,0 @@
-name: Build Node.js (shared libraries)
-
-on:
@@ -921,21 +938,18 @@ index 7d77c13e56..0000000000
- -I "nixpkgs=$TAR_DIR/tools/nix/pkgs.nix" \
- --pure --keep TAR_DIR --keep FLAKY_TESTS \
- --keep SCCACHE_GHA_ENABLED --keep ACTIONS_CACHE_SERVICE_V2 --keep ACTIONS_RESULTS_URL --keep ACTIONS_RUNTIME_TOKEN \
-- --arg loadJSBuiltinsDynamically false \
- --arg ccache "${NIX_SCCACHE:-null}" \
-- --arg devTools '[]' \
-- --arg benchmarkTools '[]' \
- --arg pkcs11 ${{ inputs.pkcs11-store-test }} \
- ${{ inputs.extra-nix-flags }} \
- --run '
- make -C "$TAR_DIR" run-ci -j4 V=1 TEST_CI_ARGS="-p actions --measure-flakiness 9 --skip-tests=$CI_SKIP_TESTS"
-- ' "$TAR_DIR/shell.nix"
+- ' "$TAR_DIR/tools/nix/ci-shell.nix"
diff --git a/.github/workflows/build-tarball.yml b/.github/workflows/build-tarball.yml
deleted file mode 100644
-index 045bd3ae93..0000000000
+index 53e625ef88..0000000000
--- a/.github/workflows/build-tarball.yml
+++ /dev/null
-@@ -1,132 +0,0 @@
+@@ -1,137 +0,0 @@
-name: Build from tarball
-
-on:
@@ -1005,6 +1019,7 @@ index 045bd3ae93..0000000000
- PYTHON_VERSION: '3.14'
- FLAKY_TESTS: keep_retrying
- CLANG_VERSION: '19'
+- NODE_VERSION: lts/*
-
-permissions:
- contents: read
@@ -1022,12 +1037,16 @@ index 045bd3ae93..0000000000
- with:
- python-version: ${{ env.PYTHON_VERSION }}
- allow-prereleases: true
+- - name: Use Node.js ${{ env.NODE_VERSION }}
+- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+- with:
+- node-version: ${{ env.NODE_VERSION }}
- - name: Make tarball
- run: |
- export DISTTYPE=nightly
- export DATESTRING=$(date "+%Y-%m-%d")
- export COMMIT=$(git rev-parse --short=10 "$GITHUB_SHA")
-- ./configure && make tar -j4 SKIP_XZ=1
+- ./configure && make tar -j4 SKIP_XZ=1 GZIP_COMPRESSION=6
- - name: Upload tarball artifact
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
- with:
@@ -1112,7 +1131,7 @@ index 3c7ab04339..0000000000
- days-before-stale: -1
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
deleted file mode 100644
-index 1b3179ef4a..0000000000
+index d8eca1f2b7..0000000000
--- a/.github/workflows/codeql.yml
+++ /dev/null
@@ -1,44 +0,0 @@
@@ -1148,16 +1167,16 @@ index 1b3179ef4a..0000000000
-
- # Initializes the CodeQL tools for scanning.
- - name: Initialize CodeQL
-- uses: github/codeql-action/init@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
+- uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
- with:
- languages: ${{ matrix.language }}
- config-file: ./.github/codeql-config.yml
-
- - name: Autobuild
-- uses: github/codeql-action/autobuild@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
+- uses: github/codeql-action/autobuild@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
-
- - name: Perform CodeQL Analysis
-- uses: github/codeql-action/analyze@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
+- uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
- with:
- category: /language:${{matrix.language}}
diff --git a/.github/workflows/comment-labeled.yml b/.github/workflows/comment-labeled.yml
@@ -1643,7 +1662,7 @@ index 4035ea9320..0000000000
- gh pr comment "$NUMBER" --repo "$GITHUB_REPOSITORY" --body-file -
diff --git a/.github/workflows/coverage-linux-without-intl.yml b/.github/workflows/coverage-linux-without-intl.yml
deleted file mode 100644
-index a98492ec66..0000000000
+index 9b8f8e2fd8..0000000000
--- a/.github/workflows/coverage-linux-without-intl.yml
+++ /dev/null
@@ -1,90 +0,0 @@
@@ -1734,12 +1753,12 @@ index a98492ec66..0000000000
- - name: Clean tmp
- run: rm -rf coverage/tmp && rm -rf out
- - name: Upload
-- uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
+- uses: codecov/codecov-action@0b35c9ecc4f0529d0eb674914510c22f85b196b4 # v7.1.0
- with:
- directory: ./coverage
diff --git a/.github/workflows/coverage-linux.yml b/.github/workflows/coverage-linux.yml
deleted file mode 100644
-index 4ba373b790..0000000000
+index 0c7fc292d3..0000000000
--- a/.github/workflows/coverage-linux.yml
+++ /dev/null
@@ -1,90 +0,0 @@
@@ -1830,12 +1849,12 @@ index 4ba373b790..0000000000
- - name: Clean tmp
- run: rm -rf coverage/tmp && rm -rf out
- - name: Upload
-- uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
+- uses: codecov/codecov-action@0b35c9ecc4f0529d0eb674914510c22f85b196b4 # v7.1.0
- with:
- directory: ./coverage
diff --git a/.github/workflows/coverage-windows.yml b/.github/workflows/coverage-windows.yml
deleted file mode 100644
-index 5da225b171..0000000000
+index a4db4ccc08..0000000000
--- a/.github/workflows/coverage-windows.yml
+++ /dev/null
@@ -1,106 +0,0 @@
@@ -1942,7 +1961,7 @@ index 5da225b171..0000000000
- - name: Clean tmp
- run: npx rimraf ./coverage/tmp
- - name: Upload
-- uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
+- uses: codecov/codecov-action@0b35c9ecc4f0529d0eb674914510c22f85b196b4 # v7.1.0
- with:
- directory: ./coverage
diff --git a/.github/workflows/create-release-proposal.yml b/.github/workflows/create-release-proposal.yml
@@ -2662,10 +2681,10 @@ index 027aee336a..0000000000
- GH_TOKEN: ${{ github.token }}
diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml
deleted file mode 100644
-index 848fffc117..0000000000
+index 9afdb2ef5a..0000000000
--- a/.github/workflows/linters.yml
+++ /dev/null
-@@ -1,255 +0,0 @@
+@@ -1,278 +0,0 @@
-name: Linters
-
-on:
@@ -2709,6 +2728,12 @@ index 848fffc117..0000000000
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- with:
- persist-credentials: false
+- # Skip large vendored trees that lint-cpp does not use.
+- sparse-checkout: |
+- /*
+- !/deps/openssl/
+- !/deps/v8/
+- sparse-checkout-cone-mode: false
- - name: Set up Python ${{ env.PYTHON_VERSION }}
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
- with:
@@ -2722,7 +2747,8 @@ index 848fffc117..0000000000
- steps:
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- with:
-- fetch-depth: 0
+- # The first parent of the PR merge commit is the base branch commit.
+- fetch-depth: 2
- persist-credentials: false
- - name: Use Node.js ${{ env.NODE_VERSION }}
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
@@ -2740,7 +2766,7 @@ index 848fffc117..0000000000
- # because it is irrelevant. We already check if the formatter produced
- # a diff in the next line.
- # Refs: https://github.com/nodejs/node/pull/42764
-- CLANG_FORMAT_START="$(git merge-base HEAD refs/remotes/origin/$GITHUB_BASE_REF)" \
+- CLANG_FORMAT_START=HEAD^ \
- make format-cpp || true
- git --no-pager diff --exit-code && EXIT_CODE="$?" || EXIT_CODE="$?"
- if [ "$EXIT_CODE" != "0" ]
@@ -2761,13 +2787,29 @@ index 848fffc117..0000000000
- with:
- persist-credentials: false
- - name: Use Node.js ${{ env.NODE_VERSION }}
+- id: setup-node
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
- with:
- node-version: ${{ env.NODE_VERSION }}
+- cache: npm
+- cache-dependency-path: |
+- tools/doc/package-lock.json
+- tools/eslint/package-lock.json
+- tools/lint-md/package-lock.json
+- - name: Cache ESLint results
+- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
+- env:
+- # ESLint only invalidates its cache on config changes, so the key also
+- # covers the custom rules and the files they read.
+- cache-prefix: eslint-${{ runner.os }}-${{ steps.setup-node.outputs.node-version }}-${{ hashFiles('eslint.config.mjs', '*/eslint.config_partial.mjs', 'tools/eslint/*', 'tools/eslint-rules/**', 'doc/api/errors.md', 'doc/api/deprecations.md') }}
+- with:
+- path: .eslintcache
+- key: ${{ env.cache-prefix }}-${{ github.sha }}
+- restore-keys: ${{ env.cache-prefix }}-
- - name: Lint JavaScript files
- run: |
- set +e
-- NODE=$(command -v node) make lint-js
+- NODE=$(command -v node) make lint-js ESLINT_CACHE_FLAGS='--cache --cache-strategy content'
- EXIT_CODE="$?"
- if [ "$EXIT_CODE" != "0" ]; then
- echo
@@ -2797,7 +2839,7 @@ index 848fffc117..0000000000
- - name: Lint markdown files
- run: |
- echo "::add-matcher::.github/workflows/remark-lint-problem-matcher.json"
-- NODE=$(command -v node) make lint-md
+- NODE=$(command -v node) make lint-md ESLINT_CACHE_FLAGS='--cache --cache-strategy content'
- env:
- NODE_RELEASED_VERSIONS: ${{ steps.get-released-versions.outputs.NODE_RELEASED_VERSIONS }}
-
@@ -3082,10 +3124,10 @@ index db96ff4a09..0000000000
- PR_NUMBER: ${{ steps.pr-data.outputs.NUMBER }}
diff --git a/.github/workflows/nix-changes.yml b/.github/workflows/nix-changes.yml
deleted file mode 100644
-index 8aeaf2c0e4..0000000000
+index c80e9e53f5..0000000000
--- a/.github/workflows/nix-changes.yml
+++ /dev/null
-@@ -1,122 +0,0 @@
+@@ -1,128 +0,0 @@
-# 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
@@ -3156,10 +3198,16 @@ index 8aeaf2c0e4..0000000000
-
- - name: Compute requisites before change
- run: |
-- git reset HEAD^ --hard
-- # TODO(aduh95): remove this once list-requisites.sh has reached `main`
-- [ -f tools/nix/list-requisites.sh ] || git checkout FETCH_HEAD -- tools/nix/list-requisites.sh
+- BASE_SHA="$(git rev-parse HEAD^)"
+- if [ -n "$PUSH_BEFORE_SHA" ] && [ "$PUSH_BEFORE_SHA" != "$BASE_SHA" ] \
+- && [ "$PUSH_BEFORE_SHA" != "0000000000000000000000000000000000000000" ] \
+- && git fetch --no-tags --depth=1 origin "$PUSH_BEFORE_SHA"; then
+- BASE_SHA="$PUSH_BEFORE_SHA"
+- fi
+- git reset "$BASE_SHA" --hard
- ./tools/nix/list-requisites.sh > requisites-${{ matrix.system }}-before.list
+- env:
+- PUSH_BEFORE_SHA: ${{ github.event_name == 'push' && github.event.before || '' }}
-
- - name: Output diff
- run: |
@@ -3398,7 +3446,7 @@ index cfb281310a..0000000000
-}
diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml
deleted file mode 100644
-index 4a7415bc90..0000000000
+index 2663019bcc..0000000000
--- a/.github/workflows/scorecard.yml
+++ /dev/null
@@ -1,81 +0,0 @@
@@ -3480,7 +3528,7 @@ index 4a7415bc90..0000000000
-
- # Upload the results to GitHub's code scanning dashboard.
- - name: Upload to code-scanning
-- uses: github/codeql-action/upload-sarif@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
+- uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
- with:
- sarif_file: results.sarif
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
@@ -3738,10 +3786,10 @@ index 43d71f5f31..0000000000
- run: make test-internet -j4 V=1;
diff --git a/.github/workflows/test-linux-perfetto.yml b/.github/workflows/test-linux-perfetto.yml
deleted file mode 100644
-index ce31704f81..0000000000
+index d2bfbfbd68..0000000000
--- a/.github/workflows/test-linux-perfetto.yml
+++ /dev/null
-@@ -1,70 +0,0 @@
+@@ -1,73 +0,0 @@
-name: Test Linux (with Perfetto)
-
-on:
@@ -3806,6 +3854,9 @@ index ce31704f81..0000000000
- - name: Build
- working-directory: node
- run: make build-ci -j4 V=1 CONFIG_FLAGS="--error-on-warn --v8-enable-temporal-support --with-perfetto"
+- - name: Get trace_processor
+- working-directory: node
+- run: make tools/perfetto/trace_processor_shell
- - name: Test
- working-directory: node
- run: make test-ci -j1 V=1 TEST_CI_ARGS="-p actions --measure-flakiness 9"
@@ -4154,10 +4205,10 @@ index 6954112115..0000000000
- DIR: dir%20with $unusual"chars?'åß∂ƒ©∆¬…`
diff --git a/.github/workflows/test-shared.yml b/.github/workflows/test-shared.yml
deleted file mode 100644
-index 9f4d2e031f..0000000000
+index 8292ac253f..0000000000
--- a/.github/workflows/test-shared.yml
+++ /dev/null
-@@ -1,274 +0,0 @@
+@@ -1,273 +0,0 @@
-# This action uses the following secrets:
-# CACHIX_AUTH_TOKEN: Write access to nodejs.cachix.org – without it, the cache is read-only.
-name: Test Shared libraries
@@ -4173,11 +4224,12 @@ index 9f4d2e031f..0000000000
- - android-patches/**
- - benchmarks/**
- - codecov.yml
+- - deps/LIEF/**
- - deps/ada/**
- - deps/brotli/**
- - deps/cares/**
-- - deps/crates/**
- - deps/corepack/**
+- - deps/crates/**
- - deps/googletest/**
- - deps/histogram/**
- - deps/icu-small/**
@@ -4230,11 +4282,12 @@ index 9f4d2e031f..0000000000
- - android-patches/**
- - benchmarks/**
- - codecov.yml
+- - deps/LIEF/**
- - deps/ada/**
- - deps/brotli/**
- - deps/cares/**
-- - deps/crates/**
- - deps/corepack/**
+- - deps/crates/**
- - deps/googletest/**
- - deps/histogram/**
- - deps/icu-small/**
@@ -4293,7 +4346,7 @@ index 9f4d2e031f..0000000000
- run: |
- export DATESTRING=$(date "+%Y-%m-%d")
- export COMMIT=$(git rev-parse --short=10 "$GITHUB_SHA")
-- ./configure && make tar -j4 SKIP_XZ=1 SKIP_SHARED_DEPS=1
+- ./configure && make tar -j4 SKIP_XZ=1 SKIP_SHARED_DEPS=1 GZIP_COMPRESSION=6
- env:
- DISTTYPE: nightly
-
@@ -4325,10 +4378,7 @@ index 9f4d2e031f..0000000000
- with:
- runner: ${{ matrix.runner }}
- with-sccache: ${{ github.base_ref == 'main' || github.ref_name == 'main' }}
-- extra-nix-flags: |
-- --arg useSeparateDerivationForV8 true \
-- ${{ matrix.perfetto && '--arg withPerfetto true \' || '\' }}
-- ${{ endsWith(matrix.system, '-darwin') && '--arg withAmaro false --arg withLief false --arg withSQLite false --arg withFFI false --arg extraConfigFlags ''["--without-inspector" "--without-node-options"]'' \' || '\' }}
+- extra-nix-flags: ${{ matrix.perfetto && '--arg withPerfetto true' || '' }}
- secrets:
- CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
-
@@ -4360,7 +4410,7 @@ index 9f4d2e031f..0000000000
- id: v8-drv
- run: |
- V8_DRV=$(
-- nix-instantiate -E "builtins.filter (p: p.pname == ''v8'') (import $TAR_DIR/shell.nix { useSeparateDerivationForV8=true; }).buildInputs"
+- nix-instantiate -E "builtins.filter (p: p.pname == ''v8'') (import $TAR_DIR/tools/nix/ci-shell.nix {}).buildInputs"
- )
- V8_STORE_PATH=$(nix-store -q --outputs "$V8_DRV")
- echo "V8_DRV=$V8_DRV" >> "$GITHUB_OUTPUT"
@@ -4514,10 +4564,10 @@ index abe4516db4..0000000000
- update-pull-request-title-and-body: true
diff --git a/.github/workflows/tools.yml b/.github/workflows/tools.yml
deleted file mode 100644
-index fe2db7ac95..0000000000
+index 497a5c1230..0000000000
--- a/.github/workflows/tools.yml
+++ /dev/null
-@@ -1,355 +0,0 @@
+@@ -1,365 +0,0 @@
-name: Tools and deps update
-on:
- schedule:
@@ -4548,6 +4598,7 @@ index fe2db7ac95..0000000000
- - inspector_protocol
- - libffi
- - libuv
+- - lief
- - llhttp
- - minimatch
- - nbytes
@@ -4670,6 +4721,7 @@ index fe2db7ac95..0000000000
- - id: icu
- subsystem: deps
- label: dependencies, test, icu
+- requires-python: true
- run: |
- ./tools/dep_updaters/update-icu.sh > temp-output
- cat temp-output
@@ -4678,6 +4730,7 @@ index fe2db7ac95..0000000000
- - id: inspector_protocol
- subsystem: deps
- label: dependencies, inspector
+- requires-python: true
- run: |
- ./tools/dep_updaters/update-inspector-protocol.sh > temp-output
- cat temp-output
@@ -4699,6 +4752,15 @@ index fe2db7ac95..0000000000
- cat temp-output
- tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true
- rm temp-output
+- - id: lief
+- subsystem: deps
+- label: dependencies, dont-land-on-v22.x
+- requires-python: true
+- run: |
+- ./tools/dep_updaters/update-lief.sh > temp-output
+- cat temp-output
+- tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true
+- rm temp-output
- - id: llhttp
- subsystem: deps
- label: dependencies
@@ -4841,9 +4903,7 @@ index fe2db7ac95..0000000000
- with:
- persist-credentials: false
- - name: Set up Python ${{ env.PYTHON_VERSION }}
-- if: |
-- (matrix.id == 'icu' || matrix.id == 'inspector_protocol') &&
-- (github.event_name == 'schedule' || inputs.id == 'all' || inputs.id == matrix.id)
+- if: matrix.requires-python && (github.event_name == 'schedule' || inputs.id == 'all' || inputs.id == matrix.id)
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
- with:
- python-version: ${{ env.PYTHON_VERSION }}
@@ -4875,10 +4935,10 @@ index fe2db7ac95..0000000000
- author: Node.js GitHub Bot
diff --git a/.github/workflows/update-openssl.yml b/.github/workflows/update-openssl.yml
deleted file mode 100644
-index 10eb3289a1..0000000000
+index b81a260312..0000000000
--- a/.github/workflows/update-openssl.yml
+++ /dev/null
-@@ -1,61 +0,0 @@
+@@ -1,69 +0,0 @@
-name: OpenSSL update
-on:
- schedule:
@@ -4908,6 +4968,7 @@ index 10eb3289a1..0000000000
- env:
- GITHUB_TOKEN: ${{ secrets.GH_USER_TOKEN }}
- - name: Create PR with first commit
+- id: create-pull-request
- if: env.NEW_VERSION
- uses: gr2m/create-or-update-pull-request-action@b65137ca591da0b9f43bad7b24df13050ea45d1b # v1.10.1
- # Creates a PR with the new OpenSSL source code committed
@@ -4922,6 +4983,7 @@ index 10eb3289a1..0000000000
- title: 'deps: update OpenSSL to ${{ env.NEW_VERSION }}'
- path: deps/openssl
- update-pull-request-title-and-body: true
+- draft: true
- - name: Regenerate platform specific files
- if: env.NEW_VERSION
- run: |
@@ -4940,6 +5002,12 @@ index 10eb3289a1..0000000000
- branch: actions/tools-update-openssl # Custom branch *just* for this Action.
- commit-message: 'deps: update archs files for openssl-${{ env.NEW_VERSION }}'
- path: deps/openssl
+- - name: Mark PR as ready for review
+- if: env.NEW_VERSION
+- env:
+- GITHUB_TOKEN: ${{ secrets.GH_USER_TOKEN }}
+- PR_ID: ${{ steps.create-pull-request.outputs.pull-request-number }}
+- run: gh --repo "$GITHUB_REPOSITORY" pr ready "$PR_ID"
diff --git a/.github/workflows/update-v8.yml b/.github/workflows/update-v8.yml
deleted file mode 100644
index 8e71e56389..0000000000
diff --git a/patches/doc-build-mobile-notes-and-ignores.patch b/patches/doc-build-mobile-notes-and-ignores.patch
index 2aa24a42c0..4116702f1f 100644
--- a/patches/doc-build-mobile-notes-and-ignores.patch
+++ b/patches/doc-build-mobile-notes-and-ignores.patch
@@ -16,7 +16,7 @@ Co-authored-by: Andre Staltz
2 files changed, 32 insertions(+), 893 deletions(-)
diff --git a/.gitignore b/.gitignore
-index 9277cdf090..b6f48a2e7c 100644
+index 2a87849224..96f28306c5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,6 +38,10 @@
@@ -39,7 +39,7 @@ index 9277cdf090..b6f48a2e7c 100644
/deps/openssl/openssl.props
/deps/openssl/openssl.targets
diff --git a/README.md b/README.md
-index d90887e0f5..1d3ab7e321 100644
+index d14fba943d..1d3ab7e321 100644
--- a/README.md
+++ b/README.md
@@ -1,915 +1,49 @@
@@ -516,6 +516,8 @@ index d90887e0f5..1d3ab7e321 100644
- **Vladimir Morozov** <> (he/him)
-* [watilde](https://github.com/watilde) -
- **Daijiro Wachi** <> (he/him)
+-* [XadillaX](https://github.com/XadillaX) -
+- **Khaidi Chu** <> (he/him)
-* [ZYSzys](https://github.com/ZYSzys) -
- **Yongsheng Zhang** <> (he/him)
-
@@ -796,8 +798,6 @@ index d90887e0f5..1d3ab7e321 100644
- **Thomas Watson** <>
-* [whitlockjc](https://github.com/whitlockjc) -
- **Jeremy Whitlock** <>
--* [XadillaX](https://github.com/XadillaX) -
-- **Khaidi Chu** <> (he/him)
-* [yashLadha](https://github.com/yashLadha) -
- **Yash Ladha** <> (he/him)
-* [yhwang](https://github.com/yhwang) -
diff --git a/patches/files.map b/patches/files.map
index 70a3c0c623..219ba50d38 100644
--- a/patches/files.map
+++ b/patches/files.map
@@ -34,6 +34,7 @@ test-mobile-test-harness.patch test/message/message.status
test-mobile-test-harness.patch test/message/testcfg.py
test-mobile-test-harness.patch tools/test.py
test-skip-adapt-parallel-and-sequential-tests-for-mo.patch test/parallel/parallel.status
+test-skip-adapt-parallel-and-sequential-tests-for-mo.patch test/parallel/test-bench-stream.js
test-skip-adapt-parallel-and-sequential-tests-for-mo.patch test/parallel/test-common-wpt-inspect.js
test-skip-adapt-parallel-and-sequential-tests-for-mo.patch test/parallel/test-dgram-bind-fd.js
test-skip-adapt-parallel-and-sequential-tests-for-mo.patch test/parallel/test-dgram-membership.js
@@ -41,6 +42,7 @@ test-skip-adapt-parallel-and-sequential-tests-for-mo.patch test/parallel/test-dg
test-skip-adapt-parallel-and-sequential-tests-for-mo.patch test/parallel/test-fs-copyfile.js
test-skip-adapt-parallel-and-sequential-tests-for-mo.patch test/parallel/test-fs-lchmod.js
test-skip-adapt-parallel-and-sequential-tests-for-mo.patch test/parallel/test-fs-mkdir.js
+test-skip-adapt-parallel-and-sequential-tests-for-mo.patch test/parallel/test-fs-negative-zero.js
test-skip-adapt-parallel-and-sequential-tests-for-mo.patch test/parallel/test-fs-open-flags.js
test-skip-adapt-parallel-and-sequential-tests-for-mo.patch test/parallel/test-fs-promises.js
test-skip-adapt-parallel-and-sequential-tests-for-mo.patch test/parallel/test-fs-read-file-sync-hostname.js
diff --git a/patches/lib-install-a-WebAssembly-polyfill-when-the-engine-h.patch b/patches/lib-install-a-WebAssembly-polyfill-when-the-engine-h.patch
index f09ccc7ba3..1eaa3de85e 100644
--- a/patches/lib-install-a-WebAssembly-polyfill-when-the-engine-h.patch
+++ b/patches/lib-install-a-WebAssembly-polyfill-when-the-engine-h.patch
@@ -46,7 +46,7 @@ index 67cd2feb69..994885b996 100644
+ SOFTWARE.
+ """
diff --git a/lib/internal/process/pre_execution.js b/lib/internal/process/pre_execution.js
-index 00647022a8..c70a1e99f5 100644
+index 2111579151..9a1b965e5c 100644
--- a/lib/internal/process/pre_execution.js
+++ b/lib/internal/process/pre_execution.js
@@ -10,6 +10,7 @@ const {
@@ -57,7 +57,7 @@ index 00647022a8..c70a1e99f5 100644
NumberParseInt,
ObjectDefineProperty,
ObjectFreeze,
-@@ -113,6 +114,7 @@ function prepareExecution(options) {
+@@ -111,6 +112,7 @@ function prepareExecution(options) {
// Patch the process object and get the resolved main entry point.
const mainEntry = patchProcessObject(expandArgv1);
@@ -65,7 +65,7 @@ index 00647022a8..c70a1e99f5 100644
setupTraceCategoryState();
setupInspectorHooks();
setupNetworkInspection();
-@@ -492,6 +494,94 @@ function setupEventsource() {
+@@ -451,6 +453,94 @@ function setupEventsource() {
}
}
diff --git a/patches/src-expose-the-mobile-version-key-in-metadata.patch b/patches/src-expose-the-mobile-version-key-in-metadata.patch
index 1b85b7e2f8..9b88c1e6d0 100644
--- a/patches/src-expose-the-mobile-version-key-in-metadata.patch
+++ b/patches/src-expose-the-mobile-version-key-in-metadata.patch
@@ -15,7 +15,7 @@ Co-authored-by: Jaime Bernardo
2 files changed, 14 insertions(+)
diff --git a/src/node_metadata.cc b/src/node_metadata.cc
-index b91b1b4881..e7ffe0457e 100644
+index 0f3a76105a..1ef9ec58df 100644
--- a/src/node_metadata.cc
+++ b/src/node_metadata.cc
@@ -55,6 +55,10 @@
diff --git a/patches/src-mobile-platform-guards-in-node.cc-node_internals.patch b/patches/src-mobile-platform-guards-in-node.cc-node_internals.patch
index 53827956fd..5bc362d085 100644
--- a/patches/src-mobile-platform-guards-in-node.cc-node_internals.patch
+++ b/patches/src-mobile-platform-guards-in-node.cc-node_internals.patch
@@ -18,7 +18,7 @@ Co-authored-by: Jaime Bernardo
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/node.cc b/src/node.cc
-index cd83e7c043..63dd2b6e2d 100644
+index 163070b501..468b5d2c2c 100644
--- a/src/node.cc
+++ b/src/node.cc
@@ -706,6 +706,8 @@ void ResetStdio() {
@@ -39,10 +39,10 @@ index cd83e7c043..63dd2b6e2d 100644
#endif // __POSIX__
}
diff --git a/src/node_internals.h b/src/node_internals.h
-index cf3ce9e13f..2795f738dd 100644
+index 1c4da8e2d2..3dc190f0a0 100644
--- a/src/node_internals.h
+++ b/src/node_internals.h
-@@ -319,7 +319,7 @@ class ThreadPoolWork {
+@@ -326,7 +326,7 @@ class ThreadPoolWork {
// Functions defined in node.cc that are exposed via the bootstrapper object
diff --git a/patches/src-skip-unclonable-env-vars-on-Android.patch b/patches/src-skip-unclonable-env-vars-on-Android.patch
index 35a5cf6c7c..f8ec57267c 100644
--- a/patches/src-skip-unclonable-env-vars-on-Android.patch
+++ b/patches/src-skip-unclonable-env-vars-on-Android.patch
@@ -19,10 +19,10 @@ Co-authored-by: Jaime Bernardo
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/node_env_var.cc b/src/node_env_var.cc
-index 25e405be86..6c7ac610c5 100644
+index 8a16f9ec13..2340ed463b 100644
--- a/src/node_env_var.cc
+++ b/src/node_env_var.cc
-@@ -240,7 +240,9 @@ std::shared_ptr KVStore::Clone(Isolate* isolate) const {
+@@ -300,7 +300,9 @@ std::shared_ptr KVStore::Clone(Isolate* isolate) const {
}
CHECK(key->IsString());
if (!Get(isolate, key.As()).ToLocal(&value)) {
diff --git a/patches/test-mobile-test-harness.patch b/patches/test-mobile-test-harness.patch
index d74ad0ee99..d0c4bbd8bb 100644
--- a/patches/test-mobile-test-harness.patch
+++ b/patches/test-mobile-test-harness.patch
@@ -24,7 +24,7 @@ Co-authored-by: Andre Staltz
diff --git a/test/common/index.js b/test/common/index.js
old mode 100755
new mode 100644
-index ce083ee729..bef5f99a3c
+index a2c7106a52..d94274866a
--- a/test/common/index.js
+++ b/test/common/index.js
@@ -184,6 +184,8 @@ const isFreeBSD = process.platform === 'freebsd';
@@ -36,7 +36,7 @@ index ce083ee729..bef5f99a3c
const isASan = process.config.variables.asan === 1;
const isRiscv64 = process.arch === 'riscv64';
const isDebug = process.features.debug;
-@@ -1032,8 +1034,10 @@ const common = {
+@@ -1033,8 +1035,10 @@ const common = {
invalidArgTypeHelper,
isAlive,
isASan,
@@ -153,10 +153,10 @@ index 5470fd2bd9..4db0a9d5c6 100644
return result
diff --git a/tools/test.py b/tools/test.py
-index 90df2cfb2f..8f226307df 100755
+index fee5172b41..1b71ecba21 100755
--- a/tools/test.py
+++ b/tools/test.py
-@@ -1749,6 +1749,12 @@ def Main():
+@@ -1752,6 +1752,12 @@ def Main():
'asan': get_asan_state(vm, context),
'pointer_compression': get_pointer_compression_state(vm, context),
}
diff --git a/patches/test-skip-adapt-parallel-and-sequential-tests-for-mo.patch b/patches/test-skip-adapt-parallel-and-sequential-tests-for-mo.patch
index 8207f52d06..8c2d8e2157 100644
--- a/patches/test-skip-adapt-parallel-and-sequential-tests-for-mo.patch
+++ b/patches/test-skip-adapt-parallel-and-sequential-tests-for-mo.patch
@@ -19,7 +19,8 @@ Co-authored-by: Andre Staltz
Signed-off-by: fogtape
Assisted-by: Codex
---
- test/parallel/parallel.status | 1627 +++++++++++++++++
+ test/parallel/parallel.status | 1667 +++++++++++++++++
+ test/parallel/test-bench-stream.js | 3 +-
test/parallel/test-common-wpt-inspect.js | 10 +-
test/parallel/test-dgram-bind-fd.js | 2 +-
test/parallel/test-dgram-membership.js | 10 +-
@@ -27,6 +28,7 @@ Assisted-by: Codex
test/parallel/test-fs-copyfile.js | 3 +-
test/parallel/test-fs-lchmod.js | 4 +-
test/parallel/test-fs-mkdir.js | 5 +-
+ test/parallel/test-fs-negative-zero.js | 19 +-
test/parallel/test-fs-open-flags.js | 3 +-
test/parallel/test-fs-promises.js | 6 +-
.../test-fs-read-file-sync-hostname.js | 2 +-
@@ -58,21 +60,35 @@ Assisted-by: Codex
test/parallel/test-vfs-fs-readFileSync.js | 3 +-
test/parallel/test-vfs-fs-writeFileSync.js | 3 +-
.../test-watch-mode-files_watcher.mjs | 3 +
- test/sequential/sequential.status | 129 ++
+ test/sequential/sequential.status | 143 ++
test/sequential/test-async-wrap-getasyncid.js | 15 +-
test/sequential/test-fs-watch.js | 4 +
...test-vm-timeout-escape-promise-module-2.js | 6 +-
- 43 files changed, 1980 insertions(+), 57 deletions(-)
+ 45 files changed, 2047 insertions(+), 66 deletions(-)
diff --git a/test/parallel/parallel.status b/test/parallel/parallel.status
-index d8fba447e9..d52a0ea801 100644
+index 0fe217d4e4..4578a1cf97 100644
--- a/test/parallel/parallel.status
+++ b/test/parallel/parallel.status
-@@ -145,6 +145,1633 @@ test-tls-set-default-ca-certificates-append-fetch: SKIP
+@@ -138,6 +138,1673 @@ test-tls-set-default-ca-certificates-append-fetch: SKIP
test-tls-set-default-ca-certificates-reset-fetch: SKIP
test-inspector-invalid-protocol: SKIP
+[$system==android]
++# Node 26.11: inferred subprocess exclusions; confirm with the full device suite.
++# These tests require independent child Node executables, unavailable in apps.
++test-crypto-randomfillsync-trace-sync-io: SKIP
++test-module-builtin-policies: SKIP
++test-permission-net-cluster: SKIP
++test-permission-net-socket-fd: SKIP
++test-process-timeout: SKIP
++test-process-timeout-blocked: SKIP
++test-process-timeout-coverage: SKIP
++test-process-timeout-inspector: SKIP
++test-process-timeout-options: SKIP
++test-snapshot-perf-hooks-gc-observer: SKIP
++test-worker-message-port-transfer-filehandle-fd0: SKIP
++
+# Snapshot creation needs child Node executables. The Android full suite
+# confirms that process.execPath launches an app host instead.
+test-crypto-provider-cache-snapshot: SKIP
@@ -112,7 +128,7 @@ index d8fba447e9..d52a0ea801 100644
+test-snapshot-invalid-blob: SKIP
+test-vfs-flag: SKIP
+test-vfs-module-paths: SKIP
-+test-vfs-mount-load: SKIP
++test-vfs-load: SKIP
+test-zlib-zip-experimental-warning: SKIP
+test-zlib-zip-security-hardening: SKIP
+# Permission flags deny the mobile harness its per-launch verdict write.
@@ -192,23 +208,31 @@ index d8fba447e9..d52a0ea801 100644
+test-crypto-secure-heap: SKIP
+test-cwd-enoent*: SKIP
+test-debug-usage: SKIP
++# The Node 26.11 Android sweep exposed missing debugger CLI exclusions.
++# These tests spawn app_process via test/common/debugger.js (or directly).
++# Keep the in-process run-restart-init and wait-for-debugger tests runnable.
+test-debugger-address: SKIP
+test-debugger-auto-resume: SKIP
++test-debugger-backtrace: SKIP
+test-debugger-break: SKIP
+test-debugger-breakpoint-exists: SKIP
+test-debugger-clear-breakpoints: SKIP
+test-debugger-exceptions: SKIP
++test-debugger-exec: SKIP
+test-debugger-exec-scope: SKIP
+test-debugger-extract-function-name: SKIP
+test-debugger-heap-profiler: SKIP
+test-debugger-help: SKIP
+test-debugger-invalid-json: SKIP
+test-debugger-list: SKIP
++test-debugger-low-level: SKIP
+test-debugger-object-type-remote-object: SKIP
+test-debugger-profile: SKIP
+test-debugger-profile-command: SKIP
+test-debugger-random-port: SKIP
+test-debugger-random-port-with-inspect-port: SKIP
++test-debugger-repeat-last: SKIP
++test-debugger-restart-message: SKIP
+test-debugger-run-after-quit-restart: SKIP
+test-debugger-sb-before-load: SKIP
+test-debugger-scripts: SKIP
@@ -800,6 +824,20 @@ index d8fba447e9..d52a0ea801 100644
+#
+test-worker-cleanup-handles: PASS, FLAKY
+[$system==ios]
++# Node 26.11: inferred subprocess exclusions; confirm with the full device suite.
++# These tests require independent child Node executables, unavailable in apps.
++test-crypto-randomfillsync-trace-sync-io: SKIP
++test-module-builtin-policies: SKIP
++test-permission-net-cluster: SKIP
++test-permission-net-socket-fd: SKIP
++test-process-timeout: SKIP
++test-process-timeout-blocked: SKIP
++test-process-timeout-coverage: SKIP
++test-process-timeout-inspector: SKIP
++test-process-timeout-options: SKIP
++test-snapshot-perf-hooks-gc-observer: SKIP
++test-worker-message-port-transfer-filehandle-fd0: SKIP
++
+# Snapshot creation needs child Node executables. The Android full suite
+# confirms that process.execPath launches an app host instead.
+test-crypto-provider-cache-snapshot: SKIP
@@ -837,7 +875,7 @@ index d8fba447e9..d52a0ea801 100644
+test-snapshot-invalid-blob: SKIP
+test-vfs-flag: SKIP
+test-vfs-module-paths: SKIP
-+test-vfs-mount-load: SKIP
++test-vfs-load: SKIP
+test-zlib-zip-experimental-warning: SKIP
+test-zlib-zip-security-hardening: SKIP
+# Permission flags deny the mobile harness its per-launch verdict write.
@@ -1699,9 +1737,25 @@ index d8fba447e9..d52a0ea801 100644
+#
+test-https-client-renegotiation-limit: PASS, FLAKY
+test-https-selfsigned-no-keycertsign-no-crash: PASS, FLAKY
- [$asan==on]
- # https://github.com/nodejs/node/issues/39655
- test-cluster-primary-error: PASS, FLAKY
++[$asan==on]
++# https://github.com/nodejs/node/issues/39655
++test-cluster-primary-error: PASS, FLAKY
++
+ [$arch==loong64]
+ # https://github.com/nodejs/node/issues/51662
+ test-http-correct-hostname: SKIP
+diff --git a/test/parallel/test-bench-stream.js b/test/parallel/test-bench-stream.js
+index dbb68f8be9..5347abd707 100644
+--- a/test/parallel/test-bench-stream.js
++++ b/test/parallel/test-bench-stream.js
+@@ -412,5 +412,6 @@ function testOperatorsWithoutStreamModule() {
+ await testReportingFailureSettlesBenchmarks();
+ await testSummaryListenerFailure();
+ await testRecordOwnership();
+- testOperatorsWithoutStreamModule();
++ // nodejs-mobile: only this startup check needs an independent child Node.
++ if (!common.isAndroid && !common.isIOS) testOperatorsWithoutStreamModule();
+ })().then(common.mustCall());
diff --git a/test/parallel/test-common-wpt-inspect.js b/test/parallel/test-common-wpt-inspect.js
index e614a4cfaa..47f206fb4e 100644
--- a/test/parallel/test-common-wpt-inspect.js
@@ -1840,6 +1894,42 @@ index 185da03aeb..5a47def129 100644
const pathname = tmpdir.resolve(nextdir());
fs.mkdirSync(pathname);
process.chdir(pathname);
+diff --git a/test/parallel/test-fs-negative-zero.js b/test/parallel/test-fs-negative-zero.js
+index 09f965eb17..b9d11059a3 100644
+--- a/test/parallel/test-fs-negative-zero.js
++++ b/test/parallel/test-fs-negative-zero.js
+@@ -1,6 +1,6 @@
+ 'use strict';
+
+-require('../common');
++const common = require('../common');
+
+ const assert = require('assert');
+ const { spawnSync } = require('child_process');
+@@ -36,13 +36,16 @@ ignoreExpectedError(() => fs.writeFileSync(missing, '', { mode: -0 }));
+ fs.writeFileSync(-0, '');
+ fs.appendFileSync(-0, '');
+
+-const child = spawnSync(
+- process.execPath,
+- ['-e', 'process.stdout.write(require("fs").readFileSync(-0, "utf8"))'],
+- { input: 'hello' },
+-);
+-assert.strictEqual(child.status, 0);
+-assert.strictEqual(child.stdout.toString(), 'hello');
++// nodejs-mobile: retain the in-process checks; stdin startup needs a child Node.
++if (!common.isAndroid && !common.isIOS) {
++ const child = spawnSync(
++ process.execPath,
++ ['-e', 'process.stdout.write(require("fs").readFileSync(-0, "utf8"))'],
++ { input: 'hello' },
++ );
++ assert.strictEqual(child.status, 0);
++ assert.strictEqual(child.stdout.toString(), 'hello');
++}
+
+ fs.watchFile(missing, { interval: -0 }, () => {});
+ fs.unwatchFile(missing);
diff --git a/test/parallel/test-fs-open-flags.js b/test/parallel/test-fs-open-flags.js
index 55478247f6..cdfaaa0b88 100644
--- a/test/parallel/test-fs-open-flags.js
@@ -2440,7 +2530,7 @@ index b84d2063a2..4411854e0b 100644
}, 1);
diff --git a/test/parallel/test-sqlite-backup.mjs b/test/parallel/test-sqlite-backup.mjs
-index ee5f54fd90..64e8371ac2 100644
+index cae097aa92..31e159f2a3 100644
--- a/test/parallel/test-sqlite-backup.mjs
+++ b/test/parallel/test-sqlite-backup.mjs
@@ -1,6 +1,8 @@
@@ -2507,10 +2597,10 @@ index 7bde5f6c7b..2001bd55dd 100644
fs.mkdirSync(root, { recursive: true });
diff --git a/test/parallel/test-watch-mode-files_watcher.mjs b/test/parallel/test-watch-mode-files_watcher.mjs
-index e1595350cd..f5d84f7480 100644
+index 10e078286e..9c4ec8787d 100644
--- a/test/parallel/test-watch-mode-files_watcher.mjs
+++ b/test/parallel/test-watch-mode-files_watcher.mjs
-@@ -199,6 +199,8 @@ describe('watch mode file watcher', () => {
+@@ -228,6 +228,8 @@ describe('watch mode file watcher', () => {
assert.deepStrictEqual(watcher.watchedPaths, []);
});
@@ -2519,21 +2609,25 @@ index e1595350cd..f5d84f7480 100644
it('should watch files from subprocess IPC events', async () => {
const file = fixtures.path('watch-mode/ipc.js');
const child = spawn(process.execPath, [file], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'], encoding: 'utf8' });
-@@ -210,4 +212,5 @@ describe('watch mode file watcher', () => {
+@@ -241,4 +243,5 @@ describe('watch mode file watcher', () => {
}
assert.deepStrictEqual(watcher.watchedPaths, expected);
});
+ */
});
diff --git a/test/sequential/sequential.status b/test/sequential/sequential.status
-index 6b5714500b..9c8a8e0f5c 100644
+index f155b67473..c052ca2ceb 100644
--- a/test/sequential/sequential.status
+++ b/test/sequential/sequential.status
-@@ -43,6 +43,135 @@ test-perf-hooks: SKIP
+@@ -33,6 +33,149 @@ test-perf-hooks: SKIP
# https://github.com/nodejs/node/issues/39683
test-tls-psk-client: PASS, FLAKY
+[$system==android]
++# Node 26.11: inferred subprocess exclusions; confirm with the full device suite.
++# These tests require independent child Node executables, unavailable in apps.
++test-watch-mode-process-timeout: SKIP
++
+#
+# Tests that require child process that currently is not supported on mobile
+#
@@ -2559,6 +2653,10 @@ index 6b5714500b..9c8a8e0f5c 100644
+test-stream2-stderr-sync: SKIP
+test-util-debug: SKIP
+test-vm-timeout-rethrow: SKIP
++# These CLI tests spawn execPath (app_process), which aborts outside zygote.
++# Confirmed in Build 37473151347: dalvik-cache ownership / SIGABRT.
++test-watch-mode: SKIP
++test-watch-mode-inspect: SKIP
+test-worker-prof: SKIP
+#
+# Tests that require the inspector, not included in the build
@@ -2591,6 +2689,10 @@ index 6b5714500b..9c8a8e0f5c 100644
+test-without-async-context-frame: SKIP
+test-write-heapsnapshot-options: SKIP
+[$system==ios]
++# Node 26.11: inferred subprocess exclusions; confirm with the full device suite.
++# These tests require independent child Node executables, unavailable in apps.
++test-watch-mode-process-timeout: SKIP
++
+#
+# Tests that require child process that currently is not supported on mobile
+#
@@ -2616,6 +2718,8 @@ index 6b5714500b..9c8a8e0f5c 100644
+test-stream2-stderr-sync: SKIP
+test-util-debug: SKIP
+test-vm-timeout-rethrow: SKIP
++# NodeInstance launches a child Node, permitted only on the simulator.
++test-watch-mode-inspect: SKIP
+test-worker-prof: SKIP
+#
+# Tests that require the inspector, not included in the build
@@ -2666,7 +2770,7 @@ index 6b5714500b..9c8a8e0f5c 100644
# https://github.com/nodejs/node/issues/49933
test-watch-mode-inspect: SKIP
diff --git a/test/sequential/test-async-wrap-getasyncid.js b/test/sequential/test-async-wrap-getasyncid.js
-index 0842f8fdfa..03233c2fa4 100644
+index 0800f07788..a738ae2c39 100644
--- a/test/sequential/test-async-wrap-getasyncid.js
+++ b/test/sequential/test-async-wrap-getasyncid.js
@@ -12,6 +12,8 @@ const providers = { ...internalBinding('async_wrap').Providers };
@@ -2678,7 +2782,7 @@ index 0842f8fdfa..03233c2fa4 100644
// Make sure that all Providers are tested.
{
-@@ -79,6 +81,12 @@ const { getSystemErrorName } = require('util');
+@@ -78,6 +80,12 @@ const { getSystemErrorName } = require('util');
delete providers.DTLS_ENDPOINT;
delete providers.DTLS_SESSION;
@@ -2691,7 +2795,7 @@ index 0842f8fdfa..03233c2fa4 100644
const objKeys = Object.keys(providers);
if (objKeys.length > 0)
process._rawDebug(objKeys);
-@@ -200,7 +208,8 @@ if (common.hasCrypto) { // eslint-disable-line node-core/crypto-check
+@@ -199,7 +207,8 @@ if (common.hasCrypto) { // eslint-disable-line node-core/crypto-check
testInitialized(handle, 'Pipe');
}
@@ -2701,7 +2805,7 @@ index 0842f8fdfa..03233c2fa4 100644
tmpdir.refresh();
const server = net.createServer(common.mustCall((socket) => {
-@@ -332,6 +341,8 @@ if (common.hasCrypto) { // eslint-disable-line node-core/crypto-check
+@@ -331,6 +340,8 @@ if (common.hasCrypto) { // eslint-disable-line node-core/crypto-check
// DIRHANDLE
{
const dirBinding = internalBinding('fs_dir');
diff --git a/release-ready.txt b/release-ready.txt
deleted file mode 100644
index 538901308c..0000000000
--- a/release-ready.txt
+++ /dev/null
@@ -1 +0,0 @@
-26.10.0-0
diff --git a/scripts/clone-upstream.sh b/scripts/clone-upstream.sh
new file mode 100755
index 0000000000..95c1f0d6d7
--- /dev/null
+++ b/scripts/clone-upstream.sh
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+# Clone an upstream tag/branch or an immutable PR commit, without inventing tags.
+# Usage: scripts/clone-upstream.sh
+set -euo pipefail
+
+REPO=$1
+REF=$2
+OUT=$3
+[ ! -e "$OUT" ] || { echo "error: $OUT already exists" >&2; exit 1; }
+
+if [[ "$REF" =~ ^[0-9a-f]{40}$ ]]; then
+ git init -q "$OUT"
+ git -C "$OUT" remote add origin "$REPO"
+ git -C "$OUT" fetch --depth 1 origin "$REF"
+ git -C "$OUT" checkout -q --detach FETCH_HEAD
+ GOT=$(git -C "$OUT" rev-parse HEAD)
+ [ "$GOT" = "$REF" ] || { echo "error: upstream commit mismatch: $GOT != $REF" >&2; exit 1; }
+else
+ git clone --depth 1 --branch "$REF" "$REPO" "$OUT"
+fi
+# Local metadata for the in-tree test audit, including unreleased PR snapshots.
+git -C "$OUT" update-ref refs/nodejs-mobile/upstream-base HEAD
diff --git a/scripts/prepare.sh b/scripts/prepare.sh
index cc47969d35..ae4c589c4a 100755
--- a/scripts/prepare.sh
+++ b/scripts/prepare.sh
@@ -9,7 +9,7 @@
#
# Env overrides (mainly for CI/offline use):
# UPSTREAM_REPO git URL or local path (default https://github.com/nodejs/node.git)
-# UPSTREAM_REF tag/branch to clone (default: first line of upstream-base.txt)
+# UPSTREAM_REF tag/branch/full SHA (default: first line of upstream-base.txt)
# SKIP_VERIFY=1 don't fail on an expected-tree mismatch (prints both hashes)
set -euo pipefail
@@ -27,7 +27,7 @@ if [ -e "${OUT}" ]; then
fi
echo "1/4 Cloning ${UPSTREAM_REPO} @ ${UPSTREAM_REF} (shallow)..."
-git clone --depth 1 --branch "${UPSTREAM_REF}" "${UPSTREAM_REPO}" "${OUT}"
+"${HERE}/scripts/clone-upstream.sh" "${UPSTREAM_REPO}" "${UPSTREAM_REF}" "${OUT}"
git -C "${OUT}" config user.name "${GIT_AUTHOR_NAME:-nodejs-mobile}"
git -C "${OUT}" config user.email "${GIT_AUTHOR_EMAIL:-nodejs-mobile@invalid}"
diff --git a/scripts/release-control.py b/scripts/release-control.py
index 8f7fce9a70..d3c5c838ba 100644
--- a/scripts/release-control.py
+++ b/scripts/release-control.py
@@ -38,7 +38,10 @@ def upstream(root):
lines = [line.strip() for line in (root / 'upstream-base.txt').read_text().splitlines()
if line.strip() and not line.lstrip().startswith('#')]
if len(lines) != 1:
- raise ReleaseError('upstream-base.txt must pin exactly one upstream release tag')
+ raise ReleaseError('upstream-base.txt must pin exactly one upstream ref')
+ if re.fullmatch(r'[0-9a-f]{40}', lines[0]):
+ raise ReleaseError('This source pins an unreleased upstream PR commit. '
+ 'Build a CI preview; pin the official release tag before Cut release or publication.')
return parse_version(lines[0])
diff --git a/scripts/test-android-suite.py b/scripts/test-android-suite.py
new file mode 100644
index 0000000000..6494a1128d
--- /dev/null
+++ b/scripts/test-android-suite.py
@@ -0,0 +1,174 @@
+#!/usr/bin/env python3
+"""Check Android shard coverage and preserve failures through the log pipeline."""
+import ast
+import contextlib
+import importlib.util
+import io
+import json
+import os
+from pathlib import Path
+import re
+import shutil
+import subprocess
+import sys
+import tempfile
+from types import SimpleNamespace
+import unittest
+import warnings
+
+RECIPE = Path(__file__).resolve().parent.parent
+SOURCE = Path(sys.argv.pop(1)).resolve() if len(sys.argv) > 1 else RECIPE / 'out'
+RUNNER = SOURCE / 'tools/mobile-test/android/run-suite-shard.sh'
+
+
+def load_module(name, path):
+ spec = importlib.util.spec_from_file_location(name, path)
+ module = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(module)
+ return module
+
+
+class QuietOutput(io.StringIO):
+ def reconfigure(self, **_):
+ pass
+
+
+class AndroidSuite(unittest.TestCase):
+ def setUp(self):
+ workflow = (RECIPE / '.github/workflows/full-device-suite.yml').read_text()
+ android = workflow.split('\n android:\n', 1)[1].split('\n ios:\n', 1)[0]
+ self.shards = ast.literal_eval(re.search(r'^ shard: (.+)$', android, re.M)[1])
+ self.count = int(re.search(r'^ ANDROID_SUITE_SHARDS: (\d+)$', android, re.M)[1])
+
+ def test_matrix_matches_denominator(self):
+ self.assertEqual(self.shards, list(range(self.count)))
+
+ def test_watch_cli_exclusions_preserve_in_process_device_tests(self):
+ sys.path.insert(0, str(SOURCE / 'tools'))
+ self.addCleanup(sys.path.remove, str(SOURCE / 'tools'))
+ runner = load_module('mobile_watch_status_runner', SOURCE / 'tools/test.py')
+ for suite, names in {
+ 'sequential': ['test-watch-mode', 'test-watch-mode-inspect', 'test-tls-connect'],
+ 'parallel': ['test-debugger-run-restart-init', 'test-debugger-wait-for-debugger',
+ 'test-fs-watch', 'test-fs-watch-persistent'],
+ }.items():
+ sections, defs = [], {}
+ with warnings.catch_warnings():
+ warnings.simplefilter('ignore', ResourceWarning)
+ runner.ReadConfigurationInto(str(SOURCE / 'test' / suite / (suite + '.status')),
+ sections, defs)
+ config = runner.Configuration(sections, defs)
+ for platform in ('android', 'ios', 'linux'):
+ with self.subTest(suite=suite, platform=platform):
+ cases = [SimpleNamespace(path=[suite, name]) for name in names]
+ env = dict(system=platform, arch='x64', mode='release', type='simple',
+ asan='off', pointer_compression='false')
+ classified, _ = config.ClassifyTests(cases, env)
+ skipped = {case.path[-1] for case in classified if runner.SKIP in case.outcomes}
+ expected = set(names[:2]) if suite == 'sequential' and platform != 'linux' else set()
+ self.assertEqual(skipped, expected)
+
+ def test_real_test_selector_covers_every_runnable_case_once(self):
+ # Use upstream's actual classification and --run selector, replacing
+ # only device execution. This detects missing/duplicate coverage when
+ # the workflow matrix or a future upstream test runner changes.
+ sys.path.insert(0, str(SOURCE / 'tools'))
+ self.addCleanup(sys.path.remove, str(SOURCE / 'tools'))
+ runner = load_module('android_suite_test_runner', SOURCE / 'tools/test.py')
+ coverage = load_module('android_suite_coverage',
+ SOURCE / 'tools/mobile-test/coverage-manifest.py')
+ selected = []
+
+ def collect(cases, *_):
+ selected.extend((case.arch, case.mode, case.file, tuple(case.path))
+ for case in cases)
+ return {'allPassed': True, 'failed': []}
+
+ runner.RunTestCases = collect
+ saved_cwd, saved_argv = os.getcwd(), sys.argv
+ try:
+ os.chdir(SOURCE)
+ with tempfile.TemporaryDirectory() as folder, coverage.host_binary_present():
+ stub = str(Path(folder) / 'stub-node')
+ coverage.write_stub(stub)
+
+ def select(shard=None):
+ selected.clear()
+ sys.argv = ['tools/test.py', '-j', '1', '--flaky-tests=skip',
+ '--arch', 'android', '--shell', stub]
+ if shard is not None:
+ sys.argv.append(f'--run={shard},{self.count}')
+ sys.argv.extend(['parallel', 'sequential'])
+ with contextlib.redirect_stdout(QuietOutput()), warnings.catch_warnings():
+ # Upstream Execute reads temporary files without an
+ # explicit close. This host-only selection check calls
+ # it repeatedly; keep those warnings out of CI output.
+ warnings.simplefilter('ignore', ResourceWarning)
+ self.assertEqual(runner.Main(), 0)
+ return list(selected)
+
+ expected = select()
+ actual = []
+ sizes = []
+ for shard in self.shards:
+ cases = select(shard)
+ sizes.append(len(cases))
+ actual.extend(cases)
+ self.assertTrue(expected)
+ self.assertEqual(len(actual), len(set(actual)))
+ self.assertCountEqual(actual, expected)
+ print(f'Android coverage: {len(expected)} runnable cases, '
+ f'{self.count} shards of {min(sizes)}-{max(sizes)}; no gaps or duplicates')
+ finally:
+ os.chdir(saved_cwd)
+ sys.argv = saved_argv
+
+ def run_fake_suite(self, exit_code, args):
+ with tempfile.TemporaryDirectory() as folder:
+ root = Path(folder)
+ script = root / 'tools/mobile-test/android/run-suite-shard.sh'
+ script.parent.mkdir(parents=True)
+ shutil.copy2(RUNNER, script)
+ record = root / 'invocation.json'
+ test = root / 'tools/test.py'
+ test.write_text(f'#!{sys.executable}\nimport json,sys\n'
+ f'open({str(record)!r}, "w").write(json.dumps(sys.argv[1:]))\n'
+ 'print("suite output")\n'
+ f'sys.exit({exit_code})\n')
+ test.chmod(0o755)
+ # Keep diagnostics local; no device or adb daemon is required.
+ fake_adb = root / 'adb'
+ fake_adb.write_text('#!/bin/sh\nexit 0\n')
+ fake_adb.chmod(0o755)
+ proc = subprocess.run(['bash', str(script), *args], cwd=root,
+ env=dict(os.environ, PATH=str(root) + os.pathsep + os.environ['PATH']),
+ capture_output=True, text=True, timeout=20)
+ invocation = json.loads(record.read_text()) if record.exists() else None
+ log = (root / 'shard.log').read_text() if (root / 'shard.log').exists() else ''
+ resources = (root / 'shard-resources.log').exists()
+ return proc, invocation, log, resources
+
+ def test_success_keeps_test_output_and_selector(self):
+ proc, invocation, log, resources = self.run_fake_suite(0, ['3', str(self.count)])
+ self.assertEqual(proc.returncode, 0, proc.stderr)
+ self.assertIn(f'--run=3,{self.count}', invocation)
+ self.assertEqual(invocation[-2:], ['parallel', 'sequential'])
+ self.assertIn('suite output', log)
+ self.assertTrue(resources)
+
+ def test_failure_is_not_hidden_by_tee_or_cleanup(self):
+ proc, _, log, resources = self.run_fake_suite(7, ['0', str(self.count)])
+ self.assertEqual(proc.returncode, 7, proc.stderr)
+ self.assertIn('suite output', log)
+ self.assertTrue(resources)
+
+ def test_invalid_shard_cannot_run_tests(self):
+ for args in (['-1', '16'], ['16', '16'], ['0', '0'], ['x', '16']):
+ with self.subTest(args=args):
+ proc, invocation, _, _ = self.run_fake_suite(0, args)
+ self.assertEqual(proc.returncode, 2, proc.stderr)
+ self.assertIsNone(invocation)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/scripts/test-mobile-flavors.py b/scripts/test-mobile-flavors.py
index 4a43415a57..b8c337d59a 100755
--- a/scripts/test-mobile-flavors.py
+++ b/scripts/test-mobile-flavors.py
@@ -4,6 +4,7 @@
import json
import os
from pathlib import Path
+import re
import shlex
import subprocess
import sys
@@ -15,9 +16,8 @@
SOURCE = Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else ROOT / 'out'
if len(sys.argv) > 1:
del sys.argv[1]
-MAJOR = int(next(line.strip().lstrip('v').split('.')[0]
- for line in (ROOT / 'upstream-base.txt').read_text().splitlines()
- if line.strip() and not line.startswith('#')))
+MAJOR = int(re.search(r'^#define NODE_MAJOR_VERSION (\d+)$',
+ (SOURCE / 'src/node_version.h').read_text(), re.M)[1])
# android_configure supports Linux/macOS build hosts; simulate Linux so this
# configure-only test also runs on Termux's Python (which reports Android).
ANDROID_WRAPPER = [sys.executable, '-c',
diff --git a/scripts/test-release-control.py b/scripts/test-release-control.py
index dd6846a1d2..b3b0e6d102 100644
--- a/scripts/test-release-control.py
+++ b/scripts/test-release-control.py
@@ -70,6 +70,19 @@ def test_auto_and_explicit_upstream_choose_free_revision(self):
def test_choose_exact_unused_version(self):
self.assertEqual(self.plan('v26.10.0-7')['version'], '26.10.0-7')
+ def test_upstream_pr_snapshot_builds_but_cannot_arm_or_publish(self):
+ (self.root / 'upstream-base.txt').write_text('7f68d75ee7963cd38ddc3783dd529f826bbde614\n')
+ for event in ('push', 'pull_request', 'workflow_dispatch'):
+ with self.subTest(event=event):
+ result = self.check(event=event, operation='build', version='')
+ self.assertEqual(result['release'], 'false')
+ with self.assertRaisesRegex(CONTROL.ReleaseError, 'unreleased upstream PR'):
+ self.plan()
+ for operation in ('prerelease', 'prerelease-dryrun'):
+ with self.subTest(operation=operation), self.assertRaisesRegex(
+ CONTROL.ReleaseError, 'unreleased upstream PR'):
+ self.check(operation=operation)
+
def test_plan_never_mutates_source(self):
before = {p.relative_to(self.root): p.read_bytes() for p in self.root.rglob('*') if p.is_file()}
self.plan('26.10.0-0')
diff --git a/scripts/test-upstream-clone.py b/scripts/test-upstream-clone.py
new file mode 100644
index 0000000000..2039860b04
--- /dev/null
+++ b/scripts/test-upstream-clone.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python3
+"""Verify immutable upstream clones and cache transport without public network."""
+from pathlib import Path
+import os
+import re
+import subprocess
+import tempfile
+import unittest
+
+CLONE = Path(__file__).with_name('clone-upstream.sh').resolve()
+
+
+class UpstreamClone(unittest.TestCase):
+ def setUp(self):
+ self.temp = tempfile.TemporaryDirectory(prefix='node-mobile-upstream-')
+ self.addCleanup(self.temp.cleanup)
+ self.root = Path(self.temp.name)
+ self.repo = self.root / 'upstream'
+ self.repo.mkdir()
+ self.git(self.repo, 'init', '-q')
+ self.git(self.repo, 'config', 'user.name', 'fixture')
+ self.git(self.repo, 'config', 'user.email', 'fixture@invalid')
+ (self.repo / 'version').write_text('released\n')
+ self.git(self.repo, 'add', 'version')
+ self.git(self.repo, 'commit', '-qm', 'release')
+ self.git(self.repo, 'tag', '-a', 'v26.10.0', '-m', 'release')
+ self.released = self.git(self.repo, 'rev-parse', 'HEAD')
+ (self.repo / 'version').write_text('proposal\n')
+ self.git(self.repo, 'commit', '-qam', 'proposal')
+ self.proposal = self.git(self.repo, 'rev-parse', 'HEAD')
+
+ def git(self, repo, *args):
+ return subprocess.check_output(['git', '-C', str(repo), *args], text=True).strip()
+
+ def clone(self, repo, ref, dest, *, check=True):
+ return subprocess.run([str(CLONE), repo.as_uri(), ref, str(dest)],
+ text=True, capture_output=True, check=check)
+
+ def test_sha_clone_and_cached_reclone_preserve_exact_commit_without_tags(self):
+ cache, output = self.root / 'cache', self.root / 'output'
+ self.clone(self.repo, self.proposal, cache)
+ self.clone(cache, self.proposal, output)
+ self.assertEqual(self.git(output, 'rev-parse', 'HEAD'), self.proposal)
+ self.assertEqual(self.git(output, 'rev-parse', 'refs/nodejs-mobile/upstream-base'),
+ self.proposal)
+ self.assertEqual((output / 'version').read_text(), 'proposal\n')
+ self.assertEqual(self.git(output, 'tag', '-l'), '')
+ self.assertEqual(self.git(output, 'rev-parse', '--is-shallow-repository'), 'true')
+
+ def test_annotated_release_tag_still_clones(self):
+ output = self.root / 'output'
+ self.clone(self.repo, 'v26.10.0', output)
+ self.assertEqual(self.git(output, 'rev-parse', 'HEAD'), self.released)
+ self.assertEqual(self.git(output, 'rev-parse', 'v26.10.0^{commit}'), self.released)
+
+ def test_existing_destination_is_preserved(self):
+ output = self.root / 'output'
+ output.mkdir()
+ (output / 'keep').write_text('existing work\n')
+ self.assertNotEqual(self.clone(self.repo, self.proposal, output, check=False).returncode, 0)
+ self.assertEqual((output / 'keep').read_text(), 'existing work\n')
+
+ def test_missing_sha_fails_without_checked_out_source(self):
+ output = self.root / 'output'
+ self.assertNotEqual(self.clone(self.repo, '0' * 40, output, check=False).returncode, 0)
+ self.assertFalse((output / 'version').exists())
+
+ def test_ci_upstream_check_verifies_snapshot_sha_and_rejects_mismatch(self):
+ workflow = CLONE.parent.parent / '.github/workflows/build.yml'
+ match = re.search(r' - name: Check the pinned upstream base is on this fork\n'
+ r'.*? run: \|\n((?: {10}[^\n]*\n|\n)+)',
+ workflow.read_text(), re.S)
+ self.assertIsNotNone(match)
+ script = '\n'.join(line[10:] if line else '' for line in match[1].splitlines())
+ (self.root / 'upstream-base.txt').write_text(self.proposal + '\n')
+ tools = self.root / 'tools'
+ tools.mkdir()
+ gh = tools / 'gh'
+ gh.write_text('#!/bin/sh\nprintf "%s\\n" "$*" > "$API_RECORD"\n'
+ 'printf "%s\\n" "$API_SHA"\n')
+ gh.chmod(0o755)
+ record = self.root / 'api-record'
+ env = dict(os.environ, PATH=str(tools) + os.pathsep + os.environ['PATH'],
+ API_RECORD=str(record), API_SHA=self.proposal)
+ result = subprocess.run(['bash', '-c', script], cwd=self.root,
+ env=env, capture_output=True, text=True)
+ self.assertEqual(result.returncode, 0, result.stderr)
+ self.assertIn('Verified upstream PR snapshot', result.stdout)
+ self.assertEqual(record.read_text().strip(),
+ f'api repos/nodejs/node/commits/{self.proposal} --jq .sha')
+ result = subprocess.run(['bash', '-c', script], cwd=self.root,
+ env=dict(env, API_SHA='0' * 40), capture_output=True, text=True)
+ self.assertNotEqual(result.returncode, 0)
+ self.assertIn('snapshot SHA mismatch', result.stdout)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/upstream-base.txt b/upstream-base.txt
index 8cc2e82470..0bb94b677e 100644
--- a/upstream-base.txt
+++ b/upstream-base.txt
@@ -1,4 +1,4 @@
-# The upstream nodejs/node release the patch series applies to.
-# First non-comment line is the tag (or SHA); update it as part of every
-# upstream bump (see README.md).
-v26.10.0
+# Node.js 26.11.0 release proposal, not yet an official release tag.
+# https://github.com/nodejs/node/pull/66546 (v26.11.0-proposal)
+# Pin the reviewed PR commit; do not follow the moving proposal branch.
+49072a0be4c7410982557aebf5b5c924fe7db597