Skip to content

[WIP] feat!: upgrade to Node.js 24, refresh security dependencies, SHA-pin GitHub Actions, and make backports strategy-independent and conflict-safe - #3

Draft
chandler-solo wants to merge 2 commits into
mainfrom
chandler/node24
Draft

[WIP] feat!: upgrade to Node.js 24, refresh security dependencies, SHA-pin GitHub Actions, and make backports strategy-independent and conflict-safe#3
chandler-solo wants to merge 2 commits into
mainfrom
chandler/node24

Conversation

@chandler-solo

@chandler-solo chandler-solo commented Aug 6, 2026

Copy link
Copy Markdown

Summary

This PR modernizes and secures the GitHub Cherry Pick Action by upgrading its runtime to Node.js 24, refreshing its dependency stack, pinning every GitHub Actions dependency to an immutable commit SHA, and correcting several issues discovered during a repository-wide code review.

The backport implementation has also been redesigned so it reliably handles pull requests merged through merge commits, squash merges, or rebase merges. Conflicting or empty backports now fail safely without committing unresolved files or pushing partial results.

Motivation

The existing implementation had several correctness and security problems:

  • The action attempted to pass || true as arguments to git cherry-pick; @actions/exec does not invoke a shell, so these were treated as invalid Git arguments.
  • The generated branch name included the complete author identity, producing invalid branch names containing spaces and angle brackets.
  • Backporting merge_commit_sha did not work consistently across GitHub merge strategies.
  • Cherry-pick failures were ignored, after which every working-tree file was staged and committed. This could commit unresolved conflict markers.
  • Author and committer fields were crossed, and some configured identity fields were ignored.
  • The action token was used for GitHub API calls but not Git fetch and push operations.
  • The team-reviewers metadata name did not match the input name read by the implementation.
  • Workflow permissions were incomplete.
  • The Dependabot labeling workflow could not reliably write labels when triggered by Dependabot pull requests.
  • Tests mocked every Git command as successful and did not verify the generated commands.
  • CI rebuilt the bundled action but did not detect an uncommitted dist/index.js.
  • Several metadata and README examples were incomplete or inaccurate.

Runtime and dependency modernization

  • Upgrade the action runtime in action.yml to Node.js 24.
  • Require Node.js 24 or newer through the package engine declaration.
  • Configure CI and publishing workflows to use Node.js 24.
  • Refresh production and development dependencies to current compatible releases.
  • Keep Node type definitions aligned with the Node.js 24 runtime.
  • Retain the latest compatible TypeScript and ESLint versions where newer majors are not yet supported by the surrounding toolchain.
  • Regenerate package-lock.json.
  • Rebuild the checked-in dist/index.js bundle.

Backport correctness

  • Require a merged pull-request event before attempting a backport.

  • Validate the source, target, and generated branch names with git check-ref-format.

  • Replace the invalid author-based default branch with:

    cherry-pick-{sanitized target branch}-{source PR number}

  • Fetch the target branch, source base branch, and pull-request head explicitly.

  • Detect shallow checkouts and unshallow the repository when required.

  • Generate the complete binary pull-request diff from the event’s base SHA to the pull-request head.

  • Apply the diff with git apply --3way --index.

  • Preserve binary changes and full object identifiers in generated patches.

  • Refuse to continue when the pull request is empty, already applied, or cannot be merged cleanly.

  • Create one non-interactive backport commit containing the complete pull-request change set.

  • Use the configured author name and email as the Git author.

  • Use the configured committer name and email as the Git committer.

  • Use --force-with-lease instead of an unconditional force push.

  • Fetch an existing backport branch before using force-with-lease.

This makes the backport behavior independent of whether the source PR was merged with a merge commit, squash merge, or rebase merge.

Authentication and API handling

  • Use the action’s token input for both Git operations and GitHub API requests.
  • Mark the token and derived Git authorization values as secrets.
  • Store Git identity and authentication configuration locally instead of modifying global runner configuration.
  • Correctly read the hyphenated team-reviewers input.
  • Parse boolean inputs through the Actions toolkit’s strict boolean parser.
  • Reject malformed repository context instead of returning an undefined pull-request response.
  • Replace every title and body template placeholder, not only the first occurrence.
  • Deduplicate inherited and explicitly configured labels without mutating input arrays.
  • Skip the source PR author when requesting reviewers.
  • Combine user and team reviewer requests into one API operation.
  • Avoid logging untrusted pull-request titles and bodies.

