diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 22fef9e8c220..000000000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,253 +0,0 @@ -# Node.js Project Codeowners - -# This file does not define any requirements for landing PRs. -# Its purpose is to allow automation of courtesy pings to the -# relevant team(s) when any of the paths listed here are modified. -# Criteria for landing PRs are defined in -# https://github.com/nodejs/node/blob/main/doc/contributing/collaborator-guide.md#code-reviews. -# -# Codeowners must always be teams, never individuals. - -# tsc - -/.github/CODEOWNERS @nodejs/tsc -/.github/PULL_REQUEST_TEMPLATE.md @nodejs/tsc -/.github/ISSUE_TEMPLATE/* @nodejs/tsc -/CODE_OF_CONDUCT.md @nodejs/tsc -/CONTRIBUTING.md @nodejs/tsc -/doc/contributing/**/* @nodejs/tsc -/GOVERNANCE.md @nodejs/tsc -/SECURITY.md @nodejs/tsc -/BUILDING.md @nodejs/build @nodejs/tsc -/LICENSE @nodejs/tsc -/onboarding.md @nodejs/tsc - -# nodejs.org website -/tools/doc @nodejs/web-infra - -# streams - -/lib/internal/streams/* @nodejs/streams -/lib/stream.js @nodejs/streams -/lib/stream/* @nodejs/streams - -# net - -/deps/cares @nodejs/net -/doc/api/dgram.md @nodejs/net -/doc/api/dns.md @nodejs/net -/doc/api/net.md @nodejs/net -/lib/dgram.js @nodejs/net -/lib/dns.js @nodejs/net -/lib/internal/dgram.js @nodejs/net -/lib/internal/dns/* @nodejs/net -/lib/internal/js_stream_socket.js @nodejs/net -/lib/internal/net.js @nodejs/net -/lib/internal/socket_list.js @nodejs/net -/lib/net.js @nodejs/net -/src/cares_wrap.cc @nodejs/net -/src/connect_wrap.* @nodejs/net -/src/connection_wrap.* @nodejs/net -/src/node_sockaddr* @nodejs/net -/src/tcp_wrap.* @nodejs/net -/src/udp_wrap.* @nodejs/net - -# tls/crypto - -/doc/api/crypto.md @nodejs/crypto -/doc/api/webcrypto.md @nodejs/crypto -/lib/crypto.js @nodejs/crypto -/lib/internal/crypto/* @nodejs/crypto -/lib/internal/tls/* @nodejs/crypto @nodejs/net -/lib/tls.js @nodejs/crypto @nodejs/net -/src/crypto/* @nodejs/crypto -/src/node_crypto* @nodejs/crypto -/deps/ncrypto/* @nodejs/crypto - -# http - -/deps/llhttp/* @nodejs/http @nodejs/net -/doc/api/http.md @nodejs/http @nodejs/net -/lib/_http_* @nodejs/http @nodejs/net -/lib/http.js @nodejs/http @nodejs/net -/lib/https.js @nodejs/crypto @nodejs/net @nodejs/http -/src/node_http_common* @nodejs/http @nodejs/http2 @nodejs/net -/src/node_http_parser.cc @nodejs/http @nodejs/net - -# http2 - -/deps/nghttp2/* @nodejs/http2 @nodejs/net -/doc/api/http2.md @nodejs/http2 @nodejs/http @nodejs/net -/lib/http2.js @nodejs/http2 @nodejs/net -/lib/internal/http2/* @nodejs/http2 @nodejs/net -/src/node_http2* @nodejs/http2 @nodejs/net -/src/node_mem* @nodejs/http2 - -# quic - -/deps/ngtcp2/* @nodejs/quic -/doc/api/quic.md @nodejs/quic -/lib/quic.js @nodejs/quic -/lib/internal/quic/* @nodejs/quic -/src/quic/* @nodejs/quic -/test/parallel/test-quic-* @nodejs/quic - -# modules, including loaders - -/doc/api/esm.md @nodejs/loaders -/doc/api/module.md @nodejs/loaders -/doc/api/modules.md @nodejs/loaders -/doc/api/packages.md @nodejs/loaders -/lib/internal/bootstrap/realm.js @nodejs/loaders -/lib/internal/modules/* @nodejs/loaders -/lib/internal/process/execution.js @nodejs/loaders -/lib/module.js @nodejs/loaders -/src/module_wrap* @nodejs/loaders @nodejs/vm - -# Node-API - -/doc/api/n-api.md @nodejs/node-api -/doc/contributing/adding-new-napi-api.md @nodejs/node-api -/src/js_native_api* @nodejs/node-api -/src/node_api* @nodejs/node-api - -# gyp - -*.gyp @nodejs/gyp -*.gypi @nodejs/gyp -/tools/gyp/**/* @nodejs/gyp - -# WASI - -/deps/uvwasi/ @nodejs/wasi -/doc/api/wasi.md @nodejs/wasi -/lib/wasi.js @nodejs/wasi -/src/node_wasi* @nodejs/wasi -/test/fixtures/wasi/ @nodejs/wasi -/test/wasi/ @nodejs/wasi - -# Startup - -/benchmark/misc/startup-* @nodejs/startup -/lib/internal/bootstrap/* @nodejs/startup -/src/node_builtins* @nodejs/startup -/src/node_realm* @nodejs/startup @nodejs/realm -/src/node_snapshot* @nodejs/startup -/src/node.cc @nodejs/startup -/test/parallel/test-bootstrap-* @nodejs/startup -/test/parallel/test-snapshot-* @nodejs/startup -/tools/snapshot/* @nodejs/startup - -# V8 - -/deps/v8/* @nodejs/v8-update -/tools/v8_gypfiles/* @nodejs/v8-update - -# Actions - -/.github/workflows/* @nodejs/actions -/.github/workflows/create-release-proposal.yml @nodejs/releasers -/tools/actions/* @nodejs/actions - -# Test runner - -/doc/api/test.md @nodejs/test_runner -/lib/internal/main/test_runner.js @nodejs/test_runner -/lib/internal/test_runner/* @nodejs/test_runner -/lib/test.js @nodejs/test_runner -/lib/test/reporters.js @nodejs/test_runner -/test/parallel/test-runner-* @nodejs/test_runner - -# Single Executable Applications -/deps/LIEF @nodejs/single-executable -/deps/postject @nodejs/single-executable -/doc/api/single-executable-applications.md @nodejs/single-executable -/doc/contributing/maintaining/maintaining-single-executable-application-support.md @nodejs/single-executable -/src/node_sea* @nodejs/single-executable -/test/fixtures/postject-copy @nodejs/single-executable -/test/sea @nodejs/single-executable -/tools/dep_updaters/update-postject.sh @nodejs/single-executable -/tools/dep_updaters/update-lief.sh @nodejs/single-executable - -# Permission Model -/doc/api/permissions.md @nodejs/security-wg -/lib/internal/process/permission.js @nodejs/security-wg -/src/permission/* @nodejs/security-wg -/test/parallel/test-permission-* @nodejs/security-wg - -# Security Release -/doc/contributing/security-release-process.md @nodejs/security-stewards - -# Dependency Update Tools - -/.github/workflows/tools.yml @nodejs/security-wg -/.github/workflows/update-openssl.yml @nodejs/security-wg -/.github/workflows/update-v8.yml @nodejs/security-wg @nodejs/v8-update -/deps @nodejs/security-wg -/tools/dep_updaters/* @nodejs/security-wg - -# Web Standards -/lib/internal/bootstrap/web/* @nodejs/web-standards -/lib/internal/navigator.js @nodejs/web-standards -/test/fixtures/wpt/ @nodejs/web-standards -/test/wpt/ @nodejs/web-standards - -# TypeScript - -/deps/amaro/ @nodejs/typescript -/doc/api/typescript.md @nodejs/typescript -/test/fixtures/typescript/ @nodejs/typescript -/tools/dep_updaters/update-amaro.sh @nodejs/typescript - -# Performance -/benchmark/* @nodejs/performance - -# URL -/deps/ada @nodejs/url -/lib/internal/url.js @nodejs/url -/lib/url.js @nodejs/url -/src/node_url.* @nodejs/url -/test/fixtures/wpt/url @nodejs/url - -# SQLite -/deps/sqlite/ @nodejs/sqlite -/doc/api/sqlite.md @nodejs/sqlite -/src/node_sqlite.* @nodejs/sqlite -/test/parallel/test-sqlite* @nodejs/sqlite -/test/sqlite/ @nodejs/sqlite -/tools/dep_updaters/update-sqlite.sh @nodejs/sqlite - -# Config -/doc/node-config-schema.json @nodejs/config -/src/node_config.* @nodejs/config -/src/node_dotenv.* @nodejs/config -/src/node_options.* @nodejs/config -/test/parallel/test-config-* @nodejs/config -/test/parallel/test-dotenv-* @nodejs/config - -# Inspector -/src/inspector/* @nodejs/inspector -/src/inspector_* @nodejs/inspector -/lib/internal/inspector/* @nodejs/inspector -/lib/internal/inspector_* @nodejs/inspector -/lib/inspector.js @nodejs/inspector - -# path -/lib/path.js @nodejs/path -/lib/path/* @nodejs/path -/test/parallel/test-path-* @nodejs/path - -# userland-migrations -/doc/api/deprecations.md @nodejs/userland-migrations - -# dev container -/.devcontainer/* @nodejs/devcontainer -/doc/contributing/using-devcontainer.md @nodejs/devcontainer - -# FFI -/deps/libffi/ @nodejs/ffi -/doc/api/ffi.md @nodejs/ffi -/lib/ffi.js @nodejs/ffi -/src/ffi/ @nodejs/ffi -/src/node_ffi.* @nodejs/ffi -/test/ffi/ @nodejs/ffi diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index dc01fd3fdac5..000000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,14 +0,0 @@ -blank_issues_enabled: true -contact_links: - - name: ⁉️ Need general help with Node.js? - url: https://github.com/nodejs/help - about: Please file an issue in our help repo. - - name: πŸ“¦ Have an issue with npm? - url: https://github.com/npm/cli/issues - about: npm has a separate issue tracker. - - name: πŸ“‘ Have an issue with undici? (`WebSocket`, `fetch`, etc.) - url: https://github.com/nodejs/undici/issues - about: Undici has a separate issue tracker. - - name: 🌐 Found a problem with nodejs.org beyond the API reference docs? - url: https://github.com/nodejs/nodejs.org/issues/new/choose - about: Please file an issue in the Node.js website repo. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 936c2a061257..000000000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,41 +0,0 @@ - diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md deleted file mode 100644 index 4613512bf2d0..000000000000 --- a/.github/SUPPORT.md +++ /dev/null @@ -1,27 +0,0 @@ -# Support - -Node.js contributors have limited availability to address general support -questions. Please make sure you are using a [currently-supported version of -Node.js](https://github.com/nodejs/Release#release-schedule). - -When looking for support, please first search for your question in these venues: - -* [Node.js Website](https://nodejs.org/en/), especially the - [API docs](https://nodejs.org/api/) or the [Learn material](https://nodejs.org/en/learn) -* [Node.js's GitHub Help repository](https://github.com/nodejs/help) -* [Open or closed issues in the Node.js GitHub organization](https://github.com/issues?utf8=%E2%9C%93&q=sort%3Aupdated-desc+org%3Anodejs+is%3Aissue) -* [Ask for support on Node.js's Discord server](https://nodejs.org/discord) - -If you didn't find an answer in the resources above, try these unofficial -resources: - -* [Questions tagged 'node.js' on Stack Overflow](https://stackoverflow.com/questions/tagged/node.js) -* [#node.js channel on libera.chat](https://web.libera.chat?channels=node.js&uio=d4) -* [Node.js Slack Community](https://node-js.slack.com/) - * To register: [nodeslackers.com](https://www.nodeslackers.com/) - -**GitHub issues are for tracking enhancements and bugs, not general support.** - -The open source license grants you the freedom to use Node.js. It does not -guarantee commitments of other people's time. Please be respectful and manage -your expectations. diff --git a/.github/actions/build-test-branch/action.yml b/.github/actions/build-test-branch/action.yml new file mode 100644 index 000000000000..76a5f7911976 --- /dev/null +++ b/.github/actions/build-test-branch/action.yml @@ -0,0 +1,6 @@ +name: "Build/Test" +description: "Trigger build/test run" + +runs: + using: "node20" + main: "./main.js" diff --git a/.github/actions/build-test-branch/main.js b/.github/actions/build-test-branch/main.js new file mode 100644 index 000000000000..0ace318ea981 --- /dev/null +++ b/.github/actions/build-test-branch/main.js @@ -0,0 +1,79 @@ + +const { + getLatestRevision, + sendBuildTestRequest, + newTask, +} = require("../utils"); + +const branchName = process.env.GITHUB_REF_NAME; +console.log("BranchName", branchName); + +const nodeRevision = getLatestRevision(); + +const driverRevision = process.env.INPUT_DRIVER_REVISION; +console.log("DriverRevision", driverRevision); + +const clobberInput = process.env.INPUT_CLOBBER; +console.log("Clobber", clobberInput); +const clobber = clobberInput == "true"; + +const slotInput = process.env.INPUT_SLOT; +console.log("Slot", slotInput); +const slot = slotInput ? +slotInput : undefined; + +let requestName = `Node Build/Test Branch ${branchName} ${nodeRevision}`; +if (driverRevision) { + requestName += ` driver ${driverRevision}`; +} +if (slot) { + requestName += ` slot ${slot}`; +} + +sendBuildTestRequest({ + name: requestName, + tasks: [ + ...platformTasks("macOS"), + ...platformTasks("linux"), + ], +}); + +function platformTasks(platform) { + const buildTask = newTask( + `Build Node ${platform}`, + { + kind: "BuildRuntime", + runtime: "node", + revision: nodeRevision, + branch: branchName, + branchSlot: slot, + driverRevision, + clobber, + }, + platform + ); + + const testTask = newTask( + `Test Node ${platform}`, + { + kind: "RunTestSuite", + runtime: "node", + revision: nodeRevision, + driverRevision, + }, + platform, + [buildTask] + ); + + const jestTask = newTask( + `Run random Jest tests ${platform}`, + { + kind: "JestTests", + revision: nodeRevision, + driverRevision, + }, + platform, + [buildTask] + ); + + return [buildTask, testTask, jestTask]; +} diff --git a/.github/actions/build-test/action.yml b/.github/actions/build-test/action.yml new file mode 100644 index 000000000000..76a5f7911976 --- /dev/null +++ b/.github/actions/build-test/action.yml @@ -0,0 +1,6 @@ +name: "Build/Test" +description: "Trigger build/test run" + +runs: + using: "node20" + main: "./main.js" diff --git a/.github/actions/build-test/main.js b/.github/actions/build-test/main.js new file mode 100644 index 000000000000..eace5b00c7c1 --- /dev/null +++ b/.github/actions/build-test/main.js @@ -0,0 +1,56 @@ + +const { + getLatestRevision, + sendBuildTestRequest, + newTask, +} = require("../utils"); + +const revision = getLatestRevision(); + +const clobberInput = process.env.INPUT_CLOBBER; +console.log("Clobber", clobberInput); +const clobber = clobberInput == "true"; + +sendBuildTestRequest({ + name: `Node Build/Test ${revision}`, + tasks: [ + ...platformTasks("macOS"), + ...platformTasks("linux"), + ], +}); + +function platformTasks(platform) { + const buildTask = newTask( + `Build Node ${platform}`, + { + kind: "BuildRuntime", + runtime: "node", + revision, + clobber, + }, + platform + ); + + const testTask = newTask( + `Test Node ${platform}`, + { + kind: "RunTestSuite", + runtime: "node", + revision, + }, + platform, + [buildTask] + ); + + const jestTask = newTask( + `Run random Jest tests ${platform}`, + { + kind: "JestTests", + revision, + }, + platform, + [buildTask] + ); + + return [buildTask, testTask, jestTask]; +} diff --git a/.github/actions/release/action.yml b/.github/actions/release/action.yml new file mode 100644 index 000000000000..d8e23a627422 --- /dev/null +++ b/.github/actions/release/action.yml @@ -0,0 +1,6 @@ +name: "Release" +description: "Trigger browser release" + +runs: + using: "node20" + main: "./main.js" diff --git a/.github/actions/release/main.js b/.github/actions/release/main.js new file mode 100644 index 000000000000..2353f83db454 --- /dev/null +++ b/.github/actions/release/main.js @@ -0,0 +1,30 @@ + +const { + getLatestRevision, + sendBuildTestRequest, + spawnChecked, + newTask, +} = require("../utils"); + +const revision = getLatestRevision(); + +sendBuildTestRequest({ + name: `Node Release ${revision}`, + tasks: [ + ...platformTasks("macOS"), + ...platformTasks("linux"), + ], +}); + +function platformTasks(platform) { + const releaseTask = newTask( + `Release Node ${platform}`, + { + kind: "ReleaseRuntime", + runtime: "node", + revision, + }, + platform + ); + return [releaseTask]; +} diff --git a/.github/actions/utils.js b/.github/actions/utils.js new file mode 100644 index 000000000000..f7ac64156b2e --- /dev/null +++ b/.github/actions/utils.js @@ -0,0 +1,83 @@ + +const http = require("http"); +const https = require("https"); +const { spawnSync } = require("child_process"); + +const node = `${__dirname}/../..`; + +function getLatestRevision() { + return spawnChecked("git", ["rev-parse", "--short=12", "HEAD"], { cwd: node }) + .stdout.toString() + .trim(); +} + +function sendBuildTestRequest(contents) { + const text = JSON.stringify(contents); + + const headers = { + "Content-Type": "application/json", + "Content-Length": text.length, + Authorization: process.env.BUILD_TEST_AUTHORIZATION, + }; + + // Allow overriding the build/test connection info for testing. + const options = { + hostname: process.env.BUILD_TEST_HOSTNAME || "build-test.replay.io", + port: process.env.BUILD_TEST_PORT || 443, + path: "/", + method: "POST", + headers, + }; + + const request = (process.env.BUILD_TEST_INSECURE ? http : https).request( + options, + response => { + console.log(`RequestFinished Code ${response.statusCode}`); + process.exit(response.statusCode == 200 ? 0 : 1); + } + ); + request.on("error", e => { + throw new Error(`Error contacting build/test server: ${e}`); + }); + request.write(text); + request.end(); + + setTimeout(() => { + console.log("Timed out waiting for build/test server response"); + process.exit(1); + }, 30000); +} + +function spawnChecked(cmd, args, options) { + const prettyCmd = [cmd].concat(args).join(" "); + console.error(prettyCmd); + + const rv = spawnSync(cmd, args, options); + + if (rv.status != 0 || rv.error) { + console.error(rv.error); + throw new Error(`Spawned process failed with exit code ${rv.status}`); + } + + return rv; +} + +let gNextTaskId = 1; + +function newTask(name, task, platform, dependencies = []) { + const id = gNextTaskId++; + return { + id, + name, + task, + platform, + dependencies: dependencies.map(task => task.id), + }; +} + +module.exports = { + getLatestRevision, + sendBuildTestRequest, + spawnChecked, + newTask, +}; diff --git a/.github/approveman.yml b/.github/approveman.yml new file mode 100644 index 000000000000..76f180f9df65 --- /dev/null +++ b/.github/approveman.yml @@ -0,0 +1,5 @@ +--- +ownership_rules: + directory_matching_rules: + - name: code for testing backend + path: replay-test/** diff --git a/.github/replay-soc2.yml b/.github/replay-soc2.yml new file mode 100644 index 000000000000..4b5813c59a06 --- /dev/null +++ b/.github/replay-soc2.yml @@ -0,0 +1,16 @@ +--- +# This is the block list used for the Replay SOC2 bot. +# +# An example: +# blocklist: +# - bad/** +# - rootfile +# +# Each line maps to a glob expression that gets applied to the list of files +# that the PR touches. The Replay SOC2 bot will never approve any changes to +# .github/replay_soc2.yml. The matching is done by minimatch. +# +allowlist: + - replay-test/** + - .github/** +blocklist: diff --git a/.github/workflows/auto-start-ci.yml b/.github/workflows/auto-start-ci.yml deleted file mode 100644 index 34488eeed6d7..000000000000 --- a/.github/workflows/auto-start-ci.yml +++ /dev/null @@ -1,74 +0,0 @@ -name: Auto Start CI - -on: - schedule: - # Runs every five minutes (fastest the scheduler can run). Five minutes is - # optimistic, it can take longer to run. - # To understand why `schedule` is used instead of other events, refer to - # ./doc/contributing/commit-queue.md - - cron: '*/5 * * * *' - -concurrency: ${{ github.workflow }} - -env: - NODE_VERSION: lts/* - -permissions: - contents: read - -jobs: - get-prs-for-ci: - permissions: - pull-requests: read - if: github.repository == 'nodejs/node' - runs-on: ubuntu-slim - outputs: - numbers: ${{ steps.get_prs_for_ci.outputs.numbers }} - steps: - - name: Get Pull Requests - id: get_prs_for_ci - run: > - echo "numbers=$(gh pr list \ - --repo "$GITHUB_REPOSITORY" \ - --label 'request-ci' \ - --json 'number' \ - --search 'review:approved' \ - -t '{{ range . }}{{ .number }} {{ end }}' \ - --limit 5)" >> "$GITHUB_OUTPUT" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - start-ci: - permissions: - contents: read - pull-requests: write - needs: get-prs-for-ci - if: needs.get-prs-for-ci.outputs.numbers != '' - runs-on: ubuntu-slim - steps: - - name: Install Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: ${{ env.NODE_VERSION }} - - - name: Install @node-core/utils - run: npm install -g @node-core/utils - - - name: Setup @node-core/utils - run: | - ncu-config set username "$USERNAME" - ncu-config set token "$GH_TOKEN" - ncu-config set jenkins_token "$JENKINS_TOKEN" - ncu-config set owner "$GITHUB_REPOSITORY_OWNER" - ncu-config set repo "$(echo "$GITHUB_REPOSITORY" | cut -d/ -f2)" - env: - USERNAME: ${{ secrets.JENKINS_USER }} - GH_TOKEN: ${{ secrets.GH_USER_TOKEN }} - JENKINS_TOKEN: ${{ secrets.JENKINS_TOKEN }} - - - name: Start the CI - run: | - curl -fsSL "https://github.com/${GITHUB_REPOSITORY}/raw/${GITHUB_SHA}/tools/actions/start-ci.sh" \ - | sh -s -- ${{ needs.get-prs-for-ci.outputs.numbers }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_REPO: ${{ github.repository }} diff --git a/.github/workflows/build-tarball.yml b/.github/workflows/build-tarball.yml deleted file mode 100644 index c9cf288dc938..000000000000 --- a/.github/workflows/build-tarball.yml +++ /dev/null @@ -1,141 +0,0 @@ -name: Build from tarball - -on: - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - paths-ignore: - - '**.md' - - '**.nix' - - eslint.config.mjs - - '**/eslint.config_partial.mjs' - - android-configure - - android-configure.py - - android-patches/** - - benchmarks/** - - codecov.yml - - doc/** - - pyproject.yml - - tsconfig.json - - test/internet/** - - tools/actions/** - - tools/dep_updaters/** - - tools/doc/** - - tools/eslint-rules/** - - tools/eslint/** - - tools/lint-md/** - - typings/** - - vcbuild.bat - - .** - - '!.github/workflows/build-tarball.yml' - push: - branches: - - main - - v[0-9]+.x-staging - - v[0-9]+.x - paths-ignore: - - '**.md' - - '**.nix' - - eslint.config.mjs - - '**/eslint.config_partial.mjs' - - android-configure - - android-configure.py - - android-patches/** - - benchmarks/** - - codecov.yml - - doc/** - - pyproject.yml - - tsconfig.json - - test/internet/** - - tools/actions/** - - tools/dep_updaters/** - - tools/doc/** - - tools/eslint-rules/** - - tools/eslint/** - - tools/lint-md/** - - typings/** - - vcbuild.bat - - .** - - '!.github/workflows/build-tarball.yml' - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -env: - PYTHON_VERSION: '3.14' - FLAKY_TESTS: keep_retrying - CLANG_VERSION: '19' - -permissions: - contents: read - -jobs: - build-tarball: - if: github.event.pull_request.draft == false - runs-on: ubuntu-slim - steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - persist-credentials: false - - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: ${{ env.PYTHON_VERSION }} - allow-prereleases: true - - name: Environment Information - run: npx envinfo - - 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 - - name: Upload tarball artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: tarballs - path: '*.tar.gz' - compression-level: 0 - test-tarball-linux: - needs: build-tarball - runs-on: ubuntu-24.04-arm - env: - CC: ${{ (github.base_ref == 'main' || github.ref_name == 'main') && 'sccache' || '' }} clang-19 - CXX: ${{ (github.base_ref == 'main' || github.ref_name == 'main') && 'sccache' || '' }} clang++-19 - SCCACHE_GHA_ENABLED: ${{ github.base_ref == 'main' || github.ref_name == 'main' }} - SCCACHE_IDLE_TIMEOUT: '0' - steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - persist-credentials: false - sparse-checkout: .github/actions/install-clang - sparse-checkout-cone-mode: false - - name: Install Clang ${{ env.CLANG_VERSION }} - uses: ./.github/actions/install-clang - with: - clang-version: ${{ env.CLANG_VERSION }} - - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: ${{ env.PYTHON_VERSION }} - allow-prereleases: true - - name: Set up sccache - if: github.base_ref == 'main' || github.ref_name == 'main' - uses: Mozilla-Actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # v0.0.10 - with: - version: v0.12.0 - - name: Environment Information - run: npx envinfo - - name: Download tarball - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: tarballs - path: tarballs - - name: Extract tarball - run: | - tar xzf tarballs/*.tar.gz -C "$RUNNER_TEMP" - echo "TAR_DIR=$RUNNER_TEMP/$(basename tarballs/*.tar.gz .tar.gz)" >> "$GITHUB_ENV" - - name: Build - run: make -C "$TAR_DIR" build-ci -j4 V=1 - - name: Test - run: make -C "$TAR_DIR" test-ci -j1 V=1 TEST_CI_ARGS="-p dots --measure-flakiness 9" diff --git a/.github/workflows/build-test-branch.yml b/.github/workflows/build-test-branch.yml new file mode 100644 index 000000000000..cdb55f359451 --- /dev/null +++ b/.github/workflows/build-test-branch.yml @@ -0,0 +1,36 @@ +name: Build/Test Branch +on: + workflow_dispatch: + inputs: + driver_revision: + description: "Driver revision to use, if not the latest" + required: false + clobber: + description: "Build node from scratch" + type: boolean + required: false + slot: + description: "Checked out repository slot to use" + required: false + +jobs: + build-test: + name: Trigger build/test run on current branch + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Connect to Tailscale + uses: tailscale/github-action@v3 + with: + authkey: ${{ secrets.TAILSCALE_API_KEY }} + tags: tag:node-fork-ci + - uses: actions/checkout@v2 + - uses: ./.github/actions/build-test-branch + env: + BUILD_TEST_AUTHORIZATION: ${{ secrets.BUILD_TEST_AUTHORIZATION }} + BUILD_TEST_HOSTNAME: a49855c191a944333918aea7ad31bc76-6a8a830d89921d8a.elb.us-east-2.amazonaws.com + BUILD_TEST_PORT: ${{ secrets.BUILD_TEST_PORT }} + BUILD_TEST_INSECURE: ${{ secrets.BUILD_TEST_INSECURE }} + INPUT_DRIVER_REVISION: ${{ github.event.inputs.driver_revision }} + INPUT_CLOBBER: ${{ github.event.inputs.clobber }} + INPUT_SLOT: ${{ github.event.inputs.slot }} diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml new file mode 100644 index 000000000000..aa7dc83f9f78 --- /dev/null +++ b/.github/workflows/build-test.yml @@ -0,0 +1,32 @@ +name: Build/Test +on: + workflow_dispatch: + inputs: + clobber: + description: "Build node from scratch" + type: boolean + required: false + pull_request: + push: + branches: + - master + +jobs: + build-test: + name: Trigger build/test run + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Connect to Tailscale + uses: tailscale/github-action@v3 + with: + authkey: ${{ secrets.TAILSCALE_API_KEY }} + tags: tag:node-fork-ci + - uses: actions/checkout@v2 + - uses: ./.github/actions/build-test + env: + BUILD_TEST_AUTHORIZATION: ${{ secrets.BUILD_TEST_AUTHORIZATION }} + BUILD_TEST_HOSTNAME: a49855c191a944333918aea7ad31bc76-6a8a830d89921d8a.elb.us-east-2.amazonaws.com + BUILD_TEST_PORT: ${{ secrets.BUILD_TEST_PORT }} + BUILD_TEST_INSECURE: ${{ secrets.BUILD_TEST_INSECURE }} + INPUT_CLOBBER: ${{ github.event.inputs.clobber }} diff --git a/.github/workflows/close-stalled.yml b/.github/workflows/close-stalled.yml deleted file mode 100644 index 56c0f2542b9e..000000000000 --- a/.github/workflows/close-stalled.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Close stalled issues and PRs -on: - schedule: - - cron: 0 0 * * * - -env: - CLOSE_MESSAGE: > - Closing this because it has stalled. Feel free to reopen if this issue/PR - is still relevant, or to ping the collaborator who labelled it stalled if - you have any questions. - -permissions: - contents: read - -jobs: - stale: - permissions: - issues: write # for actions/stale to close stale issues - pull-requests: write # for actions/stale to close stale PRs - if: github.repository == 'nodejs/node' - runs-on: ubuntu-slim - steps: - - uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - days-before-close: 30 - stale-pr-label: stalled - stale-issue-label: stalled - close-issue-message: ${{ env.CLOSE_MESSAGE }} - close-pr-message: ${{ env.CLOSE_MESSAGE }} - # used to filter issues to check whether or not should be closed, avoids hitting maximum operations allowed if needing to paginate through all open issues - only-labels: stalled - # max requests it will send per run to the GitHub API before it deliberately exits to avoid hitting API rate limits - operations-per-run: 500 - # deactivates automatic stale labelling as we prefer to do that manually - days-before-stale: -1 diff --git a/.github/workflows/comment-labeled.yml b/.github/workflows/comment-labeled.yml deleted file mode 100644 index e66525074841..000000000000 --- a/.github/workflows/comment-labeled.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: Comment on issues and PRs when labeled -on: - issues: - types: [labeled] - pull_request_target: - types: [labeled] - -env: - STALE_MESSAGE: > - This issue/PR was marked as stalled, it will be automatically closed in 30 days. - If it should remain open, please leave a comment explaining why it should remain open. - FAST_TRACK_MESSAGE: Fast-track has been requested by @${{ github.actor }}. Please πŸ‘ to approve. - NOTABLE_CHANGE_MESSAGE: | - The https://github.com/nodejs/node/labels/notable-change label has been added by @${{ github.actor }}. - - Please suggest a text for the release notes if you'd like to include a more detailed summary, then proceed to update the PR description with the text or a link to the notable change suggested text comment. Otherwise, the commit will be placed in the _Other Notable Changes_ section. - -permissions: - contents: read - -jobs: - stale-comment: - permissions: - issues: write - pull-requests: write - if: github.repository == 'nodejs/node' && github.event.label.name == 'stalled' - runs-on: ubuntu-slim - steps: - - name: Post stalled comment - env: - NUMBER: ${{ github.event.issue.number || github.event.pull_request.number }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: gh issue comment "$NUMBER" --repo ${{ github.repository }} --body "$STALE_MESSAGE" - - fast-track: - permissions: - pull-requests: write - if: github.repository == 'nodejs/node' && github.event_name == 'pull_request_target' && github.event.label.name == 'fast-track' - runs-on: ubuntu-slim - steps: - - name: Request Fast-Track - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: gh pr comment ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --body "$FAST_TRACK_MESSAGE" - - notable-change: - permissions: - pull-requests: write - if: github.repository == 'nodejs/node' && github.event_name == 'pull_request_target' && github.event.label.name == 'notable-change' - runs-on: ubuntu-slim - steps: - - name: Add notable change description - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: gh pr comment ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --body "$NOTABLE_CHANGE_MESSAGE" diff --git a/.github/workflows/commit-lint-problem-matcher.json b/.github/workflows/commit-lint-problem-matcher.json deleted file mode 100644 index 72dd13b9e092..000000000000 --- a/.github/workflows/commit-lint-problem-matcher.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "problemMatcher": [ - { - "owner": "core-validate-commit", - "pattern": [ - { - "regexp": "^not ok \\d+ (.*)$", - "message": 1 - } - ] - } - ] -} diff --git a/.github/workflows/commit-lint.yml b/.github/workflows/commit-lint.yml deleted file mode 100644 index 93ca3425be49..000000000000 --- a/.github/workflows/commit-lint.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: First commit message adheres to guidelines - -on: - pull_request: - branches: - - main - -env: - NODE_VERSION: lts/* - -permissions: - contents: read - -jobs: - lint-commit-message: - runs-on: ubuntu-slim - steps: - - name: Compute number of commits in the PR - id: nb-of-commits - run: | - echo "plusOne=$((${{ github.event.pull_request.commits }} + 1))" >> $GITHUB_OUTPUT - echo "minusOne=$((${{ github.event.pull_request.commits }} - 1))" >> $GITHUB_OUTPUT - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - fetch-depth: ${{ steps.nb-of-commits.outputs.plusOne }} - persist-credentials: false - - run: git reset HEAD^2 - - name: Install Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: ${{ env.NODE_VERSION }} - - name: Validate commit message - run: | - echo "::add-matcher::.github/workflows/commit-lint-problem-matcher.json" - git rev-parse HEAD~${{ steps.nb-of-commits.outputs.minusOne }} | xargs npx -q core-validate-commit --no-validate-metadata --tap diff --git a/.github/workflows/commit-queue.yml b/.github/workflows/commit-queue.yml deleted file mode 100644 index 8b5f37807b12..000000000000 --- a/.github/workflows/commit-queue.yml +++ /dev/null @@ -1,99 +0,0 @@ -# This action requires the following secrets to be set on the repository: -# GH_USER_TOKEN: GitHub user token, to be used by ncu and to push changes -# JENKINS_USER: GitHub user whose Jenkins token is defined below -# JENKINS_TOKEN: Jenkins token, to be used to check CI status - -name: Commit Queue - -on: - # `schedule` event is used instead of `pull_request` because when a - # `pull_request` event is triggered on a PR from a fork, GITHUB_TOKEN will - # be read-only, and the Action won't have access to any other repository - # secrets, which it needs to access Jenkins API. - schedule: - - cron: '*/5 * * * *' - -concurrency: ${{ github.workflow }} - -env: - NODE_VERSION: lts/* - -permissions: - contents: read - -jobs: - get_mergeable_prs: - permissions: - pull-requests: read - if: github.repository == 'nodejs/node' - runs-on: ubuntu-slim - outputs: - numbers: ${{ steps.get_mergeable_prs.outputs.numbers }} - steps: - - name: Get Pull Requests - id: get_mergeable_prs - run: | - prs=$(gh pr list \ - --repo "$GITHUB_REPOSITORY" \ - --base "$GITHUB_REF_NAME" \ - --label 'commit-queue' \ - --json 'number' \ - --search "created:<=$(date --date="2 days ago" +"%Y-%m-%dT%H:%M:%S%z") -label:blocked" \ - -t '{{ range . }}{{ .number }} {{ end }}' \ - --limit 100) - fast_track_prs=$(gh pr list \ - --repo "$GITHUB_REPOSITORY" \ - --base "$GITHUB_REF_NAME" \ - --label 'commit-queue' \ - --label 'fast-track' \ - --search "-label:blocked" \ - --json 'number' \ - -t '{{ range . }}{{ .number }} {{ end }}' \ - --limit 100) - numbers=$(echo $prs' '$fast_track_prs | jq -r -s 'unique | join(" ")') - echo "numbers=$numbers" >> "$GITHUB_OUTPUT" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - commitQueue: - needs: get_mergeable_prs - if: needs.get_mergeable_prs.outputs.numbers != '' - runs-on: ubuntu-slim - steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - # A personal token is required because pushing with GITHUB_TOKEN will - # prevent commits from running CI after they land. It needs - # to be set here because `checkout` configures GitHub authentication - # for push as well. - token: ${{ secrets.GH_USER_TOKEN }} - - # Install dependencies - - name: Install Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: ${{ env.NODE_VERSION }} - - name: Install @node-core/utils - run: npm install -g @node-core/utils - - - name: Set variables - run: | - echo "REPOSITORY=$(echo "$GITHUB_REPOSITORY" | cut -d/ -f2)" >> "$GITHUB_ENV" - - - name: Configure @node-core/utils - run: | - ncu-config set branch "${GITHUB_REF_NAME}" - ncu-config set upstream origin - ncu-config set username "$USERNAME" - ncu-config set token "$GITHUB_TOKEN" - ncu-config set jenkins_token "$JENKINS_TOKEN" - ncu-config set repo "${REPOSITORY}" - ncu-config set owner "${GITHUB_REPOSITORY_OWNER}" - env: - USERNAME: ${{ secrets.JENKINS_USER }} - GITHUB_TOKEN: ${{ secrets.GH_USER_TOKEN }} - JENKINS_TOKEN: ${{ secrets.JENKINS_TOKEN }} - - - name: Start the Commit Queue - run: ./tools/actions/commit-queue.sh "${GITHUB_REPOSITORY_OWNER}" "${REPOSITORY}" ${{ needs.get_mergeable_prs.outputs.numbers }} - env: - GITHUB_TOKEN: ${{ secrets.GH_USER_TOKEN }} diff --git a/.github/workflows/coverage-linux.yml b/.github/workflows/coverage-linux.yml deleted file mode 100644 index 4a60b092dbc9..000000000000 --- a/.github/workflows/coverage-linux.yml +++ /dev/null @@ -1,92 +0,0 @@ -name: Coverage Linux - -on: - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - paths: - - lib/**/*.js - - Makefile - - src/**/*.cc - - src/**/*.h - - test/** - - tools/gyp/** - - tools/test.py - - .github/workflows/coverage-linux.yml - - codecov.yml - - .nycrc - push: - branches: - - main - paths: - - lib/**/*.js - - Makefile - - src/**/*.cc - - src/**/*.h - - test/** - - tools/gyp/** - - tools/test.py - - .github/workflows/coverage-linux.yml - - codecov.yml - - .nycrc - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -env: - PYTHON_VERSION: '3.14' - FLAKY_TESTS: keep_retrying - CLANG_VERSION: '19' - CC: ${{ (github.base_ref == 'main' || github.ref_name == 'main') && 'sccache' || '' }} clang-19 - CXX: ${{ (github.base_ref == 'main' || github.ref_name == 'main') && 'sccache' || '' }} clang++-19 - SCCACHE_GHA_ENABLED: ${{ github.base_ref == 'main' || github.ref_name == 'main' }} - SCCACHE_IDLE_TIMEOUT: '0' - -permissions: - contents: read - -jobs: - coverage-linux: - if: github.event.pull_request.draft == false - runs-on: ubuntu-24.04-arm - steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - persist-credentials: false - - name: Install Clang ${{ env.CLANG_VERSION }} - uses: ./.github/actions/install-clang - with: - clang-version: ${{ env.CLANG_VERSION }} - - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: ${{ env.PYTHON_VERSION }} - allow-prereleases: true - - name: Set up sccache - if: github.base_ref == 'main' || github.ref_name == 'main' - uses: Mozilla-Actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # v0.0.10 - with: - version: v0.12.0 - - name: Environment Information - run: npx envinfo - - name: Install gcovr - run: pip install gcovr==7.2 - - name: Configure - run: ./configure --verbose --error-on-warn --coverage - # TODO(bcoe): fix the couple tests that fail with the inspector enabled. - # The cause is most likely coverage's use of the inspector. - - name: Build and test - run: NODE_V8_COVERAGE=coverage/tmp make test-cov -j4 V=1 TEST_CI_ARGS="-p dots --measure-flakiness 9" || exit 0 - - name: Report JS - run: npx c8 report --check-coverage - env: - NODE_OPTIONS: --max-old-space-size=8192 - - name: Report C++ - run: gcovr --object-directory=out -v --filter src --xml -o ./coverage/coverage-cxx.xml --root=./ --gcov-executable="llvm-cov-18 gcov" - # Clean temporary output from gcov and c8, so that it's not uploaded: - - name: Clean tmp - run: rm -rf coverage/tmp && rm -rf out - - name: Upload - uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1 - with: - directory: ./coverage diff --git a/.github/workflows/coverage-windows.yml b/.github/workflows/coverage-windows.yml deleted file mode 100644 index 607e27a8d6cd..000000000000 --- a/.github/workflows/coverage-windows.yml +++ /dev/null @@ -1,105 +0,0 @@ -name: Coverage Windows - -on: - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - paths-ignore: - - '**.md' - - '**.nix' - - eslint.config.mjs - - '**/eslint.config_partial.mjs' - - android-configure - - android-configure.py - - android-patches/** - - benchmarks/** - - doc/** - - pyproject.yml - - tsconfig.json - - test/internet/** - - tools/actions/** - - tools/bootstrap/** - - tools/dep_updaters/** - - tools/doc/** - - tools/eslint-rules/** - - tools/eslint/** - - tools/lint-md/** - - typings/** - - .** - - '!.github/workflows/coverage-windows.yml' - push: - branches: - - main - paths-ignore: - - '**.md' - - '**.nix' - - eslint.config.mjs - - '**/eslint.config_partial.mjs' - - android-configure - - android-configure.py - - android-patches/** - - benchmarks/** - - doc/** - - pyproject.yml - - tsconfig.json - - test/internet/** - - tools/actions/** - - tools/bootstrap/** - - tools/dep_updaters/** - - tools/doc/** - - tools/eslint-rules/** - - tools/eslint/** - - tools/lint-md/** - - typings/** - - .** - - '!.github/workflows/coverage-windows.yml' - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -env: - PYTHON_VERSION: '3.14' - FLAKY_TESTS: keep_retrying - -permissions: - contents: read - -jobs: - coverage-windows: - if: github.event.pull_request.draft == false - runs-on: windows-2025 - steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - persist-credentials: false - - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: ${{ env.PYTHON_VERSION }} - allow-prereleases: true - - name: Install deps - run: choco install nasm - - name: Install Rust ${{ env.RUSTC_VERSION }} - run: | - rustup override set "$RUSTC_VERSION" - rustup --version - - name: Environment Information - run: npx envinfo - - name: Build - run: ./vcbuild.bat clang-cl v8temporal - # TODO(bcoe): investigate tests that fail with coverage enabled - # on Windows. - - name: Test - run: ./vcbuild.bat noprojgen nobuild test-ci-js; node -e 'process.exit(0)' - env: - NODE_V8_COVERAGE: ./coverage/tmp - - name: Report - run: npx c8 report - env: - NODE_OPTIONS: --max-old-space-size=8192 - - name: Clean tmp - run: npx rimraf ./coverage/tmp - - name: Upload - uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1 - with: - directory: ./coverage diff --git a/.github/workflows/daily.yml b/.github/workflows/daily.yml deleted file mode 100644 index 23ffe66fb244..000000000000 --- a/.github/workflows/daily.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Node.js daily job - -on: - workflow_dispatch: - schedule: - - cron: 0 0 * * * - -env: - NODE_VERSION: lts/* - -permissions: - contents: read - -jobs: - build-lto: - if: github.repository == 'nodejs/node' || github.event_name == 'workflow_dispatch' - runs-on: ubuntu-24.04-arm - steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - persist-credentials: false - - name: Use Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: ${{ env.NODE_VERSION }} - - name: Environment Information - run: npx envinfo - - name: Build lto - run: | - sudo apt-get update && sudo apt-get install ninja-build -y - ./configure --enable-lto --ninja - ninja -C out/Release diff --git a/.github/workflows/find-inactive-collaborators.yml b/.github/workflows/find-inactive-collaborators.yml deleted file mode 100644 index 35a0f4b1f5c7..000000000000 --- a/.github/workflows/find-inactive-collaborators.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Find inactive collaborators - -on: - schedule: - # Run every Monday at 4:05 AM UTC. - - cron: 5 4 * * 1 - - workflow_dispatch: - -env: - NODE_VERSION: lts/* - -permissions: - contents: read - -jobs: - find: - if: github.repository == 'nodejs/node' - runs-on: ubuntu-slim - - steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - fetch-depth: 0 - persist-credentials: false - - - name: Use Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: ${{ env.NODE_VERSION }} - - - name: Find inactive collaborators - run: tools/find-inactive-collaborators.mjs - - - name: Open pull request - uses: gr2m/create-or-update-pull-request-action@b65137ca591da0b9f43bad7b24df13050ea45d1b # v1.10.1 - # Creates a PR or update the Action's existing PR, or - # no-op if the base branch is already up-to-date. - env: - GITHUB_TOKEN: ${{ secrets.GH_USER_TOKEN }} - with: - author: Node.js GitHub Bot - branch: actions/inactive-collaborators - body: | - This PR was generated by the [`find-inactive-collaborators.yml` workflow](https://github.com/nodejs/node/blob/main/.github/workflows/find-inactive-collaborators.yml). - - @nodejs/tsc Please follow up with the [offboarding tasks](https://github.com/nodejs/node/blob/main/doc/contributing/offboarding.md). - commit-message: 'meta: move one or more collaborators to emeritus' - labels: meta - title: 'meta: move one or more collaborators to emeritus' diff --git a/.github/workflows/find-inactive-tsc.yml b/.github/workflows/find-inactive-tsc.yml deleted file mode 100644 index 8deeaccf5dfd..000000000000 --- a/.github/workflows/find-inactive-tsc.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: Find inactive TSC voting members - -on: - schedule: - # Run every Tuesday 12:05 AM UTC. - - cron: 5 0 * * 2 - - workflow_dispatch: - -env: - NODE_VERSION: lts/* - -permissions: - contents: read - -jobs: - find: - if: github.repository == 'nodejs/node' - runs-on: ubuntu-slim - - steps: - - name: Checkout the repo - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - fetch-depth: 0 - persist-credentials: false - - - name: Clone nodejs/TSC repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - fetch-depth: 0 - path: .tmp - persist-credentials: false - repository: nodejs/TSC - - - name: Use Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: ${{ env.NODE_VERSION }} - - - name: Find inactive TSC voting members - run: tools/find-inactive-tsc.mjs >> $GITHUB_ENV - - - name: Open pull request - uses: gr2m/create-or-update-pull-request-action@b65137ca591da0b9f43bad7b24df13050ea45d1b # v1.10.1 - # Creates a PR or update the Action's existing PR, or - # no-op if the base branch is already up-to-date. - env: - GITHUB_TOKEN: ${{ secrets.GH_USER_TOKEN }} - with: - author: Node.js GitHub Bot - branch: actions/inactive-tsc - body: | - This PR was generated by tools/find-inactive-tsc.yml. - - @nodejs/tsc ${{ env.INACTIVE_TSC_HANDLES }} - - ${{ env.DETAILS_FOR_COMMIT_BODY }} - commit-message: 'meta: move TSC voting member(s) to regular member(s)' - labels: meta - title: 'meta: move TSC voting member(s) to regular member(s)' - update-pull-request-title-and-body: true diff --git a/.github/workflows/label-pr.yml b/.github/workflows/label-pr.yml deleted file mode 100644 index 03aa1fa9a8d0..000000000000 --- a/.github/workflows/label-pr.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Label PRs - -on: - pull_request_target: - types: [opened] - -permissions: - contents: read - -jobs: - label: - runs-on: ubuntu-slim - - steps: - - uses: nodejs/node-pr-labeler@d4cf1b8b9f23189c37917000e5e17e796c770a6b # v1 - with: - repo-token: ${{ secrets.GH_USER_TOKEN }} - configuration-path: .github/label-pr-config.yml diff --git a/.github/workflows/license-builder.yml b/.github/workflows/license-builder.yml deleted file mode 100644 index 36d303f9e121..000000000000 --- a/.github/workflows/license-builder.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: License update -on: - schedule: - # 00:00:00 every Monday - # https://crontab.guru/#0_0_*_*_1 - - cron: 0 0 * * 1 - workflow_dispatch: - -permissions: - contents: read - -jobs: - update_license: - permissions: - contents: write # for gr2m/create-or-update-pull-request-action to push local changes - pull-requests: write # for gr2m/create-or-update-pull-request-action to create a PR - if: github.repository == 'nodejs/node' - runs-on: ubuntu-slim - steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - persist-credentials: false - - run: ./tools/license-builder.sh # Run the license builder tool - - uses: gr2m/create-or-update-pull-request-action@b65137ca591da0b9f43bad7b24df13050ea45d1b # v1.10.1 - # Creates a PR or update the Action's existing PR, or - # no-op if the base branch is already up-to-date. - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - author: Node.js GitHub Bot - branch: actions/license-builder - title: 'doc: run license-builder' - body: > - License is likely out of date. This is an automatically generated PR by - the `license-builder.yml` GitHub Action, which runs `license-builder.sh` - and submits a new PR or updates an existing PR. - commit-message: 'doc: run license-builder' - labels: meta diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml deleted file mode 100644 index eb107edccdd9..000000000000 --- a/.github/workflows/linters.yml +++ /dev/null @@ -1,285 +0,0 @@ -name: Linters - -on: - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - push: - branches: - - main - - v[0-9]+.x-staging - - v[0-9]+.x - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -env: - PYTHON_VERSION: '3.14' - NODE_VERSION: lts/* - -permissions: - contents: read - -jobs: - lint-addon-docs: - if: github.event.pull_request.draft == false - runs-on: ubuntu-slim - steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - persist-credentials: false - - name: Use Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: ${{ env.NODE_VERSION }} - - name: Environment Information - run: npx envinfo - - name: Lint addon docs - run: NODE=$(command -v node) make lint-addon-docs - lint-cpp: - if: github.event.pull_request.draft == false - runs-on: ubuntu-slim - steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - persist-credentials: false - - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: ${{ env.PYTHON_VERSION }} - allow-prereleases: true - - name: Environment Information - run: npx envinfo - - name: Lint C/C++ files - run: make lint-cpp - format-cpp: - if: ${{ github.event.pull_request && github.event.pull_request.draft == false && github.base_ref == github.event.repository.default_branch }} - runs-on: ubuntu-slim - steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - fetch-depth: 0 - persist-credentials: false - - name: Use Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: ${{ env.NODE_VERSION }} - - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: ${{ env.PYTHON_VERSION }} - allow-prereleases: true - - name: Environment Information - run: npx envinfo - - name: Format C/C++ files - run: | - make format-cpp-build - # The `make format-cpp` error code is intentionally ignored here - # 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)" \ - make format-cpp || true - git --no-pager diff --exit-code && EXIT_CODE="$?" || EXIT_CODE="$?" - if [ "$EXIT_CODE" != "0" ] - then - echo - echo 'ERROR: Please run:' - echo - echo " CLANG_FORMAT_START="$\(git merge-base HEAD ${GITHUB_BASE_REF}\)" make format-cpp" - echo - echo 'to format the commits in your branch.' - exit "$EXIT_CODE" - fi - lint-js-and-md: - if: github.event.pull_request.draft == false - runs-on: ubuntu-slim - steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - persist-credentials: false - - name: Use Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: ${{ env.NODE_VERSION }} - - name: Environment Information - run: npx envinfo - - name: Lint JavaScript files - run: | - set +e - NODE=$(command -v node) make lint-js - EXIT_CODE="$?" - if [ "$EXIT_CODE" != "0" ]; then - echo - echo 'ERROR: The JavaScript lint validation failed (the errors are logged above).' - echo ' Please fix the lint errors.' - if NODE=$(command -v node) make lint-js-fix > /dev/null 2>&1; then - echo ' Run:' - echo ' make lint-js-fix' - echo ' to fix the lint issues.' - git --no-pager diff - elif git diff --quiet --exit-code; then - echo ' None of the issue is auto-fixable, so manual fixes for' - echo ' all of the issues are required.' - else - echo ' Run:' - echo ' make lint-js-fix' - echo ' to fix the auto-fixable lint issues.' - echo ' Note that some manual fixes are also required.' - fi - echo - exit "$EXIT_CODE" - fi - - name: Get release version numbers - if: ${{ github.event.pull_request && github.event.pull_request.base.ref == github.event.pull_request.base.repo.default_branch }} - id: get-released-versions - run: ./tools/lint-md/list-released-versions-from-changelogs.mjs >> $GITHUB_OUTPUT - - name: Lint markdown files - run: | - echo "::add-matcher::.github/workflows/remark-lint-problem-matcher.json" - NODE=$(command -v node) make lint-md - env: - NODE_RELEASED_VERSIONS: ${{ steps.get-released-versions.outputs.NODE_RELEASED_VERSIONS }} - lint-nix: - if: github.event.pull_request.draft == false - runs-on: ubuntu-slim - steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - persist-credentials: false - sparse-checkout: '*.nix' - sparse-checkout-cone-mode: false - - uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6 - - name: Lint Nix files - run: | - nix-shell -I nixpkgs=./tools/nix/pkgs.nix -p 'nixfmt-tree' --run ' - treefmt --quiet --ci - ' && EXIT_CODE="$?" || EXIT_CODE="$?" - if [ "$EXIT_CODE" != "0" ] - then - git --no-pager diff || true - exit "$EXIT_CODE" - fi - - lint-py: - if: github.event.pull_request.draft == false - runs-on: ubuntu-slim - steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - persist-credentials: false - sparse-checkout: | - /Makefile - /benchmark/ - /doc/ - /lib/ - /src/node_version.h - /tools/ - pyproject.toml - *.py - sparse-checkout-cone-mode: false - - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: ${{ env.PYTHON_VERSION }} - allow-prereleases: true - - name: Environment Information - run: npx envinfo - - name: Lint Python - run: | - make lint-py-build - make lint-py - lint-yaml: - if: github.event.pull_request.draft == false - runs-on: ubuntu-slim - steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - persist-credentials: false - sparse-checkout: | - /Makefile - /tools/pip/ - *.yml - *.yaml - sparse-checkout-cone-mode: false - - name: Use Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: ${{ env.PYTHON_VERSION }} - allow-prereleases: true - - name: Environment Information - run: npx envinfo - - name: Lint YAML - run: | - make lint-yaml-build || true - make lint-yaml - - lint-sh: - if: github.event.pull_request.draft == false - runs-on: ubuntu-slim - steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - persist-credentials: false - sparse-checkout: | - /tools/lint-sh.mjs - *.sh - sparse-checkout-cone-mode: false - - run: shellcheck -V - - name: Lint Shell scripts - run: tools/lint-sh.mjs . - lint-codeowners: - if: github.event.pull_request.draft == false - # cannot use ubuntu-slim here because mszostok/codeowners-validator is dockerized - # cannot use ubuntu-24.04-arm here because the docker image is x86 only - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - persist-credentials: false - - uses: mszostok/codeowners-validator@7f3f5e28c6d7b8dfae5731e54ce2272ca384592f - with: - checks: files,duppatterns - lint-pr-url: - if: ${{ github.event.pull_request }} - runs-on: ubuntu-slim - steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - fetch-depth: 2 - persist-credentials: false - sparse-checkout: | - /tools/lint-pr-url.mjs - /doc/api/ - sparse-checkout-cone-mode: false - # GH Actions squashes all PR commits, HEAD^ refers to the base branch. - - run: git diff HEAD^ HEAD -G"pr-url:" -- "*.md" | ./tools/lint-pr-url.mjs ${{ github.event.pull_request.html_url }} - lint-readme: - runs-on: ubuntu-slim - steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - persist-credentials: false - sparse-checkout: | - README.md - /tools/lint-readme-lists.mjs - sparse-checkout-cone-mode: false - - name: Get team members if possible - if: ${{ (github.event.pull_request && github.event.pull_request.base.ref == github.event.pull_request.base.repo.default_branch) || github.event.ref == github.event.repository.default_branch }} - id: team_members - run: | - get_list_members() { - TEAM="$1" - QUOTE='"' - gh api "/orgs/nodejs/teams/$TEAM/members" -X GET -f per_page=100 --jq "map(.login) | ${QUOTE}${TEAM}=\(tojson)${QUOTE}" - } - [ -z "$GITHUB_TOKEN" ] || ( - get_list_members "collaborators" - get_list_members "issue-triage" - get_list_members "tsc" - ) >> "$GITHUB_OUTPUT" - env: - GITHUB_TOKEN: ${{ secrets.GH_USER_TOKEN }} - - run: tools/lint-readme-lists.mjs "$TEAMS" - env: - TEAMS: ${{ tojson(steps.team_members.outputs) }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000000..0e21ee2c4df6 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,22 @@ +name: Release Latest Browsers +on: + workflow_dispatch: + +jobs: + release: + name: Trigger release run + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Connect to Tailscale + uses: tailscale/github-action@v3 + with: + authkey: ${{ secrets.TAILSCALE_API_KEY }} + tags: tag:node-fork-ci + - uses: actions/checkout@v2 + - uses: ./.github/actions/release + env: + BUILD_TEST_AUTHORIZATION: ${{ secrets.BUILD_TEST_AUTHORIZATION }} + BUILD_TEST_HOSTNAME: a49855c191a944333918aea7ad31bc76-6a8a830d89921d8a.elb.us-east-2.amazonaws.com + BUILD_TEST_PORT: ${{ secrets.BUILD_TEST_PORT }} + BUILD_TEST_INSECURE: ${{ secrets.BUILD_TEST_INSECURE }} diff --git a/.github/workflows/remark-lint-problem-matcher.json b/.github/workflows/remark-lint-problem-matcher.json deleted file mode 100644 index cfb281310a9a..000000000000 --- a/.github/workflows/remark-lint-problem-matcher.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "problemMatcher": [ - { - "owner": "remark-lint", - "pattern": [ - { - "regexp": "^(?:\\x1b\\[\\d+m)*(.+?)(?:\\x1b\\[\\d+m)*$", - "file": 1 - }, - { - "regexp": "^\\s+(?:\\d+:\\d+-)?(\\d+):(\\d+)\\s+\\S*(error|warning|info)\\S*\\s+(.+)\\s+(\\S+)\\s+(?:\\S+)$", - "line": 1, - "column": 2, - "severity": 3, - "message": 4, - "code": 5, - "loop": true - } - ] - } - ] -} diff --git a/.github/workflows/test-internet.yml b/.github/workflows/test-internet.yml deleted file mode 100644 index cb8759c559ae..000000000000 --- a/.github/workflows/test-internet.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: Test internet - -on: - workflow_dispatch: - schedule: - - cron: 5 0 * * * - - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - paths: - - .github/workflows/test-internet.yml - - test/internet/** - - internal/dns/** - - lib/dns.js - - lib/net.js - push: - branches: - - main - - canary - - v[0-9]+.x-staging - - v[0-9]+.x - paths: - - .github/workflows/test-internet.yml - - test/internet/** - - internal/dns/** - - lib/dns.js - - lib/net.js - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -env: - PYTHON_VERSION: '3.14' - FLAKY_TESTS: keep_retrying - CLANG_VERSION: '19' - CC: ${{ (github.base_ref == 'main' || github.ref_name == 'main') && 'sccache' || '' }} clang-19 - CXX: ${{ (github.base_ref == 'main' || github.ref_name == 'main') && 'sccache' || '' }} clang++-19 - SCCACHE_GHA_ENABLED: ${{ github.base_ref == 'main' || github.ref_name == 'main' }} - SCCACHE_IDLE_TIMEOUT: '0' - -permissions: - contents: read - -jobs: - test-internet: - if: (github.event_name == 'schedule' && github.repository == 'nodejs/node') || (github.event.pull_request && github.event.pull_request.draft == false) - runs-on: ubuntu-24.04-arm - steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - persist-credentials: false - - name: Install Clang ${{ env.CLANG_VERSION }} - uses: ./.github/actions/install-clang - with: - clang-version: ${{ env.CLANG_VERSION }} - - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: ${{ env.PYTHON_VERSION }} - allow-prereleases: true - - name: Set up sccache - if: github.base_ref == 'main' || github.ref_name == 'main' - uses: Mozilla-Actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # v0.0.10 - with: - version: v0.12.0 - - name: Environment Information - run: npx envinfo - - name: Build - run: make build-ci -j4 V=1 CONFIG_FLAGS="--error-on-warn" - - name: Test Internet - run: make test-internet -j4 V=1; diff --git a/.github/workflows/test-linux.yml b/.github/workflows/test-linux.yml deleted file mode 100644 index 4dbc51909191..000000000000 --- a/.github/workflows/test-linux.yml +++ /dev/null @@ -1,100 +0,0 @@ -name: Test Linux - -on: - pull_request: - paths-ignore: - - .mailmap - - README.md - - vcbuild.bat - - tools/actions/** - - tools/clang-format/** - - tools/dep_updaters/** - - test/internet/** - - '**.nix' - - .github/** - - '!.github/workflows/test-linux.yml' - types: [opened, synchronize, reopened, ready_for_review] - push: - branches: - - main - - canary - - v[0-9]+.x-staging - - v[0-9]+.x - paths-ignore: - - .mailmap - - README.md - - vcbuild.bat - - tools/actions/** - - tools/clang-format/** - - tools/dep_updaters/** - - test/internet/** - - '**.nix' - - .github/** - - '!.github/workflows/test-linux.yml' - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -env: - PYTHON_VERSION: '3.14' - FLAKY_TESTS: keep_retrying - CLANG_VERSION: '19' - CC: ${{ (github.base_ref == 'main' || github.ref_name == 'main') && 'sccache' || '' }} clang-19 - CXX: ${{ (github.base_ref == 'main' || github.ref_name == 'main') && 'sccache' || '' }} clang++-19 - SCCACHE_GHA_ENABLED: ${{ github.base_ref == 'main' || github.ref_name == 'main' }} - SCCACHE_IDLE_TIMEOUT: '0' - RUSTC_VERSION: '1.86' - -permissions: - contents: read - -jobs: - test-linux: - if: github.event.pull_request.draft == false - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [ubuntu-24.04, ubuntu-24.04-arm] - steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - persist-credentials: false - path: node - - name: Install Clang ${{ env.CLANG_VERSION }} - uses: ./node/.github/actions/install-clang - with: - clang-version: ${{ env.CLANG_VERSION }} - - name: Install Rust ${{ env.RUSTC_VERSION }} - run: | - rustup override set "$RUSTC_VERSION" - rustup --version - - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: ${{ env.PYTHON_VERSION }} - allow-prereleases: true - - name: Set up sccache - if: github.base_ref == 'main' || github.ref_name == 'main' - uses: Mozilla-Actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # v0.0.10 - with: - version: v0.12.0 - - name: Environment Information - run: npx envinfo - - name: Build - working-directory: node - run: make build-ci -j4 V=1 CONFIG_FLAGS="--error-on-warn --v8-enable-temporal-support" - - name: Test - working-directory: node - run: make test-ci -j1 V=1 TEST_CI_ARGS="-p actions --measure-flakiness 9" - - name: Ensure running tests did not cause any change in the tree - working-directory: node - run: git add -A && git diff --name-only --exit-code --staged - - name: Re-run test in a folder whose name contains unusual chars - run: | - mv node "$DIR" - cd "$DIR" - ./tools/test.py --flaky-tests keep_retrying -p actions -j 4 - env: - DIR: dir%20with $unusual"chars?'Γ₯ΓŸβˆ‚Ζ’Β©βˆ†Β¬β€¦` diff --git a/.github/workflows/test-macos.yml b/.github/workflows/test-macos.yml deleted file mode 100644 index cf22fb2e6a5d..000000000000 --- a/.github/workflows/test-macos.yml +++ /dev/null @@ -1,142 +0,0 @@ -name: Test macOS - -on: - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - paths-ignore: - - '**.md' - - '**.nix' - - eslint.config.mjs - - '**/eslint.config_partial.mjs' - - android-configure - - android-configure.py - - android-patches/** - - benchmarks/** - - codecov.yml - - doc/** - - pyproject.yml - - tsconfig.json - - test/internet/** - - tools/actions/** - - tools/dep_updaters/** - - tools/doc/** - - tools/eslint-rules/** - - tools/eslint/** - - tools/lint-md/** - - typings/** - - vcbuild.bat - - .** - - '!.github/workflows/test-macos.yml' - push: - branches: - - main - - canary - - v[0-9]+.x-staging - - v[0-9]+.x - paths-ignore: - - '**.md' - - '**.nix' - - eslint.config.mjs - - '**/eslint.config_partial.mjs' - - android-configure - - android-configure.py - - android-patches/** - - benchmarks/** - - codecov.yml - - doc/** - - pyproject.yml - - tsconfig.json - - test/internet/** - - tools/actions/** - - tools/dep_updaters/** - - tools/doc/** - - tools/eslint-rules/** - - tools/eslint/** - - tools/lint-md/** - - typings/** - - vcbuild.bat - - .** - - '!.github/workflows/test-macos.yml' - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -env: - PYTHON_VERSION: '3.14' - XCODE_VERSION: '16.4' - FLAKY_TESTS: keep_retrying - RUSTC_VERSION: '1.86' - -permissions: - contents: read - -jobs: - test-macOS: - if: github.event.pull_request.draft == false - strategy: - fail-fast: false - runs-on: macos-15 - env: - CC: ${{ (github.base_ref == 'main' || github.ref_name == 'main') && 'sccache' || '' }} gcc - CXX: ${{ (github.base_ref == 'main' || github.ref_name == 'main') && 'sccache' || '' }} g++ - SCCACHE_GHA_ENABLED: ${{ github.base_ref == 'main' || github.ref_name == 'main' }} - SCCACHE_IDLE_TIMEOUT: '0' - steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - persist-credentials: false - path: node - - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: ${{ env.PYTHON_VERSION }} - allow-prereleases: true - - name: Set up Xcode ${{ env.XCODE_VERSION }} - run: sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app - - name: Install Rust ${{ env.RUSTC_VERSION }} - run: | - rustup override set "$RUSTC_VERSION" - rustup --version - - name: Set up sccache - if: github.base_ref == 'main' || github.ref_name == 'main' - uses: Mozilla-Actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # v0.0.10 - with: - version: v0.12.0 - - name: Environment Information - run: npx envinfo - # The `npm ci` for this step fails a lot as part of the Test step. Run it - # now so that we don't have to wait 2 hours for the Build step to pass - # first before that failure happens. (And if there's something about - # `make run-ci -j3` that is causing the failure and the failure doesn't - # happen anymore running this step here first, that's also useful - # information.) - - name: tools/doc/node_modules workaround - run: make -C node tools/doc/node_modules - # This is needed due to https://github.com/nodejs/build/issues/3878 - - name: Cleanup - run: | - echo "::group::Free space before cleanup" - df -h - echo "::endgroup::" - echo "::group::Cleaned Files" - - sudo rm -rf /Users/runner/Library/Android/sdk - - echo "::endgroup::" - echo "::group::Free space after cleanup" - df -h - echo "::endgroup::" - - name: Build - run: make -C node build-ci -j$(getconf _NPROCESSORS_ONLN) V=1 CONFIG_FLAGS="--error-on-warn --v8-enable-temporal-support" - - name: Free Space After Build - run: df -h - - name: Test - run: make -C node test-ci -j1 V=1 TEST_CI_ARGS="-p actions --measure-flakiness 9" - - name: Re-run test in a folder whose name contains unusual chars - run: | - mv node "$DIR" - cd "$DIR" - ./tools/test.py --flaky-tests keep_retrying -p actions -j 4 - env: - DIR: dir%20with $unusual"chars?'Γ₯ΓŸβˆ‚Ζ’Β©βˆ†Β¬β€¦` diff --git a/.github/workflows/tools.yml b/.github/workflows/tools.yml deleted file mode 100644 index 7966102b4676..000000000000 --- a/.github/workflows/tools.yml +++ /dev/null @@ -1,346 +0,0 @@ -name: Tools and deps update -on: - schedule: - # Run once a week at 00:05 AM UTC on Sunday. - - cron: 5 0 * * 0 - - workflow_dispatch: - inputs: - id: - description: The ID of the job to run - required: true - default: all - type: choice - options: - - all - - acorn - - acorn-walk - - ada - - amaro - - brotli - - c-ares - - merve - - corepack - - googletest - - gyp-next - - histogram - - icu - - inspector_protocol - - libffi - - libuv - - llhttp - - minimatch - - nbytes - - nixpkgs-unstable - - nghttp2 - - nghttp3 - - ngtcp2 - - postject - - root-certificates - - simdjson - - sqlite - - test426-fixtures - - undici - - uvwasi - - zlib - - zstd - -env: - PYTHON_VERSION: '3.14' - -permissions: - contents: read - -jobs: - tools-deps-update: - if: github.repository == 'nodejs/node' || github.event_name == 'workflow_dispatch' - # cannot use ubuntu-slim here because some update scripts require Docker - runs-on: ubuntu-latest - strategy: - fail-fast: false # Prevent other jobs from aborting if one fails - matrix: - include: - - id: acorn - subsystem: deps - label: dependencies - run: | - ./tools/dep_updaters/update-acorn.sh > temp-output - cat temp-output - tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true - rm temp-output - - id: acorn-walk - subsystem: deps - label: dependencies - run: | - ./tools/dep_updaters/update-acorn-walk.sh > temp-output - cat temp-output - tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true - rm temp-output - - id: ada - subsystem: deps - label: dependencies - run: | - ./tools/dep_updaters/update-ada.sh > temp-output - cat temp-output - tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true - rm temp-output - - id: amaro - subsystem: deps - label: dependencies, strip-types - run: | - ./tools/dep_updaters/update-amaro.sh > temp-output - cat temp-output - tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true - rm temp-output - - id: brotli - subsystem: deps - label: dependencies, zlib - run: | - ./tools/dep_updaters/update-brotli.sh > temp-output - cat temp-output - tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true - rm temp-output - - id: c-ares - subsystem: deps - label: dependencies, cares - run: | - ./tools/dep_updaters/update-c-ares.sh > temp-output - cat temp-output - tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true - rm temp-output - - id: merve - subsystem: deps - label: dependencies - run: | - ./tools/dep_updaters/update-merve.sh > temp-output - cat temp-output - tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true - rm temp-output - - id: corepack - subsystem: deps - label: dependencies - run: | - make corepack-update - echo "NEW_VERSION=$(node deps/corepack/dist/corepack.js --version)" >> $GITHUB_ENV - - id: googletest - subsystem: deps - label: dependencies, test - run: | - ./tools/dep_updaters/update-googletest.sh > temp-output - cat temp-output - tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true - rm temp-output - - id: gyp-next - subsystem: tools - label: tools, gyp - run: | - ./tools/dep_updaters/update-gyp-next.sh > temp-output - cat temp-output - tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true - rm temp-output - - id: histogram - subsystem: deps - label: dependencies - run: | - ./tools/dep_updaters/update-histogram.sh > temp-output - cat temp-output - tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true - rm temp-output - - id: icu - subsystem: deps - label: dependencies, test, icu - run: | - ./tools/dep_updaters/update-icu.sh > temp-output - cat temp-output - tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true - rm temp-output - - id: inspector_protocol - subsystem: deps - label: dependencies, inspector - run: | - ./tools/dep_updaters/update-inspector-protocol.sh > temp-output - cat temp-output - tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true - rm temp-output - - id: libffi - subsystem: deps - label: dependencies, ffi - run: | - ./tools/dep_updaters/update-libffi.sh > temp-output - cat temp-output - tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true - rm temp-output - - id: libuv - subsystem: deps - label: dependencies - run: | - ./tools/dep_updaters/update-libuv.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 - run: | - ./tools/dep_updaters/update-llhttp.sh > temp-output - cat temp-output - tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true - rm temp-output - - id: minimatch - subsystem: deps - label: dependencies - run: | - ./tools/dep_updaters/update-minimatch.sh > temp-output - cat temp-output - tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true - rm temp-output - - id: nbytes - subsystem: deps - label: dependencies - run: | - ./tools/dep_updaters/update-nbytes.sh > temp-output - cat temp-output - tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true - rm temp-output - - id: nixpkgs-unstable - subsystem: tools - # dont-land labels are there so we can guarantee released versions of - # Node.js can be built with the same env along the whole release line life – or - # at least we can detect and document necessary updates. - label: tools, dont-land-on-v22.x, dont-land-on-v24.x, dont-land-on-v26.x - run: | - ./tools/dep_updaters/update-nixpkgs-pin.sh > temp-output - cat temp-output - tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true - rm temp-output - - id: nghttp2 - subsystem: deps - label: dependencies - run: | - ./tools/dep_updaters/update-nghttp2.sh > temp-output - cat temp-output - tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true - rm temp-output - - id: nghttp3 - subsystem: deps - label: dependencies - run: | - ./tools/dep_updaters/update-nghttp3.sh > temp-output - cat temp-output - tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true - rm temp-output - - id: ngtcp2 - subsystem: deps - label: dependencies - run: | - ./tools/dep_updaters/update-ngtcp2.sh > temp-output - cat temp-output - tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true - rm temp-output - - id: postject - subsystem: deps,test - label: test - run: | - ./tools/dep_updaters/update-postject.sh > temp-output - cat temp-output - tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true - rm temp-output - - id: root-certificates - subsystem: crypto - label: crypto, notable-change - run: | - node ./tools/dep_updaters/update-root-certs.mjs -v -f "$GITHUB_ENV" - - id: simdjson - subsystem: deps - label: dependencies - run: | - ./tools/dep_updaters/update-simdjson.sh > temp-output - cat temp-output - tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true - rm temp-output - - id: sqlite - subsystem: deps - label: dependencies, sqlite - run: | - ./tools/dep_updaters/update-sqlite.sh > temp-output - cat temp-output - tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true - rm temp-output - - id: test426-fixtures - subsystem: test - label: test - run: | - bash tools/dep_updaters/update-test426-fixtures.sh > temp-output - cat temp-output - tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true - rm temp-output - - id: undici - subsystem: deps - label: dependencies - run: | - ./tools/dep_updaters/update-undici.sh > temp-output - cat temp-output - tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true - rm temp-output - - id: uvwasi - subsystem: deps - label: dependencies - run: | - ./tools/dep_updaters/update-uvwasi.sh > temp-output - cat temp-output - tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true - rm temp-output - - id: zlib - subsystem: deps - label: dependencies, zlib - run: | - ./tools/dep_updaters/update-zlib.sh > temp-output - cat temp-output - tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true - rm temp-output - - id: zstd - subsystem: deps - label: dependencies, zlib - run: | - ./tools/dep_updaters/update-zstd.sh > temp-output - cat temp-output - tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true - rm temp-output - steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - if: github.event_name == 'schedule' || inputs.id == 'all' || inputs.id == matrix.id - 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) - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: ${{ env.PYTHON_VERSION }} - allow-prereleases: true - - name: Set up Nix - if: matrix.id == 'nixpkgs-unstable' && (github.event_name == 'schedule' || inputs.id == 'all' || inputs.id == matrix.id) - uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6 - - run: ${{ matrix.run }} - if: github.event_name == 'schedule' || inputs.id == 'all' || inputs.id == matrix.id - env: - GITHUB_TOKEN: ${{ secrets.GH_USER_TOKEN }} - - name: Generate commit message if not set - if: env.COMMIT_MSG == '' && (github.event_name == 'schedule' || inputs.id == 'all' || inputs.id == matrix.id) - run: | - echo "COMMIT_MSG=${{ matrix.subsystem }}: update ${{ matrix.id }} to ${{ env.NEW_VERSION }}" >> "$GITHUB_ENV" - - uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 - if: github.event_name == 'schedule' || inputs.id == 'all' || inputs.id == matrix.id - # Creates a PR or update the Action's existing PR, or - # no-op if the base branch is already up-to-date. - with: - token: ${{ secrets.GH_USER_TOKEN }} - branch: actions/${{ github.ref_name == 'main' || format('{0}/', github.ref_name) }}tools-update-${{ matrix.id }} # Custom branch *just* for this Action. - delete-branch: true - commit-message: ${{ env.COMMIT_MSG }} - labels: ${{ matrix.label }} - title: '${{ matrix.subsystem }}: update ${{ matrix.id }} to ${{ env.NEW_VERSION }}' - body: This is an automated update of ${{ matrix.id }} to ${{ env.NEW_VERSION }}. - committer: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com> - author: Node.js GitHub Bot diff --git a/.gitignore b/.gitignore index 69c1dd205316..3c98ecc2abf9 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,7 @@ # === Rules for MSVS artifacts === Debug/ Release/ +!.github/actions/release *.sln *.suo *.vcxproj* @@ -86,6 +87,8 @@ _UpgradeReport_Files/ /deps/icu*.tgz /deps/icu-tmp ./node_modules +/replay-test/node_modules +/replay-test/package-lock.json /android-toolchain/ # generated by gyp on Windows /deps/openssl/openssl.props @@ -172,6 +175,8 @@ __pycache__ .DS_Store *~ +/src/node_record_replay_driver.cc + # === Rules for C++ development === compile_commands.json diff --git a/REPLAY_BACKEND_REV b/REPLAY_BACKEND_REV new file mode 100644 index 000000000000..43938271869c --- /dev/null +++ b/REPLAY_BACKEND_REV @@ -0,0 +1 @@ +d887c4838d6a4e305f874edd8cf3d422a068d9a2 \ No newline at end of file diff --git a/build.js b/build.js new file mode 100644 index 000000000000..95d769705416 --- /dev/null +++ b/build.js @@ -0,0 +1,146 @@ +const fs = require("fs"); +const os = require("os"); +const path = require("path"); +const { spawnSync } = require("child_process"); +const node = __dirname; +const OutDir = path.join(node, "out"); + +// Use local driver directory if provided, otherwise download from S3. +const localDriverDir = process.env.REPLAY_LOCAL_DRIVER_DIR; +const driverFile = `${currentPlatform()}-recordreplay.${driverExtension()}`; +const driverJSON = `${currentPlatform()}-recordreplay.json`; + +let driverContents; +let driverRevision; +let driverDate; + +if (localDriverDir) { + console.log("[build] Loading driver from local directory:", localDriverDir); + driverContents = fs.readFileSync(path.join(localDriverDir, driverFile)); + const driverInfo = JSON.parse(fs.readFileSync(path.join(localDriverDir, driverJSON), "utf8")); + driverRevision = driverInfo.revision; + driverDate = driverInfo.date; +} else { + console.log("[build] Downloading driver from S3..."); + let driverArchive = `${currentPlatform()}-recordreplay.tgz`; + let downloadDriverRevision = process.env.DRIVER_REVISION ? process.env.DRIVER_REVISION : fs.readFileSync("REPLAY_BACKEND_REV", "utf8"); + let downloadArchive = `${currentPlatform()}-recordreplay-${downloadDriverRevision.trim().substring(0, 12)}.tgz`; + const driverArchivePath = path.join(OutDir, driverArchive); + spawnChecked("curl", [`https://static.replay.io/downloads/${downloadArchive}`, "-o", driverArchivePath], { stdio: "inherit" }); + spawnChecked("tar", ["xf", driverArchivePath, "-C", OutDir]); + fs.unlinkSync(driverArchivePath); + + driverContents = fs.readFileSync(path.join(OutDir, driverFile)); + const driverInfo = JSON.parse(fs.readFileSync(path.join(OutDir, driverJSON), "utf8")); + driverRevision = driverInfo.revision; + driverDate = driverInfo.date; + fs.unlinkSync(path.join(OutDir, driverFile)); + fs.unlinkSync(path.join(OutDir, driverJSON)); + fs.unlinkSync(path.join(OutDir, `${driverFile}.symbols.json`)); +} +console.log("[build] Generating driver source file..."); +let driverString = ""; +for (let i = 0; i < driverContents.length; i++) { + driverString += `\\${driverContents[i].toString(8)}`; +} +fs.writeFileSync( + `${node}/src/node_record_replay_driver.cc`, + ` +namespace node { + char gRecordReplayDriver[] = "${driverString}"; + int gRecordReplayDriverSize = ${driverContents.length}; + char gBuildId[] = "${computeBuildId()}"; +} +` +); + +const numCPUs = os.cpus().length; + +function getSanitizedEnv() { + const env = { ...process.env }; + if (env.PATH) { + env.PATH = env.PATH.split(":").filter(p => !p.includes("/nix/")).join(":"); + } + delete env.NIX_PROFILES; + delete env.NIX_SSL_CERT_FILE; + return env; +} + +const buildEnv = getSanitizedEnv(); + +if (process.env.CONFIGURE_NODE) { + console.log("[build] Running configure..."); + spawnChecked(`${node}/configure`, [], { cwd: node, stdio: "inherit", env: buildEnv }); +} +console.log("[build] Running make..."); +spawnChecked("make", [`-j${numCPUs}`, "-C", OutDir, "BUILDTYPE=Release"], { + cwd: node, + stdio: "inherit", + env: { + ...buildEnv, + RECORD_REPLAY_DONT_RECORD: "1", + }, +}); + +function spawnChecked(cmd, args, options) { + const prettyCmd = [cmd].concat(args).join(" "); + console.error(prettyCmd); + + const rv = spawnSync(cmd, args, options); + + if (rv.status != 0 || rv.error) { + console.error(rv.error); + throw new Error(`Spawned process failed with exit code ${rv.status}`); + } + + return rv; +} + +function currentPlatform() { + switch (process.platform) { + case "darwin": + return "macOS"; + case "linux": + return "linux"; + default: + throw new Error(`Platform ${process.platform} not supported`); + } +} + +function driverExtension() { + return currentPlatform() == "windows" ? "dll" : "so"; +} + +/** + * @returns {string} "YYYYMMDD" format of UTC timestamp of given revision. + */ +function getRevisionDate( + revision = "HEAD", + spawnOptions +) { + const dateString = spawnChecked( + "git", + ["show", revision, "--pretty=%cd", "--date=iso-strict", "--no-patch"], + spawnOptions + ) + .stdout.toString() + .trim(); + + // convert to UTC -> then get the date only + // explanations: https://github.com/replayio/backend/pull/7115#issue-1587869475 + return new Date(dateString).toISOString().substring(0, 10).replace(/-/g, ""); +} + +/** + * WARNING: We have copy-and-pasted `computeBuildId` into all our runtimes and `backend`. + * When changing this: always keep all versions of this in sync, or else, builds will break. + */ +function computeBuildId() { + const runtimeRevision = spawnChecked("git", ["rev-parse", "--short=12", "HEAD"]).stdout.toString().trim(); + const runtimeDate = getRevisionDate(); + + // Use the later of the two dates in the build ID. + const date = +runtimeDate >= +driverDate ? runtimeDate : driverDate; + + return `${currentPlatform()}-node-${date}-${runtimeRevision}-${driverRevision}`; +} diff --git a/deps/icu-small/source/common/umutex.cpp b/deps/icu-small/source/common/umutex.cpp index 0c053968dcc8..f3964cfcaaa0 100644 --- a/deps/icu-small/source/common/umutex.cpp +++ b/deps/icu-small/source/common/umutex.cpp @@ -51,8 +51,7 @@ std::condition_variable *initCondition; // Used when ICU implementation code passes nullptr for the mutex pointer. UMutex globalMutex; -std::once_flag initFlag; -std::once_flag *pInitFlag = &initFlag; +pthread_once_t initFlag; } // Anonymous namespace @@ -62,10 +61,7 @@ static UBool U_CALLCONV umtx_cleanup() { initCondition->~condition_variable(); UMutex::cleanup(); - // Reset the once_flag, by destructing it and creating a fresh one in its place. - // Do not use this trick anywhere else in ICU; use umtx_initOnce, not std::call_once(). - pInitFlag->~once_flag(); - pInitFlag = new(&initFlag) std::once_flag(); + initFlag = PTHREAD_ONCE_INIT; return true; } @@ -80,7 +76,7 @@ U_CDECL_END std::mutex *UMutex::getMutex() { std::mutex *retPtr = fMutex.load(std::memory_order_acquire); if (retPtr == nullptr) { - std::call_once(*pInitFlag, umtx_init); + pthread_once(&initFlag, umtx_init); std::lock_guard guard(*initMutex); retPtr = fMutex.load(std::memory_order_acquire); if (retPtr == nullptr) { @@ -143,7 +139,7 @@ umtx_unlock(UMutex* mutex) // U_COMMON_API UBool U_EXPORT2 umtx_initImplPreInit(UInitOnce &uio) { - std::call_once(*pInitFlag, umtx_init); + pthread_once(&initFlag, umtx_init); std::unique_lock lock(*initMutex); if (umtx_loadAcquire(uio.fState) == 0) { umtx_storeRelease(uio.fState, 1); diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index 38378b730aca..9578a9804901 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -561,7 +561,11 @@ BignumPointer BignumPointer::NewLShift(size_t length) { // ============================================================================ // Utility methods -bool CSPRNG(void* buffer, size_t length) { +// Replay port: V8 driver hook to record/replay raw bytes for determinism. +extern "C" void V8RecordReplayAssertBytes(const char* why, const void* buf, + size_t size); + +static bool CSPRNG_impl(void* buffer, size_t length) { auto buf = reinterpret_cast(buffer); do { if (1 == RAND_status()) { @@ -597,6 +601,17 @@ bool CSPRNG(void* buffer, size_t length) { return false; } +bool CSPRNG(void* buffer, size_t length) { + // Replay port: CSPRNG is the single randomness primitive in Node 27 β€” keygen, + // QUIC connection IDs/tokens, DTLS, WebCrypto, randomUUID, etc. all route + // through here. Recording/replaying its output at this chokepoint makes every + // consumer deterministic in one place (supersedes per-callsite hooks). + bool rv = CSPRNG_impl(buffer, length); + if (rv && length > 0) + V8RecordReplayAssertBytes("ncrypto::CSPRNG", buffer, length); + return rv; +} + int NoPasswordCallback(char* buf, int size, int rwflag, void* u) { return 0; } diff --git a/deps/openssl/openssl/crypto/err/err_prn.c b/deps/openssl/openssl/crypto/err/err_prn.c index a6311098e3cb..c9da55154b30 100644 --- a/deps/openssl/openssl/crypto/err/err_prn.c +++ b/deps/openssl/openssl/crypto/err/err_prn.c @@ -34,7 +34,9 @@ void ERR_print_errors_cb(int (*cb)(const char *str, size_t len, void *u), data = ""; hex = ossl_buf2hexstr_sep((const unsigned char *)&tid, sizeof(tid), '\0'); - BIO_snprintf(buf, sizeof(buf), "%s:", hex == NULL ? "" : hex); + /* Replay port: emit a constant thread id; the real one varies when + * replaying, which would diverge the recording. */ + BIO_snprintf(buf, sizeof(buf), "0:"); offset = strlen(buf); ossl_err_string_int(l, func, buf + offset, sizeof(buf) - offset); offset += strlen(buf + offset); diff --git a/deps/openssl/openssl/crypto/o_str.c b/deps/openssl/openssl/crypto/o_str.c index 154393c7b3e7..fdb85a1a06a5 100644 --- a/deps/openssl/openssl/crypto/o_str.c +++ b/deps/openssl/openssl/crypto/o_str.c @@ -16,6 +16,43 @@ #include "internal/thread_once.h" #include "internal/to_hex.h" +/* Record/replay determinism assert shim, callable from OpenSSL's C code. + * Resolves the driver's RecordReplayAssert at runtime via dlsym, so this + * object links into both the node binary and standalone OpenSSL tools + * (e.g. openssl-cli) and forwards only when the recordreplay runtime is + * loaded. This is the canonical Replay shim; it originally lived in + * crypto/rand/rand_unix.c, which moved into providers/ in OpenSSL 3.x, so + * the definition is hosted here next to its sole remaining caller. */ +#include +#ifndef _WIN32 +#include +/* RTLD_DEFAULT is a GNU extension on glibc (needs _GNU_SOURCE); its value + * there is NULL. macOS always defines it. Fall back defensively so this + * compiles even if _GNU_SOURCE didn't reach this translation unit. */ +#ifndef RTLD_DEFAULT +#define RTLD_DEFAULT ((void *)0) +#endif + +static void (*gRecordReplayAssertFn)(const char*, va_list); + +void RecordReplayAssertFromC(const char* aFormat, ...) { + if (!gRecordReplayAssertFn) { + void* fnptr = dlsym(RTLD_DEFAULT, "RecordReplayAssert"); + if (!fnptr) { + return; + } + gRecordReplayAssertFn = fnptr; + } + + va_list ap; + va_start(ap, aFormat); + gRecordReplayAssertFn(aFormat, ap); + va_end(ap); +} +#else +void RecordReplayAssertFromC(const char* aFormat, ...) { (void) aFormat; } +#endif + #define DEFAULT_SEPARATOR ':' #define CH_ZERO '\0' @@ -372,6 +409,7 @@ char *OPENSSL_buf2hexstr(const unsigned char *buf, long buflen) int openssl_strerror_r(int errnum, char *buf, size_t buflen) { + RecordReplayAssertFromC("openssl_strerror_r %d %lu", errnum, buflen); #if defined(_MSC_VER) && _MSC_VER >= 1400 && !defined(_WIN32_WCE) return !strerror_s(buf, buflen, errnum); #elif defined(_GNU_SOURCE) diff --git a/deps/uv/include/uv.h b/deps/uv/include/uv.h index da06345557bb..f53e38b6cde0 100644 --- a/deps/uv/include/uv.h +++ b/deps/uv/include/uv.h @@ -1850,6 +1850,7 @@ UV_EXTERN void uv_mutex_destroy(uv_mutex_t* handle); UV_EXTERN void uv_mutex_lock(uv_mutex_t* handle); UV_EXTERN int uv_mutex_trylock(uv_mutex_t* handle); UV_EXTERN void uv_mutex_unlock(uv_mutex_t* handle); +UV_EXTERN void uv_mutex_mark_ordered(uv_mutex_t* handle); UV_EXTERN int uv_rwlock_init(uv_rwlock_t* rwlock); UV_EXTERN void uv_rwlock_destroy(uv_rwlock_t* rwlock); diff --git a/deps/uv/include/uv/unix.h b/deps/uv/include/uv/unix.h index c6ba419da98b..8a2027e029e0 100644 --- a/deps/uv/include/uv/unix.h +++ b/deps/uv/include/uv/unix.h @@ -325,6 +325,7 @@ typedef struct { uv_async_cb async_cb; \ struct uv__queue queue; \ int pending; \ + int ordered_lock_id; #define UV_TIMER_PRIVATE_FIELDS \ uv_timer_cb timer_cb; \ diff --git a/deps/uv/src/threadpool.c b/deps/uv/src/threadpool.c index 2a129a5d4a58..4d41976eda08 100644 --- a/deps/uv/src/threadpool.c +++ b/deps/uv/src/threadpool.c @@ -29,6 +29,8 @@ #define MAX_THREADPOOL_SIZE 1024 +extern void V8RecordReplayAssert(const char* format, ...); + static uv_once_t once = UV_ONCE_INIT; static uv_cond_t cond; static uv_mutex_t mutex; @@ -227,6 +229,7 @@ static void init_threads(void) { if (uv_cond_init(&cond)) abort(); + uv_mutex_mark_ordered(&mutex); if (uv_mutex_init(&mutex)) abort(); @@ -324,6 +327,8 @@ void uv__work_done(uv_async_t* handle) { int err; int nevents; + V8RecordReplayAssert("uv__work_done"); + loop = container_of(handle, uv_loop_t, wq_async); uv_mutex_lock(&loop->wq_mutex); uv__queue_move(&loop->wq, &wq); diff --git a/deps/uv/src/unix/async.c b/deps/uv/src/unix/async.c index f7995fc35197..431cf39e4995 100644 --- a/deps/uv/src/unix/async.c +++ b/deps/uv/src/unix/async.c @@ -70,6 +70,12 @@ static void uv__async_send(uv_loop_t* loop); static int uv__async_start(uv_loop_t* loop); static void uv__cpu_relax(void); +/* Replay port: ordered locks make cross-thread async wakeups deterministic. */ +extern void V8RecordReplayAssert(const char* format, ...); +extern size_t V8RecordReplayCreateOrderedLock(const char* name); +extern void V8RecordReplayOrderedLock(int lock); +extern void V8RecordReplayOrderedUnlock(int lock); + int uv_async_init(uv_loop_t* loop, uv_async_t* handle, uv_async_cb async_cb) { int err; @@ -81,6 +87,7 @@ int uv_async_init(uv_loop_t* loop, uv_async_t* handle, uv_async_cb async_cb) { uv__handle_init(loop, (uv_handle_t*)handle, UV_ASYNC); handle->async_cb = async_cb; handle->pending = 0; + handle->ordered_lock_id = V8RecordReplayCreateOrderedLock("uv_async_t"); handle->u.fd = 0; /* This will be used as a busy flag. */ uv__queue_insert_tail(&loop->async_handles, &handle->queue); @@ -97,9 +104,13 @@ int uv_async_send(uv_async_t* handle) { pending = (_Atomic int*) &handle->pending; busy = (_Atomic int*) &handle->u.fd; + V8RecordReplayOrderedLock(handle->ordered_lock_id); + /* Do a cheap read first. */ - if (atomic_load_explicit(pending, memory_order_relaxed) != 0) + if (atomic_load_explicit(pending, memory_order_relaxed) != 0) { + V8RecordReplayOrderedUnlock(handle->ordered_lock_id); return 0; + } /* Set the loop to busy. */ atomic_fetch_add(busy, 1); @@ -111,6 +122,7 @@ int uv_async_send(uv_async_t* handle) { /* Set the loop to not-busy. */ atomic_fetch_add(busy, -1); + V8RecordReplayOrderedUnlock(handle->ordered_lock_id); return 0; } @@ -129,13 +141,17 @@ static void uv__async_spin(uv_async_t* handle) { * threads after this function returns. */ atomic_store(pending, 1); + V8RecordReplayOrderedLock(handle->ordered_lock_id); + for (;;) { /* 997 is not completely chosen at random. It's a prime number, acyclic by * nature, and should therefore hopefully dampen sympathetic resonance. */ for (i = 0; i < 997; i++) { - if (atomic_load(busy) == 0) + if (atomic_load(busy) == 0) { + V8RecordReplayOrderedUnlock(handle->ordered_lock_id); return; + } /* Other thread is busy with this handle, spin until it's done. */ uv__cpu_relax(); @@ -165,6 +181,8 @@ void uv__async_io(uv_loop_t* loop, uv__io_t* w, unsigned int events) { uv_async_t* h; _Atomic int *pending; + V8RecordReplayAssert("uv__async_io"); + assert(w == &loop->async_io_watcher); #if UV__KQUEUE_EVFILT_USER @@ -191,6 +209,8 @@ void uv__async_io(uv_loop_t* loop, uv__io_t* w, unsigned int events) { uv__queue_move(&loop->async_handles, &queue); while (!uv__queue_empty(&queue)) { + V8RecordReplayAssert("uv__async_io #2"); + q = uv__queue_head(&queue); h = uv__queue_data(q, uv_async_t, queue); diff --git a/deps/uv/src/unix/darwin.c b/deps/uv/src/unix/darwin.c index c26a1faccbcc..8a684e487d7b 100644 --- a/deps/uv/src/unix/darwin.c +++ b/deps/uv/src/unix/darwin.c @@ -32,6 +32,11 @@ #include #include /* sysconf */ +extern int V8RecordReplayIsReplaying(void); +extern uintptr_t V8RecordReplayValue(const char* why, uintptr_t value); +extern void V8RecordReplayBeginPassThroughEvents(void); +extern void V8RecordReplayEndPassThroughEvents(void); + static uv_once_t once = UV_ONCE_INIT; static mach_timebase_info_data_t timebase; @@ -205,7 +210,20 @@ int uv_cpu_info(uv_cpu_info_t** cpu_infos, int* count) { return UV__ERR(errno); } - cpuspeed = 0; + // Some of the system interactions in uv__get_cpu_speed are not supported + // while recording, so we pass through events and manually record/replay + // the values we get from this call. + if (!V8RecordReplayIsReplaying()) { + V8RecordReplayBeginPassThroughEvents(); + err = uv__get_cpu_speed(&cpuspeed); + V8RecordReplayEndPassThroughEvents(); + V8RecordReplayValue("uv__get_cpu_speed err", err); + V8RecordReplayValue("uv__get_cpu_speed cpuspeed", cpuspeed); + } else { + err = V8RecordReplayValue("uv__get_cpu_speed err", 0); + cpuspeed = V8RecordReplayValue("uv__get_cpu_speed cpuspeed", 0); + } + size = sizeof(cpuspeed); sysctlbyname("hw.cpufrequency", &cpuspeed, &size, NULL, 0); if (cpuspeed == 0) diff --git a/deps/uv/src/unix/fs.c b/deps/uv/src/unix/fs.c index 90cb40d98d47..f4f4280ec25f 100644 --- a/deps/uv/src/unix/fs.c +++ b/deps/uv/src/unix/fs.c @@ -46,6 +46,8 @@ #include #include +extern void V8RecordReplayAssert(const char* format, ...); + #if defined(__linux__) # include #endif @@ -138,6 +140,7 @@ extern char *mkdtemp(char *template); /* See issue #740 on AIX < 7 */ #define POST \ do { \ + V8RecordReplayAssert("POST_WORK %d", (int)req->fs_type); \ if (cb != NULL) { \ uv__req_register(loop); \ uv__work_submit(loop, \ @@ -149,6 +152,7 @@ extern char *mkdtemp(char *template); /* See issue #740 on AIX < 7 */ } \ else { \ uv__fs_work(&req->work_req); \ + V8RecordReplayAssert("POST_WORK_DONE %d %d", (int)req->fs_type, req->result); \ return req->result; \ } \ } \ @@ -361,7 +365,10 @@ static int uv__fs_mkstemp(uv_fs_t* req) { static ssize_t uv__fs_open(uv_fs_t* req) { #ifdef O_CLOEXEC - return open(req->path, req->flags | O_CLOEXEC, req->mode); + ssize_t rv = open(req->path, req->flags | O_CLOEXEC, req->mode); + V8RecordReplayAssert("uv__fs_open %s %d %d %d", + req->path, req->flags, req->mode, rv); + return rv; #else /* O_CLOEXEC */ int r; @@ -370,6 +377,9 @@ static ssize_t uv__fs_open(uv_fs_t* req) { r = open(req->path, req->flags, req->mode); + V8RecordReplayAssert("uv__fs_open %s %d %d %d", + req->path, req->flags, req->mode, r); + /* In case of failure `uv__cloexec` will leave error in `errno`, * so it is enough to just set `r` to `-1`. */ @@ -377,6 +387,7 @@ static ssize_t uv__fs_open(uv_fs_t* req) { r = uv__close(r); if (r != 0) abort(); + V8RecordReplayAssert("uv__fs_open #1"); r = -1; } @@ -520,16 +531,23 @@ static ssize_t uv__fs_read(uv_fs_t* req) { if (nbufs > iovmax) nbufs = iovmax; + // Replay port: record/replay file read parameters so reads are deterministic. + V8RecordReplayAssert("uv__fs_read start %d %u %ld", fd, nbufs, (long)off); + r = 0; if (off < 0) { - if (nbufs == 1) + if (nbufs == 1) { + V8RecordReplayAssert("uv__fs_read read %d %lu", fd, + (unsigned long)bufs->iov_len); r = read(fd, bufs->iov_base, bufs->iov_len); - else if (nbufs > 1) + } else if (nbufs > 1) r = readv(fd, bufs, nbufs); } else { - if (nbufs == 1) + if (nbufs == 1) { + V8RecordReplayAssert("uv__fs_read pread %d %lu %ld", fd, + (unsigned long)bufs->iov_len, (long)off); r = pread(fd, bufs->iov_base, bufs->iov_len, off); - else if (nbufs > 1) + } else if (nbufs > 1) r = uv__preadv(fd, bufs, nbufs, off); } @@ -880,10 +898,15 @@ static ssize_t uv__fs_sendfile_emul(uv_fs_t* req) { buflen = sizeof(buf); do - if (use_pread) + if (use_pread) { + V8RecordReplayAssert("uv__fs_sendfile_emul pread %d %lu %lu", + in_fd, buflen, offset); nread = pread(in_fd, buf, buflen, offset); - else + } else { + V8RecordReplayAssert("uv__fs_sendfile_emul read %d %lu", in_fd, + buflen); nread = read(in_fd, buf, buflen); + } while (nread == -1 && errno == EINTR); if (nread == 0) @@ -1192,6 +1215,11 @@ static ssize_t uv__fs_lutime(uv_fs_t* req) { #else errno = ENOSYS; return -1; + // https://github.com/RecordReplay/backend/issues/4792 + V8RecordReplayAssert("uv__fs_write start %d %d %d %d", + req->file, (int)req->off, (int)req->nbufs, + req->nbufs ? (int)req->bufs[0].len : -1); + #endif } @@ -1637,6 +1665,9 @@ static ssize_t uv__fs_write_all(uv_fs_t* req) { ssize_t total; ssize_t result; + // https://github.com/RecordReplay/backend/issues/4792 + V8RecordReplayAssert("uv__fs_write_all start"); + iovmax = uv__getiovmax(); nbufs = req->nbufs; bufs = req->bufs; @@ -1647,9 +1678,12 @@ static ssize_t uv__fs_write_all(uv_fs_t* req) { if (req->nbufs > iovmax) req->nbufs = iovmax; - do + do { result = uv__fs_write(req); - while (result < 0 && errno == EINTR); + + // https://github.com/RecordReplay/backend/issues/4792 + V8RecordReplayAssert("uv__fs_write_all #1 %d", (int)result); + } while (result < 0 && errno == EINTR); if (result <= 0) { if (total == 0) @@ -1672,6 +1706,9 @@ static ssize_t uv__fs_write_all(uv_fs_t* req) { req->bufs = NULL; req->nbufs = 0; + // https://github.com/RecordReplay/backend/issues/4792 + V8RecordReplayAssert("uv__fs_write_all done %d", (int)total); + return total; } @@ -1688,6 +1725,8 @@ static void uv__fs_work(struct uv__work* w) { do { errno = 0; + V8RecordReplayAssert("uv__fs_work %d", (int)req->fs_type); + #define X(type, action) \ case UV_FS_ ## type: \ r = action; \ @@ -1733,6 +1772,8 @@ static void uv__fs_work(struct uv__work* w) { default: abort(); } #undef X + + V8RecordReplayAssert("uv__fs_work #1 %d", r); } while (r == -1 && errno == EINTR && retry_on_eintr); if (r == -1) @@ -1740,6 +1781,8 @@ static void uv__fs_work(struct uv__work* w) { else req->result = r; + V8RecordReplayAssert("uv__fs_work #2 %d %d", r, req->result); + if (r == 0 && (req->fs_type == UV_FS_STAT || req->fs_type == UV_FS_FSTAT || req->fs_type == UV_FS_LSTAT)) { @@ -2002,10 +2045,15 @@ int uv_fs_open(uv_loop_t* loop, int flags, int mode, uv_fs_cb cb) { + V8RecordReplayAssert("uv_fs_open %s %d %d", path, flags, mode); + INIT(OPEN); PATH; req->flags = flags; req->mode = mode; + + V8RecordReplayAssert("uv_fs_open START_POST %d", !!cb); + if (cb != NULL) if (uv__iou_fs_open(loop, req)) return 0; @@ -2021,6 +2069,8 @@ int uv_fs_read(uv_loop_t* loop, uv_fs_t* req, uv_fs_cb cb) { INIT(READ); + V8RecordReplayAssert("uv_fs_read %d", (int)req->fs_type); + if (bufs == NULL || nbufs == 0) return UV_EINVAL; diff --git a/deps/uv/src/unix/kqueue.c b/deps/uv/src/unix/kqueue.c index 27d288143633..c644a6722ce1 100644 --- a/deps/uv/src/unix/kqueue.c +++ b/deps/uv/src/unix/kqueue.c @@ -48,6 +48,8 @@ #define EV_OOBAND EV_FLAG1 #endif +extern void V8RecordReplayAssert(const char* format, ...); + int uv__kqueue_init(uv_loop_t* loop) { loop->backend_fd = kqueue(); @@ -179,6 +181,8 @@ void uv__io_poll(uv_loop_t* loop, int timeout) { int user_timeout; int reset_timeout; + V8RecordReplayAssert("uv__io_poll"); + if (loop->nfds == 0) { assert(uv__queue_empty(&loop->watcher_queue)); return; diff --git a/deps/uv/src/unix/loop.c b/deps/uv/src/unix/loop.c index da9fa3c8b80b..0a6268ec4f62 100644 --- a/deps/uv/src/unix/loop.c +++ b/deps/uv/src/unix/loop.c @@ -93,6 +93,7 @@ int uv_loop_init(uv_loop_t* loop) { err = uv_mutex_init(&loop->wq_mutex); if (err) goto fail_mutex_init; + uv_mutex_mark_ordered(&loop->wq_mutex); err = uv_async_init(loop, &loop->wq_async, uv__work_done); if (err) diff --git a/deps/uv/src/unix/process.c b/deps/uv/src/unix/process.c index 539e7d9417d3..f7f1b43c2bd6 100644 --- a/deps/uv/src/unix/process.c +++ b/deps/uv/src/unix/process.c @@ -70,6 +70,8 @@ extern char **environ; #define UV_USE_SIGCHLD #endif +extern void V8RecordReplayAssert(const char* format, ...); +extern void V8RecordReplayDiagnostic(const char* format, ...); #ifdef UV_USE_SIGCHLD static void uv__chld(uv_signal_t* handle, int signum) { @@ -109,6 +111,8 @@ void uv__wait_children(uv_loop_t* loop) { struct uv__queue* q; struct uv__queue* h; + V8RecordReplayAssert("uv__chld start"); + uv__queue_init(&pending); h = &loop->process_handles; @@ -288,6 +292,8 @@ static void uv__process_child_init(const uv_process_options_t* options, int stdio_count, int (*pipes)[2], int error_fd) { + V8RecordReplayDiagnostic("uv__process_child_init start"); + sigset_t signewset; int close_fd; int use_fd; @@ -934,9 +940,11 @@ static int uv__spawn_and_init_child( uv__close(signal_pipe[1]); if (err == 0) { - do - r = read(signal_pipe[0], &exec_errorno, sizeof(exec_errorno)); - while (r == -1 && errno == EINTR); + do { + V8RecordReplayDiagnostic("uv_spawn read pipe start"); + r = read(signal_pipe[0], &exec_errorno, sizeof(exec_errorno)); + V8RecordReplayDiagnostic("uv_spawn read pipe done %d %d", r, errno); + } while (r == -1 && errno == EINTR); if (r == 0) ; /* okay, EOF */ @@ -978,6 +986,12 @@ int uv_spawn(uv_loop_t* loop, int exec_errorno; int i; + V8RecordReplayDiagnostic("uv_spawn start"); + char** nargs; + for (nargs = options->args; *nargs; nargs++) { + V8RecordReplayDiagnostic("uv_spawn arg %s", *nargs); + } + assert(options->file != NULL); assert(!(options->flags & ~(UV_PROCESS_DETACHED | UV_PROCESS_SETGID | diff --git a/deps/uv/src/unix/proctitle.c b/deps/uv/src/unix/proctitle.c index 9d1f00ddf66e..d07dc7f8cf2c 100644 --- a/deps/uv/src/unix/proctitle.c +++ b/deps/uv/src/unix/proctitle.c @@ -97,6 +97,11 @@ int uv_set_process_title(const char* title) { struct uv__process_title* pt; size_t len; + // FIXME disabling for now. When replaying the arguments are not adjacent + // and the logic in uv_setup_args behaves incorrectly and corrupts most + // of the process memory. + return 0; + /* If uv_setup_args wasn't called or failed, we can't continue. */ if (args_mem == NULL) return UV_ENOBUFS; diff --git a/deps/uv/src/unix/signal.c b/deps/uv/src/unix/signal.c index 91fb149464e3..6bb009554deb 100644 --- a/deps/uv/src/unix/signal.c +++ b/deps/uv/src/unix/signal.c @@ -32,6 +32,15 @@ # define SA_RESTART 0 #endif +extern int V8RecordReplayIsRecording(void); +extern int V8RecordReplayIsReplaying(void); +extern void V8RecordReplayAssert(const char* format, ...); +extern void V8RecordReplayDiagnostic(const char* format, ...); +extern uintptr_t V8RecordReplayValue(const char* why, uintptr_t value); +extern void V8RecordReplayRegisterPointer(const void* ptr); +extern int V8RecordReplayPointerId(const void* ptr); +extern void* V8RecordReplayIdPointer(int id); + typedef struct { uv_signal_t* handle; int signum; @@ -426,6 +435,10 @@ static int uv__signal_start(uv_signal_t* handle, RB_INSERT(uv__signal_tree_s, &uv__signal_tree, handle); + // Note: Handles added to the tree are not unregistered, which is a minor + // memory leak but is otherwise fine. + V8RecordReplayRegisterPointer(handle); + uv__signal_unlock_and_unblock(&saved_sigmask); handle->signal_cb = signal_cb; @@ -476,6 +489,27 @@ void uv__signal_event(uv_loop_t* loop, uv__io_t* w, unsigned int events) { msg = (uv__signal_msg_t*) (buf + i); handle = msg->handle; + /* When recording/replaying, the data we read from the pipe will be the + * data which was originally read from the pipe while recording, because + * the write to the pipe happened inside the signal handler and is not + * replayed. + * + * Because of this the handle pointer we have here is invalid when + * replaying, and we need to save/restore the actual pointer we can use + * in this process. + */ + if (V8RecordReplayIsRecording()) { + int id = V8RecordReplayPointerId(handle); + if (!id) { + fprintf(stderr, "uv__signal_event: Missing pointer ID for message handle\n"); + abort(); + } + V8RecordReplayValue("uv__signal_event handle", id); + } else if (V8RecordReplayIsReplaying()) { + int id = V8RecordReplayValue("uv__signal_event handle", 0); + handle = V8RecordReplayIdPointer(id); + } + if (msg->signum == handle->signum) { assert(!(handle->flags & UV_HANDLE_CLOSING)); handle->signal_cb(handle, handle->signum); diff --git a/deps/uv/src/unix/stream.c b/deps/uv/src/unix/stream.c index 08a18314b1ec..953ceeefeacc 100644 --- a/deps/uv/src/unix/stream.c +++ b/deps/uv/src/unix/stream.c @@ -70,6 +70,8 @@ union uv__cmsg { STATIC_ASSERT(256 == sizeof(union uv__cmsg)); +extern void V8RecordReplayAssert(const char* format, ...); + static void uv__stream_connect(uv_stream_t*); static void uv__write(uv_stream_t* stream); static void uv__read(uv_stream_t* stream); @@ -766,6 +768,12 @@ static int uv__try_write(uv_stream_t* stream, iov = (struct iovec*) bufs; iovcnt = nbufs; + // https://linear.app/replay/issue/RUN-650 + V8RecordReplayAssert("uv__try_write %d %u", uv__stream_fd(stream), nbufs); + if (nbufs == 1) { + V8RecordReplayAssert("uv__try_write #1 %u", iov[0].iov_len); + } + iovmax = uv__getiovmax(); /* Limit iov count to avoid EINVALs from writev() */ @@ -1021,6 +1029,7 @@ static int uv__stream_recv_cmsg(uv_stream_t* stream, struct msghdr* msg) { } + static void uv__read(uv_stream_t* stream) { uv_buf_t buf; ssize_t nread; @@ -1058,6 +1067,7 @@ static void uv__read(uv_stream_t* stream) { if (!is_ipc) { do { + V8RecordReplayAssert("uv__read read %d %lu", uv__stream_fd(stream), buf.len); nread = read(uv__stream_fd(stream), buf.base, buf.len); } while (nread < 0 && errno == EINTR); diff --git a/deps/uv/src/unix/thread.c b/deps/uv/src/unix/thread.c index 001b6d744ba8..39b851204756 100644 --- a/deps/uv/src/unix/thread.c +++ b/deps/uv/src/unix/thread.c @@ -387,6 +387,14 @@ void uv_mutex_unlock(uv_mutex_t* mutex) { } +extern void V8RecordReplayAddOrderedPthreadMutex(const char* name, + pthread_mutex_t* mutex); + +void uv_mutex_mark_ordered(uv_mutex_t* mutex) { + V8RecordReplayAddOrderedPthreadMutex("UVMutex", mutex); +} + + int uv_rwlock_init(uv_rwlock_t* rwlock) { return UV__ERR(pthread_rwlock_init(rwlock, NULL)); } diff --git a/deps/uv/src/uv-common.c b/deps/uv/src/uv-common.c index f1e8928d37eb..fa352611360b 100644 --- a/deps/uv/src/uv-common.c +++ b/deps/uv/src/uv-common.c @@ -1062,3 +1062,51 @@ void uv_free_interface_addresses(uv_interface_address_t* addresses, uv__free(addresses); } #endif /* !__MVS__ */ + + +/* Replay record/replay instrumentation: weak fallback definitions. + * + * libuv's record/replay hooks (in unix/*.c, threadpool.c) call these + * V8RecordReplay* functions, which are defined strongly in libv8 (api.cc). + * Host build tools such as node_js2c link libuv.a but NOT libv8, so those + * references would otherwise be undefined at link time. Provide weak no-op + * definitions here (compiled into every libuv.a) so libuv-only tools link; + * in the real node binary the strong libv8 definitions are statically linked + * in the same link and override these. Build tools never record, so the + * no-ops are correct. Guarded to GCC/Clang on non-Windows (where these unix + * hooks are actually referenced and __attribute__((weak)) is available). + */ +#if (defined(__GNUC__) || defined(__clang__)) && !defined(_WIN32) +__attribute__((weak)) int V8RecordReplayIsRecording(void) { return 0; } +__attribute__((weak)) int V8RecordReplayIsReplaying(void) { return 0; } +__attribute__((weak)) int V8RecordReplayPointerId(const void* ptr) { + (void) ptr; return 0; +} +__attribute__((weak)) size_t V8RecordReplayCreateOrderedLock(const char* name) { + (void) name; return 0; +} +__attribute__((weak)) uintptr_t V8RecordReplayValue(const char* why, + uintptr_t value) { + (void) why; return value; +} +__attribute__((weak)) void V8RecordReplayAddOrderedPthreadMutex( + const char* name, pthread_mutex_t* mutex) { + (void) name; (void) mutex; +} +__attribute__((weak)) void V8RecordReplayAssert(const char* format, ...) { + (void) format; +} +__attribute__((weak)) void V8RecordReplayDiagnostic(const char* format, ...) { + (void) format; +} +__attribute__((weak)) void V8RecordReplayBeginPassThroughEvents(void) {} +__attribute__((weak)) void V8RecordReplayEndPassThroughEvents(void) {} +__attribute__((weak)) void V8RecordReplayOrderedLock(int lock) { (void) lock; } +__attribute__((weak)) void V8RecordReplayOrderedUnlock(int lock) { (void) lock; } +__attribute__((weak)) void V8RecordReplayRegisterPointer(const void* ptr) { + (void) ptr; +} +__attribute__((weak)) void* V8RecordReplayIdPointer(int id) { + (void) id; return NULL; +} +#endif /* (__GNUC__ || __clang__) && !_WIN32 */ diff --git a/deps/v8/BUILD.gn b/deps/v8/BUILD.gn index 167d9a9003d8..5fdec86d5a0c 100644 --- a/deps/v8/BUILD.gn +++ b/deps/v8/BUILD.gn @@ -6342,6 +6342,13 @@ v8_cluster_source_set("v8_base_without_compiler") { "src/zone/type-stats.cc", "src/zone/zone-segment.cc", "src/zone/zone.cc", + "src/replay/replay-isolate-data.cc", + "src/replay/replay-isolate-data.h", + "src/replay/replayio.cc", + "src/replay/replayio.h", + "src/replay/replay-runtime-weak-refs.cc", + "src/replay/weak-refs.cc", + "src/replay/weak-refs.h", "third_party/siphash/halfsiphash.cc", ] diff --git a/deps/v8/include/js_protocol.pdl b/deps/v8/include/js_protocol.pdl index bc86332d869b..1fe831586d3d 100644 --- a/deps/v8/include/js_protocol.pdl +++ b/deps/v8/include/js_protocol.pdl @@ -107,6 +107,8 @@ domain Debugger # Deprecated in favor of using the `location.scriptId` to resolve the URL via a previously # sent `Debugger.scriptParsed` event. deprecated string url + # Execution context associated with this frame. + Runtime.ExecutionContextId contextId # Scope chain for this call frame. array of Scope scopeChain # `this` object for this call frame. @@ -572,6 +574,28 @@ domain Debugger # Actual breakpoint location. Location location + command getCallFrames + parameters + # Maximum number of frames to return. + optional integer maxFrames + # Don't generate scopes or "this" object for returned frames. + optional boolean noContents + # Symbolic group name that can be used to release multiple objects. + optional string objectGroup + returns + array of CallFrame callFrames + + command getTopFrameLocation + returns + optional Location location + + command getPendingException + parameters + # Symbolic group name that can be used to release multiple objects. + optional string objectGroup + returns + optional Runtime.RemoteObject exception + # Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria. event paused parameters @@ -1157,6 +1181,8 @@ domain Runtime experimental optional DeepSerializedValue deepSerializedValue # Unique object identifier (for non-primitive values). optional RemoteObjectId objectId + # Any available persistent object identifier when recording/replaying. + optional string persistentId # Preview containing abbreviated property values. Specified for `object` type values only. experimental optional ObjectPreview preview experimental optional CustomPreview customPreview @@ -1589,6 +1615,8 @@ domain Runtime # Size in bytes of backing storage for array buffers and external strings. number backingStorageSize + type KeyIterationIndex extends integer + # Returns properties of a given object. Object group of the result is inherited from the target # object. command getProperties @@ -1605,6 +1633,10 @@ domain Runtime experimental optional boolean generatePreview # If true, returns non-indexed properties only. experimental optional boolean nonIndexedPropertiesOnly + # If > 0, returns up to `pageSize` properties only. + optional KeyIterationIndex pageSize + # If pageIndex > 0 and pageSize > 0, returns properties starting at pageIndex * pageSize. + optional KeyIterationIndex pageIndex returns # Object properties. array of PropertyDescriptor result diff --git a/deps/v8/include/replayio-macros.h b/deps/v8/include/replayio-macros.h new file mode 100644 index 000000000000..5a619253b2ab --- /dev/null +++ b/deps/v8/include/replayio-macros.h @@ -0,0 +1,25 @@ +// Copyright (c) 2024 Record Replay Inc. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// API for interacting with the record/replay driver. +// Some parts are still in v8.h and still need to be migrated. + + +#ifndef INCLUDE_REPLAYIO_MACROS_H_ +#define INCLUDE_REPLAYIO_MACROS_H_ + +// Use this to wrap Asserts on non-trivial data, to avoid the +// overhead of argument evaluation when Asserts are disabled. +#define REPLAY_ASSERT(format, ...) \ + if (recordreplay::HasAsserts()) \ + recordreplay::Assert(format, ##__VA_ARGS__); \ + static_assert(true, "require semicolon") + +// Same as |REPLAY_ASSERT| but won't Assert when Events are disallowed. +#define REPLAY_ASSERT_MAYBE_EVENTS_DISALLOWED(format, ...) \ + if (recordreplay::HasAsserts() && !recordreplay::AreEventsDisallowed()) \ + recordreplay::Assert(format, ##__VA_ARGS__); \ + static_assert(true, "require semicolon") + +#endif // INCLUDE_REPLAYIO_MACROS_H_ diff --git a/deps/v8/include/replayio.h b/deps/v8/include/replayio.h new file mode 100644 index 000000000000..db681621110a --- /dev/null +++ b/deps/v8/include/replayio.h @@ -0,0 +1,31 @@ +// Copyright (c) 2024 Record Replay Inc. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// API for interacting with the record/replay driver. +// Some parts are still in v8.h and still need to be migrated. + + +#ifndef INCLUDE_RECORD_REPLAY_H_ +#define INCLUDE_RECORD_REPLAY_H_ + +#include "v8.h" + +namespace v8 { +namespace replayio { + +struct AutoPassThroughEvents { + AutoPassThroughEvents() { v8::recordreplay::BeginPassThroughEvents(); } + ~AutoPassThroughEvents() { v8::recordreplay::EndPassThroughEvents(); } +}; + +struct AutoDisallowEvents { + AutoDisallowEvents() { v8::recordreplay::BeginDisallowEvents(); } + AutoDisallowEvents(const char* label) { v8::recordreplay::BeginDisallowEventsWithLabel(label); } + ~AutoDisallowEvents() { v8::recordreplay::EndDisallowEvents(); } +}; + +} // namespace replayio +} // namespace v8 + +#endif // INCLUDE_RECORD_REPLAY_H_ diff --git a/deps/v8/include/v8-container.h b/deps/v8/include/v8-container.h index 380999e5ee45..fd11f68ed4e6 100644 --- a/deps/v8/include/v8-container.h +++ b/deps/v8/include/v8-container.h @@ -123,7 +123,8 @@ class V8_EXPORT Map : public Object { * Returns an array of length Size() * 2, where index N is the Nth key and * index N + 1 is the Nth value. */ - Local AsArray() const; + Local AsArray(const v8::KeyIterationParams* params = + v8::KeyIterationParams::Default()) const; /** * Creates a new empty Map. @@ -159,7 +160,7 @@ class V8_EXPORT Set : public Object { /** * Returns an array of the keys in this Set. */ - Local AsArray() const; + Local AsArray(const v8::KeyIterationParams* params = v8::KeyIterationParams::Default()) const; /** * Creates a new empty Set. diff --git a/deps/v8/include/v8-inspector.h b/deps/v8/include/v8-inspector.h index 05e9edadfdc8..99ad198a1ead 100644 --- a/deps/v8/include/v8-inspector.h +++ b/deps/v8/include/v8-inspector.h @@ -193,6 +193,9 @@ class V8_EXPORT V8InspectorSession { virtual void setSkipAllPauses(bool) = 0; virtual void resume(bool setTerminateOnResume = false) = 0; virtual void stepOver() = 0; + + virtual v8::MaybeLocal getArgumentsOfCallFrame(StringView callFrameId) = 0; + virtual std::vector> searchInTextByLines(StringView text, StringView query, bool caseSensitive, bool isRegex) = 0; @@ -202,6 +205,12 @@ class V8_EXPORT V8InspectorSession { v8::Local, v8::Local, StringView groupName, bool generatePreview) = 0; + // Replay edit: Return objectId + virtual std::u16string wrapObjectGetObjectId(v8::Local, + v8::Local, + StringView groupName, + bool generatePreview) = 0; + virtual bool unwrapObject(std::unique_ptr* error, StringView objectId, v8::Local*, v8::Local*, diff --git a/deps/v8/include/v8-object.h b/deps/v8/include/v8-object.h index f283857a4d18..c8e14bd55a64 100644 --- a/deps/v8/include/v8-object.h +++ b/deps/v8/include/v8-object.h @@ -260,6 +260,69 @@ enum class KeyConversionMode { kConvertToString, kKeepNumbers, kNoNumbers }; */ enum class IntegrityLevel { kFrozen, kSealed }; +using KeyIterationIndex = int; + +/** + * [RecordReplay] + * Used to paginate keys for performance reasons (RUN-1315). + * This is currently only implemented as part of the debugger's + * `Runtime.getProperties` command. It is also not a guarantee on pagination, + * but purely a performance improvement. We apply these params in many scenarios + * where large key sets would slow things down a lot. + * WARNING: |page_index_| has not been fully implemented. + * NOTE: In the future, this can be updated to encapsulate all key query + * parameters in a single object (`KeyCollectionMode`, `IndexFilter` etc.). + */ +class KeyIterationParams { + KeyIterationIndex page_size_; + /** WARNING: |page_index_| is not fully implemented. */ + KeyIterationIndex page_index_; + public: + /** + * Create new KeyIterationParams to limit iteration to between |page_index| and + * |page_index + page_size|. + */ + KeyIterationParams(KeyIterationIndex page_size, KeyIterationIndex page_index) + : page_size_(page_size) , page_index_(page_index) {} + + /** + * The min of page_size_ or target size. + * Ignores page_size_ if no parameters are given. + */ + KeyIterationIndex PageSize(KeyIterationIndex numberOfElements) const { + return (*this && page_size_ < numberOfElements) ? page_size_ + : numberOfElements; + } + + /** + * First index of iteration. + */ + KeyIterationIndex KeyFirstIndex() const { return page_index_ * page_size_; } + + /** + * Final index of iteration + 1, indicating end of iteration. + */ + KeyIterationIndex KeyEndIndex(KeyIterationIndex numberOfElements) const { + return (*this && (KeyFirstIndex() + page_size_) < numberOfElements) + ? KeyFirstIndex() + page_size_ + : numberOfElements; + } + + /** + * Whether parameters are given. + * If no parameters are given, key iteration will iterate all keys at once. + */ + operator bool() const { return page_size_ > 0; } + + /** + * Default (i.e. empty) key iteration params. + */ + static const KeyIterationParams* Default() { + static KeyIterationParams params(0, 0); + return ¶ms; + } +}; + /** * A JavaScript object (ECMA-262, 4.3.3) */ @@ -895,7 +958,8 @@ class V8_EXPORT Object : public Value { * For other types, this will return an empty MaybeLocal (without * scheduling an exception). */ - MaybeLocal PreviewEntries(bool* is_key_value); + MaybeLocal PreviewEntries(bool* is_key_value, + const v8::KeyIterationParams* params = KeyIterationParams::Default()); static Local New(Isolate* isolate); diff --git a/deps/v8/include/v8-platform.h b/deps/v8/include/v8-platform.h index 8f42ac0c8788..0df19d389542 100644 --- a/deps/v8/include/v8-platform.h +++ b/deps/v8/include/v8-platform.h @@ -52,6 +52,12 @@ class Task { virtual ~Task() = default; virtual void Run() = 0; + + /** + * When recording/replaying, whether this task might not consistently occur + * between recording and replaying. + */ + virtual bool IsRecordReplayNonDeterministic() const { return false; } }; /** diff --git a/deps/v8/include/v8.h b/deps/v8/include/v8.h index cc23d207aea6..17ed8314b2e9 100644 --- a/deps/v8/include/v8.h +++ b/deps/v8/include/v8.h @@ -17,6 +17,7 @@ #include #include +#include #include @@ -62,6 +63,8 @@ #include "v8-wasm.h" // NOLINT(build/include_directory) #include "v8config.h" // NOLINT(build/include_directory) +#include "replayio-macros.h" + // We reserve the V8_* prefix for macros defined in V8 public API and // assume there are no name conflicts with the embedder's code. @@ -82,6 +85,128 @@ class Platform; * \example process.cc */ +bool IsMainThread(); + +class Isolate; // Replay port: needed by recordreplay::AssertScriptedCaller. + +// Static container class for record/replay methods. +class V8_EXPORT recordreplay { + public: + +static void SetRecordingOrReplaying(void* handle); +static bool IsRecordingOrReplaying(const char* feature = nullptr, + const char* subfeature = nullptr); +static bool IsRecording(); +static bool IsReplaying(); +static const char* GetRecordingId(); + +static bool IsARMRecording(); + +static bool FeatureEnabled(const char* feature, const char* subfeature = nullptr); +static bool HasDisabledFeatures(); + +static char* ReadAssetFileContents(const char* path, size_t* size); +static void Print(const char* format, ...); +static void Diagnostic(const char* format, ...); +static void CommandDiagnostic(const char* format, ...); +static void CommandDiagnosticTrace(const char* format, ...); +static void Warning(const char* format, ...); +static void Trace(const char* format, ...); +static void Crash(const char* format, ...); +static bool HadMismatch(); +static bool HasAsserts(); +static void AssertRaw(const char* format, ...); +static void Assert(const char* format, ...); +static void AssertMaybeEventsDisallowed(const char* format, ...); +static void AssertBytes(const char* why, const void* buf, size_t size); +// Replay port: needed by Node hooks (node_file.cc, node_process_methods.cc). +static void AssertScriptedCaller(Isolate* isolate, const char* why); +static void OnAnnotation(const char* kind, const char* contents); +static bool AreAssertsDisabled(); + +static uintptr_t RecordReplayValue(const char* why, uintptr_t v); +static void RecordReplayBytes(const char* why, void* buf, size_t size); +static void RecordReplayString(const char* why, std::string& str); + +static size_t CreateOrderedLock(const char* name); +static void OrderedLock(int lock); +static void OrderedUnlock(int lock); + +static void InvalidateRecording(const char* why); +static void NewCheckpoint(); + +static void BeginPassThroughEvents(); +static void EndPassThroughEvents(); +static bool AreEventsPassedThrough(const char* why = nullptr); + +static void BeginDisallowEvents(); +static void BeginDisallowEventsWithLabel(const char* label); +static void EndDisallowEvents(); + +// A "why" string should be used whenever there are substantive behavior changes +// resulting from this check. +static bool AreEventsDisallowed(const char* why = nullptr); + +// A "why" string should be used whenever there are substantive behavior changes +// resulting from this check. +static bool IsInReplayCode(const char* why = nullptr); + +static bool HasDivergedFromRecording(); +static bool AllowSideEffects(); + +static int NewDependencyGraphNode(const char* json); +static void AddDependencyGraphEdge(int source, int target, const char* json); +static void BeginDependencyExecution(int node); +static void EndDependencyExecution(); + +struct AutoDependencyExecution { + AutoDependencyExecution(int node) { + BeginDependencyExecution(node); + } + ~AutoDependencyExecution() { + EndDependencyExecution(); + } +}; + +static void BeginAssertBufferAllocations(const char* issueLabel = ""); +static void EndAssertBufferAllocations(); + +struct AutoOrderedLock { + AutoOrderedLock(int id) : id_(id) { OrderedLock(id_); } + ~AutoOrderedLock() { OrderedUnlock(id_); } + int id_; +}; + +struct AutoAssertMaybeEventsDisallowed { + AutoAssertMaybeEventsDisallowed(const char* format, ...); + ~AutoAssertMaybeEventsDisallowed(); + std::string msg_; +}; + +struct AssertBufferAllocationState { + size_t enabled = 0; + std::string issueLabel = ""; +}; + + +// RAII class to enable recording assertions on dynamic-length buffer +// allocations. Used to track down the allocation causing mismatched message +// sizes when replaying. +// TODO: Merge this with the similar `AuxtoRecordReplayAssertBufferAllocations` +// in mojo/public/cpp/bindings/lib/buffer.cc (for main-thread only). +struct AutoAssertBufferAllocations { + static AssertBufferAllocationState* GetState(); + + AutoAssertBufferAllocations(const char* issueLabel = ""); + ~AutoAssertBufferAllocations(); +}; + +static void RegisterPointer(const char* name, const void* ptr); +static void UnregisterPointer(const void* ptr); +static int PointerId(const void* ptr); +static void* IdPointer(int id); + +}; // class recordreplay } // namespace v8 diff --git a/deps/v8/src/api/api.cc b/deps/v8/src/api/api.cc index 315659e78fef..696b71f53e57 100644 --- a/deps/v8/src/api/api.cc +++ b/deps/v8/src/api/api.cc @@ -172,6 +172,13 @@ #include #include +#if !V8_OS_WIN +#include +#include +#endif + +extern const char* gCrashReason; + #if V8_ENABLE_WEBASSEMBLY #include "include/v8-wasm-trap-handler-posix.h" #include "src/trap-handler/handler-inside-posix.h" @@ -238,6 +245,12 @@ static ScriptOrigin GetScriptOriginForScript( // --- E x c e p t i o n B e h a v i o r --- +static __attribute__((noinline)) void BusyWait() { + fprintf(stderr, "Busy-waiting ... (pid %d)\n", getpid()); + volatile int x = 1; + while (x) {} +} + // When V8 cannot allocate memory FatalProcessOutOfMemory is called. The default // OOM error handler is called and execution is stopped. void i::V8::FatalProcessOutOfMemory(i::Isolate* i_isolate, const char* location, @@ -293,6 +306,9 @@ void Utils::ReportApiFailure(const char* location, const char* message) { if (callback == nullptr) { base::OS::PrintError("\n#\n# Fatal error in %s\n# %s\n#\n\n", location, message); + if (getenv("RECORD_REPLAY_WAIT_AT_FATAL_ERROR")) { + BusyWait(); + } base::OS::Abort(); } else { callback(location, message); @@ -2031,6 +2047,14 @@ MaybeLocal Script::Run(Local context, } #endif // V8_ENABLE_ETW_STACK_WALKING auto fun = i::Cast(Utils::OpenDirectHandle(this)); + + // TODO: IsInReplayCode (RUN-1502) + v8::recordreplay::AssertMaybeEventsDisallowed( + "JS Script::Run %d", + i::IsScript(fun->shared()->script()) + ? i::Cast(fun->shared()->script())->id() + : 0); + i::DirectHandle receiver = i_isolate->global_proxy(); // TODO(cbruni, chromium:1244145): Remove once migrated to the context. i::DirectHandle options( @@ -2425,6 +2449,9 @@ MaybeLocal Module::Evaluate(Local context) { Utils::ApiCheck(self->status() >= i::Module::kLinked, "Module::Evaluate", "Expected instantiated module"); + // TODO: IsInReplayCode (RUN-1502) + v8::recordreplay::AssertMaybeEventsDisallowed("JS Module::Evaluate %d", ScriptId()); + return api_scope.EscapeMaybe(i::Module::Evaluate(i_isolate, self)); } @@ -2578,6 +2605,56 @@ i::ScriptDetails GetScriptDetails( } // namespace +static const char* RecordReplayReplaceSourceContents(const char* contents); + +namespace internal { + +// If we are compiling a script while replaying that replaces another one, +// the ID of the script being replaced. Main thread only. +int gReplaceSourceContentsScriptId; + +MaybeHandle +ReplayingReplaceScriptContents(Isolate* isolate, Handle source) { + if (!recordreplay::IsReplaying() || !IsMainThread()) { + return MaybeHandle(); + } + + std::unique_ptr contents = source->ToCString(); + const char* new_contents = RecordReplayReplaceSourceContents(contents.get()); + if (!new_contents) { + return MaybeHandle(); + } + + return isolate->factory()->NewStringFromUtf8(base::CStrVector(new_contents)).ToHandleChecked(); +} + +MaybeDirectHandle +ReplayingMaybeReplaceScript( + Isolate* isolate, + MaybeDirectHandle maybe_function_info, + const ScriptDetails& script_details, Handle source) { + MaybeHandle new_source = ReplayingReplaceScriptContents(isolate, source); + + if (new_source.is_null() || maybe_function_info.is_null()) { + return maybe_function_info; + } + + CHECK(!gReplaceSourceContentsScriptId); + gReplaceSourceContentsScriptId = + Cast