Skip to content

ci(github-actions): update update-node-npm.yml workflow to use PR template fileΒ #1077

@beatrizsmerino

Description

@beatrizsmerino

ci(github-actions): update update-node-npm.yml workflow to use PR template file

⏱️ Estimate πŸ“Š Priority πŸ“ Size πŸ“… Start πŸ“… End
4h P2 M 05-02-2026 05-02-2026

πŸ“Έ Screenshots

Current Expected
N/A β€” This change has no visual impact. N/A β€” This change has no visual impact.

πŸ“ Summary

  • Extract the PR body to a template file .github/WORKFLOW_TEMPLATE/pr-node-npm.md with placeholders
  • Update the workflow to read the template, replace placeholders and generate the PR body
  • Includes improvements to the job summary output

πŸ’‘ Why this change?

  • The PR body is currently built inline as a YAML multiline string, making it hard to read and maintain
  • A separate template file is easier to edit, review and version control
  • The current format uses outdated headers (⏱️ Time, πŸ“… Date, πŸ”§ Build Type, πŸ“ Description)
  • Missing sections: πŸ“Έ Screenshots, πŸ“Œ Notes, structured πŸ”— References
  • Tests should not be pre-checked [x] since the workflow does not verify them
  • Branch name uses dependabot/ prefix which is misleading
  • The Changes Made section should use Update/Keep logic based on actual changes

βœ… Benefits

  • Easier template maintenance through a separate markdown file instead of inline YAML strings
  • Consistent PR format aligned with the project's current issue and PR standards
  • Accurate change tracking with Update/Keep logic based on actual version differences
  • Better version visibility with type indicators (Major/Minor/Patch) in the job summary

πŸ“‹ Steps

Phase 1: Create PR template file

  • Create .github/WORKFLOW_TEMPLATE/pr-node-npm.md with placeholders:
# build(deps): update `node@{{LATEST_NODE}}` and `npm@{{LATEST_NPM}}` versions

| ⏱️ Estimate | πŸ“Š Priority | πŸ“ Size | πŸ“… Start | πŸ“… End |
| --- | --- | --- | --- | --- |
| 2h | P2 | XS | {{DATE}} | {{DATE}} |

## πŸ“Έ Screenshots

| Before | After |
| :---: | :---: |
| N/A β€” This change has no visual impact. | N/A β€” This change has no visual impact. |

## πŸ”„ Type of Change

- [ ] Bug fix
- [ ] Breaking change
- [x] Dependency
- [ ] New feature
- [ ] Improvement
- [x] Configuration
- [ ] Documentation
- [x] CI/CD

## πŸ“ Summary

- Automatic update of `node` and `npm` versions detected by the `update-node-npm.yml` workflow
- Runs monthly (first Saturday) or manually via `workflow_dispatch`
- Checks for new Node.js LTS versions and creates this PR to update the project configuration files
- Updates `package.json` `engines` (`node` and `npm`) to the bundled versions
- If `.noderc.json` exists with `maxMajorVersion`, updates only within that major version

## πŸ“‹ Changes Made

{{CHANGES_MADE}}

## πŸ§ͺ Tests

- [ ] Confirm `node -v` matches the updated version in `.nvmrc`:

```bash
node -v
```
- [ ] Run `npm install` without errors:

```bash
npm install
```
- [ ] Run `npm run build` without errors:

```bash
npm run build
```
- [ ] Run `npm run dev` and check the app loads correctly:

```bash
npm run dev
```
- [ ] Check CI workflows pass on the PR branch

## πŸ“Œ Notes

Version changes:

| File | Configuration | From | To | Type |
| --- | --- | --- | --- | --- |
{{NOTES_TABLE}}

## πŸ”— References

### Files to modify
- `.nvmrc`
- `package.json`
- `.github/workflows/check-node.yml`