Workflow and supply-chain hardening

  • Pin every GitHub Action used by repository workflows to a full 40-character commit SHA.
  • Retain version comments beside SHA pins for maintainability.
  • Add explicit read-only permissions to the test workflow.
  • Add the required contents: write permission to the publishing workflow.
  • Add explicit label and pull-request read permissions to the label verification workflow.
  • Remove the Dependabot autolabel workflow, whose token could be read-only for Dependabot-authored pull requests.
  • Configure Dependabot itself to apply the maintenance label.
  • Add a generated-bundle check so CI detects when dist/index.js is not committed after a source change.
  • Ensure lint and aggregate verification scripts do not silently rewrite repository files.

Metadata and documentation

  • Declare the action’s data, number, and html_url outputs.
  • Correct the inverted inherit_labels description.
  • Document the new conflict-safe aggregate-diff behavior.
  • Document required workflow permissions.
  • Document token forwarding for Git and API operations.
  • Correct the output example’s YAML structure.
  • Document fork and pull_request_target security considerations.
  • Explain the approval behavior of pull requests created with GITHUB_TOKEN.
  • Correct package name, description, keywords, and repository metadata.

Test coverage

The test suite now verifies:

  • Exact Git argument ordering
  • Valid default branch generation
  • Custom branch handling
  • Hyphenated team-reviewer input handling
  • Force-with-lease behavior
  • Patch data being forwarded through standard input
  • Correct author and committer identities
  • Safe failure when a patch cannot be applied
  • Refusal to process unmerged pull requests
  • Strict boolean parsing
  • Array input parsing
  • Branch-component sanitization
  • Author identity validation
  • Pull-request title and body template replacement
  • Label inheritance and deduplication
  • Input immutability
  • Assignee handling
  • Reviewer filtering
  • Invalid repository context handling

Breaking and operational changes

  • Node.js 24 is now required.
  • The default backport branch naming convention has changed.
  • Backport conflicts now fail the action instead of creating a potentially invalid pull request.
  • force: true now uses force-with-lease.
  • Workflows using the default token must grant contents: write, pull-requests: write, and issues: write when all optional PR configuration features are used.
  • Consumers requiring automatically started downstream workflows may need a GitHub App token or personal access token.

Validation

Validated under Node.js v24.19.0:

  • npm ci
  • 3 test suites and 12 tests passing
  • TypeScript type-check
  • ESLint, including source and tests
  • Prettier format check
  • NCC production bundle generation
  • Bundled JavaScript syntax check
  • GitHub Actions workflow validation with actionlint
  • Complete dependency-tree validation
  • Full and production-only npm security audits
  • git diff --check

Both npm audits report zero known vulnerabilities.

…s, tooling, workflows, and security automation

## Summary

This PR upgrades the action from the deprecated Node.js 16 runtime to Node.js 24 and comprehensively modernizes its runtime dependencies, development toolchain, generated distribution, tests, and GitHub Actions workflows.

The upgrade also removes deprecated packages, resolves all currently reported npm security vulnerabilities, and adds automated tracking for future GitHub Actions dependency updates.

## Motivation

The action currently declares `runs.using: node16`, which is end-of-life and no longer supported by current GitHub Actions runners.

The repository also relies on substantially outdated versions of the GitHub Actions Toolkit, TypeScript, Jest, ESLint, Prettier, and associated tooling. Several transitive dependencies in the old lockfile had known security advisories.

This update moves the project onto the supported Node.js 24 runtime and refreshes the complete dependency graph.

## Changes

### Node.js 24 runtime

- Changed the action runtime from `node16` to `node24`.
- Added `engines.node: ">=24"` to `package.json`.
- Updated CI and release workflows to install and test with Node.js 24 explicitly.
- Enabled `FORCE_JAVASCRIPT_ACTIONS_TO_NODE24` in workflows that still invoke pinned or legacy third-party JavaScript actions.
- Updated README examples to use `actions/checkout@v6`.

### Runtime dependency upgrades

Updated the GitHub Actions Toolkit packages:

- `@actions/core`: `^1.9.0` → `^3.0.1`
- `@actions/exec`: `^1.1.1` → `^3.0.0`
- `@actions/github`: `^5.1.1` → `^9.1.1`
- `@actions/io`: `^1.1.2` → `^3.0.2`

