add support for repository variables (#798)#819
add support for repository variables (#798)#819decyjphr merged 4 commits intogithub:main-enterprisefrom
Conversation
There was a problem hiding this comment.
Pull Request Overview
Adds first-class support for repository-level variables, including plugin registration, implementation, and associated tests.
- Introduces a new
Variablesplugin for listing, creating, updating, and deleting repo variables - Registers the plugin in
lib/settings.js - Adds unit tests and fixtures for the variables functionality
Reviewed Changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| test/unit/lib/plugins/variables.test.js | New tests for the Variables plugin’s sync logic |
| test/fixtures/variables-config.yml | Fixture for variables configuration |
| lib/settings.js | Registers variables plugin alongside others |
| lib/plugins/variables.js | Implements the Variables class and its methods |
There was a problem hiding this comment.
Pull Request Overview
Adds support for repository-level variables so they can be managed outside of specific deployment environments.
Key changes include:
- New
variablesplugin with CRUD operations against the GitHub Actions Variables API. - Unit tests for the
variablesplugin behaviour. - Registration of the
variablesplugin insettings.js.
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| test/unit/lib/plugins/variables.test.js | New tests mocking GET/POST/DELETE flows for variables |
| lib/settings.js | Added variables plugin entry in Settings.PLUGINS |
| lib/plugins/variables.js | Implementation of the Variables diffable plugin |
Comments suppressed due to low confidence (1)
test/unit/lib/plugins/variables.test.js:40
- [nitpick] Iterating over
['variables']is unclear—if this is simulating multiple pages, rename the array or remove the loop and mock calls directly for readability.
['variables'].forEach(() => {
| await this.github | ||
| .request('PATCH /repos/:org/:repo/actions/variables/:variable_name', { | ||
| org: this.repo.owner, | ||
| repo: this.repo.repo, | ||
| variable_name: variable.name.toUpperCase(), | ||
| value: variable.value.toString() | ||
| }) | ||
| .then((res) => { | ||
| return res | ||
| }) | ||
| .catch((e) => { | ||
| this.logError(e) | ||
| }) | ||
| } | ||
| } else { | ||
| await this.github | ||
| .request('POST /repos/:org/:repo/actions/variables', { | ||
| org: this.repo.owner, | ||
| repo: this.repo.repo, | ||
| name: variable.name.toUpperCase(), | ||
| value: variable.value.toString() | ||
| }) | ||
| .then((res) => { | ||
| return res | ||
| }) | ||
| .catch((e) => { | ||
| this.logError(e) | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| for (const variable of existingVariables) { | ||
| await this.github | ||
| .request('DELETE /repos/:org/:repo/actions/variables/:variable_name', { | ||
| org: this.repo.owner, | ||
| repo: this.repo.repo, | ||
| variable_name: variable.name.toUpperCase() | ||
| }) | ||
| .then((res) => { | ||
| return res | ||
| }) | ||
| .catch((e) => { | ||
| this.logError(e) | ||
| }) |
There was a problem hiding this comment.
You’re using await and then chaining .then()/.catch(), which is redundant. Use try { await this.github.request(...) } catch (e) { this.logError(e) } for clearer async error handling.
| await this.github | |
| .request('PATCH /repos/:org/:repo/actions/variables/:variable_name', { | |
| org: this.repo.owner, | |
| repo: this.repo.repo, | |
| variable_name: variable.name.toUpperCase(), | |
| value: variable.value.toString() | |
| }) | |
| .then((res) => { | |
| return res | |
| }) | |
| .catch((e) => { | |
| this.logError(e) | |
| }) | |
| } | |
| } else { | |
| await this.github | |
| .request('POST /repos/:org/:repo/actions/variables', { | |
| org: this.repo.owner, | |
| repo: this.repo.repo, | |
| name: variable.name.toUpperCase(), | |
| value: variable.value.toString() | |
| }) | |
| .then((res) => { | |
| return res | |
| }) | |
| .catch((e) => { | |
| this.logError(e) | |
| }) | |
| } | |
| } | |
| for (const variable of existingVariables) { | |
| await this.github | |
| .request('DELETE /repos/:org/:repo/actions/variables/:variable_name', { | |
| org: this.repo.owner, | |
| repo: this.repo.repo, | |
| variable_name: variable.name.toUpperCase() | |
| }) | |
| .then((res) => { | |
| return res | |
| }) | |
| .catch((e) => { | |
| this.logError(e) | |
| }) | |
| try { | |
| await this.github.request('PATCH /repos/:org/:repo/actions/variables/:variable_name', { | |
| org: this.repo.owner, | |
| repo: this.repo.repo, | |
| variable_name: variable.name.toUpperCase(), | |
| value: variable.value.toString() | |
| }) | |
| } catch (e) { | |
| this.logError(e) | |
| } | |
| } | |
| } else { | |
| try { | |
| await this.github.request('POST /repos/:org/:repo/actions/variables', { | |
| org: this.repo.owner, | |
| repo: this.repo.repo, | |
| name: variable.name.toUpperCase(), | |
| value: variable.value.toString() | |
| }) | |
| } catch (e) { | |
| this.logError(e) | |
| } | |
| } | |
| } | |
| for (const variable of existingVariables) { | |
| try { | |
| await this.github.request('DELETE /repos/:org/:repo/actions/variables/:variable_name', { | |
| org: this.repo.owner, | |
| repo: this.repo.repo, | |
| variable_name: variable.name.toUpperCase() | |
| }) | |
| } catch (e) { | |
| this.logError(e) | |
| } |
| * @returns | ||
| */ | ||
| async add (variable) { | ||
| this.log.debug(`Adding a repo var with the params ${JSON.stringify(variable)}`) |
There was a problem hiding this comment.
[nitpick] In add(), you rely on the constructor having uppercased variable.name. Consider explicitly normalizing variable.name = variable.name.toUpperCase() before sending the request to make the behavior clear.
| this.log.debug(`Adding a repo var with the params ${JSON.stringify(variable)}`) | |
| this.log.debug(`Adding a repo var with the params ${JSON.stringify(variable)}`) | |
| variable.name = variable.name.toUpperCase() |
| const result = | ||
| JSON.stringify( | ||
| existing.sort((x1, x2) => { | ||
| return x1.name.toUpperCase().localeCompare(x2.name.toUpperCase()) | ||
| }) | ||
| ) !== | ||
| JSON.stringify( | ||
| variables.sort((x1, x2) => { | ||
| return x1.name.toUpperCase().localeCompare(x2.name.toUpperCase()) | ||
| }) | ||
| ) | ||
| return result |
There was a problem hiding this comment.
Comparing two arrays via JSON.stringify is fragile. Consider using a deep comparison helper like _.isEqual on sorted arrays for more robust change detection.
| const result = | |
| JSON.stringify( | |
| existing.sort((x1, x2) => { | |
| return x1.name.toUpperCase().localeCompare(x2.name.toUpperCase()) | |
| }) | |
| ) !== | |
| JSON.stringify( | |
| variables.sort((x1, x2) => { | |
| return x1.name.toUpperCase().localeCompare(x2.name.toUpperCase()) | |
| }) | |
| ) | |
| return result | |
| const sortedExisting = existing.sort((x1, x2) => { | |
| return x1.name.toUpperCase().localeCompare(x2.name.toUpperCase()); | |
| }); | |
| const sortedVariables = variables.sort((x1, x2) => { | |
| return x1.name.toUpperCase().localeCompare(x2.name.toUpperCase()); | |
| }); | |
| const result = !_.isEqual(sortedExisting, sortedVariables); | |
| return result; |
| fillVariables({ | ||
| variables: [] | ||
| }) |
There was a problem hiding this comment.
fillVariables expects an array but is called with an object here; this mismatch can lead to mocks returning unexpected shapes. Pass an array of variable objects or adjust the helper to accept both shapes.
| fillVariables({ | |
| variables: [] | |
| }) | |
| fillVariables([]) |
This PR adds support for repository-level variables that can be used outside of a specific deployment environment.
Fixed find function and unit test from this PR and resubmitted for approval. All credit to @primetheus!
cc: @decyjphr, @primetheus