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