### Documentation
- [Node.js β€” Release schedule](https://nodejs.org/en/about/releases/)
- [Node.js β€” Changelog](https://github.com/nodejs/node/blob/main/CHANGELOG.md)
- [NPM CLI β€” Releases](https://github.com/npm/cli/releases)
- [NVM β€” Node Version Manager](https://github.com/nvm-sh/nvm)
- [GitHub Actions β€” `setup-node` matrix testing](https://github.com/actions/setup-node?tab=readme-ov-file#matrix-testing)

### Related Files
- `.github/workflows/update-node-npm.yml`

Phase 2: Add change detection step

  • Add step after πŸ”„ Check if update is needed to detect change types and version types:
- name: πŸ” Detect change types
  if: steps.check-update.outputs.update_needed == 'true'
  id: changes
  run: |
    get_change_type() {
      local old=$1 new=$2
      local old_major=$(echo "$old" | tr -d 'v[]' | cut -d. -f1 | tr -d ' ')
      local old_minor=$(echo "$old" | tr -d 'v[]' | cut -d. -f2 | tr -d ' ')
      local new_major=$(echo "$new" | tr -d 'v[]' | cut -d. -f1 | tr -d ' ')
      local new_minor=$(echo "$new" | tr -d 'v[]' | cut -d. -f2 | tr -d ' ')
      if [ "$old_major" != "$new_major" ]; then echo "πŸ”΄ Major"
      elif [ "$old_minor" != "$new_minor" ]; then echo "🟑 Minor"
      else echo "🟒 Patch"; fi
    }

    CURRENT_NODE="${{ steps.current-versions.outputs.current_node }}"
    LATEST_NODE="${{ steps.node-versions.outputs.latest_version }}"
    CURRENT_NPM="${{ steps.current-versions.outputs.current_npm }}"
    LATEST_NPM="${{ steps.node-versions.outputs.npm_version }}"
    CURRENT_MATRIX="${{ steps.current-versions.outputs.current_matrix }}"
    NEW_MATRIX="${{ steps.node-versions.outputs.matrix }}"

    echo "node_type=$(get_change_type "$CURRENT_NODE" "$LATEST_NODE")" >> $GITHUB_OUTPUT

    if [ "$CURRENT_NPM" != "$LATEST_NPM" ]; then
      echo "npm_changed=true" >> $GITHUB_OUTPUT
      echo "npm_type=$(get_change_type "$CURRENT_NPM" "$LATEST_NPM")" >> $GITHUB_OUTPUT
    else
      echo "npm_changed=false" >> $GITHUB_OUTPUT
      echo "npm_type=-" >> $GITHUB_OUTPUT
    fi

    if [ "$CURRENT_MATRIX" != "$NEW_MATRIX" ]; then
      echo "matrix_changed=true" >> $GITHUB_OUTPUT
      echo "matrix_type=πŸ”΄ Major" >> $GITHUB_OUTPUT
    else
      echo "matrix_changed=false" >> $GITHUB_OUTPUT
      echo "matrix_type=-" >> $GITHUB_OUTPUT
    fi

Phase 3: Build PR body from template

  • Add step to generate dynamic sections and replace placeholders in template:
- name: πŸ“„ Build PR body
  if: steps.check-update.outputs.update_needed == 'true'
  run: |
    CURRENT_NODE="${{ steps.current-versions.outputs.current_node }}"
    LATEST_NODE="${{ steps.node-versions.outputs.latest_version }}"
    CURRENT_NPM="${{ steps.current-versions.outputs.current_npm }}"
    LATEST_NPM="${{ steps.node-versions.outputs.npm_version }}"
    CURRENT_MATRIX="${{ steps.current-versions.outputs.current_matrix }}"
    NEW_MATRIX="${{ steps.node-versions.outputs.matrix }}"
    DATE="${{ steps.check-update.outputs.date }}"
    NODE_TYPE="${{ steps.changes.outputs.node_type }}"
    NPM_TYPE="${{ steps.changes.outputs.npm_type }}"
    MATRIX_TYPE="${{ steps.changes.outputs.matrix_type }}"

    # Build Changes Made
    CHANGES="- Update \`.nvmrc\` file: \`v${CURRENT_NODE}\` β†’ \`v${LATEST_NODE}\`\n"
    CHANGES+="- Update \`package.json\` file in \`engines.node\`: \`${CURRENT_NODE}\` β†’ \`${LATEST_NODE}\`\n"
    if [ "${{ steps.changes.outputs.npm_changed }}" == "true" ]; then
      CHANGES+="- Update \`package.json\` file in \`engines.npm\`: \`${CURRENT_NPM}\` β†’ \`${LATEST_NPM}\`\n"
    else
      CHANGES+="- Keep \`package.json\` file in \`engines.npm\`: \`${CURRENT_NPM}\`\n"
    fi
    if [ "${{ steps.changes.outputs.matrix_changed }}" == "true" ]; then
      CHANGES+="- Update \`check-node.yml\` file in matrix: \`${CURRENT_MATRIX}\` β†’ \`${NEW_MATRIX}\`"
    else
      CHANGES+="- Keep \`check-node.yml\` file in matrix: \`${CURRENT_MATRIX}\`"
    fi

    # Build Notes table rows
    NOTES="| \`.nvmrc\` | Node version | \`v${CURRENT_NODE}\` | \`v${LATEST_NODE}\` | ${NODE_TYPE} |\n"
    NOTES+="| \`package.json\` | engines.node | \`${CURRENT_NODE}\` | \`${LATEST_NODE}\` | ${NODE_TYPE} |\n"
    if [ "${{ steps.changes.outputs.npm_changed }}" == "true" ]; then
      NOTES+="| \`package.json\` | engines.npm | \`${CURRENT_NPM}\` | \`${LATEST_NPM}\` | ${NPM_TYPE} |\n"
    else
      NOTES+="| \`package.json\` | engines.npm | \`${CURRENT_NPM}\` | \`-\` | \`-\` |\n"
    fi
    if [ "${{ steps.changes.outputs.matrix_changed }}" == "true" ]; then
      NOTES+="| \`check-node.yml\` | matrix | \`${CURRENT_MATRIX}\` | \`${NEW_MATRIX}\` | ${MATRIX_TYPE} |"
    else
      NOTES+="| \`check-node.yml\` | matrix | \`${CURRENT_MATRIX}\` | \`-\` | \`-\` |"
    fi

    # Write dynamic sections to temp files
    echo -e "$CHANGES" > /tmp/changes.txt
    echo -e "$NOTES" > /tmp/notes.txt

    # Copy template and replace simple placeholders
    cp .github/WORKFLOW_TEMPLATE/pr-node-npm.md pr-body.md
    sed -i "s|{{LATEST_NODE}}|${LATEST_NODE}|g" pr-body.md
    sed -i "s|{{LATEST_NPM}}|${LATEST_NPM}|g" pr-body.md
    sed -i "s|{{DATE}}|${DATE}|g" pr-body.md

    # Replace multi-line placeholders
    awk '/{{CHANGES_MADE}}/{system("cat /tmp/changes.txt"); next}1' pr-body.md > pr-body.tmp && mv pr-body.tmp pr-body.md
    awk '/{{NOTES_TABLE}}/{system("cat /tmp/notes.txt"); next}1' pr-body.md > pr-body.tmp && mv pr-body.tmp pr-body.md

Phase 4: Update create-pull-request step

  • Update title, commit message, branch, labels and use body-path:
- name: πŸš€ Create Pull Request
  if: |
    steps.check-update.outputs.update_needed == 'true' &&
    (steps.current-versions.outputs.has_nvmrc == 'true' ||
     steps.current-versions.outputs.has_engines == 'true' ||
     steps.current-versions.outputs.has_node_yml == 'true')
  id: create-pr
  uses: peter-evans/create-pull-request@v8
  with:
    token: ${{ secrets.PAT_WORKFLOW }}
    commit-message: "build(deps): update `node@${{ steps.node-versions.outputs.latest_version }}` and `npm@${{ steps.node-versions.outputs.npm_version }}` versions"
    title: "build(deps): update `node@${{ steps.node-versions.outputs.latest_version }}` and `npm@${{ steps.node-versions.outputs.npm_version }}` versions"
    body-path: pr-body.md
    branch: update/node-${{ steps.node-versions.outputs.latest_version }}
    delete-branch: true
    labels: |
      dependencies
      github_actions
      configuration
    assignees: beatrizsmerino

Phase 5: Update job summary

  • Replace the πŸ“Š Summary step with the new format:
- name: πŸ“Š Summary
  run: |
    if [ "${{ steps.check-update.outputs.update_needed }}" == "true" ]; then
      STATUS_BADGE="![Update](https://img.shields.io/badge/Status-Update%20Available-yellow)"
    else
      STATUS_BADGE="![Up to date](https://img.shields.io/badge/Status-Up%20to%20Date-green)"
    fi

    if [ "${{ steps.config.outputs.has_config }}" == "true" ]; then
      LIMIT_BADGE="![Limited](https://img.shields.io/badge/Max%20Version-v${{ steps.config.outputs.max_major }}-blue)"
      MAX_VALUE="\`${{ steps.config.outputs.max_major }}\`"
    else
      LIMIT_BADGE="![No Limit](https://img.shields.io/badge/Max%20Version-None-gray)"
      MAX_VALUE="\`-\`"
    fi

    NODE_TYPE="${{ steps.changes.outputs.node_type }}"
    NPM_TYPE="${{ steps.changes.outputs.npm_type }}"
    MATRIX_TYPE="${{ steps.changes.outputs.matrix_type }}"
    if [ "${{ steps.check-update.outputs.update_needed }}" != "true" ]; then
      NODE_TYPE="-"; NPM_TYPE="-"; MATRIX_TYPE="-"
    fi

    NVMRC_STATUS=$( [ "${{ steps.current-versions.outputs.has_nvmrc }}" == "true" ] && echo "βœ… Found" || echo "❌ Not found" )
    ENGINES_STATUS=$( [ "${{ steps.current-versions.outputs.has_engines }}" == "true" ] && echo "βœ… Found" || echo "❌ Not found" )
    NODE_YML_STATUS=$( [ "${{ steps.current-versions.outputs.has_node_yml }}" == "true" ] && echo "βœ… Found" || echo "❌ Not found" )
    NODERC_STATUS=$( [ "${{ steps.config.outputs.has_config }}" == "true" ] && echo "βœ… Found" || echo "❌ Not found" )

    PR_URL="${{ steps.create-pr.outputs.pull-request-url }}"
    PR_NUMBER="${{ steps.create-pr.outputs.pull-request-number }}"
    if [ -n "$PR_URL" ]; then
      PR_LINK="βœ… [PR #${PR_NUMBER} β€” build(deps): update \`node@${{ steps.node-versions.outputs.latest_version }}\` and \`npm@${{ steps.node-versions.outputs.npm_version }}\` versions](${PR_URL})"
    else
      PR_LINK="ℹ️ No update needed β€” all versions are up to date."
    fi

    cat >> $GITHUB_STEP_SUMMARY << EOF
    # Node.js Update Check Summary

    ${STATUS_BADGE} ${LIMIT_BADGE}

    > πŸ“… Last check: $(date +%d-%m-%Y)

    ## πŸ”’ Version Comparison

    | Package | Files | Max | Current | Latest | Type |
    | --- | --- | :---: | :---: | :---: | :---: |
    | **Node** | \`.nvmrc\`, \`package.json\` | ${MAX_VALUE} | \`${{ steps.current-versions.outputs.current_node }}\` | \`${{ steps.node-versions.outputs.latest_version }}\` | ${NODE_TYPE} |
    | **NPM** | \`package.json\` | \`-\` | \`${{ steps.current-versions.outputs.current_npm }}\` | \`${{ steps.node-versions.outputs.npm_version }}\` | ${NPM_TYPE} |
    | **CI Matrix** | \`check-node.yml\` | \`-\` | \`${{ steps.current-versions.outputs.current_matrix }}\` | \`${{ steps.node-versions.outputs.matrix }}\` | ${MATRIX_TYPE} |

    > **Max** is read from \`.noderc.json\` (\`maxMajorVersion\` field). If the file doesn't exist, updates to the latest LTS version.

    ## πŸ“ Project Files

    | File | Status |
    | --- | :---: |
    | \`.noderc.json\` | ${NODERC_STATUS} |
    | \`.nvmrc\` | ${NVMRC_STATUS} |
    | \`package.json\` | ${ENGINES_STATUS} |
    | \`check-node.yml\` | ${NODE_YML_STATUS} |

    ## πŸ”— Pull Request

    ${PR_LINK}
    EOF

πŸ§ͺ Tests

  • Trigger the workflow manually with workflow_dispatch
  • Confirm the PR is created with the new body format from template
  • Check that the PR title uses build(deps): prefix
  • Ensure the PR branch uses update/node- prefix instead of dependabot/
  • Check that the PR labels include dependencies, github_actions and configuration
  • Confirm the Changes Made section uses Update/Keep correctly
  • Check that the Notes table shows correct version types (🟒 Patch / 🟑 Minor / πŸ”΄ Major)
  • Confirm the job summary shows Version Comparison table with Type and Max columns
  • Ensure the job summary shows PR link when created
  • Check that the job summary shows "No update needed" when up to date
  • Check that npm -v matches the updated version in package.json and engines.npm:
npm -v

πŸ”— References

Files to create

  • .github/WORKFLOW_TEMPLATE/pr-node-npm.md

Files to modify

  • .github/workflows/update-node-npm.yml

Related Issues

Metadata

Metadata

Labels

configurationProject setup and configuration filesdependenciesDependency updatesgithub_actionsGitHub .github/ folder configuration

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions