| #!/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. |
| # |
| # VERSIONING: master carries the in-flight dev version (e.g. 0.9.0-dev), like |
| # SkyWalking's -SNAPSHOT / horizon-ui's -dev. In a FRESH recursive clone this |
| # script cuts a release branch, strips the -dev suffix and commits + tags that |
| # commit (the release candidate), then adds a second commit bumping back to the |
| # next dev version, and opens ONE PR carrying both. The tag is pushed ONLY AFTER |
| # the artifacts are built, signed and self-verified, so a build failure never |
| # leaves a public, immutable release tag behind. Merge the PR after the vote |
| # passes (master then returns to -dev with the release in its history). |
| # |
| # 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}" |
| GH_REPO="${SW_RELEASE_GH_REPO:-apache/skywalking-nodejs}" |
| 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" |
| |
| # --dry-run / SW_RELEASE_DRY_RUN=1: run the full LOCAL pipeline (clone, strip -dev, |
| # build, sign, verify) but perform ZERO remote mutations — no tag/branch push, no |
| # svn upload, no PR. Lets a committer rehearse against the real repo with nothing to undo. |
| DRY_RUN=false |
| if [ "${1:-}" = "--dry-run" ] || [ -n "${SW_RELEASE_DRY_RUN:-}" ]; then |
| DRY_RUN=true |
| fi |
| |
| 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)" |
| } |
| |
| $DRY_RUN && note "DRY RUN — full local build + sign + verify, but NO tag/branch push, NO svn upload, NO PR" |
| |
| # ========================== 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" |
| |
| # master carries the in-flight dev version (e.g. 0.9.0-dev). The release version |
| # is the bare semver; the next dev version bumps the minor (patch reset to 0). |
| CURRENT_VERSION=$(read_version "${PROJECT_DIR}/package.json") |
| [ -n "$CURRENT_VERSION" ] || { err "Could not read version from package.json."; exit 1; } |
| RELEASE_VERSION="${CURRENT_VERSION%-dev}" |
| RELEASE_VERSION="${RELEASE_VERSION%-SNAPSHOT}" |
| if [ "${CURRENT_VERSION}" = "${RELEASE_VERSION}" ]; then |
| err "package.json version '${CURRENT_VERSION}' has no -dev/-SNAPSHOT suffix." |
| err "master must carry the dev-suffixed version between releases; bump it first." |
| exit 1 |
| fi |
| if ! printf '%s' "${RELEASE_VERSION}" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then |
| err "Release version '${RELEASE_VERSION}' (from '${CURRENT_VERSION}') is not X.Y.Z."; exit 1 |
| fi |
| MAJOR=$(printf '%s' "${RELEASE_VERSION}" | cut -d. -f1) |
| MINOR=$(printf '%s' "${RELEASE_VERSION}" | cut -d. -f2) |
| NEXT_DEV_VERSION="${MAJOR}.$((MINOR + 1)).0-dev" |
| |
| echo "Current (master): ${CURRENT_VERSION}" |
| echo "Release: ${RELEASE_VERSION}" |
| echo "Next dev: ${NEXT_DEV_VERSION}" |
| if ! confirm "Are these correct?"; then |
| RELEASE_VERSION=$(ask "Enter the release version (X.Y.Z)") |
| NEXT_DEV_VERSION=$(ask "Enter the next dev version (e.g. 0.10.0-dev)") |
| printf '%s' "${RELEASE_VERSION}" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$' \ |
| || { err "Release version must be X.Y.Z (got '${RELEASE_VERSION}')."; exit 1; } |
| printf '%s' "${NEXT_DEV_VERSION}" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+-dev$' \ |
| || { err "Next dev version must be X.Y.Z-dev (got '${NEXT_DEV_VERSION}')."; exit 1; } |
| [ "${NEXT_DEV_VERSION}" != "${RELEASE_VERSION}" ] \ |
| || { err "Next dev version must differ from the release version."; exit 1; } |
| fi |
| TAG="v${RELEASE_VERSION}" |
| RELEASE_BRANCH="prepare-release-${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, strip -dev, commit + tag LOCALLY ========================== |
| note "Step 6 — Fresh clone, strip -dev, commit + 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}" != "${CURRENT_VERSION}" ]; then |
| err "Fresh clone of ${REPO_BRANCH} has version ${CLONE_VERSION}, expected ${CURRENT_VERSION}." |
| err "Re-run after ${REPO_BRANCH} settles, or fix its package.json version." |
| exit 1 |
| fi |
| |
| cd "${CLONE_DIR}" |
| # Capture ls-remote success explicitly: a FAILED ls-remote must not be read |
| # as "tag/branch absent" (which would let us re-create an existing one). |
| 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 |
| REMOTE_HEADS=$(git ls-remote --heads origin "${RELEASE_BRANCH}") || { err "git ls-remote origin failed."; exit 1; } |
| if [ -n "${REMOTE_HEADS}" ]; then |
| err "Branch ${RELEASE_BRANCH} already exists on origin. Close/delete it first to re-cut." |
| exit 1 |
| fi |
| |
| # The release commit lands via PR (master is protected). Cut a dedicated branch; |
| # the tag points at the version-strip commit, and the next-dev bump is added as a |
| # second commit on the SAME branch in Step 9 — so one PR carries both. |
| git checkout -q -b "${RELEASE_BRANCH}" |
| npm version "${RELEASE_VERSION}" --no-git-tag-version --allow-same-version >/dev/null |
| # Install deps BEFORE committing so the committed (and tagged) lockfile is exactly |
| # the one the tarball ships — no post-build drift. This also runs prepare->protoc.sh |
| # (needs the protocol/ submodule + grpc-tools; on Apple Silicon protoc runs via Rosetta). |
| npm install |
| git add package.json package-lock.json |
| git commit -q --no-verify -m "Prepare release ${RELEASE_VERSION}" |
| # Tag the version-strip commit LOCALLY only. It is pushed in Step 9, AFTER the |
| # artifacts are built + verified — never before. Use ^{commit} so RELEASE_COMMIT is |
| # the COMMIT sha (an annotated tag's own object sha would 404 in the vote email). |
| git tag -a "${TAG}" -m "Release Apache SkyWalking-NodeJS ${RELEASE_VERSION}" |
| RELEASE_COMMIT=$(git rev-parse "${TAG}^{commit}") |
| echo "Branch ${RELEASE_BRANCH} + local tag ${TAG} at ${RELEASE_COMMIT} (NOT pushed yet)." |
| |
| # ========================== Step 7: Build + sign source release ========================== |
| note "Step 7 — Build + sign source tarball (npm run release-src)" |
| |
| # Deps were installed in Step 6 (before the commit). release-src runs prepare |
| # (protoc) + package-src (tar) + gpg detached sig (signer pinned via SW_GPG_KEY) |
| # + sha512, from the tagged working tree. |
| 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 tag + branch, next-dev bump, open PR ========================== |
| note "Step 9 — Push tag ${TAG} + branch ${RELEASE_BRANCH}, open the release PR" |
| |
| cd "${CLONE_DIR}" |
| # Add the next-dev bump as a SECOND commit on the branch so one PR carries both: |
| # version strip (tagged commit 1) -> back to -dev (commit 2). The tag stays on |
| # commit 1. (checkout guards against any stray working-tree drift first.) |
| git checkout -q -- package.json package-lock.json 2>/dev/null || true |
| npm version "${NEXT_DEV_VERSION}" --no-git-tag-version >/dev/null |
| git add package.json package-lock.json |
| git commit -q --no-verify -m "Prepare next release ${NEXT_DEV_VERSION}" |
| |
| TAG_PUSHED=false |
| if $DRY_RUN; then |
| echo "DRY RUN: prepared branch ${RELEASE_BRANCH} (2 commits) + local tag ${TAG} in ${CLONE_DIR}." |
| echo "DRY RUN: skipping push of branch + tag and the release PR." |
| elif confirm "Artifacts built + verified. Push tag ${TAG} + branch ${RELEASE_BRANCH} (atomic) and open the release PR now?"; then |
| # --atomic: both refs land together or neither, avoiding a branch-without-tag |
| # half-state that the re-run guards would then trip on. |
| git push --atomic origin "${RELEASE_BRANCH}" "${TAG}" |
| TAG_PUSHED=true |
| echo "Pushed ${RELEASE_BRANCH} + ${TAG}." |
| if gh pr create --repo "${GH_REPO}" --base "${REPO_BRANCH}" --head "${RELEASE_BRANCH}" \ |
| --title "Release ${RELEASE_VERSION}, bump to ${NEXT_DEV_VERSION}" \ |
| --body "Release branch for ${RELEASE_VERSION} — two commits: strip -dev (tagged ${TAG}, the RC the vote runs against) and bump to ${NEXT_DEV_VERSION}. Merge AFTER the [VOTE] passes; the ${TAG} tag stays pinned to the release commit."; then |
| echo "Opened release PR: ${RELEASE_BRANCH} -> ${REPO_BRANCH}." |
| else |
| echo "Could not open the PR automatically — open it by hand (${RELEASE_BRANCH} -> ${REPO_BRANCH})." |
| fi |
| else |
| echo "Tag ${TAG} + branch ${RELEASE_BRANCH} NOT pushed; the prepared clone is in ${CLONE_DIR}." |
| echo "To finish by hand: git -C ${CLONE_DIR} push --atomic origin ${RELEASE_BRANCH} ${TAG}, then open the PR." |
| fi |
| |
| # ========================== Step 10: Upload RC to svn dev ========================== |
| note "Step 10 — Upload RC to ${SVN_DEV_URL}/${RELEASE_VERSION}" |
| |
| UPLOADED=false |
| if $DRY_RUN; then |
| echo "DRY RUN: skipping svn upload to ${SVN_DEV_URL}/${RELEASE_VERSION}." |
| elif 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 dev@skywalking.apache.org |
| ======================================================================== |
| |
| 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 — $($DRY_RUN && echo 'DRY RUN for' || echo 'release candidate') ${RELEASE_VERSION}$($DRY_RUN || echo ' staged')" |
| $DRY_RUN && echo " (DRY RUN: nothing was pushed/uploaded; the prepared clone is in ${CLONE_DIR}.)" |
| echo " Tag: ${TAG} ($($TAG_PUSHED && echo pushed || echo 'NOT pushed'))" |
| 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 \"${RELEASE_VERSION}\"" |
| echo " 2. Send the [VOTE] email above to dev@skywalking.apache.org (>=72h)." |
| echo " 3. After the vote passes:" |
| echo " - run: bash scripts/release-finalize.sh" |
| echo " - merge the release PR (${RELEASE_BRANCH} -> ${REPO_BRANCH}); ${REPO_BRANCH} returns to ${NEXT_DEV_VERSION}, tag ${TAG} stays put." |