This is an automated email from the ASF dual-hosted git repository.

wu-sheng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking-nodejs.git


The following commit(s) were added to refs/heads/master by this push:
     new 211d976  chore: add release automation scripts (release.sh + 
release-finalize.sh) (#133)
211d976 is described below

commit 211d976da221be4db18e56f63d39c81188acbac9
Author: 吴晟 Wu Sheng <[email protected]>
AuthorDate: Tue Jun 23 21:06:33 2026 +0800

    chore: add release automation scripts (release.sh + release-finalize.sh) 
(#133)
---
 .gitignore                               |   7 +
 docs/How-to-release.md                   |  26 ++-
 package.json                             |   6 +-
 scripts/release-finalize.sh              | 256 ++++++++++++++++++++++
 scripts/release.sh                       | 358 +++++++++++++++++++++++++++++++
 tests/plugins/axios/docker-compose.yml   |   9 +
 tests/plugins/axios/expected.data.yaml   |   4 +-
 tests/plugins/axios/server.ts            |   2 +-
 tests/plugins/common/base-compose.yml    |   8 +
 tests/plugins/express/docker-compose.yml |   9 +
 tests/plugins/express/expected.data.yaml |   4 +-
 tests/plugins/express/server.ts          |  12 +-
 tests/plugins/http/docker-compose.yml    |   9 +
 tests/plugins/http/expected.data.yaml    |   4 +-
 tests/plugins/http/server.ts             |  12 +-
 15 files changed, 700 insertions(+), 26 deletions(-)

diff --git a/.gitignore b/.gitignore
index d6ef811..e9bf187 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,10 @@ lib
 src/proto/
 *.log
 tsconfig.tsbuildinfo
+
+# Release tooling working dirs + generated source artifacts 
(scripts/release*.sh)
+scripts/.release-work/
+scripts/.finalize-work/
+skywalking-nodejs-src-*.tgz
+skywalking-nodejs-src-*.tgz.asc
+skywalking-nodejs-src-*.tgz.sha512
diff --git a/docs/How-to-release.md b/docs/How-to-release.md
index 3214fb6..fabe4be 100644
--- a/docs/How-to-release.md
+++ b/docs/How-to-release.md
@@ -2,10 +2,26 @@
 
 This documentation guides the release manager to release the SkyWalking NodeJS 
in the Apache Way, and also helps people to check the release for voting.
 
+## Automated release (recommended)
+
+Most of the steps below are automated by two scripts. Run them on a 
single-user trusted host, with your Apache GPG key (an `@apache.org` uid, 
already in the 
[KEYS](https://dist.apache.org/repos/dist/release/skywalking/KEYS) file) 
configured and Node >= 20:
+
+```shell
+# 1. Bump `version` in package.json to the release version and merge that PR 
first.
+npm run release            # GPG/preflight checks, fresh recursive clone, 
build + sign the source
+                           # release, verify it, push the tag, upload the RC 
to svn dev, print the [VOTE] email
+# 2. ... after the vote passes (open >= 72h, >= 3 binding +1, more +1 than -1) 
...
+npm run release:finalize   # svn move dev -> release, publish the GitHub 
release, optionally publish to npm
+```
+
+If you intend to publish to npm in `release:finalize`, run `npm login` first 
(you must be a maintainer of `skywalking-backend-js`). The script verifies npm 
auth **up front** — before the irreversible svn move — and auto-skips the npm 
step if that version is already published.
+
+The rest of this guide is the reference those scripts implement, and the 
fallback for running a step by hand.
+
 ## Prerequisites
 
 1. Close (if finished, or move to next milestone otherwise) all issues in the 
current milestone from 
[skywalking-nodejs](https://github.com/apache/skywalking-nodejs/milestones) and 
[skywalking](https://github.com/apache/skywalking/milestones), create a new 
milestone for the next release.
-1. Update [CHANGELOG.md](../CHANGELOG.md) and `version` in 
[package.json](../package.json).
+1. Update `version` in [package.json](../package.json). (CHANGELOG.md is a 
stub — release notes are the auto-generated [GitHub 
Release](https://github.com/apache/skywalking-nodejs/releases) notes.)
 
 
 ## Add your GPG public key to Apache svn
@@ -55,7 +71,7 @@ This is a call for vote to release Apache SkyWalking NodeJS 
version $VERSION.
 
 Release notes:
 
- * https://github.com/apache/skywalking-nodejs/blob/v$VERSION/CHANGELOG.md
+ * https://github.com/apache/skywalking-nodejs/releases/tag/v$VERSION
 
 Release Candidate:
 
@@ -95,14 +111,14 @@ Thanks.
 All PMC members and committers should check these before voting +1:
 
 1. Features test.
-1. All artifacts in staging repository are published with `.asc`, `.md5`, and 
`sha` files.
+1. All artifacts in staging repository are published with `.asc` and `.sha512` 
files.
 1. Source codes and distribution packages 
(`skywalking-nodejs-src-$VERSION.tgz`)
 are in `https://dist.apache.org/repos/dist/dev/skywalking/node-js/$VERSION` 
with `.asc`, `.sha512`.
 1. `LICENSE` and `NOTICE` are in source codes and distribution package.
 1. Check `shasum -c skywalking-nodejs-src-$VERSION.tgz.sha512`.
 1. Check `gpg --verify skywalking-nodejs-src-$VERSION.tgz.asc 
skywalking-nodejs-src-$VERSION.tgz`.
 1. Build distribution from source code package by following this [the build 
guide](#build-and-sign-the-source-code-package).
-1. Licenses check, `make license`.
+1. License-header check via license-eye (`apache/skywalking-eyes`, as run by 
`.github/workflows/license.yaml`); style lint via `npm run lint`.
 
 Vote result should follow these:
 
@@ -166,7 +182,7 @@ Vote result should follow these:
 
     Download Links: http://skywalking.apache.org/downloads/
 
-    Release Notes : 
https://github.com/apache/skywalking-nodejs/blob/v$VERSION/CHANGELOG.md
+    Release Notes : 
https://github.com/apache/skywalking-nodejs/releases/tag/v$VERSION
 
     Website: http://skywalking.apache.org/
 
diff --git a/package.json b/package.json
index 36f0289..2ee556e 100644
--- a/package.json
+++ b/package.json
@@ -19,8 +19,10 @@
     "test": "DEBUG=testcontainers* jest",
     "format": "prettier --write \"src/**/*.ts\"",
     "clean": "(rm -rf src/proto || true) && (rm -rf src/proto || true) && (rm 
-rf lib || true)",
-    "package-src": "touch skywalking-nodejs-src-$npm_package_version.tgz && 
tar -zcvf skywalking-nodejs-src-$npm_package_version.tgz --exclude bin 
--exclude .git --exclude .idea --exclude .DS_Store --exclude .github --exclude 
node_modules --exclude skywalking-nodejs-src-$npm_package_version.tgz .",
-    "release-src": "npm run prepare && npm run package-src && gpg --batch 
--yes --armor --detach-sig skywalking-nodejs-src-$npm_package_version.tgz && 
shasum -a 512 skywalking-nodejs-src-$npm_package_version.tgz > 
skywalking-nodejs-src-$npm_package_version.tgz.sha512"
+    "package-src": "touch skywalking-nodejs-src-$npm_package_version.tgz && 
tar -zcvf skywalking-nodejs-src-$npm_package_version.tgz --exclude bin 
--exclude .git --exclude .idea --exclude .DS_Store --exclude .github --exclude 
.claude --exclude node_modules --exclude 
skywalking-nodejs-src-$npm_package_version.tgz .",
+    "release-src": "npm run prepare && npm run package-src && gpg --batch 
--yes ${SW_GPG_KEY:+-u $SW_GPG_KEY} --armor --detach-sig 
skywalking-nodejs-src-$npm_package_version.tgz && shasum -a 512 
skywalking-nodejs-src-$npm_package_version.tgz > 
skywalking-nodejs-src-$npm_package_version.tgz.sha512",
+    "release": "bash scripts/release.sh",
+    "release:finalize": "bash scripts/release-finalize.sh"
   },
   "files": [
     "lib/**/*"
diff --git a/scripts/release-finalize.sh b/scripts/release-finalize.sh
new file mode 100755
index 0000000..7cef472
--- /dev/null
+++ b/scripts/release-finalize.sh
@@ -0,0 +1,256 @@
+#!/usr/bin/env bash
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Apache SkyWalking NodeJS — POST-VOTE release finalization.
+#
+# Run AFTER the [VOTE] on [email protected] passes (>=72h, >=3
+# binding +1, more +1 than -1). Second half of the flow; scripts/release.sh
+# is the first half.
+#
+# In order:
+#   1. Promote on svn: server-side move dev/<v>/ -> release/<v>/, then remove
+#      the PREVIOUS (strictly-older) release (auto-archived to 
archive.apache.org).
+#   2. Cut/publish the GitHub release on tag v<v> with auto-generated notes,
+#      attaching the SAME voted bytes fetched back from svn release (checksum
+#      AND signature verified before attaching).
+#   3. (optional, IRREVERSIBLE) publish skywalking-backend-js@<v> to npm,
+#      built from a fresh clone of the tag.
+#
+# Every irreversible step confirms first. Run on a single-user trusted host.
+#
+# Usage:  bash scripts/release-finalize.sh
+
+set -e -o pipefail
+
+SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
+PROJECT_DIR=$(cd "${SCRIPT_DIR}/.." && pwd)
+GH_REPO="apache/skywalking-nodejs"
+NPM_PACKAGE="skywalking-backend-js"
+REPO_URL="${SW_RELEASE_REPO_URL:-https://github.com/apache/skywalking-nodejs.git}";
+SVN_DEV_URL="https://dist.apache.org/repos/dist/dev/skywalking/node-js";
+SVN_RELEASE_URL="https://dist.apache.org/repos/dist/release/skywalking/node-js";
+KEYS_URL="https://dist.apache.org/repos/dist/release/skywalking/KEYS";
+WORK_DIR="${SCRIPT_DIR}/.finalize-work"
+
+# ========================== Helpers ==========================
+
+err()  { echo "ERROR: $*" >&2; }
+note() { echo ""; echo "=== $* ==="; }
+
+confirm() {
+    local prompt="$1" ans
+    read -r -p "${prompt} [y/N] " ans || { err "No input (no TTY?)."; exit 1; }
+    [[ "$ans" == "y" || "$ans" == "Y" ]]
+}
+
+ask() {
+    local prompt="$1" val
+    read -r -p "${prompt}: " val || { err "No input for '${prompt}' (no 
TTY?)."; exit 1; }
+    [ -n "$val" ] || { err "'${prompt}' must not be empty."; exit 1; }
+    printf '%s' "$val"
+}
+
+svn_exists() { svn ls "$1" >/dev/null 2>&1; }
+
+# ========================== Step 1: Preflight ==========================
+note "Step 1 — Tool + auth preflight"
+
+MISSING=()
+for t in svn gh git node npm gpg shasum curl; do
+    command -v "$t" >/dev/null || MISSING+=("$t")
+done
+if [ ${#MISSING[@]} -gt 0 ]; then err "Missing required tools: ${MISSING[*]}"; 
exit 1; fi
+gh auth status >/dev/null 2>&1 || { err "gh is not authenticated. Run: gh auth 
login"; exit 1; }
+# Fetch tags up front so version auto-detect (Step 2) sees the RC tag even on
+# a checkout that never pulled it.
+(cd "${PROJECT_DIR}" && git fetch --tags --quiet origin) || err "git fetch 
--tags failed (continuing with local tags)."
+echo "gh authenticated; tools present."
+
+# ========================== Step 2: Detect version ==========================
+note "Step 2 — Detect release version"
+
+DETECTED=$(cd "${PROJECT_DIR}" && git tag --list 'v*' --sort=-version:refname 
| head -1 | sed 's/^v//')
+echo "Most recent git tag: v${DETECTED:-<none>}"
+read -r -p "Release version to finalize [${DETECTED}]: " RELEASE_VERSION || { 
err "No input (no TTY?)."; exit 1; }
+RELEASE_VERSION="${RELEASE_VERSION:-${DETECTED}}"
+[ -n "${RELEASE_VERSION}" ] || { err "No release version provided."; exit 1; }
+TAG="v${RELEASE_VERSION}"
+SRC_TGZ="skywalking-nodejs-src-${RELEASE_VERSION}.tgz"
+
+# The tag MUST exist locally (we fetched in Step 1) — fail loudly, not later
+# inside an opaque gh error.
+(cd "${PROJECT_DIR}" && git rev-parse "${TAG}" >/dev/null 2>&1) || {
+    err "Tag ${TAG} not found locally or on origin. Did scripts/release.sh 
push it?"; exit 1; }
+echo "Finalizing ${RELEASE_VERSION} (tag ${TAG})."
+confirm "Proceed?" || { echo "Aborted."; exit 1; }
+
+rm -rf "${WORK_DIR}"; mkdir -p "${WORK_DIR}"
+
+# ---- npm-publish intent + auth, decided NOW (before any irreversible step) 
----
+# The npm publish is the last step (Step 5), but we decide whether you'll do it
+# and verify `npm login` HERE, so a missing login fails fast — BEFORE the svn
+# move (Step 3) and the GitHub release (Step 4), not after them.
+WILL_PUBLISH_NPM=false
+if npm view "${NPM_PACKAGE}@${RELEASE_VERSION}" version >/dev/null 2>&1; then
+    echo "npm: ${NPM_PACKAGE}@${RELEASE_VERSION} is already published — the 
npm step will be skipped."
+elif confirm "Will you publish ${NPM_PACKAGE}@${RELEASE_VERSION} to npm in 
this run? (the binding artifact is the svn tarball; npm is a convenience)"; then
+    NPM_USER=$(npm whoami 2>/dev/null || true)
+    [ -n "${NPM_USER}" ] || { err "You chose to publish to npm but are not 
logged in. Run 'npm login' first, then re-run."; exit 1; }
+    echo "npm user: ${NPM_USER} — will publish after the GitHub release."
+    WILL_PUBLISH_NPM=true
+else
+    echo "npm publish will be skipped this run."
+fi
+
+# ========================== Step 3: svn move dev -> release 
==========================
+note "Step 3 — Promote on svn: dev (RC) -> release (official)"
+
+echo "  FROM (release candidate): ${SVN_DEV_URL}/${RELEASE_VERSION}/"
+echo "  TO   (official release):  ${SVN_RELEASE_URL}/${RELEASE_VERSION}/"
+
+# NOTE: svn takes the password on argv; run only on a trusted host, never with 
set -x.
+SVN_USER=$(ask "Apache SVN username")
+read -r -s -p "Apache SVN password: " SVN_PASS || { err "No SVN password (no 
TTY?)."; exit 1; }
+echo ""
+[ -n "$SVN_PASS" ] || { err "SVN password must not be empty."; exit 1; }
+SVN_AUTH=(--username "${SVN_USER}" --password "${SVN_PASS}" --non-interactive 
--no-auth-cache)
+
+if ! svn ls "${SVN_AUTH[@]}" "${SVN_DEV_URL}/${RELEASE_VERSION}" >/dev/null 
2>&1; then
+    err "Release candidate not found at ${SVN_DEV_URL}/${RELEASE_VERSION}/. 
Did scripts/release.sh upload it?"
+    exit 1
+fi
+
+if svn ls "${SVN_AUTH[@]}" "${SVN_RELEASE_URL}/${RELEASE_VERSION}" >/dev/null 
2>&1; then
+    echo "Already present at release/${RELEASE_VERSION} — skipping the move 
(idempotent)."
+else
+    if ! svn ls "${SVN_AUTH[@]}" "${SVN_RELEASE_URL}" >/dev/null 2>&1; then
+        echo "Creating ${SVN_RELEASE_URL}/ …"
+        svn mkdir --parents "${SVN_AUTH[@]}" -m "Create SkyWalking NodeJS 
release directory" "${SVN_RELEASE_URL}"
+    fi
+    if confirm "Run the server-side svn mv now? (PMC-only action)"; then
+        svn mv "${SVN_AUTH[@]}" -m "Release Apache SkyWalking-NodeJS 
${RELEASE_VERSION}" \
+            "${SVN_DEV_URL}/${RELEASE_VERSION}" 
"${SVN_RELEASE_URL}/${RELEASE_VERSION}"
+        echo "Moved to ${SVN_RELEASE_URL}/${RELEASE_VERSION}/"
+    else
+        err "svn move skipped — cannot continue without the official 
artifacts."
+        exit 1
+    fi
+fi
+
+# Remove ONLY a strictly-older previous release (never the one being released,
+# never a newer one). ASF keeps only the current release live; older versions
+# stay downloadable from archive.apache.org.
+PREV_RELEASE=$(svn ls "${SVN_AUTH[@]}" "${SVN_RELEASE_URL}/" 2>/dev/null \
+    | sed 's,/$,,' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | grep -vx 
"${RELEASE_VERSION}" \
+    | sort -t. -k1,1n -k2,2n -k3,3n | tail -1 || true)
+if [ -n "${PREV_RELEASE}" ]; then
+    if printf '%s\n%s\n' "${PREV_RELEASE}" "${RELEASE_VERSION}" \
+         | sort -t. -k1,1n -k2,2n -k3,3n -C 2>/dev/null && [ "${PREV_RELEASE}" 
!= "${RELEASE_VERSION}" ]; then
+        echo "Previous release to retire: ${SVN_RELEASE_URL}/${PREV_RELEASE}/"
+        read -r -p "To remove it, type the version '${PREV_RELEASE}' exactly 
(blank = keep): " TYPED || TYPED=""
+        if [ "${TYPED}" = "${PREV_RELEASE}" ]; then
+            svn rm "${SVN_AUTH[@]}" -m "Remove superseded release 
${PREV_RELEASE} (archived)" \
+                "${SVN_RELEASE_URL}/${PREV_RELEASE}"
+            echo "Removed release/${PREV_RELEASE}/."
+        else
+            echo "Kept release/${PREV_RELEASE}/."
+        fi
+    else
+        echo "WARN: latest other release ${PREV_RELEASE} is not strictly older 
than ${RELEASE_VERSION}; not removing anything."
+    fi
+fi
+unset SVN_PASS; unset SVN_AUTH
+echo "Allow ~a few minutes for mirror propagation to downloads.apache.org 
before updating website links."
+
+# ========================== Step 4: GitHub release ==========================
+note "Step 4 — GitHub release ${TAG}"
+
+# Fetch the VOTED bytes back from svn release so the GitHub release attaches
+# byte-identical files to what the PMC voted on (not a fresh rebuild).
+ART_DIR="${WORK_DIR}/artifacts"; mkdir -p "${ART_DIR}"
+for f in "${SRC_TGZ}" "${SRC_TGZ}.asc" "${SRC_TGZ}.sha512"; do
+    echo "Fetching ${f}…"
+    curl -fSL -o "${ART_DIR}/${f}" "${SVN_RELEASE_URL}/${RELEASE_VERSION}/${f}"
+done
+(cd "${ART_DIR}" && shasum -a 512 -c "${SRC_TGZ}.sha512")
+# Re-verify the binding signature too (not just the checksum).
+(cd "${ART_DIR}" && gpg --verify "${SRC_TGZ}.asc" "${SRC_TGZ}") \
+    || { err "GPG signature verify failed on the fetched release artifact. Is 
the signing key in ${KEYS_URL}?"; exit 1; }
+echo "Checksum + signature verified."
+
+if gh release view "${TAG}" --repo "${GH_REPO}" >/dev/null 2>&1; then
+    echo "GitHub release ${TAG} already exists."
+    if confirm "Publish it (clear draft, mark latest) and (re)upload the voted 
artifacts?"; then
+        gh release edit "${TAG}" --repo "${GH_REPO}" --draft=false --latest
+        gh release upload "${TAG}" --repo "${GH_REPO}" --clobber \
+            "${ART_DIR}/${SRC_TGZ}" "${ART_DIR}/${SRC_TGZ}.asc" 
"${ART_DIR}/${SRC_TGZ}.sha512"
+        echo "GitHub release published."
+    fi
+else
+    PREV_TAG=$(cd "${PROJECT_DIR}" && git tag --list 'v*' 
--sort=-version:refname | grep -vx "${TAG}" | head -1)
+    if confirm "Create GitHub release ${TAG} (auto-notes since 
${PREV_TAG:-<none>}) and attach the artifacts?"; then
+        gh release create "${TAG}" --repo "${GH_REPO}" \
+            --title "Apache SkyWalking NodeJS ${RELEASE_VERSION}" \
+            --generate-notes ${PREV_TAG:+--notes-start-tag "${PREV_TAG}"} 
--latest \
+            "${ART_DIR}/${SRC_TGZ}" "${ART_DIR}/${SRC_TGZ}.asc" 
"${ART_DIR}/${SRC_TGZ}.sha512"
+        echo "GitHub release created."
+    fi
+fi
+
+# ========================== Step 5: npm publish (optional, IRREVERSIBLE) 
==========================
+note "Step 5 — npm publish ${NPM_PACKAGE}@${RELEASE_VERSION} (optional, 
IRREVERSIBLE)"
+
+# Intent + npm auth were already decided/verified in the preflight above.
+if ! $WILL_PUBLISH_NPM; then
+    echo "Skipping npm publish (decided in preflight)."
+else
+    echo "npm user: ${NPM_USER}"
+    # Build + publish from a FRESH clone of the tag so the published bytes
+    # match the released tag exactly (not your working tree).
+    PUB_DIR="${WORK_DIR}/publish-clone"
+    git clone --recurse-submodules --branch "${TAG}" "${REPO_URL}" "${PUB_DIR}"
+    cd "${PUB_DIR}"
+    npm install
+    npm run build
+    [ -f lib/index.js ] || { err "npm run build did not produce lib/index.js — 
aborting publish."; exit 1; }
+    PUB_VERSION=$(node -e 
"process.stdout.write(require('./package.json').version)")
+    [ "${PUB_VERSION}" = "${RELEASE_VERSION}" ] || { err "Clone version 
${PUB_VERSION} != ${RELEASE_VERSION}."; exit 1; }
+    npm publish --dry-run
+    if confirm "Dry-run looks correct — run the REAL npm publish?"; then
+        if [ -n "${NPM_OTP:-}" ]; then npm publish --otp="${NPM_OTP}"; else 
npm publish; fi
+        echo "Published ${NPM_PACKAGE}@${RELEASE_VERSION}."
+    else
+        echo "Skipped real npm publish."
+    fi
+    cd "${PROJECT_DIR}"
+fi
+
+# ========================== Done ==========================
+note "Done — ${RELEASE_VERSION} finalized"
+echo "  svn release: ${SVN_RELEASE_URL}/${RELEASE_VERSION}/"
+echo "  GitHub:      https://github.com/${GH_REPO}/releases/tag/${TAG}";
+echo "  npm:         https://www.npmjs.com/package/${NPM_PACKAGE}";
+echo ""
+echo "Remaining MANUAL steps:"
+echo "  1. Website PR (apache/skywalking-website): add the release event, bump 
the"
+echo "     NodeJS Agent block in data/releases.yml and the docs pointer in 
data/docs.yml."
+echo "  2. Send the [ANNOUNCE] email from your @apache.org address to"
+echo "     [email protected] and [email protected]."
+echo ""
+echo "Working files left in ${WORK_DIR}/ (safe to delete)."
diff --git a/scripts/release.sh b/scripts/release.sh
new file mode 100755
index 0000000..4b99df4
--- /dev/null
+++ b/scripts/release.sh
@@ -0,0 +1,358 @@
+#!/usr/bin/env bash
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Apache SkyWalking NodeJS — release-candidate automation.
+#
+# Adapted from apache/skywalking-horizon-ui scripts/release.sh for the
+# single-package npm layout of skywalking-nodejs. Produces the Apache source
+# release, stages it for the vote, and prints the [VOTE] email:
+#
+#   skywalking-nodejs-src-<v>.tgz {.asc,.sha512}
+#
+# The post-vote half lives in scripts/release-finalize.sh.
+#
+# PRECONDITION: the version bump to <v> is ALREADY merged on master
+# (package.json "version" == <v>). This script does NOT bump the version.
+# It builds the tarball from a FRESH recursive clone of master so the voted
+# bytes always match the tag, and — importantly — pushes the git tag ONLY
+# AFTER the artifacts are built, signed, and self-verified, so a build
+# failure never leaves a public, immutable release tag behind.
+#
+# Run interactively on a single-user trusted host (it reads your SVN
+# password). Requires bash, but is written to work on macOS bash 3.2.
+#
+# Usage:  bash scripts/release.sh
+
+set -e -o pipefail
+
+SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
+PROJECT_DIR=$(cd "${SCRIPT_DIR}/.." && pwd)
+REPO_URL="${SW_RELEASE_REPO_URL:-https://github.com/apache/skywalking-nodejs.git}";
+REPO_BRANCH="${SW_RELEASE_BRANCH:-master}"
+SVN_DEV_URL="https://dist.apache.org/repos/dist/dev/skywalking/node-js";
+KEYS_URL="https://dist.apache.org/repos/dist/release/skywalking/KEYS";
+WORK_DIR="${SCRIPT_DIR}/.release-work"
+CLONE_DIR="${WORK_DIR}/skywalking-nodejs"
+
+TEST_FILE=""
+# Always clean up the throwaway GPG test file, even on Ctrl-C / early exit.
+trap 'rm -f "${TEST_FILE:-}" "${TEST_FILE:-}.asc"' EXIT
+
+# ========================== Helpers ==========================
+
+err()  { echo "ERROR: $*" >&2; }
+note() { echo ""; echo "=== $* ==="; }
+
+confirm() {
+    local prompt="$1" ans
+    read -r -p "${prompt} [y/N] " ans || { err "No input (no TTY?)."; exit 1; }
+    [[ "$ans" == "y" || "$ans" == "Y" ]]
+}
+
+# Prompt for a required, non-empty value. $1=prompt var-name is echoed back 
via stdout.
+ask() {
+    local prompt="$1" val
+    read -r -p "${prompt}: " val || { err "No input for '${prompt}' (no 
TTY?)."; exit 1; }
+    [ -n "$val" ] || { err "'${prompt}' must not be empty."; exit 1; }
+    printf '%s' "$val"
+}
+
+# Read the package.json "version" without jq (runs on stock macOS / Alpine).
+read_version() {
+    node -e 
"process.stdout.write(JSON.parse(require('fs').readFileSync('$1','utf8')).version)"
+}
+
+# ========================== Step 1: GPG signer ==========================
+note "Step 1 — GPG signer check"
+
+GPG_KEY_ID=$(git config user.signingkey 2>/dev/null || true)
+if [ -z "$GPG_KEY_ID" ]; then
+    GPG_KEY_ID=$(gpg --list-secret-keys --keyid-format LONG 2>/dev/null | grep 
-A1 '^sec' | tail -1 | awk '{print $1}' || true)
+fi
+if [ -z "$GPG_KEY_ID" ]; then
+    err "No GPG secret key found. Configure your Apache GPG key first."
+    exit 1
+fi
+
+# Match @apache.org against ANY uid of the SELECTED key (not just the first
+# uid of a blind dump), and tolerate an empty result without aborting.
+GPG_EMAILS=$(gpg --list-keys --with-colons "${GPG_KEY_ID}" 2>/dev/null | awk 
-F: '/^uid:/{print $10}' || true)
+if ! printf '%s\n' "${GPG_EMAILS}" | grep -q '@apache\.org'; then
+    err "Key ${GPG_KEY_ID} has no @apache.org uid — Apache releases must be 
signed with an @apache.org key."
+    err "uids found: ${GPG_EMAILS:-<none>}"
+    exit 1
+fi
+echo "GPG Key: ${GPG_KEY_ID}"
+gpg --list-keys --keyid-format LONG "${GPG_KEY_ID}" | grep -E '^(pub|uid)' || 
true
+echo "Reminder: this key MUST already be in ${KEYS_URL} — every voter verifies 
against it."
+confirm "Is this the correct signer?" || { echo "Aborted."; exit 1; }
+
+# Pin the signer end-to-end: package.json's release-src honors SW_GPG_KEY
+# (gpg -u), so the tarball is signed by THIS key, not gpg's default key.
+export SW_GPG_KEY="${GPG_KEY_ID}"
+
+export GPG_TTY=$(tty || true)
+echo "Verifying GPG signing works (you may be prompted for the passphrase)…"
+TEST_FILE=$(mktemp); echo "test" > "${TEST_FILE}"
+if ! gpg --batch --yes -u "${GPG_KEY_ID}" --armor --detach-sig "${TEST_FILE}" 
2>/dev/null; then
+    err "GPG signing with ${GPG_KEY_ID} failed. Try:  export GPG_TTY=\$(tty)  
/  gpgconf --launch gpg-agent"
+    exit 1
+fi
+rm -f "${TEST_FILE}" "${TEST_FILE}.asc"; TEST_FILE=""
+echo "GPG signing OK (signer pinned to ${GPG_KEY_ID})."
+
+# ========================== Step 2: Required tools ==========================
+note "Step 2 — Tool check"
+
+MISSING=()
+for t in gpg svn shasum git gh node npm tar; do
+    command -v "$t" >/dev/null || MISSING+=("$t")
+done
+if [ ${#MISSING[@]} -gt 0 ]; then
+    err "Missing required tools: ${MISSING[*]}"
+    exit 1
+fi
+HAVE_LICENSE_EYE=true
+command -v license-eye >/dev/null || HAVE_LICENSE_EYE=false
+echo "All required tools present.  node: $(node --version)  npm: $(npm 
--version)"
+$HAVE_LICENSE_EYE || echo "NOTE: license-eye not installed — header check will 
be skipped (CI still enforces it)."
+
+# Node baseline: engines.node >=20, and grpc-tools' node-pre-gyp needs >=18.17.
+NODE_MAJOR=$(node -e 
"process.stdout.write(String(process.versions.node.split('.')[0]))")
+[[ "${NODE_MAJOR}" =~ ^[0-9]+$ ]] || { err "Could not parse Node major version 
('${NODE_MAJOR}')."; exit 1; }
+if [ "${NODE_MAJOR}" -lt 20 ]; then
+    err "Node ${NODE_MAJOR}.x is below the >=20 baseline; grpc-tools install 
may also fail. Use Node 20/22/24."
+    exit 1
+fi
+
+# ========================== Step 3: Detect version ==========================
+note "Step 3 — Detect version"
+
+# skywalking-nodejs does NOT use a -dev suffix; master carries the release
+# number, bumped in a prior PR.
+RELEASE_VERSION=$(read_version "${PROJECT_DIR}/package.json")
+[ -n "$RELEASE_VERSION" ] || { err "Could not read version from 
package.json."; exit 1; }
+echo "Release version (from package.json): ${RELEASE_VERSION}"
+confirm "Release this version?" || RELEASE_VERSION=$(ask "Enter the release 
version to cut")
+TAG="v${RELEASE_VERSION}"
+
+# ========================== Step 4: Consistency check 
==========================
+note "Step 4 — Consistency check"
+
+for f in LICENSE NOTICE package.json; do
+    [ -f "${PROJECT_DIR}/${f}" ] || { err "${f} missing at repo root."; exit 
1; }
+done
+echo "LICENSE / NOTICE / package.json present."
+# Soft check: README's advertised baseline should not contradict engines.node.
+if grep -qiE 'NodeJS *>= *([0-9]|1[0-9])\b' "${PROJECT_DIR}/README.md" 
2>/dev/null &&
+   ! grep -qiE 'NodeJS *>= *2[0-9]' "${PROJECT_DIR}/README.md" 2>/dev/null; 
then
+    echo "WARN: README.md advertises a Node baseline below 20 while 
engines.node is >=20. Fix before tagging."
+fi
+
+# ========================== Step 5: License-header check 
==========================
+if $HAVE_LICENSE_EYE; then
+    note "Step 5 — License-header check (license-eye)"
+    (cd "${PROJECT_DIR}" && license-eye -c .licenserc.yaml header check)
+    echo "License headers OK."
+else
+    note "Step 5 — License-header check SKIPPED (license-eye absent; CI 
enforces it)"
+fi
+
+# ========================== Step 6: Clone fresh + tag LOCALLY 
==========================
+note "Step 6 — Fresh recursive clone + local tag ${TAG}"
+
+rm -rf "${WORK_DIR}"; mkdir -p "${WORK_DIR}"
+# --recurse-submodules is MANDATORY: protocol/ holds the .proto sources;
+# without it prepare->protoc.sh codegens nothing (Step 8 guards against this).
+echo "Cloning ${REPO_URL} (branch ${REPO_BRANCH}) with submodules…"
+git clone --recurse-submodules --branch "${REPO_BRANCH}" "${REPO_URL}" 
"${CLONE_DIR}"
+
+CLONE_VERSION=$(read_version "${CLONE_DIR}/package.json")
+if [ "${CLONE_VERSION}" != "${RELEASE_VERSION}" ]; then
+    err "Fresh clone of ${REPO_BRANCH} has version ${CLONE_VERSION}, but you 
are releasing ${RELEASE_VERSION}."
+    err "Merge the version-bump PR (package.json -> ${RELEASE_VERSION}) into 
${REPO_BRANCH} first."
+    exit 1
+fi
+
+cd "${CLONE_DIR}"
+# Capture ls-remote success explicitly: a FAILED ls-remote must not be read
+# as "tag absent" (which would let us re-create an existing release tag).
+REMOTE_TAGS=$(git ls-remote --tags origin) || { err "git ls-remote origin 
failed; cannot verify ${TAG} is unused."; exit 1; }
+if printf '%s\n' "${REMOTE_TAGS}" | grep -q "refs/tags/${TAG}$"; then
+    err "Tag ${TAG} already exists on origin. Delete it first to re-cut, or 
pick a new version."
+    exit 1
+fi
+# Create the annotated tag LOCALLY only. It is pushed in Step 9, AFTER the
+# artifacts are built + verified — never before.
+git tag -a "${TAG}" -m "Release Apache SkyWalking-NodeJS ${RELEASE_VERSION}"
+RELEASE_COMMIT=$(git rev-parse "${TAG}")
+echo "Local tag ${TAG} created at ${RELEASE_COMMIT} (NOT pushed yet)."
+
+# ========================== Step 7: Build + sign source release 
==========================
+note "Step 7 — Build + sign source tarball (npm run release-src)"
+
+# `npm install` runs prepare->scripts/protoc.sh (grpc-tools protoc; on Apple
+# Silicon under Rosetta). release-src then runs prepare again + package-src
+# (tar) + gpg detached sig (signer pinned via SW_GPG_KEY) + sha512.
+npm install
+npm run release-src
+
+SRC_TGZ="skywalking-nodejs-src-${RELEASE_VERSION}.tgz"
+for f in "${SRC_TGZ}" "${SRC_TGZ}.asc" "${SRC_TGZ}.sha512"; do
+    [ -f "${CLONE_DIR}/${f}" ] || { err "Expected artifact ${f} not produced 
by release-src."; exit 1; }
+    cp "${CLONE_DIR}/${f}" "${WORK_DIR}/"
+done
+
+# ========================== Step 8: Verify the tarball 
==========================
+note "Step 8 — Verify artifact contents + signature"
+
+cd "${WORK_DIR}"
+PROTO_COUNT=$(tar -tzf "${SRC_TGZ}" | grep -cE 'protocol/.*\.proto' || true)
+[ "${PROTO_COUNT}" -gt 0 ] || { err "Tarball contains 0 protocol/*.proto — 
submodule was empty. Aborting."; exit 1; }
+if ! tar -tzf "${SRC_TGZ}" | grep -qE '(^|/)LICENSE$'; then err "Tarball 
missing LICENSE."; exit 1; fi
+if ! tar -tzf "${SRC_TGZ}" | grep -qE '(^|/)NOTICE$';  then err "Tarball 
missing NOTICE.";  exit 1; fi
+if   tar -tzf "${SRC_TGZ}" | grep -q 'node_modules/';  then err "Tarball 
unexpectedly contains node_modules/."; exit 1; fi
+echo "Contents OK: ${PROTO_COUNT} .proto files, LICENSE + NOTICE present, no 
node_modules."
+
+shasum -a 512 -c "${SRC_TGZ}.sha512"
+gpg --verify "${SRC_TGZ}.asc" "${SRC_TGZ}"
+echo "Checksum + signature self-verify OK."
+echo "Artifacts:"; ls -lh "${WORK_DIR}/${SRC_TGZ}" 
"${WORK_DIR}/${SRC_TGZ}.asc" "${WORK_DIR}/${SRC_TGZ}.sha512"
+
+# ========================== Step 9: Push the tag (artifacts are good now) 
==========================
+note "Step 9 — Push tag ${TAG}"
+
+TAG_PUSHED=false
+if confirm "Artifacts built + verified. Push tag ${TAG} to origin now? (needed 
before the vote)"; then
+    (cd "${CLONE_DIR}" && git push origin "${TAG}")
+    TAG_PUSHED=true
+    echo "Pushed ${TAG}."
+else
+    echo "Tag ${TAG} NOT pushed. Push it (from ${CLONE_DIR}) before sending 
the vote email:"
+    echo "    git -C ${CLONE_DIR} push origin ${TAG}"
+fi
+
+# ========================== Step 10: Upload RC to svn dev 
==========================
+note "Step 10 — Upload RC to ${SVN_DEV_URL}/${RELEASE_VERSION}"
+
+UPLOADED=false
+if confirm "Upload the release candidate to svn dev now?"; then
+    # NOTE: svn takes the password on argv (--password), so it is briefly
+    # visible in `ps`. Run this only on a single-user trusted host and never
+    # with `set -x`. The password is cleared from the environment below.
+    SVN_USER=$(ask "Apache SVN username")
+    read -r -s -p "Apache SVN password: " SVN_PASS || { err "No SVN password 
(no TTY?)."; exit 1; }
+    echo ""
+    [ -n "$SVN_PASS" ] || { err "SVN password must not be empty."; exit 1; }
+    SVN_AUTH=(--username "${SVN_USER}" --password "${SVN_PASS}" 
--non-interactive --no-auth-cache)
+
+    SVN_STAGE="${WORK_DIR}/svn-staging"
+    rm -rf "${SVN_STAGE}"
+    svn co --depth empty "${SVN_AUTH[@]}" "${SVN_DEV_URL}" "${SVN_STAGE}"
+    SVN_VERSION_DIR="${SVN_STAGE}/${RELEASE_VERSION}"
+    if svn ls "${SVN_AUTH[@]}" "${SVN_DEV_URL}/${RELEASE_VERSION}" >/dev/null 
2>&1; then
+        echo "Version folder exists on svn — updating in place."
+        svn update "${SVN_AUTH[@]}" --set-depth infinity "${SVN_VERSION_DIR}"
+    else
+        mkdir -p "${SVN_VERSION_DIR}"
+    fi
+    cp "${WORK_DIR}/${SRC_TGZ}" "${WORK_DIR}/${SRC_TGZ}.asc" 
"${WORK_DIR}/${SRC_TGZ}.sha512" "${SVN_VERSION_DIR}/"
+    (cd "${SVN_STAGE}" && svn add --force "${RELEASE_VERSION}" || true)
+    (cd "${SVN_STAGE}" && svn commit "${SVN_AUTH[@]}" -m "Draft Apache 
SkyWalking-NodeJS release ${RELEASE_VERSION}")
+    UPLOADED=true
+    echo "Uploaded: ${SVN_DEV_URL}/${RELEASE_VERSION}"
+    unset SVN_PASS; unset SVN_AUTH
+else
+    echo "Skipped svn upload. Artifacts are in ${WORK_DIR}/."
+fi
+
+# ========================== Step 11: Vote email ==========================
+note "Step 11 — Vote email"
+
+if ! $TAG_PUSHED || ! $UPLOADED; then
+    echo "WARNING: tag pushed=${TAG_PUSHED}, RC uploaded=${UPLOADED}."
+    echo "         Some links in the email below are DEAD until you push the 
tag and/or upload the RC."
+    echo ""
+fi
+
+SRC_SHA512=$(cat "${WORK_DIR}/${SRC_TGZ}.sha512")
+VOTE_DATE=$(LC_ALL=C date +"%B %d, %Y")
+
+cat <<EOF
+
+========================================================================
+Vote Email — copy and send to [email protected]
+========================================================================
+
+Subject: [VOTE] Release Apache SkyWalking NodeJS version ${RELEASE_VERSION}
+
+Hi the SkyWalking Community,
+
+This is a call for vote to release Apache SkyWalking NodeJS version 
${RELEASE_VERSION}.
+
+Release notes:
+
+ * https://github.com/apache/skywalking-nodejs/releases/tag/${TAG}
+
+Release Candidate:
+
+ * ${SVN_DEV_URL}/${RELEASE_VERSION}
+ * sha512 checksums
+   - ${SRC_SHA512}
+
+Release Tag :
+
+ * (Git Tag) ${TAG}
+
+Release Commit Hash :
+
+ * https://github.com/apache/skywalking-nodejs/tree/${RELEASE_COMMIT}
+
+Keys to verify the Release Candidate :
+
+ * ${KEYS_URL}
+
+Guide to build the release from source :
+
+ * 
https://github.com/apache/skywalking-nodejs/blob/${TAG}/CONTRIBUTING.md#compiling-and-building
+
+Voting will start now (${VOTE_DATE}) and will remain open for at least 72 
hours.
+A release passes with at least 3 binding +1 (PMC) votes and more +1 than -1.
+
+[ ] +1 Release this package.
+[ ] +0 No opinion.
+[ ] -1 Do not release this package because....
+
+Thanks.
+
+[1] 
https://github.com/apache/skywalking-nodejs/blob/master/docs/How-to-release.md#vote-check
+========================================================================
+EOF
+
+note "Done — release candidate ${RELEASE_VERSION} staged"
+echo "  Tag:             ${TAG} ($($TAG_PUSHED && echo pushed || echo 'NOT 
pushed — push it before voting'))"
+echo "  Artifacts:       ${WORK_DIR}/${SRC_TGZ}{,.asc,.sha512}"
+echo "  svn dev staging: $($UPLOADED && echo 
"${SVN_DEV_URL}/${RELEASE_VERSION}" || echo 'NOT uploaded')"
+echo ""
+echo "Next steps:"
+echo "  1. Draft the GitHub release notes (auto-generated; CHANGELOG.md is a 
stub):"
+echo "       gh release create ${TAG} --draft --generate-notes --verify-tag \\"
+echo "         --notes-start-tag <previous tag> --title \"Apache SkyWalking 
NodeJS ${RELEASE_VERSION}\""
+echo "  2. Send the [VOTE] email above to [email protected] (>=72h)."
+echo "  3. After the vote passes, run:  bash scripts/release-finalize.sh"
diff --git a/tests/plugins/axios/docker-compose.yml 
b/tests/plugins/axios/docker-compose.yml
index 0216550..c48ba93 100644
--- a/tests/plugins/axios/docker-compose.yml
+++ b/tests/plugins/axios/docker-compose.yml
@@ -25,6 +25,13 @@ services:
     networks:
       - traveling-light
 
+  httpbin:
+    extends:
+      file: ../common/base-compose.yml
+      service: httpbin
+    networks:
+      - traveling-light
+
   server:
     extends:
       file: ../common/base-compose.yml
@@ -42,6 +49,8 @@ services:
     depends_on:
       collector:
         condition: service_healthy
+      httpbin:
+        condition: service_started
 
   client:
     extends:
diff --git a/tests/plugins/axios/expected.data.yaml 
b/tests/plugins/axios/expected.data.yaml
index 61e897e..c14df75 100644
--- a/tests/plugins/axios/expected.data.yaml
+++ b/tests/plugins/axios/expected.data.yaml
@@ -30,11 +30,11 @@ segmentItems:
             endTime: gt 0
             componentId: 4005
             spanType: Exit
-            peer: httpbin.org
+            peer: httpbin:8080
             skipAnalysis: false
             tags:
               - key: http.url
-                value: http://httpbin.org/json
+                value: http://httpbin:8080/json
               - key: http.method
                 value: GET
               - key: http.status_code
diff --git a/tests/plugins/axios/server.ts b/tests/plugins/axios/server.ts
index bbca28d..5233e5e 100644
--- a/tests/plugins/axios/server.ts
+++ b/tests/plugins/axios/server.ts
@@ -27,7 +27,7 @@ agent.start({
 });
 
 const server = http.createServer(async (req, res) => {
-  const r = await axios.get('http://httpbin.org/json');
+  const r = await axios.get('http://httpbin:8080/json');
   res.end(JSON.stringify(r.data));
 });
 
diff --git a/tests/plugins/common/base-compose.yml 
b/tests/plugins/common/base-compose.yml
index a74b56c..504dfa9 100644
--- a/tests/plugins/common/base-compose.yml
+++ b/tests/plugins/common/base-compose.yml
@@ -41,5 +41,13 @@ services:
     networks:
       - traveling-light
 
+  # Local httpbin so the HTTP-family plugin tests (http/axios/express) do not 
depend
+  # on the flaky public httpbin.org. go-httpbin serves an identical /json 
(HTTP 200),
+  # listening on container port 8080.
+  httpbin:
+    image: mccutchen/go-httpbin:2.23.1
+    networks:
+      - traveling-light
+
 networks:
   traveling-light:
diff --git a/tests/plugins/express/docker-compose.yml 
b/tests/plugins/express/docker-compose.yml
index 69c9e35..bad6f4a 100644
--- a/tests/plugins/express/docker-compose.yml
+++ b/tests/plugins/express/docker-compose.yml
@@ -25,6 +25,13 @@ services:
     networks:
       - traveling-light
 
+  httpbin:
+    extends:
+      file: ../common/base-compose.yml
+      service: httpbin
+    networks:
+      - traveling-light
+
   server:
     extends:
       file: ../common/base-compose.yml
@@ -42,6 +49,8 @@ services:
     depends_on:
       collector:
         condition: service_healthy
+      httpbin:
+        condition: service_started
 
   client:
     extends:
diff --git a/tests/plugins/express/expected.data.yaml 
b/tests/plugins/express/expected.data.yaml
index 51b0981..891f130 100644
--- a/tests/plugins/express/expected.data.yaml
+++ b/tests/plugins/express/expected.data.yaml
@@ -61,11 +61,11 @@ segmentItems:
             endTime: gt 0
             componentId: 2
             spanType: Exit
-            peer: httpbin.org
+            peer: httpbin:8080
             skipAnalysis: false
             tags:
               - key: http.url
-                value: http://httpbin.org/json
+                value: http://httpbin:8080/json
               - key: http.method
                 value: GET
               - key: http.status_code
diff --git a/tests/plugins/express/server.ts b/tests/plugins/express/server.ts
index 9ce3346..4e1dc42 100644
--- a/tests/plugins/express/server.ts
+++ b/tests/plugins/express/server.ts
@@ -30,12 +30,12 @@ const app = express();
 
 app.get('/express', (req, res) => {
   http
-  .request('http://httpbin.org/json', (r) => {
-    let data = '';
-    r.on('data', (chunk) => (data += chunk));
-    r.on('end', () => res.send(data));
-  })
-  .end();
+    .request('http://httpbin:8080/json', (r) => {
+      let data = '';
+      r.on('data', (chunk) => (data += chunk));
+      r.on('end', () => res.send(data));
+    })
+    .end();
 });
 
 app.listen(5000, () => console.info('Listening on port 5000...'));
diff --git a/tests/plugins/http/docker-compose.yml 
b/tests/plugins/http/docker-compose.yml
index e5d8ca7..565d6dc 100644
--- a/tests/plugins/http/docker-compose.yml
+++ b/tests/plugins/http/docker-compose.yml
@@ -25,6 +25,13 @@ services:
     networks:
       - traveling-light
 
+  httpbin:
+    extends:
+      file: ../common/base-compose.yml
+      service: httpbin
+    networks:
+      - traveling-light
+
   server:
     extends:
       file: ../common/base-compose.yml
@@ -42,6 +49,8 @@ services:
     depends_on:
       collector:
         condition: service_healthy
+      httpbin:
+        condition: service_started
 
   client:
     extends:
diff --git a/tests/plugins/http/expected.data.yaml 
b/tests/plugins/http/expected.data.yaml
index 22fdad8..370aae0 100644
--- a/tests/plugins/http/expected.data.yaml
+++ b/tests/plugins/http/expected.data.yaml
@@ -61,11 +61,11 @@ segmentItems:
             endTime: gt 0
             componentId: 2
             spanType: Exit
-            peer: httpbin.org
+            peer: httpbin:8080
             skipAnalysis: false
             tags:
               - key: http.url
-                value: http://httpbin.org/json
+                value: http://httpbin:8080/json
               - key: http.method
                 value: GET
               - key: http.status_code
diff --git a/tests/plugins/http/server.ts b/tests/plugins/http/server.ts
index bf362c7..629c1c7 100644
--- a/tests/plugins/http/server.ts
+++ b/tests/plugins/http/server.ts
@@ -27,12 +27,12 @@ agent.start({
 
 const server = http.createServer((req, res) => {
   http
-  .request('http://httpbin.org/json', (r) => {
-    let data = '';
-    r.on('data', (chunk) => (data += chunk));
-    r.on('end', () => res.end(data));
-  })
-  .end();
+    .request('http://httpbin:8080/json', (r) => {
+      let data = '';
+      r.on('data', (chunk) => (data += chunk));
+      r.on('end', () => res.end(data));
+    })
+    .end();
 });
 
 server.listen(5000, () => console.info('Listening on port 5000...'));


Reply via email to