feat: support previous_tag for generate_release_notes (#372)

* feat: add generate from latest tag

* chore: refresh previous_tag docs and bundle

Signed-off-by: Rui Chen <rui@chenrui.dev>

---------

Signed-off-by: Rui Chen <rui@chenrui.dev>
Co-authored-by: Rui Chen <rui@chenrui.dev>
This commit is contained in:
Paulo CesarandRui Chen authored and GitHub committed 2026-03-15 01:17:21 -04:00
1 parent 1853d73993
commit 9312864490
7 files changed
+329 -277

No files matched your search

+15
View File
@@ -173,6 +173,20 @@ jobs:
token: ${{ secrets.CUSTOM_GITHUB_TOKEN }}
```
When you use GitHub's built-in `generate_release_notes` support, you can optionally
pin the comparison base explicitly with `previous_tag`. This is useful when the default
comparison range does not match the release series you want to publish.
```yaml
- name: Release
uses: softprops/action-gh-release@v2
with:
tag_name: stage-2026-03-15
target_commitish: ${{ github.sha }}
previous_tag: prod-2026-03-01
generate_release_notes: true
```
### 💅 Customizing
#### inputs
@@ -196,6 +210,7 @@ The following are optional as `step.with` keys
| `token` | String | Authorized GitHub token or PAT. Defaults to `${{ github.token }}` when omitted. A non-empty explicit token overrides `GITHUB_TOKEN`. Passing `""` treats the token as explicitly unset, so omit the input entirely or use an expression such as `${{ inputs.token || github.token }}` when wrapping this action in a composite action. |
| `discussion_category_name` | String | If specified, a discussion of the specified category is created and linked to the release. The value must be a category that already exists in the repository. For more information, see ["Managing categories for discussions in your repository."](https://docs.github.com/en/discussions/managing-discussions-for-your-community/managing-categories-for-discussions-in-your-repository) |
| `generate_release_notes` | Boolean | Whether to automatically generate the name and body for this release. If name is specified, the specified name will be used; otherwise, a name will be automatically generated. If body is specified, the body will be pre-pended to the automatically generated notes. See the [GitHub docs for this feature](https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes) for more information |
| `previous_tag` | String | Optional. When `generate_release_notes` is enabled, use this tag as GitHub's `previous_tag_name` comparison base. If omitted, GitHub chooses the comparison base automatically. |
| `append_body` | Boolean | Append to existing body instead of overwriting it |
| `make_latest` | String | Specifies whether this release should be set as the latest release for the repository. Drafts and prereleases cannot be set as latest. Can be `true`, `false`, or `legacy`. Uses GitHub api defaults if not provided |
+177
View File
@@ -2,6 +2,7 @@ import {
asset,
findTagFromReleases,
finalizeRelease,
GitHubReleaser,
mimeOrDefault,
release,
Release,
@@ -32,6 +33,7 @@ describe('github', () => {
input_target_commitish: undefined,
input_discussion_category_name: undefined,
input_generate_release_notes: false,
input_previous_tag: undefined,
input_append_body: false,
input_make_latest: undefined,
};
@@ -146,6 +148,86 @@ describe('github', () => {
});
});
describe('GitHubReleaser', () => {
it('passes previous_tag_name to generateReleaseNotes and strips it from createRelease', async () => {
const generateReleaseNotes = vi.fn(async () => ({
data: {
name: 'Generated release',
body: "## What's Changed\n* Added support for previous_tag",
},
}));
const createRelease = vi.fn(async (params) => ({
data: {
id: 1,
upload_url: 'test',
html_url: 'test',
tag_name: params.tag_name,
name: params.name,
body: params.body,
target_commitish: params.target_commitish || 'main',
draft: params.draft ?? false,
prerelease: params.prerelease ?? false,
assets: [],
},
}));
const releaser = new GitHubReleaser({
rest: {
repos: {
generateReleaseNotes,
createRelease,
updateRelease: vi.fn(),
getReleaseByTag: vi.fn(),
listReleaseAssets: vi.fn(),
deleteReleaseAsset: vi.fn(),
deleteRelease: vi.fn(),
updateReleaseAsset: vi.fn(),
listReleases: {
endpoint: {
merge: vi.fn(),
},
},
},
},
paginate: {
iterator: vi.fn(),
},
request: vi.fn(),
} as any);
await releaser.createRelease({
owner: 'owner',
repo: 'repo',
tag_name: 'v1.0.0',
name: 'v1.0.0',
body: 'Intro',
draft: false,
prerelease: false,
target_commitish: 'abc123',
discussion_category_name: undefined,
generate_release_notes: true,
make_latest: undefined,
previous_tag_name: 'v0.9.0',
});
expect(generateReleaseNotes).toHaveBeenCalledWith({
owner: 'owner',
repo: 'repo',
tag_name: 'v1.0.0',
target_commitish: 'abc123',
previous_tag_name: 'v0.9.0',
});
expect(createRelease).toHaveBeenCalledWith(
expect.objectContaining({
tag_name: 'v1.0.0',
body: "Intro\n\n## What's Changed\n* Added support for previous_tag",
generate_release_notes: false,
}),
);
expect(createRelease.mock.calls[0][0]).not.toHaveProperty('previous_tag_name');
});
});
describe('finalizeRelease input_draft behavior', () => {
const draftRelease: Release = {
id: 1,
@@ -340,6 +422,101 @@ describe('github', () => {
});
describe('error handling', () => {
it('passes previous_tag_name through when creating a release with generated notes', async () => {
const createReleaseSpy = vi.fn(async () => ({
data: {
id: 1,
upload_url: 'test',
html_url: 'test',
tag_name: 'v1.0.0',
name: 'test',
body: 'generated notes',
target_commitish: 'main',
draft: true,
prerelease: false,
assets: [],
},
}));
await release(
{
...config,
input_generate_release_notes: true,
input_previous_tag: 'v0.9.0',
},
{
getReleaseByTag: () => Promise.reject({ status: 404 }),
createRelease: createReleaseSpy,
updateRelease: () => Promise.reject('Not implemented'),
finalizeRelease: () => Promise.reject('Not implemented'),
allReleases: async function* () {
yield { data: [] };
},
listReleaseAssets: () => Promise.reject('Not implemented'),
deleteReleaseAsset: () => Promise.reject('Not implemented'),
deleteRelease: () => Promise.reject('Not implemented'),
updateReleaseAsset: () => Promise.reject('Not implemented'),
uploadReleaseAsset: () => Promise.reject('Not implemented'),
},
1,
);
expect(createReleaseSpy).toHaveBeenCalledWith(
expect.objectContaining({
tag_name: 'v1.0.0',
generate_release_notes: true,
previous_tag_name: 'v0.9.0',
}),
);
});
it('passes previous_tag_name through when updating a release with generated notes', async () => {
const existingRelease: Release = {
id: 1,
upload_url: 'test',
html_url: 'test',
tag_name: 'v1.0.0',
name: 'test',
body: 'existing body',
target_commitish: 'main',
draft: false,
prerelease: false,
assets: [],
};
const updateReleaseSpy = vi.fn(async () => ({ data: existingRelease }));
await release(
{
...config,
input_generate_release_notes: true,
input_previous_tag: 'v0.9.0',
},
{
getReleaseByTag: () => Promise.resolve({ data: existingRelease }),
createRelease: () => Promise.reject('Not implemented'),
updateRelease: updateReleaseSpy,
finalizeRelease: () => Promise.reject('Not implemented'),
allReleases: async function* () {
yield { data: [existingRelease] };
},
listReleaseAssets: () => Promise.reject('Not implemented'),
deleteReleaseAsset: () => Promise.reject('Not implemented'),
deleteRelease: () => Promise.reject('Not implemented'),
updateReleaseAsset: () => Promise.reject('Not implemented'),
uploadReleaseAsset: () => Promise.reject('Not implemented'),
},
1,
);
expect(updateReleaseSpy).toHaveBeenCalledWith(
expect.objectContaining({
release_id: existingRelease.id,
generate_release_notes: true,
previous_tag_name: 'v0.9.0',
}),
);
});
it('creates published prereleases without the forced draft-first path', async () => {
const prereleaseConfig = {
...config,
+45 -177
View File
@@ -174,6 +174,29 @@ describe('util', () => {
});
});
describe('parseConfig', () => {
const baseParsedConfig = {
github_ref: '',
github_repository: '',
github_token: '',
input_working_directory: undefined,
input_append_body: false,
input_body: undefined,
input_body_path: undefined,
input_draft: undefined,
input_prerelease: undefined,
input_preserve_order: undefined,
input_files: [],
input_overwrite_files: undefined,
input_name: undefined,
input_tag_name: undefined,
input_fail_on_unmatched_files: false,
input_target_commitish: undefined,
input_discussion_category_name: undefined,
input_generate_release_notes: false,
input_previous_tag: undefined,
input_make_latest: undefined,
};
it('parses basic config', () => {
assert.deepStrictEqual(
parseConfig({
@@ -186,27 +209,7 @@ describe('util', () => {
INPUT_TARGET_COMMITISH: '',
INPUT_DISCUSSION_CATEGORY_NAME: '',
}),
{
github_ref: '',
github_repository: '',
github_token: '',
input_working_directory: undefined,
input_append_body: false,
input_body: undefined,
input_body_path: undefined,
input_draft: undefined,
input_prerelease: undefined,
input_preserve_order: undefined,
input_files: [],
input_overwrite_files: undefined,
input_name: undefined,
input_tag_name: undefined,
input_fail_on_unmatched_files: false,
input_target_commitish: undefined,
input_discussion_category_name: undefined,
input_generate_release_notes: false,
input_make_latest: undefined,
},
baseParsedConfig,
);
});
@@ -216,25 +219,8 @@ describe('util', () => {
INPUT_TARGET_COMMITISH: 'affa18ef97bc9db20076945705aba8c516139abd',
}),
{
github_ref: '',
github_repository: '',
github_token: '',
input_working_directory: undefined,
input_append_body: false,
input_body: undefined,
input_body_path: undefined,
input_draft: undefined,
input_prerelease: undefined,
input_files: [],
input_overwrite_files: undefined,
input_preserve_order: undefined,
input_name: undefined,
input_tag_name: undefined,
input_fail_on_unmatched_files: false,
...baseParsedConfig,
input_target_commitish: 'affa18ef97bc9db20076945705aba8c516139abd',
input_discussion_category_name: undefined,
input_generate_release_notes: false,
input_make_latest: undefined,
},
);
});
@@ -244,25 +230,8 @@ describe('util', () => {
INPUT_DISCUSSION_CATEGORY_NAME: 'releases',
}),
{
github_ref: '',
github_repository: '',
github_token: '',
input_working_directory: undefined,
input_append_body: false,
input_body: undefined,
input_body_path: undefined,
input_draft: undefined,
input_prerelease: undefined,
input_files: [],
input_preserve_order: undefined,
input_name: undefined,
input_overwrite_files: undefined,
input_tag_name: undefined,
input_fail_on_unmatched_files: false,
input_target_commitish: undefined,
...baseParsedConfig,
input_discussion_category_name: 'releases',
input_generate_release_notes: false,
input_make_latest: undefined,
},
);
});
@@ -273,25 +242,20 @@ describe('util', () => {
INPUT_GENERATE_RELEASE_NOTES: 'true',
}),
{
github_ref: '',
github_repository: '',
github_token: '',
input_working_directory: undefined,
input_append_body: false,
input_body: undefined,
input_body_path: undefined,
input_draft: undefined,
input_prerelease: undefined,
input_preserve_order: undefined,
input_files: [],
input_overwrite_files: undefined,
input_name: undefined,
input_tag_name: undefined,
input_fail_on_unmatched_files: false,
input_target_commitish: undefined,
input_discussion_category_name: undefined,
...baseParsedConfig,
input_generate_release_notes: true,
input_make_latest: undefined,
},
);
});
it('supports an explicit previous tag for release notes generation', () => {
assert.deepStrictEqual(
parseConfig({
INPUT_PREVIOUS_TAG: ' v1.2.3 ',
}),
{
...baseParsedConfig,
input_previous_tag: 'v1.2.3',
},
);
});
@@ -306,25 +270,11 @@ describe('util', () => {
INPUT_TOKEN: 'input-token',
}),
{
github_ref: '',
github_repository: '',
...baseParsedConfig,
github_token: 'input-token',
input_working_directory: undefined,
input_append_body: false,
input_body: undefined,
input_body_path: undefined,
input_draft: false,
input_prerelease: true,
input_preserve_order: true,
input_files: [],
input_overwrite_files: undefined,
input_name: undefined,
input_tag_name: undefined,
input_fail_on_unmatched_files: false,
input_target_commitish: undefined,
input_discussion_category_name: undefined,
input_generate_release_notes: false,
input_make_latest: undefined,
},
);
});
@@ -335,25 +285,8 @@ describe('util', () => {
INPUT_TOKEN: ' ',
}),
{
github_ref: '',
github_repository: '',
...baseParsedConfig,
github_token: 'env-token',
input_working_directory: undefined,
input_append_body: false,
input_body: undefined,
input_body_path: undefined,
input_draft: undefined,
input_prerelease: undefined,
input_preserve_order: undefined,
input_files: [],
input_overwrite_files: undefined,
input_name: undefined,
input_tag_name: undefined,
input_fail_on_unmatched_files: false,
input_target_commitish: undefined,
input_discussion_category_name: undefined,
input_generate_release_notes: false,
input_make_latest: undefined,
},
);
});
@@ -365,25 +298,10 @@ describe('util', () => {
INPUT_TOKEN: 'input-token',
}),
{
github_ref: '',
github_repository: '',
...baseParsedConfig,
github_token: 'input-token',
input_working_directory: undefined,
input_append_body: false,
input_body: undefined,
input_body_path: undefined,
input_draft: false,
input_prerelease: true,
input_preserve_order: undefined,
input_files: [],
input_overwrite_files: undefined,
input_name: undefined,
input_tag_name: undefined,
input_fail_on_unmatched_files: false,
input_target_commitish: undefined,
input_discussion_category_name: undefined,
input_generate_release_notes: false,
input_make_latest: undefined,
},
);
});
@@ -394,25 +312,9 @@ describe('util', () => {
INPUT_PRERELEASE: 'true',
}),
{
github_ref: '',
github_repository: '',
github_token: '',
input_working_directory: undefined,
input_append_body: false,
input_body: undefined,
input_body_path: undefined,
...baseParsedConfig,
input_draft: false,
input_prerelease: true,
input_preserve_order: undefined,
input_files: [],
input_overwrite_files: undefined,
input_name: undefined,
input_tag_name: undefined,
input_fail_on_unmatched_files: false,
input_target_commitish: undefined,
input_discussion_category_name: undefined,
input_generate_release_notes: false,
input_make_latest: undefined,
},
);
});
@@ -422,24 +324,7 @@ describe('util', () => {
INPUT_MAKE_LATEST: 'false',
}),
{
github_ref: '',
github_repository: '',
github_token: '',
input_working_directory: undefined,
input_append_body: false,
input_body: undefined,
input_body_path: undefined,
input_draft: undefined,
input_prerelease: undefined,
input_preserve_order: undefined,
input_files: [],
input_name: undefined,
input_overwrite_files: undefined,
input_tag_name: undefined,
input_fail_on_unmatched_files: false,
input_target_commitish: undefined,
input_discussion_category_name: undefined,
input_generate_release_notes: false,
...baseParsedConfig,
input_make_latest: 'false',
},
);
@@ -450,25 +335,8 @@ describe('util', () => {
INPUT_APPEND_BODY: 'true',
}),
{
github_ref: '',
github_repository: '',
github_token: '',
input_working_directory: undefined,
...baseParsedConfig,
input_append_body: true,
input_body: undefined,
input_body_path: undefined,
input_draft: undefined,
input_prerelease: undefined,
input_preserve_order: undefined,
input_files: [],
input_overwrite_files: undefined,
input_name: undefined,
input_tag_name: undefined,
input_fail_on_unmatched_files: false,
input_target_commitish: undefined,
input_discussion_category_name: undefined,
input_generate_release_notes: false,
input_make_latest: undefined,
},
);
});
+4
View File
@@ -53,6 +53,10 @@ inputs:
generate_release_notes:
description: "Whether to automatically generate the name and body for this release. If name is specified, the specified name will be used; otherwise, a name will be automatically generated. If body is specified, the body will be pre-pended to the automatically generated notes."
required: false
previous_tag:
description: "Optional. When generate_release_notes is enabled, use this tag as GitHub's previous_tag_name comparison base. If omitted, GitHub chooses the comparison base automatically."
required: false
default: ""
append_body:
description: "Append to existing body instead of overwriting it. Default is false."
required: false
+4 -6
View File
@@ -47,16 +47,14 @@ ${o}`;break;case"retry":SB(o)&&(s[i]=o);break;case"id":UB(o)&&(s[i]=o);break;cas
`+e.errors.map(t=>` - ${t.message}`).join(`
`)}var QM=class extends Error{constructor(e,t,s){super(fM(s)),this.request=e,this.headers=t,this.response=s,this.errors=s.errors,this.data=s.data,Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor)}name="GraphqlResponseError";errors;data},BM=["method","baseUrl","url","headers","request","query","mediaType","operationName"],CM=["query","method","url"],LC=/\/api\/v3\/?$/;function IM(e,t,s){if(s){if(typeof t=="string"&&"query"in s)return Promise.reject(new Error('[@octokit/graphql] "query" cannot be used as variable name'));for(let n in s)if(CM.includes(n))return Promise.reject(new Error(`[@octokit/graphql] "${n}" cannot be used as variable name`))}let r=typeof t=="string"?Object.assign({query:t},s):t,i=Object.keys(r).reduce((n,a)=>BM.includes(a)?(n[a]=r[a],n):(n.variables||(n.variables={}),n.variables[a]=r[a],n),{}),o=r.baseUrl||e.endpoint.DEFAULTS.baseUrl;return LC.test(o)&&(i.url=o.replace(LC,"/api/graphql")),e(i).then(n=>{if(n.data.errors){let a={};for(let A of Object.keys(n.headers))a[A]=n.headers[A];throw new QM(i,a,n.data)}return n.data.data})}function Gu(e,t){let s=e.defaults(t);return Object.assign((i,o)=>IM(s,i,o),{defaults:Gu.bind(null,s),endpoint:s.endpoint})}var aJ=Gu(io,{headers:{"user-agent":`octokit-graphql.js/${mM} ${ds()}`},method:"POST",url:"/graphql"});function _C(e){return Gu(e,{method:"POST",url:"/graphql"})}var Mu="(?:[a-zA-Z0-9_-]+)",YC="\\.",OC=new RegExp(`^${Mu}${YC}${Mu}${YC}${Mu}$`),wM=OC.test.bind(OC);async function bM(e){let t=wM(e),s=e.startsWith("v1.")||e.startsWith("ghs_"),r=e.startsWith("ghu_");return{type:"token",token:e,tokenType:t?"app":s?"installation":r?"user-to-server":"oauth"}}function yM(e){return e.split(/\./).length===3?`bearer ${e}`:`token ${e}`}async function xM(e,t,s,r){let i=t.endpoint.merge(s,r);return i.headers.authorization=yM(e),t(i)}var JC=function(t){if(!t)throw new Error("[@octokit/auth-token] No token passed to createTokenAuth");if(typeof t!="string")throw new Error("[@octokit/auth-token] Token passed to createTokenAuth is not a string");return t=t.replace(/^(token|bearer) +/i,""),Object.assign(bM.bind(null,t),{hook:xM.bind(null,t)})};var Lu="7.0.6";var PC=()=>{},vM=console.warn.bind(console),kM=console.error.bind(console);function DM(e={}){return typeof e.debug!="function"&&(e.debug=PC),typeof e.info!="function"&&(e.info=PC),typeof e.warn!="function"&&(e.warn=vM),typeof e.error!="function"&&(e.error=kM),e}var HC=`octokit-core.js/${Lu} ${ds()}`,Ta=class{static VERSION=Lu;static defaults(t){return class extends this{constructor(...r){let i=r[0]||{};if(typeof t=="function"){super(t(i));return}super(Object.assign({},t,i,i.userAgent&&t.userAgent?{userAgent:`${i.userAgent} ${t.userAgent}`}:null))}}}static plugins=[];static plugin(...t){let s=this.plugins;return class extends this{static plugins=s.concat(t.filter(i=>!s.includes(i)))}}constructor(t={}){let s=new mC.Collection,r={baseUrl:io.endpoint.DEFAULTS.baseUrl,headers:{},request:Object.assign({},t.request,{hook:s.bind(null,"request")}),mediaType:{previews:[],format:""}};if(r.headers["user-agent"]=t.userAgent?`${t.userAgent} ${HC}`:HC,t.baseUrl&&(r.baseUrl=t.baseUrl),t.previews&&(r.mediaType.previews=t.previews),t.timeZone&&(r.headers["time-zone"]=t.timeZone),this.request=io.defaults(r),this.graphql=_C(this.request).defaults(r),this.log=DM(t.log),this.hook=s,t.authStrategy){let{authStrategy:o,...n}=t,a=o(Object.assign({request:this.request,log:this.log,octokit:this,octokitOptions:n},t.auth));s.wrap("request",a.hook),this.auth=a}else if(!t.auth)this.auth=async()=>({type:"unauthenticated"});else{let o=JC(t.auth);s.wrap("request",o.hook),this.auth=o}let i=this.constructor;for(let o=0;o<i.plugins.length;++o)Object.assign(this,i.plugins[o](this,t))}request;graphql;log;hook;auth};var _u="17.0.0";var RM={actions:{addCustomLabelsToSelfHostedRunnerForOrg:["POST /orgs/{org}/actions/runners/{runner_id}/labels"],addCustomLabelsToSelfHostedRunnerForRepo:["POST /repos/{owner}/{repo}/actions/runners/{runner_id}/labels"],addRepoAccessToSelfHostedRunnerGroupInOrg:["PUT /orgs/{org}/actions/runner-groups/{runner_group_id}/repositories/{repository_id}"],addSelectedRepoToOrgSecret:["PUT /orgs/{org}/actions/secrets/{secret_name}/repositories/{repository_id}"],addSelectedRepoToOrgVariable:["PUT /orgs/{org}/actions/variables/{name}/repositories/{repository_id}"],approveWorkflowRun:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/approve"],cancelWorkflowRun:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/cancel"],createEnvironmentVariable:["POST /repos/{owner}/{repo}/environments/{environment_name}/variables"],createHostedRunnerForOrg:["POST /orgs/{org}/actions/hosted-runners"],createOrUpdateEnvironmentSecret:["PUT /repos/{owner}/{repo}/environments/{environment_name}/secrets/{secret_name}"],createOrUpdateOrgSecret:["PUT /orgs/{org}/actions/secrets/{secret_name}"],createOrUpdateRepoSecret:["PUT /repos/{owner}/{repo}/actions/secrets/{secret_name}"],createOrgVariable:["POST /orgs/{org}/actions/variables"],cre Line truncated
globstar while`,e,l,t,p,g),this.matchOne(e.slice(l),t.slice(p),s))return this.debug("globstar found match!",l,a,g),!0;if(g==="."||g===".."||!r.dot&&g.charAt(0)==="."){this.debug("dot detected!",e,l,t,p);break}this.debug("globstar swallow a segment, and continue"),l++}return!!(s&&(this.debug(`
>>> no match, partial?`,e,l,t,p),l===a))}let h;if(typeof c=="string"?(h=u===c,this.debug("string match",c,u,h)):(h=c.test(u),this.debug("pattern match",c,u,h)),!h)return!1}if(o===a&&n===A)return!0;if(o===a)return s;if(n===A)return o===a-1&&e[o]==="";throw new Error("wtf?")}braceExpand(){return PI(this.pattern,this.options)}parse(e){Ja(e);let t=this.options;if(e==="**")return be;if(e==="")return"";let s,r=null;(s=e.match(DL))?r=t.dot?TL:RL:(s=e.match(QL))?r=(t.nocase?t.dot?wL:IL:t.dot?CL:BL)(s[1]):(s=e.match(FL))?r=(t.nocase?t.dot?UL:SL:t.dot?NL:GL)(s):(s=e.match(bL))?r=t.dot?xL:yL:(s=e.match(vL))&&(r=kL);let i=LI.fromGlob(e,this.options).toMMPattern();return r&&typeof i=="object"&&Reflect.defineProperty(i,"test",{value:r}),i}makeRe(){if(this.regexp||this.regexp===!1)return this.regexp;let e=this.set;if(!e.length)return this.regexp=!1,this.regexp;let t=this.options,s=t.noglobstar?_L:t.dot?YL:OL,r=new Set(t.nocase?["i"]:[]),i=e.map(a=>{let A=a.map(u=>{if(u instanceof RegExp)for(let l of u.flags.split(""))r.add(l);return typeof u=="string"?qL(u):u===be?be:u._src});A.forEach((u,l)=>{let p=A[l+1],g=A[l-1];u!==be||g===be||(g===void 0?p!==void 0&&p!==be?A[l+1]="(?:\\/|"+s+"\\/)?"+p:A[l]=s:p===void 0?A[l-1]=g+"(?:\\/|\\/"+s+")?":p!==be&&(A[l-1]=g+"(?:\\/|\\/"+s+"\\/)"+p,A[l+1]=be))});let c=A.filter(u=>u!==be);if(this.partial&&c.length>=1){let u=[];for(let l=1;l<=c.length;l++)u.push(c.slice(0,l).join("/"));return"(?:"+u.join("|")+")"}return c.join("/")}).join("|"),[o,n]=e.length>1?["(?:",")"]:["",""];i="^"+o+i+n+"$",this.partial&&(i="^(?:\\/|"+o+i.slice(1,-1)+n+")$"),this.negate&&(i="^(?!"+i+").+$");try{this.regexp=new RegExp(i,[...r].join(""))}catch{this.regexp=!1}return this.regexp}slashSplit(e){return this.preserveMultipleSlashes?e.split("/"):this.isWindows&&/^\/\/[^\/]+/.test(e)?["",...e.split(/\/+/)]:e.split(/\/+/)}match(e,t=this.partial){if(this.debug("match",e,this.pattern),this.comment)return!1;if(this.empty)return e==="";if(e==="/"&&t)return!0;let s=this.options;this.isWindows&&(e=e.split("\\").join("/"));let r=this.slashSplit(e);this.debug(this.pattern,"split",r);let i=this.set;this.debug(this.pattern,"set",i);let o=r[r.length-1];if(!o)for(let n=r.length-2;!o&&n>=0;n--)o=r[n];for(let n=0;n<i.length;n++){let a=i[n],A=r;if(s.matchBase&&a.length===1&&(A=[o]),this.matchOne(A,a,t))return s.flipNegate?!0:!this.negate}return s.flipNegate?!1:this.negate}static defaults(e){return ye.defaults(e).Minimatch}};ye.AST=LI;ye.Minimatch=fs;ye.escape=_I;ye.unescape=Xr;var WL=typeof performance=="object"&&performance&&typeof performance.now=="function"?performance:Date,VI=new Set,ep=typeof process=="object"&&process?process:{},qI=(e,t,s,r)=>{typeof ep.emitWarning=="function"?ep.emitWarning(e,t,s,r):console.error(`[${s}] ${t}: ${e}`)},Pa=globalThis.AbortController,EI=globalThis.AbortSignal;if(typeof Pa>"u"){EI=class{onabort;_onabort=[];reason;aborted=!1;addEventListener(s,r){this._onabort.push(r)}},Pa=class{constructor(){t()}signal=new EI;abort(s){if(!this.signal.aborted){this.signal.reason=s,this.signal.aborted=!0;for(let r of this.signal._onabort)r(s);this.signal.onabort?.(s)}}};let e=ep.env?.LRU_CACHE_IGNORE_AC_WARNING!=="1",t=()=>{e&&(e=!1,qI("AbortController is not defined. If using lru-cache in node 14, load an AbortController polyfill from the `node-abort-controller` package. A minimal polyfill is provided for use by LRUCache.fetch(), but it should not be relied upon in other contexts (eg, passing it to other APIs that use AbortController/AbortSignal might have undesirable effects). You may disable this with LRU_CACHE_IGNORE_AC_WARNING=1 in the env.","NO_ABORT_CONTROLLER","ENOTSUP",t))}}var jL=e=>!VI.has(e),ms=e=>e&&e===Math.floor(e)&&e>0&&isFinite(e),WI=e=>ms(e)?e<=Math.pow(2,8)?Uint8Array:e<=Math.pow(2,16)?Uint16Array:e<=Math.pow(2,32)?Uint32Array:e<=Number.MAX_SAFE_INTEGER?Ya:null:null,Ya=class extends Array{constructor(e){super(e),this.fill(0)}},zL=class po{heap;length;static#e=!1;static create(t){let s=WI(t);if(!s)return[];po.#e=!0;let r=new po(t,s);return po.#e=!1,r}constructor(t,s){if(!po.#e)throw new TypeError("instantiate Stack using Stack.create(n)");this.heap=new s(t),this.length=0}push(t){this.heap[this.length++]=t}pop(){return this.heap[--this.length]}},Va=class jI{#e;#t;#i;#s;#r;#A;#a;#c;get perf(){return this.#c}ttl;ttlResolution;ttlAutopurge;updateAgeOnGet;updateAgeOnHas;allowStale;noDisposeOnSet;noUpdateTTL;maxEntrySize;sizeCalculation;noDeleteOnFetchRejection;noDeleteOnStaleGet;allowStaleOnFetchAbort;allowStaleOnFetchRejection;ignoreFetchAbort;#l;#g;#h;#u;#n;#d;#C;#B;#E;#k;#m;#b;#y;#f;#Q;#I;#x;#o;#U;static unsafeExposeInternals(t){return{starts:t.#y,ttls:t.#f,autopurgeTimers:t.#Q,sizes:t.#b,keyMap:t.#h,keyList:t.#u,valList:t.#n,next:t.#d,prev:t.#C,get head(){return t.#B},get tail(){return t.#E},free:t.#k,isBackgroundFetch:s=>t.#p(s),backgroundFetch:(s,r,i,o)=>t.#J(s,r,i,o),moveToTail:s=>t.#G(s),indexes:s=>t.#R(s),rindexes:s=>t.#T(s),isStale:s=>t.#w(s)}}get max(){return this.#e}get maxSize(){return th Line truncated
>>> no match, partial?`,e,l,t,p),l===a))}let h;if(typeof c=="string"?(h=u===c,this.debug("string match",c,u,h)):(h=c.test(u),this.debug("pattern match",c,u,h)),!h)return!1}if(o===a&&n===A)return!0;if(o===a)return s;if(n===A)return o===a-1&&e[o]==="";throw new Error("wtf?")}braceExpand(){return PI(this.pattern,this.options)}parse(e){Ja(e);let t=this.options;if(e==="**")return be;if(e==="")return"";let s,r=null;(s=e.match(DL))?r=t.dot?TL:RL:(s=e.match(QL))?r=(t.nocase?t.dot?wL:IL:t.dot?CL:BL)(s[1]):(s=e.match(FL))?r=(t.nocase?t.dot?UL:SL:t.dot?NL:GL)(s):(s=e.match(bL))?r=t.dot?xL:yL:(s=e.match(vL))&&(r=kL);let i=LI.fromGlob(e,this.options).toMMPattern();return r&&typeof i=="object"&&Reflect.defineProperty(i,"test",{value:r}),i}makeRe(){if(this.regexp||this.regexp===!1)return this.regexp;let e=this.set;if(!e.length)return this.regexp=!1,this.regexp;let t=this.options,s=t.noglobstar?_L:t.dot?YL:OL,r=new Set(t.nocase?["i"]:[]),i=e.map(a=>{let A=a.map(u=>{if(u instanceof RegExp)for(let l of u.flags.split(""))r.add(l);return typeof u=="string"?qL(u):u===be?be:u._src});A.forEach((u,l)=>{let p=A[l+1],g=A[l-1];u!==be||g===be||(g===void 0?p!==void 0&&p!==be?A[l+1]="(?:\\/|"+s+"\\/)?"+p:A[l]=s:p===void 0?A[l-1]=g+"(?:\\/|\\/"+s+")?":p!==be&&(A[l-1]=g+"(?:\\/|\\/"+s+"\\/)"+p,A[l+1]=be))});let c=A.filter(u=>u!==be);if(this.partial&&c.length>=1){let u=[];for(let l=1;l<=c.length;l++)u.push(c.slice(0,l).join("/"));return"(?:"+u.join("|")+")"}return c.join("/")}).join("|"),[o,n]=e.length>1?["(?:",")"]:["",""];i="^"+o+i+n+"$",this.partial&&(i="^(?:\\/|"+o+i.slice(1,-1)+n+")$"),this.negate&&(i="^(?!"+i+").+$");try{this.regexp=new RegExp(i,[...r].join(""))}catch{this.regexp=!1}return this.regexp}slashSplit(e){return this.preserveMultipleSlashes?e.split("/"):this.isWindows&&/^\/\/[^\/]+/.test(e)?["",...e.split(/\/+/)]:e.split(/\/+/)}match(e,t=this.partial){if(this.debug("match",e,this.pattern),this.comment)return!1;if(this.empty)return e==="";if(e==="/"&&t)return!0;let s=this.options;this.isWindows&&(e=e.split("\\").join("/"));let r=this.slashSplit(e);this.debug(this.pattern,"split",r);let i=this.set;this.debug(this.pattern,"set",i);let o=r[r.length-1];if(!o)for(let n=r.length-2;!o&&n>=0;n--)o=r[n];for(let n=0;n<i.length;n++){let a=i[n],A=r;if(s.matchBase&&a.length===1&&(A=[o]),this.matchOne(A,a,t))return s.flipNegate?!0:!this.negate}return s.flipNegate?!1:this.negate}static defaults(e){return ye.defaults(e).Minimatch}};ye.AST=LI;ye.Minimatch=fs;ye.escape=_I;ye.unescape=Xr;var WL=typeof performance=="object"&&performance&&typeof performance.now=="function"?performance:Date,VI=new Set,ep=typeof process=="object"&&process?process:{},qI=(e,t,s,r)=>{typeof ep.emitWarning=="function"?ep.emitWarning(e,t,s,r):console.error(`[${s}] ${t}: ${e}`)},Pa=globalThis.AbortController,EI=globalThis.AbortSignal;if(typeof Pa>"u"){EI=class{onabort;_onabort=[];reason;aborted=!1;addEventListener(s,r){this._onabort.push(r)}},Pa=class{constructor(){t()}signal=new EI;abort(s){if(!this.signal.aborted){this.signal.reason=s,this.signal.aborted=!0;for(let r of this.signal._onabort)r(s);this.signal.onabort?.(s)}}};let e=ep.env?.LRU_CACHE_IGNORE_AC_WARNING!=="1",t=()=>{e&&(e=!1,qI("AbortController is not defined. If using lru-cache in node 14, load an AbortController polyfill from the `node-abort-controller` package. A minimal polyfill is provided for use by LRUCache.fetch(), but it should not be relied upon in other contexts (eg, passing it to other APIs that use AbortController/AbortSignal might have undesirable effects). You may disable this with LRU_CACHE_IGNORE_AC_WARNING=1 in the env.","NO_ABORT_CONTROLLER","ENOTSUP",t))}}var jL=e=>!VI.has(e),ms=e=>e&&e===Math.floor(e)&&e>0&&isFinite(e),WI=e=>ms(e)?e<=Math.pow(2,8)?Uint8Array:e<=Math.pow(2,16)?Uint16Array:e<=Math.pow(2,32)?Uint32Array:e<=Number.MAX_SAFE_INTEGER?Ya:null:null,Ya=class extends Array{constructor(e){super(e),this.fill(0)}},zL=class po{heap;length;static#e=!1;static create(t){let s=WI(t);if(!s)return[];po.#e=!0;let r=new po(t,s);return po.#e=!1,r}constructor(t,s){if(!po.#e)throw new TypeError("instantiate Stack using Stack.create(n)");this.heap=new s(t),this.length=0}push(t){this.heap[this.length++]=t}pop(){return this.heap[--this.length]}},Va=class jI{#e;#t;#i;#s;#r;#A;#a;#c;get perf(){return this.#c}ttl;ttlResolution;ttlAutopurge;updateAgeOnGet;updateAgeOnHas;allowStale;noDisposeOnSet;noUpdateTTL;maxEntrySize;sizeCalculation;noDeleteOnFetchRejection;noDeleteOnStaleGet;allowStaleOnFetchAbort;allowStaleOnFetchRejection;ignoreFetchAbort;#l;#g;#h;#u;#n;#d;#C;#B;#E;#k;#m;#b;#y;#f;#Q;#I;#x;#o;#U;static unsafeExposeInternals(t){return{starts:t.#y,ttls:t.#f,autopurgeTimers:t.#Q,sizes:t.#b,keyMap:t.#h,keyList:t.#u,valList:t.#n,next:t.#d,prev:t.#C,get head(){return t.#B},get tail(){return t.#E},free:t.#k,isBackgroundFetch:s=>t.#p(s),backgroundFetch:(s,r,i,o)=>t.#J(s,r,i,o),moveToTail:s=>t.#G(s),indexes:s=>t.#R(s),rindexes:s=>t.#T(s),isStale:s=>t.#w(s)}}get max(){return this.#e}get maxSize(){return th Line truncated
${s.data.body}`:t.body=s.data.body}return t.body=t.body?this.truncateReleaseNotes(t.body):void 0,this.github.rest.repos.createRelease(t)}async updateRelease(t){if(typeof t.make_latest=="string"&&!["true","false","legacy"].includes(t.make_latest)&&(t.make_latest=void 0),t.generate_release_notes){let s=await this.getReleaseNotes(t);t.generate_release_notes=!1,t.body?t.body=`${t.body}
${s.data.body}`:t.body=s.data.body}return t.body=t.body?this.truncateReleaseNotes(t.body):void 0,this.github.rest.repos.updateRelease(t)}async finalizeRelease(t){return await this.github.rest.repos.updateRelease({owner:t.owner,repo:t.repo,release_id:t.release_id,draft:!1,make_latest:t.make_latest})}allReleases(t){let s={per_page:100,...t};return this.github.paginate.iterator(this.github.rest.repos.listReleases.endpoint.merge(s))}async listReleaseAssets(t){return this.github.paginate(this.github.rest.repos.listReleaseAssets,{...t,per_page:100})}async deleteReleaseAsset(t){await this.github.rest.repos.deleteReleaseAsset(t)}async deleteRelease(t){await this.github.rest.repos.deleteRelease(t)}async updateReleaseAsset(t){return await this.github.rest.repos.updateReleaseAsset(t)}async uploadReleaseAsset(t){return this.github.request({method:"POST",url:t.url,headers:{"content-length":`${t.size}`,"content-type":t.mime,authorization:`token ${t.token}`},data:t.data})}},U_=e=>({name:(0,Dw.basename)(e),mime:N_(e),size:(0,xw.statSync)(e).size}),N_=e=>(0,kw.lookup)(e)||"application/octet-stream",Rw=async(e,t,s,r,i)=>{let[o,n]=e.github_repository.split("/"),{name:a,mime:A,size:c}=U_(r),u=s.match(/\/releases\/(\d+)\/assets/),l=u?Number(u[1]):void 0,p=i.find(({name:E,label:m})=>E===a||E===cp(a)||m===a);if(p){if(e.input_overwrite_files===!1)return console.log(`Asset ${a} already exists and overwrite_files is false...`),null;console.log(`\u267B\uFE0F Deleting previously uploaded asset ${a}...`),await t.deleteReleaseAsset({asset_id:p.id||1,owner:o,repo:n})}console.log(`\u2B06\uFE0F Uploading ${a}...`);let g=new URL(s);g.searchParams.append("name",a);let h=async()=>{let E=await(0,vw.open)(r);try{return await t.uploadReleaseAsset({url:g.toString(),size:c,mime:A,token:e.github_token,data:E.readableWebStream({type:"bytes"})})}finally{await E.close()}};try{let E=await h(),m=E.data;if(E.status!==201)throw new Error(`Failed to upload release asset ${a}. received status code ${E.status}
${i.data.body}`:r.body=i.data.body}return r.body=r.body?this.truncateReleaseNotes(r.body):void 0,r}truncateReleaseNotes(t){return t.substring(0,124999)}async createRelease(t){return this.github.rest.repos.createRelease(await this.prepareReleaseMutation(t))}async updateRelease(t){return this.github.rest.repos.updateRelease(await this.prepareReleaseMutation(t))}async finalizeRelease(t){return await this.github.rest.repos.updateRelease({owner:t.owner,repo:t.repo,release_id:t.release_id,draft:!1,make_latest:t.make_latest})}allReleases(t){let s={per_page:100,...t};return this.github.paginate.iterator(this.github.rest.repos.listReleases.endpoint.merge(s))}async listReleaseAssets(t){return this.github.paginate(this.github.rest.repos.listReleaseAssets,{...t,per_page:100})}async deleteReleaseAsset(t){await this.github.rest.repos.deleteReleaseAsset(t)}async deleteRelease(t){await this.github.rest.repos.deleteRelease(t)}async updateReleaseAsset(t){return await this.github.rest.repos.updateReleaseAsset(t)}async uploadReleaseAsset(t){return this.github.request({method:"POST",url:t.url,headers:{"content-length":`${t.size}`,"content-type":t.mime,authorization:`token ${t.token}`},data:t.data})}},U_=e=>({name:(0,Dw.basename)(e),mime:N_(e),size:(0,xw.statSync)(e).size}),N_=e=>(0,kw.lookup)(e)||"application/octet-stream",Rw=async(e,t,s,r,i)=>{let[o,n]=e.github_repository.split("/"),{name:a,mime:A,size:c}=U_(r),u=s.match(/\/releases\/(\d+)\/assets/),l=u?Number(u[1]):void 0,p=i.find(({name:E,label:m})=>E===a||E===cp(a)||m===a);if(p){if(e.input_overwrite_files===!1)return console.log(`Asset ${a} already exists and overwrite_files is false...`),null;console.log(`\u267B\uFE0F Deleting previously uploaded asset ${a}...`),await t.deleteReleaseAsset({asset_id:p.id||1,owner:o,repo:n})}console.log(`\u2B06\uFE0F Uploading ${a}...`);let g=new URL(s);g.searchParams.append("name",a);let h=async()=>{let E=await(0,vw.open)(r);try{return await t.uploadReleaseAsset({url:g.toString(),size:c,mime:A,token:e.github_token,data:E.readableWebStream({type:"bytes"})})}finally{await E.close()}};try{let E=await h(),m=E.data;if(E.status!==201)throw new Error(`Failed to upload release asset ${a}. received status code ${E.status}
${m.message}
${JSON.stringify(m.errors)}`);if(m.name&&m.name!==a&&m.id){console.log(`\u270F\uFE0F Restoring asset label to ${a}...`);try{let{data:d}=await t.updateReleaseAsset({owner:o,repo:n,asset_id:m.id,name:m.name,label:a});return console.log(`\u2705 Uploaded ${a}`),d}catch(d){console.warn(`error updating release asset label for ${a}: ${d}`)}}return console.log(`\u2705 Uploaded ${a}`),m}catch(E){let m=E?.status??E?.response?.status,d=E?.response?.data;if(e.input_overwrite_files!==!1&&m===422&&d?.errors?.[0]?.code==="already_exists"&&l!==void 0){console.log(`\u26A0\uFE0F Asset ${a} already exists (race condition), refreshing assets and retrying once...`);let C=(await t.listReleaseAssets({owner:o,repo:n,release_id:l})).find(({name:B})=>B==cp(a));if(C){await t.deleteReleaseAsset({owner:o,repo:n,asset_id:C.id});let B=await h(),b=B.data;if(B.status!==201)throw new Error(`Failed to upload release asset ${a}. received status code ${B.status}
${b.message}
${JSON.stringify(b.errors)}`);return console.log(`\u2705 Uploaded ${a}`),b}}throw E}},lp=async(e,t,s=3)=>{if(s<=0)throw console.log("\u274C Too many retries. Aborting..."),new Error("Too many retries.");let[r,i]=e.github_repository.split("/"),o=Ap(e.input_tag_name)||(fo(e.github_ref)?e.github_ref.replace("refs/tags/",""):""),n=e.input_discussion_category_name,a=e.input_generate_release_notes;try{let A=await Tw(t,r,i,o);if(A===void 0)return await yw(o,e,t,r,i,n,a,s);let c=A;console.log(`Found release ${c.name} (with id=${c.id})`);let u=c.id,l;e.input_target_commitish&&e.input_target_commitish!==c.target_commitish?(console.log(`Updating commit from "${c.target_commitish}" to "${e.input_target_commitish}"`),l=e.input_target_commitish):l=c.target_commitish;let p=o,g=e.input_name||c.name||o,h=ap(e)||"",E=c.body||"",m;e.input_append_body&&h&&E?m=E+`
`+h:m=h||E;let d=e.input_prerelease!==void 0?e.input_prerelease:c.prerelease,f=e.input_make_latest;return{release:(await t.updateRelease({owner:r,repo:i,release_id:u,tag_name:p,target_commitish:l,name:g,body:m,draft:c.draft,prerelease:d,discussion_category_name:n,generate_release_notes:a,make_latest:f})).data,created:!1}}catch(A){if(A.status!==404)throw console.log(`\u26A0\uFE0F Unexpected error fetching GitHub release for tag ${e.github_ref}: ${A}`),A;return await yw(o,e,t,r,i,n,a,s)}},up=async(e,t,s,r=!1,i=3)=>{if(e.input_draft===!0||s.draft===!1)return s;if(i<=0)throw console.log("\u274C Too many retries. Aborting..."),new Error("Too many retries.");let[o,n]=e.github_repository.split("/");try{let{data:a}=await t.finalizeRelease({owner:o,repo:n,release_id:s.id,make_latest:e.input_make_latest});return a}catch(a){if(console.warn(`error finalizing release: ${a}`),r&&s.draft&&P_(a)){let A=!1;try{console.log(`\u{1F9F9} Deleting draft release ${s.id} for tag ${s.tag_name} because tag creation is blocked by repository rules...`),await t.deleteRelease({owner:o,repo:n,release_id:s.id}),A=!0}catch(u){console.warn(`error deleting orphan draft release ${s.id}: ${u}`)}let c=A?`Deleted draft release ${s.id} to avoid leaving an orphaned draft release.`:`Failed to delete draft release ${s.id}; manual cleanup may still be required.`;throw new Error(`Tag creation for ${s.tag_name} is blocked by repository rules. ${c}`)}return console.log(`retrying... (${i-1} retries remaining)`),up(e,t,s,r,i-1)}},pp=async(e,t,s,r=3)=>{if(r<=0)throw console.log("\u274C Too many retries. Aborting..."),new Error("Too many retries.");let[i,o]=e.github_repository.split("/");try{return await t.listReleaseAssets({owner:i,repo:o,release_id:s.id})}catch(n){return console.warn(`error listing assets of release: ${n}`),console.log(`retrying... (${r-1} retries remaining)`),pp(e,t,s,r-1)}};async function Tw(e,t,s,r){try{let{data:i}=await e.getReleaseByTag({owner:t,repo:s,tag:r});return i}catch(i){if(i.status===404)return;throw i}}var G_=1e3,M_=2;async function L_(e){await new Promise(t=>setTimeout(t,e))}async function __(e,t,s,r){let i=[],o=0;for await(let n of e.allReleases({owner:t,repo:s}))if(i.push(...n.data.filter(a=>a.tag_name===r)),o+=1,o>=M_)break;return i}function Y_(e,t){return t&&e.some(s=>s.id===t.id)||e.length===0?t:[...e].sort((s,r)=>s.draft!==r.draft?Number(s.draft)-Number(r.draft):s.id-r.id)[0]}async function O_(e,t,s,r,i,o){let n=Array.from(new Map(o.map(a=>[a.id,a])).values());for(let a of n)if(!(a.id===i||!a.draft||a.assets.length>0))try{console.log(`\u{1F9F9} Removing duplicate draft release ${a.id} for tag ${r}...`),await e.deleteRelease({owner:t,repo:s,release_id:a.id})}catch(A){console.warn(`error deleting duplicate release ${a.id}: ${A}`)}}async function J_(e,t,s,r,i,o){let n=Math.max(o,1);for(let a=1;a<=n;a+=1){let A;try{A=await Tw(e,t,s,r)}catch(l){console.warn(`error reloading release for tag ${r}: ${l}`)}let c=[];try{c=await __(e,t,s,r)}catch(l){console.warn(`error listing recent releases for tag ${r}: ${l}`)}let u=Y_(c,A);if(u)return u.id!==i.id&&console.log(`\u21AA\uFE0F Using release ${u.id} for tag ${r} instead of duplicate draft ${i.id}`),await O_(e,t,s,r,u.id,[i,...c]),u;a<n&&(console.log(`Release ${i.id} is not yet discoverable by tag ${r}, retrying... (${n-a} retries remaining)`),await L_(G_))}return console.log(`\u26A0\uFE0F Continuing with newly created release ${i.id} because tag ${r} is still not discoverable`),i}async function yw(e,t,s,r,i,o,n,a){let A=e,c=t.input_name||e,u=ap(t),l=t.input_prerelease,p=l===!0?t.input_draft===!0:!0,g=t.input_target_commitish,h=t.input_make_latest,E="";g&&(E=` using commit "${g}"`),console.log(`\u{1F469}\u200D\u{1F3ED} Creating new GitHub release for tag ${A}${E}...`);try{let m=await s.createRelease({owner:r,repo:i,tag_name:A,name:c,body:u,draft:p,prerelease:l,target_commitish:g,discussion_category_name:o,generate_release_notes:n,make_latest:h}),d=await J_(s,r,i,A,m.data,a);return{release:d,created:d.id===m.data.id}}catch(m){switch(console.log(`\u26A0\uFE0F GitHub release failed with status: ${m.status}`),console.log(`${JSON.stringify(m.response.data)}`),m.status){case 403:throw console.log("Skip retry \u2014 your GitHub token/PAT does not have the required permission to create a release"),m;case 404:throw console.log("Skip retry - discussion category mismatch"),m;case 422:if(m.response?.data?.errors?.[0]?.code==="already_exists")console.log("\u26A0\uFE0F Release already exists (race condition detected), retrying to find and update existing release...");else throw console.log("Skip retry - validation failed"),m;break}return console.log(`retrying... (${a-1} retries remaining)`),lp(t,s,a-1)}}function P_(e){let t=e?.response?.data?.errors;return!Array.isArray(t)||e?.status!==422?!1:t.some(({field:s,message:r})=>s==="pre_receive"&&typeof r=="string"&&r.includes("creations being restricted"))}var Fw=require("process");async function H_(){try{let e=Cw(Fw.env);if(!e.input_tag_name&&!fo(e Line truncated
${JSON.stringify(b.errors)}`);return console.log(`\u2705 Uploaded ${a}`),b}}throw E}},lp=async(e,t,s=3)=>{if(s<=0)throw console.log("\u274C Too many retries. Aborting..."),new Error("Too many retries.");let[r,i]=e.github_repository.split("/"),o=Ap(e.input_tag_name)||(fo(e.github_ref)?e.github_ref.replace("refs/tags/",""):""),n=e.input_discussion_category_name,a=e.input_generate_release_notes,A=e.input_previous_tag;a&&A&&console.log(`\u{1F4DD} Generating release notes using previous tag ${A}`);try{let c=await Tw(t,r,i,o);if(c===void 0)return await yw(o,e,t,r,i,n,a,s,A);let u=c;console.log(`Found release ${u.name} (with id=${u.id})`);let l=u.id,p;e.input_target_commitish&&e.input_target_commitish!==u.target_commitish?(console.log(`Updating commit from "${u.target_commitish}" to "${e.input_target_commitish}"`),p=e.input_target_commitish):p=u.target_commitish;let g=o,h=e.input_name||u.name||o,E=ap(e)||"",m=u.body||"",d;e.input_append_body&&E&&m?d=m+`
`+E:d=E||m;let f=e.input_prerelease!==void 0?e.input_prerelease:u.prerelease,C=e.input_make_latest;return{release:(await t.updateRelease({owner:r,repo:i,release_id:l,tag_name:g,target_commitish:p,name:h,body:d,draft:u.draft,prerelease:f,discussion_category_name:n,generate_release_notes:a,make_latest:C,previous_tag_name:A})).data,created:!1}}catch(c){if(c.status!==404)throw console.log(`\u26A0\uFE0F Unexpected error fetching GitHub release for tag ${e.github_ref}: ${c}`),c;return await yw(o,e,t,r,i,n,a,s,A)}},up=async(e,t,s,r=!1,i=3)=>{if(e.input_draft===!0||s.draft===!1)return s;if(i<=0)throw console.log("\u274C Too many retries. Aborting..."),new Error("Too many retries.");let[o,n]=e.github_repository.split("/");try{let{data:a}=await t.finalizeRelease({owner:o,repo:n,release_id:s.id,make_latest:e.input_make_latest});return a}catch(a){if(console.warn(`error finalizing release: ${a}`),r&&s.draft&&P_(a)){let A=!1;try{console.log(`\u{1F9F9} Deleting draft release ${s.id} for tag ${s.tag_name} because tag creation is blocked by repository rules...`),await t.deleteRelease({owner:o,repo:n,release_id:s.id}),A=!0}catch(u){console.warn(`error deleting orphan draft release ${s.id}: ${u}`)}let c=A?`Deleted draft release ${s.id} to avoid leaving an orphaned draft release.`:`Failed to delete draft release ${s.id}; manual cleanup may still be required.`;throw new Error(`Tag creation for ${s.tag_name} is blocked by repository rules. ${c}`)}return console.log(`retrying... (${i-1} retries remaining)`),up(e,t,s,r,i-1)}},pp=async(e,t,s,r=3)=>{if(r<=0)throw console.log("\u274C Too many retries. Aborting..."),new Error("Too many retries.");let[i,o]=e.github_repository.split("/");try{return await t.listReleaseAssets({owner:i,repo:o,release_id:s.id})}catch(n){return console.warn(`error listing assets of release: ${n}`),console.log(`retrying... (${r-1} retries remaining)`),pp(e,t,s,r-1)}};async function Tw(e,t,s,r){try{let{data:i}=await e.getReleaseByTag({owner:t,repo:s,tag:r});return i}catch(i){if(i.status===404)return;throw i}}var G_=1e3,M_=2;async function L_(e){await new Promise(t=>setTimeout(t,e))}async function __(e,t,s,r){let i=[],o=0;for await(let n of e.allReleases({owner:t,repo:s}))if(i.push(...n.data.filter(a=>a.tag_name===r)),o+=1,o>=M_)break;return i}function Y_(e,t){return t&&e.some(s=>s.id===t.id)||e.length===0?t:[...e].sort((s,r)=>s.draft!==r.draft?Number(s.draft)-Number(r.draft):s.id-r.id)[0]}async function O_(e,t,s,r,i,o){let n=Array.from(new Map(o.map(a=>[a.id,a])).values());for(let a of n)if(!(a.id===i||!a.draft||a.assets.length>0))try{console.log(`\u{1F9F9} Removing duplicate draft release ${a.id} for tag ${r}...`),await e.deleteRelease({owner:t,repo:s,release_id:a.id})}catch(A){console.warn(`error deleting duplicate release ${a.id}: ${A}`)}}async function J_(e,t,s,r,i,o){let n=Math.max(o,1);for(let a=1;a<=n;a+=1){let A;try{A=await Tw(e,t,s,r)}catch(l){console.warn(`error reloading release for tag ${r}: ${l}`)}let c=[];try{c=await __(e,t,s,r)}catch(l){console.warn(`error listing recent releases for tag ${r}: ${l}`)}let u=Y_(c,A);if(u)return u.id!==i.id&&console.log(`\u21AA\uFE0F Using release ${u.id} for tag ${r} instead of duplicate draft ${i.id}`),await O_(e,t,s,r,u.id,[i,...c]),u;a<n&&(console.log(`Release ${i.id} is not yet discoverable by tag ${r}, retrying... (${n-a} retries remaining)`),await L_(G_))}return console.log(`\u26A0\uFE0F Continuing with newly created release ${i.id} because tag ${r} is still not discoverable`),i}async function yw(e,t,s,r,i,o,n,a,A){let c=e,u=t.input_name||e,l=ap(t),p=t.input_prerelease,g=p===!0?t.input_draft===!0:!0,h=t.input_target_commitish,E=t.input_make_latest,m="";h&&(m=` using commit "${h}"`),console.log(`\u{1F469}\u200D\u{1F3ED} Creating new GitHub release for tag ${c}${m}...`);try{let d=await s.createRelease({owner:r,repo:i,tag_name:c,name:u,body:l,draft:g,prerelease:p,target_commitish:h,discussion_category_name:o,generate_release_notes:n,make_latest:E,previous_tag_name:A}),f=await J_(s,r,i,c,d.data,a);return{release:f,created:f.id===d.data.id}}catch(d){switch(console.log(`\u26A0\uFE0F GitHub release failed with status: ${d.status}`),console.log(`${JSON.stringify(d.response.data)}`),d.status){case 403:throw console.log("Skip retry \u2014 your GitHub token/PAT does not have the required permission to create a release"),d;case 404:throw console.log("Skip retry - discussion category mismatch"),d;case 422:if(d.response?.data?.errors?.[0]?.code==="already_exists")console.log("\u26A0\uFE0F Release already exists (race condition detected), retrying to find and update existing release...");else throw console.log("Skip retry - validation failed"),d;break}return console.log(`retrying... (${a-1} retries remaining)`),lp(t,s,a-1)}}function P_(e){let t=e?.response?.data?.errors;return!Array.isArray(t)||e?.status!==422?!1:t.some(({field:s,message:r})=>s==="pre_receive"&&typeof r=="string"&&r.includes("creations being restricted"))}var Fw=require("process");async function H_(){try{ Line truncated
/*! Bundled license information:
undici/lib/web/fetch/body.js:
+82 -94
View File
@@ -31,37 +31,40 @@ export interface ReleaseResult {
created: boolean;
}
type ReleaseNotesParams = {
owner: string;
repo: string;
tag_name: string;
target_commitish: string | undefined;
previous_tag_name?: string;
};
type ReleaseMutationParams = {
owner: string;
repo: string;
tag_name: string;
name: string;
body: string | undefined;
draft: boolean | undefined;
prerelease: boolean | undefined;
target_commitish: string | undefined;
discussion_category_name: string | undefined;
generate_release_notes: boolean | undefined;
make_latest: 'true' | 'false' | 'legacy' | undefined;
previous_tag_name?: string;
};
export interface Releaser {
getReleaseByTag(params: { owner: string; repo: string; tag: string }): Promise<{ data: Release }>;
createRelease(params: {
owner: string;
repo: string;
tag_name: string;
name: string;
body: string | undefined;
draft: boolean | undefined;
prerelease: boolean | undefined;
target_commitish: string | undefined;
discussion_category_name: string | undefined;
generate_release_notes: boolean | undefined;
make_latest: 'true' | 'false' | 'legacy' | undefined;
}): Promise<{ data: Release }>;
createRelease(params: ReleaseMutationParams): Promise<{ data: Release }>;
updateRelease(params: {
owner: string;
repo: string;
release_id: number;
tag_name: string;
target_commitish: string;
name: string;
body: string | undefined;
draft: boolean | undefined;
prerelease: boolean | undefined;
discussion_category_name: string | undefined;
generate_release_notes: boolean | undefined;
make_latest: 'true' | 'false' | 'legacy' | undefined;
}): Promise<{ data: Release }>;
updateRelease(
params: ReleaseMutationParams & {
release_id: number;
target_commitish: string;
},
): Promise<{ data: Release }>;
finalizeRelease(params: {
owner: string;
@@ -113,12 +116,7 @@ export class GitHubReleaser implements Releaser {
return this.github.rest.repos.getReleaseByTag(params);
}
async getReleaseNotes(params: {
owner: string;
repo: string;
tag_name: string;
target_commitish: string | undefined;
}): Promise<{
async getReleaseNotes(params: ReleaseNotesParams): Promise<{
data: {
name: string;
body: string;
@@ -127,75 +125,55 @@ export class GitHubReleaser implements Releaser {
return await this.github.rest.repos.generateReleaseNotes(params);
}
private async prepareReleaseMutation<T extends ReleaseMutationParams>(
params: T,
): Promise<Omit<T, 'previous_tag_name'>> {
const { previous_tag_name, ...releaseParams } = params;
if (
typeof releaseParams.make_latest === 'string' &&
!['true', 'false', 'legacy'].includes(releaseParams.make_latest)
) {
releaseParams.make_latest = undefined;
}
if (releaseParams.generate_release_notes) {
const releaseNotes = await this.getReleaseNotes({
owner: releaseParams.owner,
repo: releaseParams.repo,
tag_name: releaseParams.tag_name,
target_commitish: releaseParams.target_commitish,
previous_tag_name,
});
releaseParams.generate_release_notes = false;
if (releaseParams.body) {
releaseParams.body = `${releaseParams.body}\n\n${releaseNotes.data.body}`;
} else {
releaseParams.body = releaseNotes.data.body;
}
}
releaseParams.body = releaseParams.body
? this.truncateReleaseNotes(releaseParams.body)
: undefined;
return releaseParams;
}
truncateReleaseNotes(input: string): string {
// release notes can be a maximum of 125000 characters
const githubNotesMaxCharLength = 125000;
return input.substring(0, githubNotesMaxCharLength - 1);
}
async createRelease(params: {
owner: string;
repo: string;
tag_name: string;
name: string;
body: string | undefined;
draft: boolean | undefined;
prerelease: boolean | undefined;
target_commitish: string | undefined;
discussion_category_name: string | undefined;
generate_release_notes: boolean | undefined;
make_latest: 'true' | 'false' | 'legacy' | undefined;
}): Promise<{ data: Release }> {
if (
typeof params.make_latest === 'string' &&
!['true', 'false', 'legacy'].includes(params.make_latest)
) {
params.make_latest = undefined;
}
if (params.generate_release_notes) {
const releaseNotes = await this.getReleaseNotes(params);
params.generate_release_notes = false;
if (params.body) {
params.body = `${params.body}\n\n${releaseNotes.data.body}`;
} else {
params.body = releaseNotes.data.body;
}
}
params.body = params.body ? this.truncateReleaseNotes(params.body) : undefined;
return this.github.rest.repos.createRelease(params);
async createRelease(params: ReleaseMutationParams): Promise<{ data: Release }> {
return this.github.rest.repos.createRelease(await this.prepareReleaseMutation(params));
}
async updateRelease(params: {
owner: string;
repo: string;
release_id: number;
tag_name: string;
target_commitish: string;
name: string;
body: string | undefined;
draft: boolean | undefined;
prerelease: boolean | undefined;
discussion_category_name: string | undefined;
generate_release_notes: boolean | undefined;
make_latest: 'true' | 'false' | 'legacy' | undefined;
}): Promise<{ data: Release }> {
if (
typeof params.make_latest === 'string' &&
!['true', 'false', 'legacy'].includes(params.make_latest)
) {
params.make_latest = undefined;
}
if (params.generate_release_notes) {
const releaseNotes = await this.getReleaseNotes(params);
params.generate_release_notes = false;
if (params.body) {
params.body = `${params.body}\n\n${releaseNotes.data.body}`;
} else {
params.body = releaseNotes.data.body;
}
}
params.body = params.body ? this.truncateReleaseNotes(params.body) : undefined;
return this.github.rest.repos.updateRelease(params);
async updateRelease(
params: ReleaseMutationParams & {
release_id: number;
target_commitish: string;
},
): Promise<{ data: Release }> {
return this.github.rest.repos.updateRelease(await this.prepareReleaseMutation(params));
}
async finalizeRelease(params: {
@@ -425,6 +403,11 @@ export const release = async (
const discussion_category_name = config.input_discussion_category_name;
const generate_release_notes = config.input_generate_release_notes;
const previous_tag_name = config.input_previous_tag;
if (generate_release_notes && previous_tag_name) {
console.log(`📝 Generating release notes using previous tag ${previous_tag_name}`);
}
try {
const _release: Release | undefined = await findTagFromReleases(releaser, owner, repo, tag);
@@ -438,6 +421,7 @@ export const release = async (
discussion_category_name,
generate_release_notes,
maxRetries,
previous_tag_name,
);
}
@@ -491,6 +475,7 @@ export const release = async (
discussion_category_name,
generate_release_notes,
make_latest,
previous_tag_name,
});
return {
release: release.data,
@@ -513,6 +498,7 @@ export const release = async (
discussion_category_name,
generate_release_notes,
maxRetries,
previous_tag_name,
);
}
};
@@ -796,6 +782,7 @@ async function createRelease(
discussion_category_name: string | undefined,
generate_release_notes: boolean | undefined,
maxRetries: number,
previous_tag_name: string | undefined,
): Promise<ReleaseResult> {
const tag_name = tag;
const name = config.input_name || tag;
@@ -822,6 +809,7 @@ async function createRelease(
discussion_category_name,
generate_release_notes,
make_latest,
previous_tag_name,
});
const canonicalRelease = await canonicalizeCreatedRelease(
releaser,
+2
View File
@@ -23,6 +23,7 @@ export interface Config {
input_target_commitish?: string;
input_discussion_category_name?: string;
input_generate_release_notes?: boolean;
input_previous_tag?: string;
input_append_body?: boolean;
input_make_latest: 'true' | 'false' | 'legacy' | undefined;
}
@@ -114,6 +115,7 @@ export const parseConfig = (env: Env): Config => {
input_target_commitish: env.INPUT_TARGET_COMMITISH || undefined,
input_discussion_category_name: env.INPUT_DISCUSSION_CATEGORY_NAME || undefined,
input_generate_release_notes: env.INPUT_GENERATE_RELEASE_NOTES == 'true',
input_previous_tag: env.INPUT_PREVIOUS_TAG?.trim() || undefined,
input_append_body: env.INPUT_APPEND_BODY == 'true',
input_make_latest: parseMakeLatest(env.INPUT_MAKE_LATEST),
};