The current Toolkit releases are ESM-only, so the TypeScript build configuration now preserves ES modules for NCC to bundle correctly.

### Octokit type migration

- Removed deprecated `@octokit/webhooks-definitions`.
- Added supported `@octokit/webhooks-types`.
- Converted webhook type imports to type-only imports.
- Normalized nullable pull request bodies to `undefined` when constructing the Octokit request.

### Development toolchain upgrades

Updated the development stack:

- Node.js types: Node 18 → Node 24
- TypeScript: `4.9` → `6.0`
- Jest: `26` → `30`
- ts-jest: `26` → `29`
- ESLint: `8` → `9`
- TypeScript ESLint: `5` → `8`
- Prettier: `2` → `3`
- `@vercel/ncc`: `0.36` → `0.44`
- `eslint-plugin-github`: `4` → `6`

Unused direct dependencies such as `jest-circus`, `js-yaml`, and `eslint-plugin-jest` were removed where their functionality is now built in or no longer required.

### TypeScript and bundling configuration

Updated the TypeScript compiler configuration to:

- Target ES2022.
- Preserve ES modules with `module: "ESNext"`.
- Use `moduleResolution: "Bundler"`.

This is required to bundle the ESM-only Actions Toolkit packages. Compiling them as CommonJS would otherwise produce unresolved runtime module references.

The checked-in `dist/index.js` bundle was regenerated with NCC and verified to contain the runtime dependencies without missing-module stubs.

The generated bundle is larger than the previous artifact because the complete modern ESM-based Actions Toolkit and Octokit dependency graph is now embedded in the standalone action bundle.

### ESLint migration

- Replaced the legacy `.eslintrc.json` configuration with `eslint.config.mjs`.
- Removed the obsolete `.eslintignore`.
- Migrated existing GitHub and TypeScript linting rules to ESLint’s flat configuration format.
- Preserved the existing ignored paths and project-aware TypeScript linting.
- Removed rules that no longer exist in current TypeScript ESLint releases.
- Cleaned up an unused constant and catch binding exposed by the upgraded lint configuration.

### Jest 30 compatibility

- Removed the explicit `jest-circus` runner configuration because it is now Jest’s default.
- Imported Jest globals explicitly from `@jest/globals`.
- Updated removed matcher aliases such as `toBeCalledTimes`.
- Added typed virtual mocks for the ESM-only Actions Toolkit packages.
- Added the missing `@actions/io` mock.
- Updated the pull request fixture with the `head.ref` property used by the implementation.
- Corrected the expected number of Git executions to match the implementation.

### Workflow modernization

#### Build workflow

- Upgraded `actions/checkout` from v2 to v6.
- Added `actions/setup-node@v6`.
- Configured Node.js 24 and npm caching.
- Replaced `npm install` with deterministic `npm ci`.

#### Publish workflow

- Upgraded `actions/checkout` and `actions/setup-node` to v6.
- Configured Node.js 24 and npm caching.
- Forced JavaScript actions in the job to execute using Node.js 24.

#### Dependabot labeling

- Replaced the third-party `actions-ecosystem/action-add-labels` action with the official `actions/github-script@v9`.
- Added explicit least-privilege permissions:
  - `issues: write`
  - `pull-requests: read`
- Preserved automatic application of the `maintenance` label to Dependabot pull requests.

#### Release management

- Upgraded Release Drafter from v5 to v7.
- Forced the pinned label-verification action to execute under Node.js 24.

#### Dependency automation

- Retained daily npm dependency checks.
- Added weekly Dependabot checks for GitHub Actions dependencies.

## Security impact

The dependency lockfile was completely regenerated against the upgraded package set.

Audit results:

- Production dependencies: **0 vulnerabilities**
- Complete dependency graph: **0 vulnerabilities**

The update also removes the deprecated Octokit webhook definitions package and eliminates an unnecessary third-party labeling action.

## Compatibility and operational notes

- Consumers must use a GitHub-hosted runner or a sufficiently recent self-hosted Actions runner with Node.js 24 action support.
- Local development now requires Node.js 24 or newer.
- No action inputs or outputs were added, removed, or renamed.
- The action remains distributed as a self-contained CommonJS `dist/index.js` bundle, despite its dependencies being authored as ESM.
- Existing cherry-pick and pull-request creation behavior is preserved.

