From 2bb8d133efc211f4e463d0069499ba738c81c664 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 24 Jun 2026 02:32:34 +0000 Subject: [PATCH 1/2] Initial plan From 363a116c7dd6b854fc9091345f7b45fcbc213cb7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 24 Jun 2026 02:39:40 +0000 Subject: [PATCH 2/2] Add release immutability support to repository plugin --- .../github-settings/1. repository-settings.md | 24 +++++++++ docs/sample-settings/settings.yml | 5 ++ lib/plugins/repository.js | 39 ++++++++++++++- test/unit/lib/plugins/repository.test.js | 49 +++++++++++++++++-- 4 files changed, 113 insertions(+), 4 deletions(-) diff --git a/docs/github-settings/1. repository-settings.md b/docs/github-settings/1. repository-settings.md index 46eaa64b9..4b3b92922 100644 --- a/docs/github-settings/1. repository-settings.md +++ b/docs/github-settings/1. repository-settings.md @@ -44,6 +44,8 @@ repository: security: enableVulnerabilityAlerts: true enableAutomatedSecurityFixes: true + releases: + immutable: true ``` ## Repository API Spec @@ -414,5 +416,27 @@ repository: ... ``` + + +

releasesobject

+

Settings for releases in this repository.

+ +
Properties of releases + +
+

immutableboolean

+

  Either true to enable release immutability (disallow assets and tags from being modified once a release is published), or false to disable it.

+

  See Enable immutable releases for more information.

+
+ + + +```yaml +repository: + releases: + immutable: true +... +``` + diff --git a/docs/sample-settings/settings.yml b/docs/sample-settings/settings.yml index 1ede6a079..80b605f8e 100644 --- a/docs/sample-settings/settings.yml +++ b/docs/sample-settings/settings.yml @@ -24,6 +24,11 @@ repository: enableVulnerabilityAlerts: true enableAutomatedSecurityFixes: true + # Settings for release immutability + # See https://docs.github.com/en/rest/repos/repos#enable-immutable-releases + releases: + immutable: true + # Either `true` to make the repository private, or `false` to make it public. # If this value is changed and if org members cannot change the visibility of repos # it would result in an error when updating a repo diff --git a/lib/plugins/repository.js b/lib/plugins/repository.js index 8f1a3f5dc..55f3732f5 100644 --- a/lib/plugins/repository.js +++ b/lib/plugins/repository.js @@ -37,7 +37,8 @@ const ignorableFields = [ 'force_create', 'auto_init', 'repo', - 'archived' + 'archived', + 'releases' ] module.exports = class Repository extends ErrorStash { @@ -48,6 +49,7 @@ module.exports = class Repository extends ErrorStash { this.settings = Object.assign({ mediaType: { previews: ['nebula-preview'] } }, settings, repo) this.topics = this.settings.topics this.security = this.settings.security + this.releases = this.settings.releases this.repo = repo this.log = log this.nop = nop @@ -56,6 +58,7 @@ module.exports = class Repository extends ErrorStash { delete this.settings.topics delete this.settings.force delete this.settings.template + delete this.settings.releases } sync () { @@ -109,8 +112,12 @@ module.exports = class Repository extends ErrorStash { promises.push(updateRepoPromise.then(() => { return this.updateAutomatedSecurityFixes(resp.data, resArray) })) + promises.push(updateRepoPromise.then(() => { + return this.updateReleaseImmutability(resp.data, resArray) + })) } else { promises.push(this.updateSecurity(resp.data, resArray)) + promises.push(this.updateReleaseImmutability(resp.data, resArray)) } if (this.nop) { return Promise.resolve(resArray) @@ -315,4 +322,34 @@ module.exports = class Repository extends ErrorStash { } } } + + updateReleaseImmutability (repoData, resArray) { + if (this.releases?.immutable === true || this.releases?.immutable === false) { + const parms = { + owner: repoData.owner.login, + repo: repoData.name + } + if (this.releases.immutable === true) { + this.log.debug(`Enabling release immutability for owner: ${repoData.owner.login} and repo ${repoData.name}`) + if (this.nop) { + resArray.push(new NopCommand(this.constructor.name, this.repo, this.github.request.endpoint('PUT /repos/{owner}/{repo}/releases/immutability', parms), 'Enabling release immutability')) + return Promise.resolve(resArray) + } + return this.github.request('PUT /repos/{owner}/{repo}/releases/immutability', parms) + } else { + this.log.debug(`Disabling release immutability for owner: ${repoData.owner.login} and repo ${repoData.name}`) + if (this.nop) { + resArray.push(new NopCommand(this.constructor.name, this.repo, this.github.request.endpoint('DELETE /repos/{owner}/{repo}/releases/immutability', parms), 'Disabling release immutability')) + return Promise.resolve(resArray) + } + return this.github.request('DELETE /repos/{owner}/{repo}/releases/immutability', parms) + } + } else { + this.log.debug(`no need to update release immutability for ${repoData.name}`) + if (this.nop) { + return Promise.resolve([]) + } + return Promise.resolve() + } + } } diff --git a/test/unit/lib/plugins/repository.test.js b/test/unit/lib/plugins/repository.test.js index 75b1199fc..5d64a910a 100644 --- a/test/unit/lib/plugins/repository.test.js +++ b/test/unit/lib/plugins/repository.test.js @@ -6,14 +6,18 @@ describe('Repository', () => { repos: { get: jest.fn().mockResolvedValue({ data: { - topics: [] + topics: [], + owner: { login: 'bkeepers' }, + name: 'test' } }), update: jest.fn().mockResolvedValue(), replaceAllTopics: jest.fn().mockResolvedValue() } - } + }, + request: jest.fn().mockResolvedValue() } + github.request.endpoint = jest.fn().mockReturnValue({}) const log = jest.fn() log.debug = jest.fn() log.error = jest.fn() @@ -60,7 +64,7 @@ describe('Repository', () => { }) }) - it.only('syncs topics', () => { + it('syncs topics', () => { const plugin = configure({ topics: ['foo', 'bar'] }) @@ -76,5 +80,44 @@ describe('Repository', () => { }) }) }) + + it('enables release immutability', () => { + const plugin = configure({ + releases: { immutable: true } + }) + + return plugin.sync().then(() => { + expect(github.request).toHaveBeenCalledWith( + 'PUT /repos/{owner}/{repo}/releases/immutability', + { owner: 'bkeepers', repo: 'test' } + ) + }) + }) + + it('disables release immutability', () => { + const plugin = configure({ + releases: { immutable: false } + }) + + return plugin.sync().then(() => { + expect(github.request).toHaveBeenCalledWith( + 'DELETE /repos/{owner}/{repo}/releases/immutability', + { owner: 'bkeepers', repo: 'test' } + ) + }) + }) + + it('does not call release immutability API when releases setting is absent', () => { + const plugin = configure({ + name: 'test' + }) + + return plugin.sync().then(() => { + expect(github.request).not.toHaveBeenCalledWith( + expect.stringMatching(/releases\/immutability/), + expect.anything() + ) + }) + }) }) })