Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/materialize/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
46 changes: 46 additions & 0 deletions .github/actions/upload-test-log/action.yml
Original file line number Diff line number Diff line change
@@ -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"
33 changes: 22 additions & 11 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -86,17 +86,18 @@ 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

# 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".
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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' || '' }}
Expand Down Expand Up @@ -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 }}
Expand Down Expand Up @@ -1223,13 +1224,23 @@ 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
# would kill the step before the message below ever prints.
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
Expand Down
31 changes: 20 additions & 11 deletions .github/workflows/full-device-suite.yml
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down
8 changes: 7 additions & 1 deletion .github/workflows/verify-patches.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand Down
6 changes: 4 additions & 2 deletions docs/BUILDING.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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
```

Expand Down
31 changes: 30 additions & 1 deletion docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
</tr>
<tr>
<td>
<a href="#26.11.0-0">26.11.0-0 (upstream PR preview)</a><br/>
<a href="#26.10.0-0">26.10.0-0</a><br/>
<a href="#26.1.0-0">26.1.0-0</a><br/>
<a href="#24.21.0-0">24.21.0-0</a><br/>
Expand Down Expand Up @@ -41,8 +42,36 @@
</tr>
</table>

<a id="26.11.0-0"></a>
## 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.

<a id="26.10.0-0"></a>
## 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
Expand Down
3 changes: 2 additions & 1 deletion docs/DANMU-LITE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading