tech-debt: gather-release-data and release-qa-status duplicate 184 lines of GitHub transport, and it keeps causing paired bugs
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 970
- Forks
- 486
- Avg merge
- 3d 33m
- Merged PRs (30d)
- 170
Description
Problem
release-qa-status was forked from gather-release-data. Their src/github.ts files still share a GitHub transport layer, verbatim. Since the fork, every maintenance change to that layer has had to patch both copies:
| Commit | Change | Both files? |
|---|---|---|
bf4c4c7 (#35028) |
created gather-release-data |
— |
2825aff (#37140) |
fix for #37138 — draft releases / undocumented tags | ✅ |
02e9601 (#37213) |
fix for #37201 — the (#N) squash-subject regex |
✅ |
Two for two. Both of those were duplicated bugs: the same defect, shipped twice, found twice, fixed twice.
What is duplicated
Measured by diffing function-for-function against main:
| Function | Lines | Actual difference |
|---|---|---|
resolvePRNumbers |
58 | none |
fetchCommitRange |
46 | comment placement |
listStandardReleaseTags |
22 | none |
onThrottle |
19 | none |
createOctokit |
15 | one error-message string |
findPreviousTag |
11 | brace style |
parseRepo |
7 | none |
CommitInfo, ReleaseRef |
6 | none |
| ~184 |
Why now
#37369 migrates gather-release-data's commit→PR resolution to batched GraphQL. release-qa-status still runs the per-commit REST loop, so merging it as-is re-creates exactly the drift that produced #37201.
Both scripts run inside cicd_6-release.yml and both resolve the same commit range: the release pipeline currently spends ~1,076 REST calls resolving commits→PRs twice over. Sharing the layer means the GraphQL migration lands once and applies to both (~20 calls total).
Scope
Extract — the transport layer above. It is plumbing: auth, throttling, repo parsing, release-tag listing, range resolution, commit→PR resolution. Nothing in it is specific to either script's purpose.
Do not extract — fetchPRDetails. Same name, different product: release-qa-status needs url / author / authorType / externalRefs; gather-release-data needs a truncated body and no author. Merging them means a union return type and two callers each ignoring half of it.
Leave alone — categorize, qa, exclusions, format, and both index.ts. The boundary to cut on is transport vs. domain, which is nearly the boundary the file layout already implies.
Approach
.github/scripts/shared/holding the transport layer and its tests- npm workspaces at
.github/scripts/— onenpm ci, one lockfile instead of two, and@octokit/*resolves from the shared module - Consumers import by relative path, so there is no build-ordering step between packages
gather-release-dataswitches fromtsc+node dist/tots-node, matching howrelease-qa-statusalready runs. That removes a build step from the release-notes workflow and sidesteps arootDirchange.
Known gap, not in scope here
Nothing in CI runs npm test for either script. The 38 + N unit tests only run locally. A PR-triggered job over .github/scripts/** would be the thing that actually catches this class of regression — worth its own issue.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Compare .github/scripts/gather-release-data/src/github.ts with .github/scripts/release-qa-status/src/github.ts, then read the .github/scripts workspace configuration and existing local unit tests. Extract only the shared transport layer into .github/scripts/shared/, update both consumers, and preserve each script's distinct fetchPRDetails behavior. Done means both scripts use the shared layer, the workspace and execution paths work, and the existing unit tests pass.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- github-actions, graphql, node.js, typescript
- Domain
- ci-cd, release, tooling
- Issue type
- Refactor
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100