## Validation

The following checks completed successfully:

- Clean dependency installation with `npm ci`
- Full repository validation with `npm run all`
  - Jest unit tests
  - Prettier formatting
  - ESLint
  - TypeScript compilation
  - NCC packaging
- Jest tests executed explicitly under Node.js 24
- TypeScript type-check executed explicitly under Node.js 24
- Standalone `dist/index.js` load under Node.js 24
- Verification that the bundle contains no unresolved Actions Toolkit module stubs
- Production-only `npm audit`
- Full `npm audit`
- GitHub Actions and action metadata YAML parsing
- `git diff --check`

All tests pass and both npm audits report zero vulnerabilities.

## Reviewer checklist

- [ ] Confirm the Node.js 24 runtime requirement is acceptable for supported runners.
- [ ] Review the major GitHub Actions Toolkit and Octokit upgrades.
- [ ] Review the ESLint flat-config migration.
- [ ] Confirm workflow permission changes remain appropriately scoped.
- [ ] Confirm the regenerated `dist/index.js` artifact is included in the merge.
- [ ] Confirm the increased bundle size is acceptable.

Signed-off-by: David L. Chandler <david.chandler@solo.io>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pull request does not contain a valid label. Please add one of the following labels: bug, enhancement, maintenance

…GitHub Actions, and make backports strategy-independent and conflict-safe

## Summary

This PR modernizes and secures the GitHub Cherry Pick Action by upgrading its runtime to Node.js 24, refreshing its dependency stack, pinning every GitHub Actions dependency to an immutable commit SHA, and correcting several issues discovered during a repository-wide code review.

The backport implementation has also been redesigned so it reliably handles pull requests merged through merge commits, squash merges, or rebase merges. Conflicting or empty backports now fail safely without committing unresolved files or pushing partial results.

## Motivation

The existing implementation had several correctness and security problems:

- The action attempted to pass `|| true` as arguments to `git cherry-pick`; `@actions/exec` does not invoke a shell, so these were treated as invalid Git arguments.
- The generated branch name included the complete author identity, producing invalid branch names containing spaces and angle brackets.
- Backporting `merge_commit_sha` did not work consistently across GitHub merge strategies.
- Cherry-pick failures were ignored, after which every working-tree file was staged and committed. This could commit unresolved conflict markers.
- Author and committer fields were crossed, and some configured identity fields were ignored.
- The action token was used for GitHub API calls but not Git fetch and push operations.
- The `team-reviewers` metadata name did not match the input name read by the implementation.
- Workflow permissions were incomplete.
- The Dependabot labeling workflow could not reliably write labels when triggered by Dependabot pull requests.
- Tests mocked every Git command as successful and did not verify the generated commands.
- CI rebuilt the bundled action but did not detect an uncommitted `dist/index.js`.
- Several metadata and README examples were incomplete or inaccurate.

## Runtime and dependency modernization

- Upgrade the action runtime in `action.yml` to Node.js 24.
- Require Node.js 24 or newer through the package engine declaration.
- Configure CI and publishing workflows to use Node.js 24.
- Refresh production and development dependencies to current compatible releases.
- Keep Node type definitions aligned with the Node.js 24 runtime.
- Retain the latest compatible TypeScript and ESLint versions where newer majors are not yet supported by the surrounding toolchain.
- Regenerate `package-lock.json`.
- Rebuild the checked-in `dist/index.js` bundle.

## Backport correctness

- Require a merged pull-request event before attempting a backport.
- Validate the source, target, and generated branch names with `git check-ref-format`.
- Replace the invalid author-based default branch with:

  `cherry-pick-{sanitized target branch}-{source PR number}`

- Fetch the target branch, source base branch, and pull-request head explicitly.
- Detect shallow checkouts and unshallow the repository when required.
- Generate the complete binary pull-request diff from the event’s base SHA to the pull-request head.
- Apply the diff with `git apply --3way --index`.
- Preserve binary changes and full object identifiers in generated patches.
- Refuse to continue when the pull request is empty, already applied, or cannot be merged cleanly.
- Create one non-interactive backport commit containing the complete pull-request change set.
- Use the configured author name and email as the Git author.
- Use the configured committer name and email as the Git committer.
- Use `--force-with-lease` instead of an unconditional force push.
- Fetch an existing backport branch before using force-with-lease.

