diff --git a/.github/workflows/nodejs-org.yml b/.github/workflows/nodejs-org.yml new file mode 100644 index 0000000..f3b58eb --- /dev/null +++ b/.github/workflows/nodejs-org.yml @@ -0,0 +1,164 @@ +name: 'Update nodejs.org' + +on: + push: + tags: + - 'v*' + workflow_dispatch: + inputs: + version: + description: 'nvm version tag (e.g., v0.40.4). Defaults to latest release.' + required: false + default: '' + +permissions: + contents: read + +jobs: + update-nodejs-org: + if: github.repository == 'nvm-sh/nvm' && github.actor == 'ljharb' + permissions: + contents: none + name: 'Create PR to nodejs/nodejs.org' + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@v2 + with: + allowed-endpoints: + github.com:443 + api.github.com:443 + + - name: Extract and validate version + id: version + run: | + set -euo pipefail + + INPUT_VERSION="${{ inputs.version }}" + + if [ -n "${INPUT_VERSION}" ]; then + TAG="${INPUT_VERSION}" + elif [ "${GITHUB_REF_TYPE}" = "tag" ]; then + TAG="${GITHUB_REF#refs/tags/}" + else + TAG="$(gh api "repos/${GITHUB_REPOSITORY}/releases/latest" --jq '.tag_name')" + fi + + if ! printf '%s\n' "${TAG}" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "::notice::Tag '${TAG}' does not match expected format vX.Y.Z, skipping" + exit 0 + fi + + printf 'tag=%s\n' "${TAG}" >> "${GITHUB_OUTPUT}" + env: + GH_TOKEN: ${{ github.token }} + + - name: Set up fork and branch + if: steps.version.outputs.tag + id: fork + run: | + set -euo pipefail + + BRANCH="nvm-${{ steps.version.outputs.tag }}" + + gh repo fork nodejs/nodejs.org --clone=false 2>&1 || true + FORK_OWNER="$(gh api user --jq '.login')" + + DEFAULT_BRANCH="$(gh api repos/nodejs/nodejs.org --jq '.default_branch')" + UPSTREAM_SHA="$(gh api "repos/nodejs/nodejs.org/git/ref/heads/${DEFAULT_BRANCH}" --jq '.object.sha')" + + # Create or reset branch on fork to upstream HEAD + if ! gh api "repos/${FORK_OWNER}/nodejs.org/git/refs" \ + -f "ref=refs/heads/${BRANCH}" \ + -f "sha=${UPSTREAM_SHA}" > /dev/null 2>&1; then + gh api "repos/${FORK_OWNER}/nodejs.org/git/refs/heads/${BRANCH}" \ + -X PATCH \ + -f "sha=${UPSTREAM_SHA}" \ + -f "force=true" > /dev/null + fi + + printf 'fork_owner=%s\n' "${FORK_OWNER}" >> "${GITHUB_OUTPUT}" + printf 'branch=%s\n' "${BRANCH}" >> "${GITHUB_OUTPUT}" + env: + GH_TOKEN: ${{ secrets.NODEJS_ORG_TOKEN }} + + - name: Update nvm version in English snippet + if: steps.version.outputs.tag + id: update + run: | + set -euo pipefail + + NEW_VERSION="${{ steps.version.outputs.tag }}" + FORK_OWNER="${{ steps.fork.outputs.fork_owner }}" + BRANCH="${{ steps.fork.outputs.branch }}" + FILE_PATH="apps/site/snippets/en/download/nvm.bash" + PATTERN='nvm-sh/nvm/v[0-9]+\.[0-9]+\.[0-9]+/install\.sh' + REPLACEMENT="nvm-sh/nvm/${NEW_VERSION}/install.sh" + + # Get file content via API + FILE_RESPONSE="$(gh api "repos/${FORK_OWNER}/nodejs.org/contents/${FILE_PATH}?ref=${BRANCH}")" + FILE_SHA="$(printf '%s' "${FILE_RESPONSE}" | jq -r '.sha')" + printf '%s' "${FILE_RESPONSE}" | jq -r '.content' | base64 -d > "${RUNNER_TEMP}/nvm.bash" + + # Validate exactly 1 match + MATCH_COUNT="$(grep -cE "${PATTERN}" "${RUNNER_TEMP}/nvm.bash" || true)" + + if [ "${MATCH_COUNT}" -eq 0 ]; then + echo "::error::No nvm version pattern found in ${FILE_PATH}" + exit 1 + fi + + if [ "${MATCH_COUNT}" -ne 1 ]; then + echo "::error::Expected exactly 1 nvm version match in ${FILE_PATH}, found ${MATCH_COUNT}" + exit 1 + fi + + # Replace and check for changes + cp "${RUNNER_TEMP}/nvm.bash" "${RUNNER_TEMP}/nvm.bash.orig" + sed -i -E "s|${PATTERN}|${REPLACEMENT}|g" "${RUNNER_TEMP}/nvm.bash" + + if cmp -s "${RUNNER_TEMP}/nvm.bash" "${RUNNER_TEMP}/nvm.bash.orig"; then + echo "::notice::English snippet already has version ${NEW_VERSION}" + exit 0 + fi + + if ! grep -qF "${REPLACEMENT}" "${RUNNER_TEMP}/nvm.bash"; then + echo "::error::Replacement verification failed in ${FILE_PATH}" + exit 1 + fi + + # Update file via GitHub API (avoids git push workflow scope requirement) + NEW_CONTENT_B64="$(base64 -w 0 < "${RUNNER_TEMP}/nvm.bash")" + gh api "repos/${FORK_OWNER}/nodejs.org/contents/${FILE_PATH}" \ + -X PUT \ + -f "message=meta: bump nvm to ${NEW_VERSION}" \ + -f "content=${NEW_CONTENT_B64}" \ + -f "sha=${FILE_SHA}" \ + -f "branch=${BRANCH}" \ + -f "committer[name]=github-actions[bot]" \ + -f "committer[email]=41898282+github-actions[bot]@users.noreply.github.com" > /dev/null + + printf 'updated=true\n' >> "${GITHUB_OUTPUT}" + env: + GH_TOKEN: ${{ secrets.NODEJS_ORG_TOKEN }} + + - name: Create pull request + if: steps.update.outputs.updated + run: | + set -euo pipefail + + NEW_VERSION="${{ steps.version.outputs.tag }}" + FORK_OWNER="${{ steps.fork.outputs.fork_owner }}" + BRANCH="${{ steps.fork.outputs.branch }}" + + BODY="Updates the English nvm install snippet to [\`${NEW_VERSION}\`](https://github.com/nvm-sh/nvm/releases/tag/${NEW_VERSION}). The translation system handles other locales. + + Ref: https://github.com/nodejs/nodejs.org/issues/8628" + + gh pr create \ + --repo nodejs/nodejs.org \ + --head "${FORK_OWNER}:${BRANCH}" \ + --title "meta: bump nvm to ${NEW_VERSION}" \ + --body "${BODY}" + env: + GH_TOKEN: ${{ secrets.NODEJS_ORG_TOKEN }}