This makes the backport behavior independent of whether the source PR was merged with a merge commit, squash merge, or rebase merge.

## Authentication and API handling

- Use the action’s `token` input for both Git operations and GitHub API requests.
- Mark the token and derived Git authorization values as secrets.
- Store Git identity and authentication configuration locally instead of modifying global runner configuration.
- Correctly read the hyphenated `team-reviewers` input.
- Parse boolean inputs through the Actions toolkit’s strict boolean parser.
- Reject malformed repository context instead of returning an undefined pull-request response.
- Replace every title and body template placeholder, not only the first occurrence.
- Deduplicate inherited and explicitly configured labels without mutating input arrays.
- Skip the source PR author when requesting reviewers.
- Combine user and team reviewer requests into one API operation.
- Avoid logging untrusted pull-request titles and bodies.

## Workflow and supply-chain hardening

- Pin every GitHub Action used by repository workflows to a full 40-character commit SHA.
- Retain version comments beside SHA pins for maintainability.
- Add explicit read-only permissions to the test workflow.
- Add the required `contents: write` permission to the publishing workflow.
- Add explicit label and pull-request read permissions to the label verification workflow.
- Remove the Dependabot autolabel workflow, whose token could be read-only for Dependabot-authored pull requests.
- Configure Dependabot itself to apply the `maintenance` label.
- Add a generated-bundle check so CI detects when `dist/index.js` is not committed after a source change.
- Ensure lint and aggregate verification scripts do not silently rewrite repository files.

## Metadata and documentation

- Declare the action’s `data`, `number`, and `html_url` outputs.
- Correct the inverted `inherit_labels` description.
- Document the new conflict-safe aggregate-diff behavior.
- Document required workflow permissions.
- Document token forwarding for Git and API operations.
- Correct the output example’s YAML structure.
- Document fork and `pull_request_target` security considerations.
- Explain the approval behavior of pull requests created with `GITHUB_TOKEN`.
- Correct package name, description, keywords, and repository metadata.

## Test coverage

The test suite now verifies:

- Exact Git argument ordering
- Valid default branch generation
- Custom branch handling
- Hyphenated team-reviewer input handling
- Force-with-lease behavior
- Patch data being forwarded through standard input
- Correct author and committer identities
- Safe failure when a patch cannot be applied
- Refusal to process unmerged pull requests
- Strict boolean parsing
- Array input parsing
- Branch-component sanitization
- Author identity validation
- Pull-request title and body template replacement
- Label inheritance and deduplication
- Input immutability
- Assignee handling
- Reviewer filtering
- Invalid repository context handling

## Breaking and operational changes

- Node.js 24 is now required.
- The default backport branch naming convention has changed.
- Backport conflicts now fail the action instead of creating a potentially invalid pull request.
- `force: true` now uses force-with-lease.
- Workflows using the default token must grant `contents: write`, `pull-requests: write`, and `issues: write` when all optional PR configuration features are used.
- Consumers requiring automatically started downstream workflows may need a GitHub App token or personal access token.

## Validation

Validated under Node.js `v24.19.0`:

- `npm ci`
- 3 test suites and 12 tests passing
- TypeScript type-check
- ESLint, including source and tests
- Prettier format check
- NCC production bundle generation
- Bundled JavaScript syntax check
- GitHub Actions workflow validation with actionlint
- Complete dependency-tree validation
- Full and production-only npm security audits
- `git diff --check`

Both npm audits report zero known vulnerabilities.

Signed-off-by: David L. Chandler <david.chandler@solo.io>
@chandler-solo chandler-solo changed the title [WIP] Upgrade GitHub Action runtime to Node.js 24 and modernize dependencie… feat!: upgrade to Node.js 24, refresh security dependencies, SHA-pin GitHub Actions, and make backports strategy-independent and conflict-safe Aug 7, 2026
@chandler-solo chandler-solo changed the title feat!: upgrade to Node.js 24, refresh security dependencies, SHA-pin GitHub Actions, and make backports strategy-independent and conflict-safe [WIP] feat!: upgrade to Node.js 24, refresh security dependencies, SHA-pin GitHub Actions, and make backports strategy-independent and conflict-safe Aug 7, 2026
@chandler-solo chandler-solo added the enhancement New feature or request label Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant