32 Commits

Author SHA1 Message Date
Nick Cipollo
9128f238ee preparing release 1.17.0
Some checks failed
Release / Create Release (push) Has been cancelled
Test / check_pr (push) Has been cancelled
2025-06-28 21:32:06 -04:00
Nick Cipollo
7922049688 Fixes #529 Add zip and tarball urls to output 2025-06-28 21:23:15 -04:00
Nick Cipollo
0683ea3557 Cleanup outputs table formatting 2025-06-21 10:39:02 -04:00
Nick Cipollo
01c9fac73b Add a link to uploading assets in output table 2025-06-21 10:21:16 -04:00
dependabot[bot]
90dc22b77f Bump @types/node from 22.15.18 to 22.15.29 (#524)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.15.18 to 22.15.29.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 22.15.29
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-03 09:12:23 -04:00
dependabot[bot]
10585cb926 Bump @actions/github from 6.0.0 to 6.0.1 (#525)
Bumps [@actions/github](https://github.com/actions/toolkit/tree/HEAD/packages/github) from 6.0.0 to 6.0.1.
- [Changelog](https://github.com/actions/toolkit/blob/main/packages/github/RELEASES.md)
- [Commits](https://github.com/actions/toolkit/commits/HEAD/packages/github)

---
updated-dependencies:
- dependency-name: "@actions/github"
  dependency-version: 6.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-03 09:07:35 -04:00
dependabot[bot]
1896b4fc46 Bump ts-jest from 29.3.2 to 29.3.4 (#526)
Bumps [ts-jest](https://github.com/kulshekhar/ts-jest) from 29.3.2 to 29.3.4.
- [Release notes](https://github.com/kulshekhar/ts-jest/releases)
- [Changelog](https://github.com/kulshekhar/ts-jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/kulshekhar/ts-jest/compare/v29.3.2...v29.3.4)

---
updated-dependencies:
- dependency-name: ts-jest
  dependency-version: 29.3.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-03 09:05:36 -04:00
dependabot[bot]
6996c1bc95 Bump @types/node from 22.13.8 to 22.15.3 (#519)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.13.8 to 22.15.3.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 22.15.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-14 09:12:26 -04:00
dependabot[bot]
c07d979560 Bump glob from 11.0.1 to 11.0.2 (#520)
Bumps [glob](https://github.com/isaacs/node-glob) from 11.0.1 to 11.0.2.
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v11.0.1...v11.0.2)

---
updated-dependencies:
- dependency-name: glob
  dependency-version: 11.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-14 09:06:24 -04:00
dependabot[bot]
38da029bb7 Bump ts-jest from 29.2.6 to 29.3.2 (#521)
Bumps [ts-jest](https://github.com/kulshekhar/ts-jest) from 29.2.6 to 29.3.2.
- [Release notes](https://github.com/kulshekhar/ts-jest/releases)
- [Changelog](https://github.com/kulshekhar/ts-jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/kulshekhar/ts-jest/compare/v29.2.6...v29.3.2)

---
updated-dependencies:
- dependency-name: ts-jest
  dependency-version: 29.3.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-14 09:06:13 -04:00
dependabot[bot]
bf17a40975 Bump typescript from 5.7.3 to 5.8.3 (#518)
Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.7.3 to 5.8.3.
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release-publish.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.7.3...v5.8.3)

---
updated-dependencies:
- dependency-name: typescript
  dependency-version: 5.8.3
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-14 09:02:53 -04:00
Nick Cipollo
fa4749311e Fixes #513 Don't generate release notes if omitBody is set (#517) 2025-05-11 17:38:39 -04:00
dependabot[bot]
36e78ab629 Bump @octokit/request from 8.1.6 to 8.4.1 (#508)
Bumps [@octokit/request](https://github.com/octokit/request.js) from 8.1.6 to 8.4.1.
- [Release notes](https://github.com/octokit/request.js/releases)
- [Commits](https://github.com/octokit/request.js/compare/v8.1.6...v8.4.1)

---
updated-dependencies:
- dependency-name: "@octokit/request"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-06 23:30:41 -05:00
dependabot[bot]
6c1f9baed2 Bump @octokit/plugin-paginate-rest from 9.1.5 to 9.2.2 (#507)
Bumps [@octokit/plugin-paginate-rest](https://github.com/octokit/plugin-paginate-rest.js) from 9.1.5 to 9.2.2.
- [Release notes](https://github.com/octokit/plugin-paginate-rest.js/releases)
- [Commits](https://github.com/octokit/plugin-paginate-rest.js/compare/v9.1.5...v9.2.2)

---
updated-dependencies:
- dependency-name: "@octokit/plugin-paginate-rest"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-06 23:30:27 -05:00
dependabot[bot]
db62f3f3ad Bump @types/node from 22.13.4 to 22.13.8 (#505)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.13.4 to 22.13.8.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-06 23:28:47 -05:00
dependabot[bot]
e6992e14b1 Bump ts-jest from 29.2.5 to 29.2.6 (#506)
Bumps [ts-jest](https://github.com/kulshekhar/ts-jest) from 29.2.5 to 29.2.6.
- [Release notes](https://github.com/kulshekhar/ts-jest/releases)
- [Changelog](https://github.com/kulshekhar/ts-jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/kulshekhar/ts-jest/compare/v29.2.5...v29.2.6)

---
updated-dependencies:
- dependency-name: ts-jest
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-06 23:28:38 -05:00
Nick Cipollo
440c8c1cb0 preparing release 1.16.0
Some checks failed
Release / Create Release (push) Has been cancelled
Test / check_pr (push) Has been cancelled
2025-02-22 11:35:27 -05:00
Nick Cipollo
e805702217 preparing release 1.17.0 2025-02-22 11:33:08 -05:00
Nick Cipollo
bd572a1942 preparing release 1.17.0 2025-02-22 11:31:24 -05:00
Nick Cipollo
706b28e44c Update tag scheme in sheepit 2025-02-22 11:30:58 -05:00
Nick Cipollo
411ac011df preparing release 1.16.0 2025-02-22 11:26:00 -05:00
Radek Antoniuk
a8c5a7f252 Fixes #474 Regenerate release notes on release updates (#497) 2025-02-20 09:11:25 -05:00
Nick Cipollo
62f16c02e7 Add sheepit config 2025-02-17 17:54:01 -05:00
Nick Cipollo
00e83e3d35 Attempt #1 at fixing workflow file 2025-02-17 16:33:09 -05:00
Nick Cipollo
906fc77113 Add initial releases workflow 2025-02-17 16:29:52 -05:00
Nick Cipollo
17b559883e Add biome and initial format rules 2025-02-17 16:15:54 -05:00
Nick Cipollo
33bf18d283 Install dependency updates 2025-02-16 15:28:37 -05:00
dependabot[bot]
009df9782d Bump @types/node from 22.10.3 to 22.13.4 (#500)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.10.3 to 22.13.4.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-16 15:09:58 -05:00
dependabot[bot]
4b84ad5c60 Bump glob from 11.0.0 to 11.0.1 (#495)
Bumps [glob](https://github.com/isaacs/node-glob) from 11.0.0 to 11.0.1.
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v11.0.0...v11.0.1)

---
updated-dependencies:
- dependency-name: glob
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-16 14:56:48 -05:00
dependabot[bot]
34dae7e8c0 Bump undici from 5.28.4 to 5.28.5 (#499)
Bumps [undici](https://github.com/nodejs/undici) from 5.28.4 to 5.28.5.
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v5.28.4...v5.28.5)

---
updated-dependencies:
- dependency-name: undici
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-16 14:56:32 -05:00
Cycloctane
9fc271e109 update checkout action version in example (#496) 2025-02-16 14:56:13 -05:00
dependabot[bot]
3cfa40eca4 Bump @octokit/request-error from 5.0.1 to 5.1.1 (#498) 2025-02-15 07:44:36 -05:00
43 changed files with 2268 additions and 2053 deletions

BIN
.DS_Store vendored

Binary file not shown.

22
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: Release
on:
push:
tags:
- '*'
jobs:
create_release:
if: github.ref != 'refs/tags/v1'
runs-on: ubuntu-latest
name: Create Release
steps:
- uses: actions/checkout@v4
- name: Create Release
id: create_release
uses: ncipollo/release-action@v1
with:
allowUpdates: true
draft: true
generateReleaseNotes: true

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
node_modules/ node_modules/
__tests__/runner/* __tests__/runner/*
.DS_Store
# Created by https://www.gitignore.io/api/webstorm # Created by https://www.gitignore.io/api/webstorm
# Edit at https://www.gitignore.io/?templates=webstorm # Edit at https://www.gitignore.io/?templates=webstorm

6
.idea/biome.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="BiomeSettings">
<option name="enableLspFormat" value="true" />
</component>
</project>

View File

@@ -48,7 +48,9 @@ This action will create a GitHub release and optionally upload an artifact to it
|-------------|-----------------------------------------------| |-------------|-----------------------------------------------|
| id | The identifier of the created release. | | id | The identifier of the created release. |
| html_url | The HTML URL of the release. | | html_url | The HTML URL of the release. |
| upload_url | The URL for uploading assets to the release. | | upload_url | The URL for [uploading assets](https://docs.github.com/en/rest/releases/assets?apiVersion=2022-11-28#upload-a-release-asset) to the release. |
| tarball_url | The URL for downloading the release as a tarball (.tar.gz). |
| zipball_url | The URL for downloading the release as a zipball (.zip). |
## Example ## Example
This example will create a release when a tag is pushed: This example will create a release when a tag is pushed:
@@ -68,7 +70,7 @@ jobs:
permissions: permissions:
contents: write contents: write
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: ncipollo/release-action@v1 - uses: ncipollo/release-action@v1
with: with:
artifacts: "release.tar.gz,foo/*.txt" artifacts: "release.tar.gz,foo/*.txt"

View File

@@ -71,7 +71,7 @@ jobs:
permissions: permissions:
contents: write contents: write
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: ncipollo/release-action@v1 - uses: ncipollo/release-action@v1
with: with:
artifacts: "release.tar.gz,foo/*.txt" artifacts: "release.tar.gz,foo/*.txt"

View File

@@ -1,11 +1,18 @@
import {Action} from "../src/Action"; import { Action } from "../src/Action"
import {Artifact} from "../src/Artifact"; import type { ActionSkipper } from "../src/ActionSkipper"
import {Inputs} from "../src/Inputs"; import { Artifact } from "../src/Artifact"
import {Releases} from "../src/Releases"; import type { ArtifactDestroyer } from "../src/ArtifactDestroyer"
import {ArtifactUploader} from "../src/ArtifactUploader"; import type { ArtifactUploader } from "../src/ArtifactUploader"
import {Outputs} from "../src/Outputs"; import type { Inputs } from "../src/Inputs"
import {ArtifactDestroyer} from "../src/ArtifactDestroyer"; import type { Outputs } from "../src/Outputs"
import {ActionSkipper} from "../src/ActionSkipper"; import type { Releases } from "../src/Releases"
const TEST_URLS = {
UPLOAD_URL: "http://api.example.com",
HTML_URL: "https://github.com/owner/repo/releases/tag/v1.0.0",
TARBALL_URL: "https://api.github.com/repos/owner/repo/tarball/v1.0.0",
ZIPBALL_URL: "https://api.github.com/repos/owner/repo/zipball/v1.0.0",
} as const
const applyReleaseDataMock = jest.fn() const applyReleaseDataMock = jest.fn()
const artifactDestroyMock = jest.fn() const artifactDestroyMock = jest.fn()
@@ -17,31 +24,30 @@ const listMock = jest.fn()
const shouldSkipMock = jest.fn() const shouldSkipMock = jest.fn()
const updateMock = jest.fn() const updateMock = jest.fn()
const uploadMock = jest.fn() const uploadMock = jest.fn()
const genReleaseNotesMock = jest.fn()
const artifacts = [ const artifacts = [new Artifact("a/art1"), new Artifact("b/art2")]
new Artifact('a/art1'),
new Artifact('b/art2')
]
const createBody = 'createBody' const createBody = "createBody"
const createDraft = true const createDraft = true
const createName = 'createName' const createName = "createName"
const commit = 'commit' const commit = "commit"
const discussionCategory = 'discussionCategory' const discussionCategory = "discussionCategory"
const generateReleaseNotes = true const generateReleaseNotes = true
const id = 100 const id = 100
const createPrerelease = true const createPrerelease = true
const releaseId = 101 const releaseId = 101
const replacesArtifacts = true const replacesArtifacts = true
const tag = 'tag' const tag = "tag"
const token = 'token' const token = "token"
const updateBody = 'updateBody' const updateBody = "updateBody"
const updateDraft = false const updateDraft = false
const updateName = 'updateName' const updateName = "updateName"
const updatePrerelease = false const updatePrerelease = false
const updateOnlyUnreleased = false const updateOnlyUnreleased = false
const url = 'http://api.example.com' const url = TEST_URLS.UPLOAD_URL
const makeLatest = 'legacy' const makeLatest = "legacy"
const generatedReleaseBody = "test release notes"
describe("Action", () => { describe("Action", () => {
beforeEach(() => { beforeEach(() => {
@@ -53,58 +59,12 @@ describe("Action", () => {
uploadMock.mockClear() uploadMock.mockClear()
}) })
it('creates release but does not upload if no artifact', async () => { it("creates release but does not upload if no artifact", async () => {
const action = createAction(false, false) const action = createAction(false, false)
await action.perform() await action.perform()
expect(createMock).toBeCalledWith(tag, expect(createMock).toHaveBeenCalledWith(
createBody,
commit,
discussionCategory,
createDraft,
generateReleaseNotes,
makeLatest,
createName,
createPrerelease)
expect(uploadMock).not.toBeCalled()
assertOutputApplied()
})
it('creates release if no release exists to update', async () => {
const action = createAction(true, true)
const error = {status: 404}
getMock.mockRejectedValue(error)
await action.perform()
expect(createMock).toBeCalledWith(
tag,
createBody,
commit,
discussionCategory,
createDraft,
generateReleaseNotes,
makeLatest,
createName,
createPrerelease)
expect(uploadMock).toBeCalledWith(artifacts, releaseId, url)
assertOutputApplied()
})
it('creates release if no draft releases', async () => {
const action = createAction(true, true)
const error = {status: 404}
getMock.mockRejectedValue(error)
listMock.mockResolvedValue({
data: [
{id: id, draft: false, tag_name: tag}
]
})
await action.perform()
expect(createMock).toBeCalledWith(
tag, tag,
createBody, createBody,
commit, commit,
@@ -115,17 +75,63 @@ describe("Action", () => {
createName, createName,
createPrerelease createPrerelease
) )
expect(uploadMock).toBeCalledWith(artifacts, releaseId, url) expect(uploadMock).not.toHaveBeenCalled()
assertOutputApplied() assertOutputApplied()
}) })
it('creates release then uploads artifact', async () => { it("creates release if no release exists to update", async () => {
const action = createAction(true, true)
const error = { status: 404 }
getMock.mockRejectedValue(error)
await action.perform()
expect(createMock).toHaveBeenCalledWith(
tag,
createBody,
commit,
discussionCategory,
createDraft,
generateReleaseNotes,
makeLatest,
createName,
createPrerelease
)
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
assertOutputApplied()
})
it("creates release if no draft releases", async () => {
const action = createAction(true, true)
const error = { status: 404 }
getMock.mockRejectedValue(error)
listMock.mockResolvedValue({
data: [{ id: id, draft: false, tag_name: tag }],
})
await action.perform()
expect(createMock).toHaveBeenCalledWith(
tag,
createBody,
commit,
discussionCategory,
createDraft,
generateReleaseNotes,
makeLatest,
createName,
createPrerelease
)
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
assertOutputApplied()
})
it("creates release then uploads artifact", async () => {
const action = createAction(false, true) const action = createAction(false, true)
await action.perform() await action.perform()
expect(createMock).toBeCalledWith( expect(createMock).toHaveBeenCalledWith(
tag, tag,
createBody, createBody,
commit, commit,
@@ -136,39 +142,39 @@ describe("Action", () => {
createName, createName,
createPrerelease createPrerelease
) )
expect(uploadMock).toBeCalledWith(artifacts, releaseId, url) expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
assertOutputApplied() assertOutputApplied()
}) })
it('removes all artifacts when artifact destroyer is enabled', async () => { it("removes all artifacts when artifact destroyer is enabled", async () => {
const action = createAction(false, true, true) const action = createAction(false, true, true)
await action.perform() await action.perform()
expect(artifactDestroyMock).toBeCalledWith(releaseId) expect(artifactDestroyMock).toHaveBeenCalledWith(releaseId)
assertOutputApplied() assertOutputApplied()
}) })
it('removes no artifacts when artifact destroyer is disabled', async () => { it("removes no artifacts when artifact destroyer is disabled", async () => {
const action = createAction(false, true) const action = createAction(false, true)
await action.perform() await action.perform()
expect(artifactDestroyMock).not.toBeCalled() expect(artifactDestroyMock).not.toHaveBeenCalled()
assertOutputApplied() assertOutputApplied()
}) })
it('skips action', async () => { it("skips action", async () => {
const action = createAction(false, false, false) const action = createAction(false, false, false)
shouldSkipMock.mockResolvedValue(true) shouldSkipMock.mockResolvedValue(true)
await action.perform() await action.perform()
expect(createMock).not.toBeCalled() expect(createMock).not.toHaveBeenCalled()
expect(updateMock).not.toBeCalled() expect(updateMock).not.toHaveBeenCalled()
}) })
it('throws error when create fails', async () => { it("throws error when create fails", async () => {
const action = createAction(false, true) const action = createAction(false, true)
createMock.mockRejectedValue("error") createMock.mockRejectedValue("error")
@@ -179,7 +185,7 @@ describe("Action", () => {
expect(error).toEqual("error") expect(error).toEqual("error")
} }
expect(createMock).toBeCalledWith( expect(createMock).toHaveBeenCalledWith(
tag, tag,
createBody, createBody,
commit, commit,
@@ -190,17 +196,17 @@ describe("Action", () => {
createName, createName,
createPrerelease createPrerelease
) )
expect(uploadMock).not.toBeCalled() expect(uploadMock).not.toHaveBeenCalled()
}) })
it('throws error when get fails', async () => { it("throws error when get fails", async () => {
const action = createAction(true, true) const action = createAction(true, true)
const error = { const error = {
errors: [ errors: [
{ {
code: 'already_exists' code: "already_exists",
} },
] ],
} }
createMock.mockRejectedValue(error) createMock.mockRejectedValue(error)
@@ -212,22 +218,20 @@ describe("Action", () => {
expect(error).toEqual("error") expect(error).toEqual("error")
} }
expect(getMock).toBeCalledWith(tag) expect(getMock).toHaveBeenCalledWith(tag)
expect(updateMock).not.toBeCalled() expect(updateMock).not.toHaveBeenCalled()
expect(uploadMock).not.toBeCalled() expect(uploadMock).not.toHaveBeenCalled()
}) })
it('throws error when list has no data', async () => { it("throws error when list has no data", async () => {
const action = createAction(true, true) const action = createAction(true, true)
getMock.mockRejectedValue({status: 404}) getMock.mockRejectedValue({ status: 404 })
const error = { const error = {
errors: [ errors: [
{ {
code: 'already_exists' code: "already_exists",
} },
] ],
} }
createMock.mockRejectedValue(error) createMock.mockRejectedValue(error)
@@ -239,12 +243,12 @@ describe("Action", () => {
expect(error).toEqual(Error("No releases found. Response: {}")) expect(error).toEqual(Error("No releases found. Response: {}"))
} }
expect(listMock).toBeCalled() expect(listMock).toHaveBeenCalled()
expect(createMock).not.toBeCalled() expect(createMock).not.toHaveBeenCalled()
expect(updateMock).not.toBeCalled() expect(updateMock).not.toHaveBeenCalled()
}) })
it('throws error when update fails', async () => { it("throws error when update fails", async () => {
const action = createAction(true, true) const action = createAction(true, true)
updateMock.mockRejectedValue("error") updateMock.mockRejectedValue("error")
@@ -256,10 +260,10 @@ describe("Action", () => {
expect(error).toEqual("error") expect(error).toEqual("error")
} }
expect(updateMock).toBeCalledWith( expect(updateMock).toHaveBeenCalledWith(
id, id,
tag, tag,
updateBody, generatedReleaseBody,
commit, commit,
discussionCategory, discussionCategory,
updateDraft, updateDraft,
@@ -267,12 +271,12 @@ describe("Action", () => {
updateName, updateName,
updatePrerelease updatePrerelease
) )
expect(uploadMock).not.toBeCalled() expect(uploadMock).not.toHaveBeenCalled()
}) })
it('throws error when upload fails', async () => { it("throws error when upload fails", async () => {
const action = createAction(false, true) const action = createAction(false, true)
const expectedError = {status: 404} const expectedError = { status: 404 }
uploadMock.mockRejectedValue(expectedError) uploadMock.mockRejectedValue(expectedError)
expect.hasAssertions() expect.hasAssertions()
@@ -282,7 +286,7 @@ describe("Action", () => {
expect(error).toEqual(expectedError) expect(error).toEqual(expectedError)
} }
expect(createMock).toBeCalledWith( expect(createMock).toHaveBeenCalledWith(
tag, tag,
createBody, createBody,
commit, commit,
@@ -293,23 +297,51 @@ describe("Action", () => {
createName, createName,
createPrerelease createPrerelease
) )
expect(uploadMock).toBeCalledWith(artifacts, releaseId, url) expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
}) })
it('updates draft release', async () => { it("updates draft release", async () => {
const action = createAction(true, true) const action = createAction(true, true)
const error = {status: 404} const error = { status: 404 }
getMock.mockRejectedValue(error) getMock.mockRejectedValue(error)
listMock.mockResolvedValue({ listMock.mockResolvedValue({
data: [ data: [
{id: 123, draft: false, tag_name: tag}, { id: 123, draft: false, tag_name: tag },
{id: id, draft: true, tag_name: tag} { id: id, draft: true, tag_name: tag },
] ],
}) })
await action.perform() await action.perform()
expect(updateMock).toBeCalledWith( expect(updateMock).toHaveBeenCalledWith(
id,
tag,
generatedReleaseBody,
commit,
discussionCategory,
updateDraft,
makeLatest,
updateName,
updatePrerelease
)
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
assertOutputApplied()
})
it("updates draft release with static body", async () => {
const action = createAction(true, true, false, false)
const error = { status: 404 }
getMock.mockRejectedValue(error)
listMock.mockResolvedValue({
data: [
{ id: 123, draft: false, tag_name: tag },
{ id: id, draft: true, tag_name: tag },
],
})
await action.perform()
expect(updateMock).toHaveBeenCalledWith(
id, id,
tag, tag,
updateBody, updateBody,
@@ -320,19 +352,19 @@ describe("Action", () => {
updateName, updateName,
updatePrerelease updatePrerelease
) )
expect(uploadMock).toBeCalledWith(artifacts, releaseId, url) expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
assertOutputApplied() assertOutputApplied()
}) })
it('updates release but does not upload if no artifact', async () => { it("updates release but does not upload if no artifact", async () => {
const action = createAction(true, false) const action = createAction(true, false)
await action.perform() await action.perform()
expect(updateMock).toBeCalledWith( expect(updateMock).toHaveBeenCalledWith(
id, id,
tag, tag,
updateBody, generatedReleaseBody,
commit, commit,
discussionCategory, discussionCategory,
updateDraft, updateDraft,
@@ -340,16 +372,44 @@ describe("Action", () => {
updateName, updateName,
updatePrerelease updatePrerelease
) )
expect(uploadMock).not.toBeCalled() expect(uploadMock).not.toHaveBeenCalled()
assertOutputApplied() assertOutputApplied()
}) })
it('updates release then uploads artifact', async () => { it("updates release then uploads artifact", async () => {
const action = createAction(true, true) const action = createAction(true, true)
await action.perform() await action.perform()
expect(updateMock).toBeCalledWith( expect(updateMock).toHaveBeenCalledWith(
id,
tag,
generatedReleaseBody,
commit,
discussionCategory,
updateDraft,
makeLatest,
updateName,
updatePrerelease
)
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
assertOutputApplied()
})
it("updates release with static body when generateReleaseNotes is true but omitBodyDuringUpdate is true", async () => {
const action = createAction(true, true, false, true, true)
const error = { status: 404 }
getMock.mockRejectedValue(error)
listMock.mockResolvedValue({
data: [
{ id: 123, draft: false, tag_name: tag },
{ id: id, draft: true, tag_name: tag },
],
})
await action.perform()
expect(updateMock).toHaveBeenCalledWith(
id, id,
tag, tag,
updateBody, updateBody,
@@ -360,23 +420,35 @@ describe("Action", () => {
updateName, updateName,
updatePrerelease updatePrerelease
) )
expect(uploadMock).toBeCalledWith(artifacts, releaseId, url) expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
assertOutputApplied() assertOutputApplied()
}) })
function assertOutputApplied() { function assertOutputApplied() {
expect(applyReleaseDataMock).toBeCalledWith({id: releaseId, upload_url: url}) expect(applyReleaseDataMock).toHaveBeenCalledWith({
id: releaseId,
upload_url: url,
html_url: TEST_URLS.HTML_URL,
tarball_url: TEST_URLS.TARBALL_URL,
zipball_url: TEST_URLS.ZIPBALL_URL,
})
} }
function createAction(allowUpdates: boolean, function createAction(
allowUpdates: boolean,
hasArtifact: boolean, hasArtifact: boolean,
removeArtifacts: boolean = false): Action { removeArtifacts = false,
generateReleaseNotes = true,
omitBodyDuringUpdate = false
): Action {
let inputArtifact: Artifact[] let inputArtifact: Artifact[]
if (hasArtifact) { if (hasArtifact) {
inputArtifact = artifacts inputArtifact = artifacts
} else { } else {
inputArtifact = [] inputArtifact = []
} }
const MockReleases = jest.fn<Releases, any>(() => { const MockReleases = jest.fn<Releases, any>(() => {
return { return {
create: createMock, create: createMock,
@@ -385,79 +457,93 @@ describe("Action", () => {
listArtifactsForRelease: listArtifactsMock, listArtifactsForRelease: listArtifactsMock,
listReleases: listMock, listReleases: listMock,
update: updateMock, update: updateMock,
uploadArtifact: jest.fn() uploadArtifact: jest.fn(),
generateReleaseNotes: genReleaseNotesMock,
} }
}) })
createMock.mockResolvedValue({ createMock.mockResolvedValue({
data: { data: {
id: releaseId, id: releaseId,
upload_url: url upload_url: url,
} html_url: TEST_URLS.HTML_URL,
tarball_url: TEST_URLS.TARBALL_URL,
zipball_url: TEST_URLS.ZIPBALL_URL,
},
})
genReleaseNotesMock.mockResolvedValue({
data: {
body: generatedReleaseBody,
},
}) })
getMock.mockResolvedValue({ getMock.mockResolvedValue({
data: { data: {
id: id id: id,
} },
}) })
listMock.mockResolvedValue({ listMock.mockResolvedValue({
data: [] data: [],
}) })
shouldSkipMock.mockResolvedValue(false) shouldSkipMock.mockResolvedValue(false)
updateMock.mockResolvedValue({ updateMock.mockResolvedValue({
data: { data: {
id: releaseId, id: releaseId,
upload_url: url upload_url: url,
} html_url: TEST_URLS.HTML_URL,
tarball_url: TEST_URLS.TARBALL_URL,
zipball_url: TEST_URLS.ZIPBALL_URL,
},
}) })
uploadMock.mockResolvedValue({}) uploadMock.mockResolvedValue({})
const MockInputs = jest.fn<Inputs, any>(() => { const MockInputs = jest.fn<Inputs, any>(() => {
return { return {
allowUpdates: allowUpdates, allowUpdates,
artifactErrorsFailBuild: true, artifactErrorsFailBuild: true,
artifacts: inputArtifact, artifacts: inputArtifact,
createdDraft: createDraft, createdDraft: createDraft,
createdReleaseBody: createBody, createdReleaseBody: createBody,
createdReleaseName: createName, createdReleaseName: createName,
commit: commit, commit,
discussionCategory: discussionCategory, discussionCategory,
generateReleaseNotes: true, generateReleaseNotes,
makeLatest: makeLatest, makeLatest: makeLatest,
owner: "owner", owner: "owner",
createdPrerelease: createPrerelease, createdPrerelease: createPrerelease,
replacesArtifacts: replacesArtifacts, replacesArtifacts,
removeArtifacts: removeArtifacts, removeArtifacts,
repo: "repo", repo: "repo",
skipIfReleaseExists: false, skipIfReleaseExists: false,
tag: tag, tag,
token: token, token,
updatedDraft: updateDraft, updatedDraft: updateDraft,
updatedReleaseBody: updateBody, updatedReleaseBody: updateBody,
updatedReleaseName: updateName, updatedReleaseName: updateName,
updatedPrerelease: updatePrerelease, updatedPrerelease: updatePrerelease,
updateOnlyUnreleased: updateOnlyUnreleased updateOnlyUnreleased: updateOnlyUnreleased,
omitBodyDuringUpdate,
} }
}) })
const MockOutputs = jest.fn<Outputs, any>(() => { const MockOutputs = jest.fn<Outputs, any>(() => {
return { return {
applyReleaseData: applyReleaseDataMock applyReleaseData: applyReleaseDataMock,
} }
}) })
const MockUploader = jest.fn<ArtifactUploader, any>(() => { const MockUploader = jest.fn<ArtifactUploader, any>(() => {
return { return {
uploadArtifacts: uploadMock uploadArtifacts: uploadMock,
} }
}) })
const MockArtifactDestroyer = jest.fn<ArtifactDestroyer, any>(() => { const MockArtifactDestroyer = jest.fn<ArtifactDestroyer, any>(() => {
return { return {
destroyArtifacts: artifactDestroyMock destroyArtifacts: artifactDestroyMock,
} }
}) })
const MockActionSkipper = jest.fn<ActionSkipper, any>(() => { const MockActionSkipper = jest.fn<ActionSkipper, any>(() => {
return { return {
shouldSkip: shouldSkipMock shouldSkip: shouldSkipMock,
} }
}) })

View File

@@ -1,5 +1,5 @@
import {ActionSkipper, ReleaseActionSkipper} from "../src/ActionSkipper"; import { ReleaseActionSkipper } from "../src/ActionSkipper"
import {Releases} from "../src/Releases"; import type { Releases } from "../src/Releases"
describe("shouldSkip", () => { describe("shouldSkip", () => {
const getMock = jest.fn() const getMock = jest.fn()
@@ -12,31 +12,32 @@ describe("shouldSkip", () => {
listArtifactsForRelease: jest.fn(), listArtifactsForRelease: jest.fn(),
listReleases: jest.fn(), listReleases: jest.fn(),
update: jest.fn(), update: jest.fn(),
uploadArtifact: jest.fn() uploadArtifact: jest.fn(),
generateReleaseNotes: jest.fn(),
} }
}) })
it('should return false when skipIfReleaseExists is false', async () => { it("should return false when skipIfReleaseExists is false", async () => {
const actionSkipper = new ReleaseActionSkipper(false, MockReleases(), tag) const actionSkipper = new ReleaseActionSkipper(false, MockReleases(), tag)
expect(await actionSkipper.shouldSkip()).toBe(false) expect(await actionSkipper.shouldSkip()).toBe(false)
}) })
it('should return false when error occurs', async () => { it("should return false when error occurs", async () => {
getMock.mockRejectedValue(new Error()) getMock.mockRejectedValue(new Error())
const actionSkipper = new ReleaseActionSkipper(true, MockReleases(), tag) const actionSkipper = new ReleaseActionSkipper(true, MockReleases(), tag)
expect(await actionSkipper.shouldSkip()).toBe(false) expect(await actionSkipper.shouldSkip()).toBe(false)
}) })
it('should return false when release does not exist', async () => { it("should return false when release does not exist", async () => {
getMock.mockResolvedValue({}) getMock.mockResolvedValue({})
const actionSkipper = new ReleaseActionSkipper(true, MockReleases(), tag) const actionSkipper = new ReleaseActionSkipper(true, MockReleases(), tag)
expect(await actionSkipper.shouldSkip()).toBe(false) expect(await actionSkipper.shouldSkip()).toBe(false)
}) })
it('should return true when release does exist', async () => { it("should return true when release does exist", async () => {
getMock.mockResolvedValue({data: {}}) getMock.mockResolvedValue({ data: {} })
const actionSkipper = new ReleaseActionSkipper(true, MockReleases(), tag) const actionSkipper = new ReleaseActionSkipper(true, MockReleases(), tag)
expect(await actionSkipper.shouldSkip()).toBe(true) expect(await actionSkipper.shouldSkip()).toBe(true)

View File

@@ -1,40 +1,40 @@
import {Artifact} from "../src/Artifact"; import { Artifact } from "../src/Artifact"
const contentLength = 42 const contentLength = 42
const fakeReadStream = {} const fakeReadStream = {}
jest.mock('fs', () => { jest.mock("fs", () => {
return { return {
createReadStream: () => fakeReadStream, createReadStream: () => fakeReadStream,
statSync: () => { statSync: () => {
return {size: contentLength} return { size: contentLength }
},
} }
};
}) })
describe("Artifact", () => { describe("Artifact", () => {
it('defaults contentType to raw', () => { it("defaults contentType to raw", () => {
const artifact = new Artifact('') const artifact = new Artifact("")
expect(artifact.contentType).toBe('raw') expect(artifact.contentType).toBe("raw")
}) })
it('generates name from path', () => { it("generates name from path", () => {
const artifact = new Artifact('some/artifact') const artifact = new Artifact("some/artifact")
expect(artifact.name).toBe('artifact') expect(artifact.name).toBe("artifact")
}) })
it('provides contentLength', () => { it("provides contentLength", () => {
const artifact = new Artifact('some/artifact') const artifact = new Artifact("some/artifact")
expect(artifact.contentLength).toBe(contentLength) expect(artifact.contentLength).toBe(contentLength)
}) })
it('provides path', () => { it("provides path", () => {
const artifact = new Artifact('some/artifact') const artifact = new Artifact("some/artifact")
expect(artifact.path).toBe('some/artifact') expect(artifact.path).toBe("some/artifact")
}) })
it('reads artifact', () => { it("reads artifact", () => {
const artifact = new Artifact('some/artifact') const artifact = new Artifact("some/artifact")
expect(artifact.readFile()).toBe(fakeReadStream) expect(artifact.readFile()).toBe(fakeReadStream)
}) })
}) })

View File

@@ -1,41 +1,37 @@
import {Artifact} from "../src/Artifact" import { GithubArtifactDestroyer } from "../src/ArtifactDestroyer"
import {GithubArtifactUploader} from "../src/ArtifactUploader" import type { Releases } from "../src/Releases"
import {Releases} from "../src/Releases";
import {RequestError} from '@octokit/request-error'
import {GithubArtifactDestroyer} from "../src/ArtifactDestroyer";
const releaseId = 100 const releaseId = 100
const deleteMock = jest.fn() const deleteMock = jest.fn()
const listArtifactsMock = jest.fn() const listArtifactsMock = jest.fn()
describe("ArtifactDestroyer", () => {
describe('ArtifactDestroyer', () => {
beforeEach(() => { beforeEach(() => {
deleteMock.mockClear() deleteMock.mockClear()
listArtifactsMock.mockClear() listArtifactsMock.mockClear()
}) })
it('destroys all artifacts', async () => { it("destroys all artifacts", async () => {
mockListWithAssets() mockListWithAssets()
mockDeleteSuccess() mockDeleteSuccess()
const destroyer = createDestroyer() const destroyer = createDestroyer()
await destroyer.destroyArtifacts(releaseId) await destroyer.destroyArtifacts(releaseId)
expect(deleteMock).toBeCalledTimes(2) expect(deleteMock).toHaveBeenCalledTimes(2)
}) })
it('destroys nothing when no artifacts found', async () => { it("destroys nothing when no artifacts found", async () => {
mockListWithoutAssets() mockListWithoutAssets()
const destroyer = createDestroyer() const destroyer = createDestroyer()
await destroyer.destroyArtifacts(releaseId) await destroyer.destroyArtifacts(releaseId)
expect(deleteMock).toBeCalledTimes(0) expect(deleteMock).toHaveBeenCalledTimes(0)
}) })
it('throws when delete call fails', async () => { it("throws when delete call fails", async () => {
mockListWithAssets() mockListWithAssets()
mockDeleteError() mockDeleteError()
const destroyer = createDestroyer() const destroyer = createDestroyer()
@@ -57,7 +53,8 @@ describe('ArtifactDestroyer', () => {
listArtifactsForRelease: listArtifactsMock, listArtifactsForRelease: listArtifactsMock,
listReleases: jest.fn(), listReleases: jest.fn(),
update: jest.fn(), update: jest.fn(),
uploadArtifact: jest.fn() uploadArtifact: jest.fn(),
generateReleaseNotes: jest.fn(),
} }
}) })
return new GithubArtifactDestroyer(new MockReleases()) return new GithubArtifactDestroyer(new MockReleases())
@@ -75,16 +72,16 @@ describe('ArtifactDestroyer', () => {
listArtifactsMock.mockResolvedValue([ listArtifactsMock.mockResolvedValue([
{ {
name: "art1", name: "art1",
id: 1 id: 1,
}, },
{ {
name: "art2", name: "art2",
id: 2 id: 2,
} },
]) ])
} }
function mockListWithoutAssets() { function mockListWithoutAssets() {
listArtifactsMock.mockResolvedValue([]) listArtifactsMock.mockResolvedValue([])
} }
}); })

View File

@@ -1,31 +1,31 @@
const warnMock = jest.fn() const warnMock = jest.fn()
import {FileArtifactGlobber} from "../src/ArtifactGlobber" import { FileArtifactGlobber } from "../src/ArtifactGlobber"
import {Globber} from "../src/Globber"; import { Globber } from "../src/Globber"
import {Artifact} from "../src/Artifact"; import { Artifact } from "../src/Artifact"
import untildify = require("untildify"); import untildify = require("untildify")
const contentType = "raw" const contentType = "raw"
const globMock = jest.fn() const globMock = jest.fn()
const globResults = ["file1", "file2"] const globResults = ["file1", "file2"]
jest.mock('@actions/core', () => { jest.mock("@actions/core", () => {
return {warning: warnMock}; return { warning: warnMock }
}) })
jest.mock('fs', () => { jest.mock("fs", () => {
return { return {
statSync: () => { statSync: () => {
return { return {
isDirectory(): boolean { isDirectory(): boolean {
return false return false
} },
} }
}, },
realpathSync: () => { realpathSync: () => {
return false return false
},
} }
};
}) })
describe("ArtifactGlobber", () => { describe("ArtifactGlobber", () => {
@@ -36,76 +36,63 @@ describe("ArtifactGlobber", () => {
it("expands paths in which start with a ~", () => { it("expands paths in which start with a ~", () => {
const globber = createArtifactGlobber() const globber = createArtifactGlobber()
const expectedArtifacts = const expectedArtifacts = globResults.map((path) => new Artifact(path, contentType))
globResults.map((path) => new Artifact(path, contentType))
expect(globber.globArtifactString('~/path', 'raw', false)) expect(globber.globArtifactString("~/path", "raw", false)).toEqual(expectedArtifacts)
.toEqual(expectedArtifacts) expect(globMock).toHaveBeenCalledWith(untildify("~/path"))
expect(globMock).toBeCalledWith(untildify('~/path')) expect(warnMock).not.toHaveBeenCalled()
expect(warnMock).not.toBeCalled()
}) })
it("globs simple path", () => { it("globs simple path", () => {
const globber = createArtifactGlobber() const globber = createArtifactGlobber()
const expectedArtifacts = const expectedArtifacts = globResults.map((path) => new Artifact(path, contentType))
globResults.map((path) => new Artifact(path, contentType))
expect(globber.globArtifactString('path', 'raw', false)) expect(globber.globArtifactString("path", "raw", false)).toEqual(expectedArtifacts)
.toEqual(expectedArtifacts) expect(globMock).toHaveBeenCalledWith("path")
expect(globMock).toBeCalledWith('path') expect(warnMock).not.toHaveBeenCalled()
expect(warnMock).not.toBeCalled()
}) })
it("splits multiple paths with comma separator", () => { it("splits multiple paths with comma separator", () => {
const globber = createArtifactGlobber() const globber = createArtifactGlobber()
const expectedArtifacts = const expectedArtifacts = globResults.concat(globResults).map((path) => new Artifact(path, contentType))
globResults
.concat(globResults)
.map((path) => new Artifact(path, contentType))
expect(globber.globArtifactString('path1,path2', 'raw', false)) expect(globber.globArtifactString("path1,path2", "raw", false)).toEqual(expectedArtifacts)
.toEqual(expectedArtifacts) expect(globMock).toHaveBeenCalledWith("path1")
expect(globMock).toBeCalledWith('path1') expect(globMock).toHaveBeenCalledWith("path2")
expect(globMock).toBeCalledWith('path2') expect(warnMock).not.toHaveBeenCalled()
expect(warnMock).not.toBeCalled()
}) })
it("splits multiple paths with new line separator and trims start", () => { it("splits multiple paths with new line separator and trims start", () => {
const globber = createArtifactGlobber() const globber = createArtifactGlobber()
const expectedArtifacts = const expectedArtifacts = globResults.concat(globResults).map((path) => new Artifact(path, contentType))
globResults
.concat(globResults)
.map((path) => new Artifact(path, contentType))
expect(globber.globArtifactString('path1\n path2', 'raw', false)) expect(globber.globArtifactString("path1\n path2", "raw", false)).toEqual(expectedArtifacts)
.toEqual(expectedArtifacts) expect(globMock).toHaveBeenCalledWith("path1")
expect(globMock).toBeCalledWith('path1') expect(globMock).toHaveBeenCalledWith("path2")
expect(globMock).toBeCalledWith('path2') expect(warnMock).not.toHaveBeenCalled()
expect(warnMock).not.toBeCalled()
}) })
it("warns when no glob results are produced and empty results shouldn't throw", () => { it("warns when no glob results are produced and empty results shouldn't throw", () => {
const globber = createArtifactGlobber([]) const globber = createArtifactGlobber([])
expect(globber.globArtifactString('path', 'raw', false)) expect(globber.globArtifactString("path", "raw", false)).toEqual([])
.toEqual([]) expect(warnMock).toHaveBeenCalled()
expect(warnMock).toBeCalled()
}) })
it("throws when no glob results are produced and empty results shouild throw", () => { it("throws when no glob results are produced and empty results shouild throw", () => {
const globber = createArtifactGlobber([]) const globber = createArtifactGlobber([])
expect(() => { expect(() => {
globber.globArtifactString('path', 'raw', true) globber.globArtifactString("path", "raw", true)
}).toThrow() }).toThrow()
}) })
function createArtifactGlobber(results: string[] = globResults): FileArtifactGlobber { function createArtifactGlobber(results: string[] = globResults): FileArtifactGlobber {
const MockGlobber = jest.fn<Globber, any>(() => { const MockGlobber = jest.fn<Globber, any>(() => {
return { return {
glob: globMock glob: globMock,
} }
}) })
globMock.mockReturnValue(results) globMock.mockReturnValue(results)

View File

@@ -1,20 +1,20 @@
const directoryMock = jest.fn() const directoryMock = jest.fn()
const warnMock = jest.fn() const warnMock = jest.fn()
import {ArtifactPathValidator} from "../src/ArtifactPathValidator"; import { ArtifactPathValidator } from "../src/ArtifactPathValidator"
const pattern = 'pattern' const pattern = "pattern"
jest.mock('@actions/core', () => { jest.mock("@actions/core", () => {
return {warning: warnMock}; return { warning: warnMock }
}) })
jest.mock('fs', () => { jest.mock("fs", () => {
return { return {
statSync: () => { statSync: () => {
return {isDirectory: directoryMock} return { isDirectory: directoryMock }
},
} }
};
}) })
describe("ArtifactPathValidator", () => { describe("ArtifactPathValidator", () => {
@@ -24,20 +24,20 @@ describe("ArtifactPathValidator", () => {
}) })
it("warns and filters out path which points to a directory", () => { it("warns and filters out path which points to a directory", () => {
const paths = ['path1', 'path2'] const paths = ["path1", "path2"]
directoryMock.mockReturnValueOnce(true).mockReturnValueOnce(false) directoryMock.mockReturnValueOnce(true).mockReturnValueOnce(false)
const validator = new ArtifactPathValidator(false, paths, pattern) const validator = new ArtifactPathValidator(false, paths, pattern)
const result = validator.validate() const result = validator.validate()
expect(warnMock).toBeCalled() expect(warnMock).toHaveBeenCalled()
expect(result).toEqual(['path2']) expect(result).toEqual(["path2"])
}) })
it("warns when no glob results are produced and empty results shouldn't throw", () => { it("warns when no glob results are produced and empty results shouldn't throw", () => {
const validator = new ArtifactPathValidator(false, [], pattern) const validator = new ArtifactPathValidator(false, [], pattern)
const result = validator.validate() const result = validator.validate()
expect(warnMock).toBeCalled() expect(warnMock).toHaveBeenCalled()
}) })
it("throws when no glob results are produced and empty results shouild throw", () => { it("throws when no glob results are produced and empty results shouild throw", () => {
@@ -48,7 +48,7 @@ describe("ArtifactPathValidator", () => {
}) })
it("throws when path points to directory", () => { it("throws when path points to directory", () => {
const paths = ['path1', 'path2'] const paths = ["path1", "path2"]
directoryMock.mockReturnValueOnce(true).mockReturnValueOnce(false) directoryMock.mockReturnValueOnce(true).mockReturnValueOnce(false)
const validator = new ArtifactPathValidator(true, paths, pattern) const validator = new ArtifactPathValidator(true, paths, pattern)

View File

@@ -1,79 +1,69 @@
import {Artifact} from "../src/Artifact" import { RequestError } from "@octokit/request-error"
import {GithubArtifactUploader} from "../src/ArtifactUploader" import { Artifact } from "../src/Artifact"
import {Releases} from "../src/Releases"; import { GithubArtifactUploader } from "../src/ArtifactUploader"
import {RequestError} from '@octokit/request-error' import type { Releases } from "../src/Releases"
const artifacts = [ const artifacts = [new Artifact("a/art1"), new Artifact("b/art2")]
new Artifact('a/art1'),
new Artifact('b/art2')
]
const fakeReadStream = {} const fakeReadStream = {}
const contentLength = 42 const contentLength = 42
const releaseId = 100 const releaseId = 100
const url = 'http://api.example.com' const url = "http://api.example.com"
const deleteMock = jest.fn() const deleteMock = jest.fn()
const listArtifactsMock = jest.fn() const listArtifactsMock = jest.fn()
const uploadMock = jest.fn() const uploadMock = jest.fn()
jest.mock('fs', () => { jest.mock("fs", () => {
const originalFs = jest.requireActual('fs'); const originalFs = jest.requireActual("fs")
return { return {
...originalFs, ...originalFs,
promises: {}, promises: {},
createReadStream: () => fakeReadStream, createReadStream: () => fakeReadStream,
statSync: () => { statSync: () => {
return {size: contentLength}; return { size: contentLength }
},
} }
}; })
});
describe('ArtifactUploader', () => { describe("ArtifactUploader", () => {
beforeEach(() => { beforeEach(() => {
deleteMock.mockClear() deleteMock.mockClear()
listArtifactsMock.mockClear() listArtifactsMock.mockClear()
uploadMock.mockClear() uploadMock.mockClear()
}) })
it('abort when upload failed with non-5xx response', async () => { it("abort when upload failed with non-5xx response", async () => {
mockListWithoutAssets() mockListWithoutAssets()
mockUploadArtifact(401, 2) mockUploadArtifact(401, 2)
const uploader = createUploader(true) const uploader = createUploader(true)
await uploader.uploadArtifacts(artifacts, releaseId, url) await uploader.uploadArtifacts(artifacts, releaseId, url)
expect(uploadMock).toBeCalledTimes(2) expect(uploadMock).toHaveBeenCalledTimes(2)
expect(uploadMock) expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art1', releaseId) expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art2", releaseId)
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art2', releaseId)
expect(deleteMock).toBeCalledTimes(0) expect(deleteMock).toHaveBeenCalledTimes(0)
}) })
it('abort when upload failed with 5xx response after 3 attempts', async () => { it("abort when upload failed with 5xx response after 3 attempts", async () => {
mockListWithoutAssets() mockListWithoutAssets()
mockUploadArtifact(500, 4) mockUploadArtifact(500, 4)
const uploader = createUploader(true) const uploader = createUploader(true)
await uploader.uploadArtifacts(artifacts, releaseId, url) await uploader.uploadArtifacts(artifacts, releaseId, url)
expect(uploadMock).toBeCalledTimes(5) expect(uploadMock).toHaveBeenCalledTimes(5)
expect(uploadMock) expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art1', releaseId) expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
expect(uploadMock) expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art1', releaseId) expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art2", releaseId)
expect(uploadMock) expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art2", releaseId)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art1', releaseId)
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art2', releaseId)
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art2', releaseId)
expect(deleteMock).toBeCalledTimes(0) expect(deleteMock).toHaveBeenCalledTimes(0)
}) })
it('replaces all artifacts', async () => { it("replaces all artifacts", async () => {
mockDeleteSuccess() mockDeleteSuccess()
mockListWithAssets() mockListWithAssets()
mockUploadArtifact() mockUploadArtifact()
@@ -81,18 +71,16 @@ describe('ArtifactUploader', () => {
await uploader.uploadArtifacts(artifacts, releaseId, url) await uploader.uploadArtifacts(artifacts, releaseId, url)
expect(uploadMock).toBeCalledTimes(2) expect(uploadMock).toHaveBeenCalledTimes(2)
expect(uploadMock) expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art1', releaseId) expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art2", releaseId)
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art2', releaseId)
expect(deleteMock).toBeCalledTimes(2) expect(deleteMock).toHaveBeenCalledTimes(2)
expect(deleteMock).toBeCalledWith(1) expect(deleteMock).toHaveBeenCalledWith(1)
expect(deleteMock).toBeCalledWith(2) expect(deleteMock).toHaveBeenCalledWith(2)
}) })
it('replaces no artifacts when previous asset list empty', async () => { it("replaces no artifacts when previous asset list empty", async () => {
mockDeleteSuccess() mockDeleteSuccess()
mockListWithoutAssets() mockListWithoutAssets()
mockUploadArtifact() mockUploadArtifact()
@@ -100,36 +88,30 @@ describe('ArtifactUploader', () => {
await uploader.uploadArtifacts(artifacts, releaseId, url) await uploader.uploadArtifacts(artifacts, releaseId, url)
expect(uploadMock).toBeCalledTimes(2) expect(uploadMock).toHaveBeenCalledTimes(2)
expect(uploadMock) expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art1', releaseId) expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art2", releaseId)
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art2', releaseId)
expect(deleteMock).toBeCalledTimes(0) expect(deleteMock).toHaveBeenCalledTimes(0)
}) })
it('retry when upload failed with 5xx response', async () => { it("retry when upload failed with 5xx response", async () => {
mockListWithoutAssets() mockListWithoutAssets()
mockUploadArtifact(500, 2) mockUploadArtifact(500, 2)
const uploader = createUploader(true) const uploader = createUploader(true)
await uploader.uploadArtifacts(artifacts, releaseId, url) await uploader.uploadArtifacts(artifacts, releaseId, url)
expect(uploadMock).toBeCalledTimes(4) expect(uploadMock).toHaveBeenCalledTimes(4)
expect(uploadMock) expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art1', releaseId) expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
expect(uploadMock) expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art1', releaseId) expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art2", releaseId)
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art1', releaseId)
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art2', releaseId)
expect(deleteMock).toBeCalledTimes(0) expect(deleteMock).toHaveBeenCalledTimes(0)
}) })
it('throws upload error when replacesExistingArtifacts is true', async () => { it("throws upload error when replacesExistingArtifacts is true", async () => {
mockListWithoutAssets() mockListWithoutAssets()
mockUploadError() mockUploadError()
const uploader = createUploader(true, true) const uploader = createUploader(true, true)
@@ -142,7 +124,7 @@ describe('ArtifactUploader', () => {
} }
}) })
it('throws error from replace', async () => { it("throws error from replace", async () => {
mockDeleteError() mockDeleteError()
mockListWithAssets() mockListWithAssets()
mockUploadArtifact() mockUploadArtifact()
@@ -156,7 +138,7 @@ describe('ArtifactUploader', () => {
} }
}) })
it('updates all artifacts, delete none', async () => { it("updates all artifacts, delete none", async () => {
mockDeleteError() mockDeleteError()
mockListWithAssets() mockListWithAssets()
mockUploadArtifact() mockUploadArtifact()
@@ -164,16 +146,14 @@ describe('ArtifactUploader', () => {
await uploader.uploadArtifacts(artifacts, releaseId, url) await uploader.uploadArtifacts(artifacts, releaseId, url)
expect(uploadMock).toBeCalledTimes(2) expect(uploadMock).toHaveBeenCalledTimes(2)
expect(uploadMock) expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art1', releaseId) expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art2", releaseId)
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art2', releaseId)
expect(deleteMock).toBeCalledTimes(0) expect(deleteMock).toHaveBeenCalledTimes(0)
}) })
function createUploader(replaces: boolean, throws: boolean = false): GithubArtifactUploader { function createUploader(replaces: boolean, throws = false): GithubArtifactUploader {
const MockReleases = jest.fn<Releases, any>(() => { const MockReleases = jest.fn<Releases, any>(() => {
return { return {
create: jest.fn(), create: jest.fn(),
@@ -182,7 +162,8 @@ describe('ArtifactUploader', () => {
listArtifactsForRelease: listArtifactsMock, listArtifactsForRelease: listArtifactsMock,
listReleases: jest.fn(), listReleases: jest.fn(),
update: jest.fn(), update: jest.fn(),
uploadArtifact: uploadMock uploadArtifact: uploadMock,
generateReleaseNotes: jest.fn(),
} }
}) })
return new GithubArtifactUploader(new MockReleases(), replaces, throws) return new GithubArtifactUploader(new MockReleases(), replaces, throws)
@@ -200,12 +181,12 @@ describe('ArtifactUploader', () => {
listArtifactsMock.mockResolvedValue([ listArtifactsMock.mockResolvedValue([
{ {
name: "art1", name: "art1",
id: 1 id: 1,
}, },
{ {
name: "art2", name: "art2",
id: 2 id: 2,
} },
]) ])
} }
@@ -213,10 +194,10 @@ describe('ArtifactUploader', () => {
listArtifactsMock.mockResolvedValue([]) listArtifactsMock.mockResolvedValue([])
} }
function mockUploadArtifact(status: number = 200, failures: number = 0) { function mockUploadArtifact(status = 200, failures = 0) {
const error = new RequestError(`HTTP ${status}`, status, { const error = new RequestError(`HTTP ${status}`, status, {
headers: {}, headers: {},
request: {method: 'GET', url: '', headers: {}} request: { method: "GET", url: "", headers: {} },
}) })
for (let index = 0; index < failures; index++) { for (let index = 0; index < failures; index++) {
uploadMock.mockRejectedValueOnce(error) uploadMock.mockRejectedValueOnce(error)
@@ -227,7 +208,7 @@ describe('ArtifactUploader', () => {
function mockUploadError() { function mockUploadError() {
uploadMock.mockRejectedValue({ uploadMock.mockRejectedValue({
message: "error", message: "error",
status: 502 status: 502,
}) })
} }
}); })

View File

@@ -1,93 +1,95 @@
import { GithubError } from "../src/GithubError" import { GithubError } from "../src/GithubError"
describe('ErrorMessage', () => { describe("ErrorMessage", () => {
describe("has error with code", () => {
describe('has error with code', () => {
const error = { const error = {
message: 'something bad happened', message: "something bad happened",
errors: [ errors: [
{ {
code: 'missing', code: "missing",
resource: 'release' resource: "release",
}, },
{ {
code: 'already_exists', code: "already_exists",
resource: 'release' resource: "release",
} },
], ],
status: 422 status: 422,
} }
it('does not have error', () => { it("does not have error", () => {
const githubError = new GithubError(error) const githubError = new GithubError(error)
expect(githubError.hasErrorWithCode('missing_field')).toBeFalsy() expect(githubError.hasErrorWithCode("missing_field")).toBeFalsy()
}) })
it('has error', () => { it("has error", () => {
const githubError = new GithubError(error) const githubError = new GithubError(error)
expect(githubError.hasErrorWithCode('missing')).toBeTruthy() expect(githubError.hasErrorWithCode("missing")).toBeTruthy()
}) })
}) })
describe('has error with remediation', () => { describe("has error with remediation", () => {
it('provides remediation with 404 without errors', () => { it("provides remediation with 404 without errors", () => {
const error = { status: 404, message: "message" } const error = { status: 404, message: "message" }
const githubError = new GithubError(error) const githubError = new GithubError(error)
expect(githubError.toString()) expect(githubError.toString()).toBe(
.toBe('Error 404: message\nMake sure your github token has access to the repo and has permission to author releases') "Error 404: message\nMake sure your github token has access to the repo and has permission to author releases"
)
}) })
it('provides remediation with 404 with errors', () => { it("provides remediation with 404 with errors", () => {
const error = { const error = {
message: 'message', message: "message",
errors: [ errors: [
{ {
code: 'missing', code: "missing",
resource: 'release' resource: "release",
} },
], ],
status: 404 status: 404,
} }
const githubError = new GithubError(error) const githubError = new GithubError(error)
expect(githubError.toString()) expect(githubError.toString()).toBe(
.toBe('Error 404: message\nErrors:\n- release does not exist.\nMake sure your github token has access to the repo and has permission to author releases') "Error 404: message\nErrors:\n- release does not exist.\nMake sure your github token has access to the repo and has permission to author releases"
)
}) })
}) })
it('generates message with errors', () => { it("generates message with errors", () => {
const error = { const error = {
message: 'something bad happened', message: "something bad happened",
errors: [ errors: [
{ {
code: 'missing', code: "missing",
resource: 'release' resource: "release",
}, },
{ {
code: 'already_exists', code: "already_exists",
resource: 'release' resource: "release",
} },
], ],
status: 422 status: 422,
} }
const githubError = new GithubError(error) const githubError = new GithubError(error)
const expectedString = "Error 422: something bad happened\nErrors:\n- release does not exist.\n- release already exists." const expectedString =
"Error 422: something bad happened\nErrors:\n- release does not exist.\n- release already exists."
expect(githubError.toString()).toBe(expectedString) expect(githubError.toString()).toBe(expectedString)
}) })
it('generates message without errors', () => { it("generates message without errors", () => {
const error = { const error = {
message: 'something bad happened', message: "something bad happened",
status: 422 status: 422,
} }
const githubError = new GithubError(error) const githubError = new GithubError(error)
expect(githubError.toString()).toBe('Error 422: something bad happened') expect(githubError.toString()).toBe("Error 422: something bad happened")
}) })
it('provides error status', () => { it("provides error status", () => {
const error = { status: 404 } const error = { status: 404 }
const githubError = new GithubError(error) const githubError = new GithubError(error)
expect(githubError.status).toBe(404) expect(githubError.status).toBe(404)

View File

@@ -1,22 +1,21 @@
import { GithubErrorDetail } from "../src/GithubErrorDetail" import { GithubErrorDetail } from "../src/GithubErrorDetail"
describe('GithubErrorDetail', () => { describe("GithubErrorDetail", () => {
it("provides error code", () => {
it('provides error code', () => {
const error = { const error = {
code: "missing" code: "missing",
} }
const detail = new GithubErrorDetail(error) const detail = new GithubErrorDetail(error)
expect(detail.code).toBe('missing') expect(detail.code).toBe("missing")
}) })
it('generates missing resource error message', () => { it("generates missing resource error message", () => {
const resource = "release" const resource = "release"
const error = { const error = {
code: "missing", code: "missing",
resource: resource resource: resource,
} }
const detail = new GithubErrorDetail(error) const detail = new GithubErrorDetail(error)
@@ -25,13 +24,13 @@ describe('GithubErrorDetail', () => {
expect(message).toBe(`${resource} does not exist.`) expect(message).toBe(`${resource} does not exist.`)
}) })
it('generates missing field error message', () => { it("generates missing field error message", () => {
const resource = "release" const resource = "release"
const field = "body" const field = "body"
const error = { const error = {
code: "missing_field", code: "missing_field",
field: field, field: field,
resource: resource resource: resource,
} }
const detail = new GithubErrorDetail(error) const detail = new GithubErrorDetail(error)
@@ -40,13 +39,13 @@ describe('GithubErrorDetail', () => {
expect(message).toBe(`The ${field} field on ${resource} is missing.`) expect(message).toBe(`The ${field} field on ${resource} is missing.`)
}) })
it('generates invalid field error message', () => { it("generates invalid field error message", () => {
const resource = "release" const resource = "release"
const field = "body" const field = "body"
const error = { const error = {
code: "invalid", code: "invalid",
field: field, field: field,
resource: resource resource: resource,
} }
const detail = new GithubErrorDetail(error) const detail = new GithubErrorDetail(error)
@@ -55,11 +54,11 @@ describe('GithubErrorDetail', () => {
expect(message).toBe(`The ${field} field on ${resource} is an invalid format.`) expect(message).toBe(`The ${field} field on ${resource} is an invalid format.`)
}) })
it('generates resource already exists error message', () => { it("generates resource already exists error message", () => {
const resource = "release" const resource = "release"
const error = { const error = {
code: "already_exists", code: "already_exists",
resource: resource resource: resource,
} }
const detail = new GithubErrorDetail(error) const detail = new GithubErrorDetail(error)
@@ -68,13 +67,13 @@ describe('GithubErrorDetail', () => {
expect(message).toBe(`${resource} already exists.`) expect(message).toBe(`${resource} already exists.`)
}) })
describe('generates custom error message', () => { describe("generates custom error message", () => {
it('with documentation url', () => { it("with documentation url", () => {
const url = "https://api.example.com" const url = "https://api.example.com"
const error = { const error = {
code: "custom", code: "custom",
message: "foo", message: "foo",
documentation_url: url documentation_url: url,
} }
const detail = new GithubErrorDetail(error) const detail = new GithubErrorDetail(error)
@@ -83,16 +82,16 @@ describe('GithubErrorDetail', () => {
expect(message).toBe(`foo\nPlease see ${url}.`) expect(message).toBe(`foo\nPlease see ${url}.`)
}) })
it('without documentation url', () => { it("without documentation url", () => {
const error = { const error = {
code: "custom", code: "custom",
message: "foo" message: "foo",
} }
const detail = new GithubErrorDetail(error) const detail = new GithubErrorDetail(error)
const message = detail.toString() const message = detail.toString()
expect(message).toBe('foo') expect(message).toBe("foo")
}) })
}) })
}) })

View File

@@ -1,27 +1,24 @@
const mockGetInput = jest.fn(); const mockGetInput = jest.fn()
const mockGetBooleanInput = jest.fn(); const mockGetBooleanInput = jest.fn()
const mockGlob = jest.fn() const mockGlob = jest.fn()
const mockReadFileSync = jest.fn(); const mockReadFileSync = jest.fn()
const mockStatSync = jest.fn(); const mockStatSync = jest.fn()
import {Artifact} from "../src/Artifact"; import { Artifact } from "../src/Artifact"
import {ArtifactGlobber} from "../src/ArtifactGlobber"; import { ArtifactGlobber } from "../src/ArtifactGlobber"
import {Context} from "@actions/github/lib/context"; import { Context } from "@actions/github/lib/context"
import {Inputs, CoreInputs} from "../src/Inputs"; import { Inputs, CoreInputs } from "../src/Inputs"
const artifacts = [ const artifacts = [new Artifact("a/art1"), new Artifact("b/art2")]
new Artifact('a/art1'),
new Artifact('b/art2')
]
jest.mock('@actions/core', () => { jest.mock("@actions/core", () => {
return { return {
getInput: mockGetInput, getInput: mockGetInput,
getBooleanInput: mockGetBooleanInput getBooleanInput: mockGetBooleanInput,
}; }
}) })
jest.mock('fs', () => { jest.mock("fs", () => {
// existsSync is used by Context's constructor // existsSync is used by Context's constructor
// noinspection JSUnusedGlobalSymbols // noinspection JSUnusedGlobalSymbols
return { return {
@@ -29,460 +26,416 @@ jest.mock('fs', () => {
return false return false
}, },
readFileSync: mockReadFileSync, readFileSync: mockReadFileSync,
statSync: mockStatSync statSync: mockStatSync,
}; }
}) })
describe('Inputs', () => { describe("Inputs", () => {
let context: Context; let context: Context
let inputs: Inputs; let inputs: Inputs
beforeEach(() => { beforeEach(() => {
mockGetInput.mockReset() mockGetInput.mockReset()
context = new Context() context = new Context()
inputs = new CoreInputs(createGlobber(), context) inputs = new CoreInputs(createGlobber(), context)
}) })
describe('commit', () => { describe("commit", () => {
it('returns commit', () => { it("returns commit", () => {
mockGetInput.mockReturnValueOnce('commit') mockGetInput.mockReturnValueOnce("commit")
expect(inputs.commit).toBe('commit') expect(inputs.commit).toBe("commit")
}) })
it('returns undefined when omitted', () => { it("returns undefined when omitted", () => {
mockGetInput.mockReturnValueOnce('') mockGetInput.mockReturnValueOnce("")
expect(inputs.commit).toBeUndefined() expect(inputs.commit).toBeUndefined()
}) })
}) })
it('returns token', () => { it("returns token", () => {
mockGetInput.mockReturnValue('42') mockGetInput.mockReturnValue("42")
expect(inputs.token).toBe('42') expect(inputs.token).toBe("42")
}) })
describe('allowsUpdates', () => { describe("allowsUpdates", () => {
it('returns false', () => { it("returns false", () => {
expect(inputs.allowUpdates).toBe(false) expect(inputs.allowUpdates).toBe(false)
}) })
it('returns true', () => { it("returns true", () => {
mockGetInput.mockReturnValue('true') mockGetInput.mockReturnValue("true")
expect(inputs.allowUpdates).toBe(true) expect(inputs.allowUpdates).toBe(true)
}) })
}) })
describe('artifactErrorsFailBuild', () => { describe("artifactErrorsFailBuild", () => {
it('returns false', () => { it("returns false", () => {
expect(inputs.artifactErrorsFailBuild).toBe(false) expect(inputs.artifactErrorsFailBuild).toBe(false)
}) })
it('returns true', () => { it("returns true", () => {
mockGetInput.mockReturnValue('true') mockGetInput.mockReturnValue("true")
expect(inputs.artifactErrorsFailBuild).toBe(true) expect(inputs.artifactErrorsFailBuild).toBe(true)
}) })
}) })
describe('artifacts', () => { describe("artifacts", () => {
it('globber told to throw errors', () => { it("globber told to throw errors", () => {
mockGetInput.mockReturnValueOnce('art1') mockGetInput.mockReturnValueOnce("art1").mockReturnValueOnce("contentType").mockReturnValueOnce("true")
.mockReturnValueOnce('contentType')
.mockReturnValueOnce('true')
expect(inputs.artifacts).toEqual(artifacts) expect(inputs.artifacts).toEqual(artifacts)
expect(mockGlob).toBeCalledTimes(1) expect(mockGlob).toHaveBeenCalledTimes(1)
expect(mockGlob).toBeCalledWith('art1', 'contentType', true) expect(mockGlob).toHaveBeenCalledWith("art1", "contentType", true)
}) })
it('returns empty artifacts', () => { it("returns empty artifacts", () => {
mockGetInput.mockReturnValueOnce('') mockGetInput.mockReturnValueOnce("").mockReturnValueOnce("")
.mockReturnValueOnce('')
expect(inputs.artifacts).toEqual([]) expect(inputs.artifacts).toEqual([])
expect(mockGlob).toBeCalledTimes(0) expect(mockGlob).toHaveBeenCalledTimes(0)
}) })
it('returns input.artifacts', () => { it("returns input.artifacts", () => {
mockGetInput.mockReturnValueOnce('art1') mockGetInput.mockReturnValueOnce("art1").mockReturnValueOnce("contentType").mockReturnValueOnce("false")
.mockReturnValueOnce('contentType')
.mockReturnValueOnce('false')
expect(inputs.artifacts).toEqual(artifacts) expect(inputs.artifacts).toEqual(artifacts)
expect(mockGlob).toBeCalledTimes(1) expect(mockGlob).toHaveBeenCalledTimes(1)
expect(mockGlob).toBeCalledWith('art1', 'contentType', false) expect(mockGlob).toHaveBeenCalledWith("art1", "contentType", false)
}) })
it('returns input.artifacts with default contentType', () => { it("returns input.artifacts with default contentType", () => {
mockGetInput.mockReturnValueOnce('art1') mockGetInput.mockReturnValueOnce("art1").mockReturnValueOnce("").mockReturnValueOnce("false")
.mockReturnValueOnce('')
.mockReturnValueOnce('false')
expect(inputs.artifacts).toEqual(artifacts) expect(inputs.artifacts).toEqual(artifacts)
expect(mockGlob).toBeCalledTimes(1) expect(mockGlob).toHaveBeenCalledTimes(1)
expect(mockGlob).toBeCalledWith('art1', 'raw', false) expect(mockGlob).toHaveBeenCalledWith("art1", "raw", false)
}) })
it('returns input.artifact', () => { it("returns input.artifact", () => {
mockGetInput.mockReturnValueOnce('') mockGetInput
.mockReturnValueOnce('art2') .mockReturnValueOnce("")
.mockReturnValueOnce('contentType') .mockReturnValueOnce("art2")
.mockReturnValueOnce('false') .mockReturnValueOnce("contentType")
.mockReturnValueOnce("false")
expect(inputs.artifacts).toEqual(artifacts) expect(inputs.artifacts).toEqual(artifacts)
expect(mockGlob).toBeCalledTimes(1) expect(mockGlob).toHaveBeenCalledTimes(1)
expect(mockGlob).toBeCalledWith('art2', 'contentType', false) expect(mockGlob).toHaveBeenCalledWith("art2", "contentType", false)
}) })
}) })
describe('createdDraft', () => { describe("createdDraft", () => {
it('returns false', () => { it("returns false", () => {
expect(inputs.createdDraft).toBe(false) expect(inputs.createdDraft).toBe(false)
}) })
it('returns true', () => { it("returns true", () => {
mockGetInput.mockReturnValue('true') mockGetInput.mockReturnValue("true")
expect(inputs.createdDraft).toBe(true) expect(inputs.createdDraft).toBe(true)
}) })
}) })
describe('createdReleaseBody', () => { describe("createdReleaseBody", () => {
it('returns input body', () => { it("returns input body", () => {
mockGetInput mockGetInput.mockReturnValueOnce("false").mockReturnValueOnce("body")
.mockReturnValueOnce('false') expect(inputs.createdReleaseBody).toBe("body")
.mockReturnValueOnce('body')
expect(inputs.createdReleaseBody).toBe('body')
}) })
it('returns body file contents', () => { it("returns body file contents", () => {
mockGetInput mockGetInput.mockReturnValueOnce("false").mockReturnValueOnce("").mockReturnValueOnce("a/path")
.mockReturnValueOnce('false') mockReadFileSync.mockReturnValue("file")
.mockReturnValueOnce('')
.mockReturnValueOnce('a/path')
mockReadFileSync.mockReturnValue('file')
expect(inputs.createdReleaseBody).toBe('file') expect(inputs.createdReleaseBody).toBe("file")
}) })
it('returns empty', () => { it("returns empty", () => {
mockGetInput mockGetInput.mockReturnValueOnce("false").mockReturnValueOnce("").mockReturnValueOnce("")
.mockReturnValueOnce('false') expect(inputs.createdReleaseBody).toBe("")
.mockReturnValueOnce('')
.mockReturnValueOnce('')
expect(inputs.createdReleaseBody).toBe('')
}) })
it('returns undefined when omitted', () => { it("returns undefined when omitted", () => {
mockGetInput mockGetInput.mockReturnValueOnce("true").mockReturnValueOnce("body")
.mockReturnValueOnce('true')
.mockReturnValueOnce('body')
expect(inputs.createdReleaseBody).toBeUndefined() expect(inputs.createdReleaseBody).toBeUndefined()
}) })
}) })
describe('createdReleaseName', () => { describe("createdReleaseName", () => {
it('returns input name', () => { it("returns input name", () => {
mockGetInput mockGetInput.mockReturnValueOnce("false").mockReturnValueOnce("name")
.mockReturnValueOnce('false') expect(inputs.createdReleaseName).toBe("name")
.mockReturnValueOnce('name')
expect(inputs.createdReleaseName).toBe('name')
}) })
it('returns undefined when omitted', () => { it("returns undefined when omitted", () => {
mockGetInput mockGetInput.mockReturnValueOnce("true").mockReturnValueOnce("name")
.mockReturnValueOnce('true')
.mockReturnValueOnce('name')
expect(inputs.createdReleaseName).toBeUndefined() expect(inputs.createdReleaseName).toBeUndefined()
}) })
it('returns tag', () => { it("returns tag", () => {
mockGetInput mockGetInput.mockReturnValueOnce("false").mockReturnValueOnce("")
.mockReturnValueOnce('false') context.ref = "refs/tags/sha-tag"
.mockReturnValueOnce('') expect(inputs.createdReleaseName).toBe("sha-tag")
context.ref = 'refs/tags/sha-tag'
expect(inputs.createdReleaseName).toBe('sha-tag')
}) })
}) })
describe('discussionCategory', () => { describe("discussionCategory", () => {
it('returns category', () => { it("returns category", () => {
mockGetInput.mockReturnValue('Release') mockGetInput.mockReturnValue("Release")
expect(inputs.discussionCategory).toBe('Release') expect(inputs.discussionCategory).toBe("Release")
}) })
it('returns undefined', () => { it("returns undefined", () => {
mockGetInput.mockReturnValue('') mockGetInput.mockReturnValue("")
expect(inputs.discussionCategory).toBe(undefined) expect(inputs.discussionCategory).toBe(undefined)
}) })
}) })
describe('generateReleaseNotes', () => { describe("generateReleaseNotes", () => {
it('returns returns true', function () { it("returns returns true", function () {
mockGetInput.mockReturnValue("true") mockGetInput.mockReturnValue("true")
expect(inputs.generateReleaseNotes).toBe(true) expect(inputs.generateReleaseNotes).toBe(true)
}); })
it('returns false when omitted', function () { it("returns false when omitted", function () {
mockGetInput.mockReturnValue("") mockGetInput.mockReturnValue("")
expect(inputs.generateReleaseNotes).toBe(false) expect(inputs.generateReleaseNotes).toBe(false)
}); })
}) })
describe('makeLatest', () => { describe("makeLatest", () => {
it('returns legacy', () => { it("returns legacy", () => {
mockGetInput.mockReturnValueOnce('legacy') mockGetInput.mockReturnValueOnce("legacy")
expect(inputs.makeLatest).toBe('legacy') expect(inputs.makeLatest).toBe("legacy")
}) })
it('returns false', () => { it("returns false", () => {
mockGetInput.mockReturnValueOnce('false') mockGetInput.mockReturnValueOnce("false")
expect(inputs.makeLatest).toBe('false') expect(inputs.makeLatest).toBe("false")
}) })
it('returns true', () => { it("returns true", () => {
mockGetInput.mockReturnValueOnce('true') mockGetInput.mockReturnValueOnce("true")
expect(inputs.makeLatest).toBe('true') expect(inputs.makeLatest).toBe("true")
}) })
it('returns undefined', () => { it("returns undefined", () => {
mockGetInput.mockReturnValueOnce('something_else') mockGetInput.mockReturnValueOnce("something_else")
expect(inputs.makeLatest).toBe(undefined) expect(inputs.makeLatest).toBe(undefined)
}) })
}) })
describe('owner', () => { describe("owner", () => {
it('returns owner from context', function () { it("returns owner from context", function () {
process.env.GITHUB_REPOSITORY = "owner/repo" process.env.GITHUB_REPOSITORY = "owner/repo"
mockGetInput.mockReturnValue("") mockGetInput.mockReturnValue("")
expect(inputs.owner).toBe("owner") expect(inputs.owner).toBe("owner")
}); })
it('returns owner from inputs', function () { it("returns owner from inputs", function () {
mockGetInput.mockReturnValue("owner") mockGetInput.mockReturnValue("owner")
expect(inputs.owner).toBe("owner") expect(inputs.owner).toBe("owner")
}); })
}) })
describe('createdPrerelase', () => { describe("createdPrerelase", () => {
it('returns false', () => { it("returns false", () => {
expect(inputs.createdPrerelease).toBe(false) expect(inputs.createdPrerelease).toBe(false)
}) })
it('returns true', () => { it("returns true", () => {
mockGetInput.mockReturnValue('true') mockGetInput.mockReturnValue("true")
expect(inputs.createdPrerelease).toBe(true) expect(inputs.createdPrerelease).toBe(true)
}) })
}) })
describe('replacesArtifacts', () => { describe("replacesArtifacts", () => {
it('returns false', () => { it("returns false", () => {
expect(inputs.replacesArtifacts).toBe(false) expect(inputs.replacesArtifacts).toBe(false)
}) })
it('returns true', () => { it("returns true", () => {
mockGetInput.mockReturnValue('true') mockGetInput.mockReturnValue("true")
expect(inputs.replacesArtifacts).toBe(true) expect(inputs.replacesArtifacts).toBe(true)
}) })
}) })
describe('removeArtifacts', () => { describe("removeArtifacts", () => {
it('returns false', () => { it("returns false", () => {
expect(inputs.removeArtifacts).toBe(false) expect(inputs.removeArtifacts).toBe(false)
}) })
it('returns true', () => { it("returns true", () => {
mockGetInput.mockReturnValue('true') mockGetInput.mockReturnValue("true")
expect(inputs.removeArtifacts).toBe(true) expect(inputs.removeArtifacts).toBe(true)
}) })
}) })
describe('repo', () => { describe("repo", () => {
it('returns repo from context', function () { it("returns repo from context", function () {
process.env.GITHUB_REPOSITORY = "owner/repo" process.env.GITHUB_REPOSITORY = "owner/repo"
mockGetInput.mockReturnValue("") mockGetInput.mockReturnValue("")
expect(inputs.repo).toBe("repo") expect(inputs.repo).toBe("repo")
}); })
it('returns repo from inputs', function () { it("returns repo from inputs", function () {
mockGetInput.mockReturnValue("repo") mockGetInput.mockReturnValue("repo")
expect(inputs.repo).toBe("repo") expect(inputs.repo).toBe("repo")
}); })
}) })
describe('skipIfReleaseExists', () => { describe("skipIfReleaseExists", () => {
it('returns false', () => { it("returns false", () => {
mockGetBooleanInput.mockReturnValue(false) mockGetBooleanInput.mockReturnValue(false)
expect(inputs.skipIfReleaseExists).toBe(false) expect(inputs.skipIfReleaseExists).toBe(false)
}) })
it('returns true', () => { it("returns true", () => {
mockGetBooleanInput.mockReturnValue(true) mockGetBooleanInput.mockReturnValue(true)
expect(inputs.skipIfReleaseExists).toBe(true) expect(inputs.skipIfReleaseExists).toBe(true)
}) })
}) })
describe('tag', () => { describe("tag", () => {
it('returns input tag', () => { it("returns input tag", () => {
mockGetInput.mockReturnValue('tag') mockGetInput.mockReturnValue("tag")
expect(inputs.tag).toBe('tag') expect(inputs.tag).toBe("tag")
}) })
it('returns context sha when input is empty', () => { it("returns context sha when input is empty", () => {
mockGetInput.mockReturnValue('') mockGetInput.mockReturnValue("")
context.ref = 'refs/tags/sha-tag' context.ref = "refs/tags/sha-tag"
expect(inputs.tag).toBe('sha-tag') expect(inputs.tag).toBe("sha-tag")
}) })
it('returns context sha when input is null', () => { it("returns context sha when input is null", () => {
mockGetInput.mockReturnValue(null) mockGetInput.mockReturnValue(null)
context.ref = 'refs/tags/sha-tag' context.ref = "refs/tags/sha-tag"
expect(inputs.tag).toBe('sha-tag') expect(inputs.tag).toBe("sha-tag")
}) })
it('throws if no tag', () => { it("throws if no tag", () => {
context.ref = "" context.ref = ""
expect(() => inputs.tag).toThrow() expect(() => inputs.tag).toThrow()
}) })
}) })
describe('updatedDraft', () => { describe("updatedDraft", () => {
it('returns false', () => { it("returns false", () => {
expect(inputs.updatedDraft).toBe(false) expect(inputs.updatedDraft).toBe(false)
}) })
it('returns true', () => { it("returns true", () => {
mockGetInput mockGetInput.mockReturnValueOnce("false").mockReturnValue("true")
.mockReturnValueOnce('false')
.mockReturnValue('true')
expect(inputs.updatedDraft).toBe(true) expect(inputs.updatedDraft).toBe(true)
}) })
it('returns true when omitted is blank', () => { it("returns true when omitted is blank", () => {
mockGetInput mockGetInput.mockReturnValueOnce("").mockReturnValue("true")
.mockReturnValueOnce('')
.mockReturnValue('true')
expect(inputs.updatedDraft).toBe(true) expect(inputs.updatedDraft).toBe(true)
}) })
it('returns undefined when omitted for update', () => { it("returns undefined when omitted for update", () => {
mockGetInput mockGetInput.mockReturnValueOnce("true").mockReturnValueOnce("true")
.mockReturnValueOnce('true')
.mockReturnValueOnce('true')
expect(inputs.updatedDraft).toBeUndefined() expect(inputs.updatedDraft).toBeUndefined()
}) })
}) })
describe('updatedReleaseBody', () => { describe("updatedReleaseBody", () => {
it('returns input body', () => { it("returns input body", () => {
mockGetInput mockGetInput.mockReturnValueOnce("false").mockReturnValueOnce("false").mockReturnValueOnce("body")
.mockReturnValueOnce('false') expect(inputs.updatedReleaseBody).toBe("body")
.mockReturnValueOnce('false')
.mockReturnValueOnce('body')
expect(inputs.updatedReleaseBody).toBe('body')
}) })
it('returns body file contents', () => { it("returns body file contents", () => {
mockGetInput mockGetInput
.mockReturnValueOnce('false') .mockReturnValueOnce("false")
.mockReturnValueOnce('false') .mockReturnValueOnce("false")
.mockReturnValueOnce('') .mockReturnValueOnce("")
.mockReturnValueOnce('a/path') .mockReturnValueOnce("a/path")
mockReadFileSync.mockReturnValue('file') mockReadFileSync.mockReturnValue("file")
expect(inputs.updatedReleaseBody).toBe('file') expect(inputs.updatedReleaseBody).toBe("file")
}) })
it('returns empty', () => { it("returns empty", () => {
mockGetInput mockGetInput
.mockReturnValueOnce('false') .mockReturnValueOnce("false")
.mockReturnValueOnce('false') .mockReturnValueOnce("false")
.mockReturnValueOnce('') .mockReturnValueOnce("")
.mockReturnValueOnce('') .mockReturnValueOnce("")
expect(inputs.updatedReleaseBody).toBe('') expect(inputs.updatedReleaseBody).toBe("")
}) })
it('returns undefined when omitted', () => { it("returns undefined when omitted", () => {
mockGetInput mockGetInput.mockReturnValueOnce("true").mockReturnValueOnce("false").mockReturnValueOnce("body")
.mockReturnValueOnce('true')
.mockReturnValueOnce('false')
.mockReturnValueOnce('body')
expect(inputs.updatedReleaseBody).toBeUndefined() expect(inputs.updatedReleaseBody).toBeUndefined()
}) })
it('returns undefined when omitted for update', () => { it("returns undefined when omitted for update", () => {
mockGetInput mockGetInput.mockReturnValueOnce("false").mockReturnValueOnce("true").mockReturnValueOnce("body")
.mockReturnValueOnce('false')
.mockReturnValueOnce('true')
.mockReturnValueOnce('body')
expect(inputs.updatedReleaseBody).toBeUndefined() expect(inputs.updatedReleaseBody).toBeUndefined()
}) })
}) })
describe('updatedReleaseName', () => { describe("updatedReleaseName", () => {
it('returns input name', () => { it("returns input name", () => {
mockGetInput mockGetInput.mockReturnValueOnce("false").mockReturnValueOnce("false").mockReturnValueOnce("name")
.mockReturnValueOnce('false') expect(inputs.updatedReleaseName).toBe("name")
.mockReturnValueOnce('false')
.mockReturnValueOnce('name')
expect(inputs.updatedReleaseName).toBe('name')
}) })
it('returns undefined when omitted', () => { it("returns undefined when omitted", () => {
mockGetInput mockGetInput.mockReturnValueOnce("true").mockReturnValueOnce("false").mockReturnValueOnce("name")
.mockReturnValueOnce('true')
.mockReturnValueOnce('false')
.mockReturnValueOnce('name')
expect(inputs.updatedReleaseName).toBeUndefined() expect(inputs.updatedReleaseName).toBeUndefined()
}) })
it('returns undefined when omitted for update', () => { it("returns undefined when omitted for update", () => {
mockGetInput mockGetInput.mockReturnValueOnce("false").mockReturnValueOnce("true").mockReturnValueOnce("name")
.mockReturnValueOnce('false')
.mockReturnValueOnce('true')
.mockReturnValueOnce('name')
expect(inputs.updatedReleaseName).toBeUndefined() expect(inputs.updatedReleaseName).toBeUndefined()
}) })
it('returns tag', () => { it("returns tag", () => {
mockGetInput mockGetInput.mockReturnValueOnce("false").mockReturnValueOnce("false").mockReturnValueOnce("")
.mockReturnValueOnce('false') context.ref = "refs/tags/sha-tag"
.mockReturnValueOnce('false') expect(inputs.updatedReleaseName).toBe("sha-tag")
.mockReturnValueOnce('')
context.ref = 'refs/tags/sha-tag'
expect(inputs.updatedReleaseName).toBe('sha-tag')
}) })
}) })
describe('updatedPrerelease', () => { describe("updatedPrerelease", () => {
it('returns false', () => { it("returns false", () => {
mockGetInput mockGetInput.mockReturnValueOnce("false").mockReturnValueOnce("false")
.mockReturnValueOnce('false')
.mockReturnValueOnce('false')
expect(inputs.updatedPrerelease).toBe(false) expect(inputs.updatedPrerelease).toBe(false)
}) })
it('returns true', () => { it("returns true", () => {
mockGetInput mockGetInput.mockReturnValueOnce("false").mockReturnValueOnce("true")
.mockReturnValueOnce('false')
.mockReturnValueOnce('true')
expect(inputs.updatedPrerelease).toBe(true) expect(inputs.updatedPrerelease).toBe(true)
}) })
it('returns undefined when omitted for update', () => { it("returns undefined when omitted for update", () => {
mockGetInput mockGetInput.mockReturnValueOnce("true").mockReturnValueOnce("false")
.mockReturnValueOnce('true')
.mockReturnValueOnce('false')
expect(inputs.updatedPrerelease).toBeUndefined() expect(inputs.updatedPrerelease).toBeUndefined()
}) })
}) })
describe('updateOnlyUnreleased', () => { describe("updateOnlyUnreleased", () => {
it('returns false', () => { it("returns false", () => {
expect(inputs.updateOnlyUnreleased).toBe(false) expect(inputs.updateOnlyUnreleased).toBe(false)
}) })
it('returns true', () => { it("returns true", () => {
mockGetInput.mockReturnValueOnce('true') mockGetInput.mockReturnValueOnce("true")
expect(inputs.updateOnlyUnreleased).toBe(true) expect(inputs.updateOnlyUnreleased).toBe(true)
}) })
}) })
describe("omitBodyDuringUpdate", () => {
it("returns false", () => {
expect(inputs.omitBodyDuringUpdate).toBe(false)
})
it("returns true", () => {
mockGetInput.mockReturnValueOnce("true")
expect(inputs.omitBodyDuringUpdate).toBe(true)
})
})
function createGlobber(): ArtifactGlobber { function createGlobber(): ArtifactGlobber {
const MockGlobber = jest.fn<ArtifactGlobber, any>(() => { const MockGlobber = jest.fn<ArtifactGlobber, any>(() => {
return { return {
globArtifactString: mockGlob globArtifactString: mockGlob,
} }
}) })
mockGlob.mockImplementation(() => artifacts) mockGlob.mockImplementation(() => artifacts)

View File

@@ -1,18 +1,18 @@
import {Action} from "../src/Action"; import { Action } from "../src/Action"
import * as github from "@actions/github"; import * as github from "@actions/github"
import {Inputs} from "../src/Inputs"; import { Inputs } from "../src/Inputs"
import {GithubReleases, ReleaseData} from "../src/Releases"; import { GithubReleases, ReleaseData } from "../src/Releases"
import {GithubArtifactUploader} from "../src/ArtifactUploader"; import { GithubArtifactUploader } from "../src/ArtifactUploader"
import * as path from "path"; import * as path from "path"
import {FileArtifactGlobber} from "../src/ArtifactGlobber"; import { FileArtifactGlobber } from "../src/ArtifactGlobber"
import {Outputs} from "../src/Outputs"; import { Outputs } from "../src/Outputs"
import {GithubArtifactDestroyer} from "../src/ArtifactDestroyer"; import { GithubArtifactDestroyer } from "../src/ArtifactDestroyer"
import {ReleaseActionSkipper} from "../src/ActionSkipper"; import { ReleaseActionSkipper } from "../src/ActionSkipper"
// This test is currently intended to be manually run during development. To run: // This test is currently intended to be manually run during development. To run:
// - Make sure you have an environment variable named GITHUB_TOKEN assigned to your token // - Make sure you have an environment variable named GITHUB_TOKEN assigned to your token
// - Remove skip from the test below // - Remove skip from the test below
describe.skip('Integration Test', () => { describe.skip("Integration Test", () => {
let action: Action let action: Action
beforeEach(() => { beforeEach(() => {
@@ -22,18 +22,14 @@ describe.skip('Integration Test', () => {
const inputs = getInputs() const inputs = getInputs()
const outputs = getOutputs() const outputs = getOutputs()
const releases = new GithubReleases(inputs, git) const releases = new GithubReleases(inputs, git)
const uploader = new GithubArtifactUploader( const uploader = new GithubArtifactUploader(releases, inputs.replacesArtifacts, inputs.artifactErrorsFailBuild)
releases,
inputs.replacesArtifacts,
inputs.artifactErrorsFailBuild,
)
const artifactDestroyer = new GithubArtifactDestroyer(releases) const artifactDestroyer = new GithubArtifactDestroyer(releases)
const actionSkipper = new ReleaseActionSkipper(inputs.skipIfReleaseExists, releases, inputs.tag) const actionSkipper = new ReleaseActionSkipper(inputs.skipIfReleaseExists, releases, inputs.tag)
action = new Action(inputs, outputs, releases, uploader, artifactDestroyer, actionSkipper) action = new Action(inputs, outputs, releases, uploader, artifactDestroyer, actionSkipper)
}) })
it('Performs action', async () => { it("Performs action", async () => {
await action.perform() await action.perform()
}) })
@@ -47,8 +43,9 @@ describe.skip('Integration Test', () => {
createdReleaseBody: "This release was generated by release-action's integration test", createdReleaseBody: "This release was generated by release-action's integration test",
createdReleaseName: "Releases Action Integration Test", createdReleaseName: "Releases Action Integration Test",
commit: undefined, commit: undefined,
discussionCategory: 'Release', discussionCategory: "Release",
generateReleaseNotes: true, generateReleaseNotes: true,
omitBodyDuringUpdate: false,
owner: "ncipollo", owner: "ncipollo",
createdPrerelease: false, createdPrerelease: false,
replacesArtifacts: true, replacesArtifacts: true,
@@ -61,10 +58,10 @@ describe.skip('Integration Test', () => {
updatedReleaseBody: "This release was updated by release-action's integration test", updatedReleaseBody: "This release was updated by release-action's integration test",
updatedReleaseName: "Releases Action Integration Test", updatedReleaseName: "Releases Action Integration Test",
updatedPrerelease: false, updatedPrerelease: false,
updateOnlyUnreleased: false updateOnlyUnreleased: false,
} }
}) })
return new MockInputs(); return new MockInputs()
} }
function getOutputs(): Outputs { function getOutputs(): Outputs {
@@ -72,7 +69,7 @@ describe.skip('Integration Test', () => {
return { return {
applyReleaseData(releaseData: ReleaseData) { applyReleaseData(releaseData: ReleaseData) {
console.log(`Release Data: ${releaseData}`) console.log(`Release Data: ${releaseData}`)
} },
} }
}) })
return new MockOutputs() return new MockOutputs()
@@ -80,7 +77,7 @@ describe.skip('Integration Test', () => {
function artifacts() { function artifacts() {
const globber = new FileArtifactGlobber() const globber = new FileArtifactGlobber()
const artifactPath = path.join(__dirname, 'Integration.test.ts') const artifactPath = path.join(__dirname, "Integration.test.ts")
const artifactString = `~/Desktop,~/Desktop/test.txt,blarg.tx, ${artifactPath}` const artifactString = `~/Desktop,~/Desktop/test.txt,blarg.tx, ${artifactPath}`
return globber.globArtifactString(artifactString, "raw", false) return globber.globArtifactString(artifactString, "raw", false)
} }
@@ -88,5 +85,4 @@ describe.skip('Integration Test', () => {
function getToken(): string { function getToken(): string {
return process.env.GITHUB_TOKEN ?? "" return process.env.GITHUB_TOKEN ?? ""
} }
}) })

View File

@@ -1,29 +1,51 @@
const mockSetOutput = jest.fn(); const mockSetOutput = jest.fn()
import {CoreOutputs, Outputs} from "../src/Outputs"; import { CoreOutputs, Outputs } from "../src/Outputs"
import {ReleaseData} from "../src/Releases"; import { ReleaseData } from "../src/Releases"
jest.mock('@actions/core', () => { jest.mock("@actions/core", () => {
return {setOutput: mockSetOutput}; return { setOutput: mockSetOutput }
}) })
describe('Outputs', () => { const TEST_URLS = {
let outputs: Outputs; HTML_URL: "https://api.example.com/assets",
UPLOAD_URL: "https://api.example.com",
TARBALL_URL: "https://api.example.com/tarball",
ZIPBALL_URL: "https://api.example.com/zipball",
} as const
describe("Outputs", () => {
let outputs: Outputs
let releaseData: ReleaseData let releaseData: ReleaseData
beforeEach(() => { beforeEach(() => {
outputs = new CoreOutputs() outputs = new CoreOutputs()
releaseData = { releaseData = {
id: 1, id: 1,
html_url: 'https://api.example.com/assets', html_url: TEST_URLS.HTML_URL,
upload_url: 'https://api.example.com' upload_url: TEST_URLS.UPLOAD_URL,
tarball_url: TEST_URLS.TARBALL_URL,
zipball_url: TEST_URLS.ZIPBALL_URL,
} }
}) })
it('Applies the release data to the action output', () => { it("Applies the release data to the action output", () => {
outputs.applyReleaseData(releaseData) outputs.applyReleaseData(releaseData)
expect(mockSetOutput).toBeCalledWith('id', releaseData.id) expect(mockSetOutput).toHaveBeenCalledWith("id", releaseData.id)
expect(mockSetOutput).toBeCalledWith('html_url', releaseData.html_url) expect(mockSetOutput).toHaveBeenCalledWith("html_url", releaseData.html_url)
expect(mockSetOutput).toBeCalledWith('upload_url', releaseData.upload_url) expect(mockSetOutput).toHaveBeenCalledWith("upload_url", releaseData.upload_url)
expect(mockSetOutput).toHaveBeenCalledWith("tarball_url", releaseData.tarball_url)
expect(mockSetOutput).toHaveBeenCalledWith("zipball_url", releaseData.zipball_url)
})
it("Handles null tarball_url and zipball_url", () => {
const releaseDataWithNulls = {
...releaseData,
tarball_url: null,
zipball_url: null,
}
outputs.applyReleaseData(releaseDataWithNulls)
expect(mockSetOutput).toHaveBeenCalledWith("tarball_url", "")
expect(mockSetOutput).toHaveBeenCalledWith("zipball_url", "")
}) })
}) })

View File

@@ -1,13 +1,13 @@
import {ReleaseValidator} from "../src/ReleaseValidator"; import { ReleaseValidator } from "../src/ReleaseValidator"
describe("validateReleaseUpdate", () => { describe("validateReleaseUpdate", () => {
describe("updateOnlyUnreleased is disabled", () => { describe("updateOnlyUnreleased is disabled", () => {
const validator = new ReleaseValidator(false) const validator = new ReleaseValidator(false)
it('should not throw', () => { it("should not throw", () => {
const releaseResponse = { const releaseResponse = {
draft: false, draft: false,
prerelease: false, prerelease: false,
name: "Name" name: "Name",
} }
expect(() => { expect(() => {
validator.validateReleaseUpdate(releaseResponse) validator.validateReleaseUpdate(releaseResponse)
@@ -16,55 +16,55 @@ describe("validateReleaseUpdate", () => {
}) })
describe("updateOnlyUnreleased is enabled", () => { describe("updateOnlyUnreleased is enabled", () => {
const validator = new ReleaseValidator(true) const validator = new ReleaseValidator(true)
it('should throw if neither draft or prerelease are enabled', () => { it("should throw if neither draft or prerelease are enabled", () => {
const releaseResponse = { const releaseResponse = {
draft: false, draft: false,
prerelease: false, prerelease: false,
name: "Name" name: "Name",
} }
expect(() => { expect(() => {
validator.validateReleaseUpdate(releaseResponse) validator.validateReleaseUpdate(releaseResponse)
}).toThrow() }).toThrow()
}) })
it('should not throw if draft is enabled', () => { it("should not throw if draft is enabled", () => {
const releaseResponse = { const releaseResponse = {
draft: true, draft: true,
prerelease: false, prerelease: false,
name: "Name" name: "Name",
} }
expect(() => { expect(() => {
validator.validateReleaseUpdate(releaseResponse) validator.validateReleaseUpdate(releaseResponse)
}).not.toThrow() }).not.toThrow()
}) })
it('should not throw if prerelease is enabled', () => { it("should not throw if prerelease is enabled", () => {
const releaseResponse = { const releaseResponse = {
draft: false, draft: false,
prerelease: true, prerelease: true,
name: "Name" name: "Name",
} }
expect(() => { expect(() => {
validator.validateReleaseUpdate(releaseResponse) validator.validateReleaseUpdate(releaseResponse)
}).not.toThrow() }).not.toThrow()
}) })
it('should not throw if draft & prerelease is enabled', () => { it("should not throw if draft & prerelease is enabled", () => {
const releaseResponse = { const releaseResponse = {
draft: true, draft: true,
prerelease: true, prerelease: true,
name: "Name" name: "Name",
} }
expect(() => { expect(() => {
validator.validateReleaseUpdate(releaseResponse) validator.validateReleaseUpdate(releaseResponse)
}).not.toThrow() }).not.toThrow()
}) })
it('should default error message release name to release', () => { it("should default error message release name to release", () => {
const releaseResponse = { const releaseResponse = {
draft: false, draft: false,
prerelease: false, prerelease: false,
name: null name: null,
} }
expect(() => { expect(() => {
validator.validateReleaseUpdate(releaseResponse) validator.validateReleaseUpdate(releaseResponse)

35
biome.json Normal file
View File

@@ -0,0 +1,35 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"ignoreUnknown": false,
"ignore": ["node_modules/**/*", "dist/"]
},
"formatter": {
"enabled": true,
"formatWithErrors": false,
"indentStyle": "space",
"lineEnding": "lf",
"indentWidth": 4,
"lineWidth": 120
},
"organizeImports": { "enabled": true },
"linter": {
"enabled": false,
"rules": {
"recommended": true
}
},
"javascript": {
"formatter": {
"quoteStyle": "double",
"trailingCommas": "es5",
"semicolons": "asNeeded",
"indentWidth": 4
}
}
}

1980
dist/index.js vendored

File diff suppressed because one or more lines are too long

2
dist/index.js.map vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -9,6 +9,7 @@
"clean": "rm -rf lib/*", "clean": "rm -rf lib/*",
"coverage": "jest --coverage", "coverage": "jest --coverage",
"debug": "yarn clean && yarn install && yarn build && yarn package", "debug": "yarn clean && yarn install && yarn build && yarn package",
"format": "yarn biome format --write .",
"package": "ncc build --source-map --license licenses.txt", "package": "ncc build --source-map --license licenses.txt",
"release": "yarn clean && yarn install --production && yarn build && yarn package", "release": "yarn clean && yarn install --production && yarn build && yarn package",
"test": "jest" "test": "jest"
@@ -17,11 +18,7 @@
"type": "git", "type": "git",
"url": "git+https://github.com/ncipollo/release-action.git" "url": "git+https://github.com/ncipollo/release-action.git"
}, },
"keywords": [ "keywords": ["actions", "node", "setup"],
"actions",
"node",
"setup"
],
"author": "GitHub", "author": "GitHub",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -30,10 +27,7 @@
"jest": { "jest": {
"clearMocks": true, "clearMocks": true,
"collectCoverage": true, "collectCoverage": true,
"coveragePathIgnorePatterns": [ "coveragePathIgnorePatterns": ["src/Globber.ts", "src/Releases.ts"],
"src/Globber.ts",
"src/Releases.ts"
],
"coverageThreshold": { "coverageThreshold": {
"global": { "global": {
"branches": 95, "branches": 95,
@@ -42,14 +36,9 @@
"statements": 100 "statements": 100
} }
}, },
"moduleFileExtensions": [ "moduleFileExtensions": ["js", "ts"],
"js",
"ts"
],
"testEnvironment": "node", "testEnvironment": "node",
"testMatch": [ "testMatch": ["**/*.test.ts"],
"**/*.test.ts"
],
"testRunner": "jest-circus/runner", "testRunner": "jest-circus/runner",
"transform": { "transform": {
"^.+\\.ts$": "ts-jest" "^.+\\.ts$": "ts-jest"
@@ -58,17 +47,18 @@
}, },
"dependencies": { "dependencies": {
"@actions/core": "^1.11.1", "@actions/core": "^1.11.1",
"@actions/github": "^6.0.0", "@actions/github": "^6.0.1",
"@types/node": "^22.10.3", "@types/node": "^22.15.29",
"glob": "^11.0.0", "glob": "^11.0.2",
"untildify": "^4.0.0" "untildify": "^4.0.0"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "1.9.4",
"@types/jest": "^29.5.14", "@types/jest": "^29.5.14",
"jest": "^29.7.0", "jest": "^29.7.0",
"jest-circus": "^29.7.0", "jest-circus": "^29.7.0",
"ts-jest": "^29.2.5", "ts-jest": "^29.3.4",
"typescript": "^5.7.3" "typescript": "^5.8.3"
}, },
"resolutions": { "resolutions": {
"jest-cli/yargs": "^17.3.1" "jest-cli/yargs": "^17.3.1"

6
sheepit.toml Normal file
View File

@@ -0,0 +1,6 @@
[repository]
enable_commit = true
tag_pattern = "v{version}"
[scripts]
before_commit = "yarn release"

View File

@@ -1,18 +1,18 @@
import * as core from '@actions/core'; import * as core from "@actions/core"
import {Inputs} from "./Inputs"; import type { ActionSkipper } from "./ActionSkipper"
import { import type { ArtifactDestroyer } from "./ArtifactDestroyer"
import type { ArtifactUploader } from "./ArtifactUploader"
import { GithubError } from "./GithubError"
import type { Inputs } from "./Inputs"
import type { Outputs } from "./Outputs"
import { ReleaseValidator } from "./ReleaseValidator"
import type {
CreateOrUpdateReleaseResponse, CreateOrUpdateReleaseResponse,
CreateReleaseResponse, CreateReleaseResponse,
ReleaseByTagResponse, ReleaseByTagResponse,
Releases, Releases,
UpdateReleaseResponse UpdateReleaseResponse,
} from "./Releases"; } from "./Releases"
import {ArtifactUploader} from "./ArtifactUploader";
import {GithubError} from "./GithubError";
import {Outputs} from "./Outputs";
import {ArtifactDestroyer} from "./ArtifactDestroyer";
import {ReleaseValidator} from "./ReleaseValidator";
import {ActionSkipper} from "./ActionSkipper";
export class Action { export class Action {
private inputs: Inputs private inputs: Inputs
@@ -24,12 +24,14 @@ export class Action {
private releaseValidator: ReleaseValidator private releaseValidator: ReleaseValidator
constructor(inputs: Inputs, constructor(
inputs: Inputs,
outputs: Outputs, outputs: Outputs,
releases: Releases, releases: Releases,
uploader: ArtifactUploader, uploader: ArtifactUploader,
artifactDestroyer: ArtifactDestroyer, artifactDestroyer: ArtifactDestroyer,
skipper: ActionSkipper) { skipper: ActionSkipper
) {
this.inputs = inputs this.inputs = inputs
this.outputs = outputs this.outputs = outputs
this.releases = releases this.releases = releases
@@ -45,7 +47,7 @@ export class Action {
return return
} }
const releaseResponse = await this.createOrUpdateRelease(); const releaseResponse = await this.createOrUpdateRelease()
const releaseData = releaseResponse.data const releaseData = releaseResponse.data
const releaseId = releaseData.id const releaseId = releaseData.id
const uploadUrl = releaseData.upload_url const uploadUrl = releaseData.upload_url
@@ -89,10 +91,17 @@ export class Action {
} }
private async updateRelease(id: number): Promise<UpdateReleaseResponse> { private async updateRelease(id: number): Promise<UpdateReleaseResponse> {
let releaseBody = this.inputs.updatedReleaseBody
if (this.inputs.generateReleaseNotes && !this.inputs.omitBodyDuringUpdate) {
const response = await this.releases.generateReleaseNotes(this.inputs.tag)
releaseBody = response.data.body
}
return await this.releases.update( return await this.releases.update(
id, id,
this.inputs.tag, this.inputs.tag,
this.inputs.updatedReleaseBody, releaseBody,
this.inputs.commit, this.inputs.commit,
this.inputs.discussionCategory, this.inputs.discussionCategory,
this.inputs.updatedDraft, this.inputs.updatedDraft,
@@ -120,11 +129,11 @@ export class Action {
const tag = this.inputs.tag const tag = this.inputs.tag
const response = await this.releases.listReleases() const response = await this.releases.listReleases()
const releases = response.data const releases = response.data
if(!releases) { if (!releases) {
throw new Error(`No releases found. Response: ${JSON.stringify(response)}`) throw new Error(`No releases found. Response: ${JSON.stringify(response)}`)
} }
const draftRelease = releases.find(release => release.draft && release.tag_name == tag) const draftRelease = releases.find((release) => release.draft && release.tag_name == tag)
return draftRelease?.id return draftRelease?.id
} }

View File

@@ -1,19 +1,20 @@
import {Releases} from "./Releases"; import { Releases } from "./Releases"
export interface ActionSkipper { export interface ActionSkipper {
shouldSkip(): Promise<boolean> shouldSkip(): Promise<boolean>
} }
export class ReleaseActionSkipper { export class ReleaseActionSkipper {
constructor(private skipIfReleaseExists: boolean, constructor(
private skipIfReleaseExists: boolean,
private releases: Releases, private releases: Releases,
private tag: string) { private tag: string
} ) {}
async shouldSkip(): Promise<boolean> { async shouldSkip(): Promise<boolean> {
if (!this.skipIfReleaseExists) { if (!this.skipIfReleaseExists) {
// Bail if skip flag isn't set. // Bail if skip flag isn't set.
return false; return false
} }
try { try {
@@ -21,7 +22,7 @@ export class ReleaseActionSkipper {
return getResponse.data != null return getResponse.data != null
} catch (error: any) { } catch (error: any) {
// There is either no release or something else went wrong. Either way, run the action. // There is either no release or something else went wrong. Either way, run the action.
return false; return false
} }
} }
} }

View File

@@ -1,5 +1,5 @@
import { basename } from "path"; import { basename } from "path"
import {createReadStream, readFileSync, ReadStream, statSync} from "fs"; import { createReadStream, readFileSync, ReadStream, statSync } from "fs"
export class Artifact { export class Artifact {
readonly contentType: string readonly contentType: string
@@ -9,7 +9,7 @@ export class Artifact {
constructor(path: string, contentType: string = "raw") { constructor(path: string, contentType: string = "raw") {
this.path = path this.path = path
this.name = basename(path) this.name = basename(path)
this.contentType = contentType; this.contentType = contentType
} }
get contentLength(): number { get contentLength(): number {

View File

@@ -1,13 +1,12 @@
import {Releases} from "./Releases"; import { Releases } from "./Releases"
import * as core from "@actions/core"; import * as core from "@actions/core"
export interface ArtifactDestroyer { export interface ArtifactDestroyer {
destroyArtifacts(releaseId: number): Promise<void> destroyArtifacts(releaseId: number): Promise<void>
} }
export class GithubArtifactDestroyer implements ArtifactDestroyer { export class GithubArtifactDestroyer implements ArtifactDestroyer {
constructor(private releases: Releases) { constructor(private releases: Releases) {}
}
async destroyArtifacts(releaseId: number): Promise<void> { async destroyArtifacts(releaseId: number): Promise<void> {
const releaseAssets = await this.releases.listArtifactsForRelease(releaseId) const releaseAssets = await this.releases.listArtifactsForRelease(releaseId)

View File

@@ -1,9 +1,9 @@
import * as core from '@actions/core'; import * as core from "@actions/core"
import {Globber, FileGlobber} from "./Globber"; import { Globber, FileGlobber } from "./Globber"
import {Artifact} from "./Artifact"; import { Artifact } from "./Artifact"
import untildify from "untildify"; import untildify from "untildify"
import {ArtifactPathValidator} from "./ArtifactPathValidator"; import { ArtifactPathValidator } from "./ArtifactPathValidator"
import {PathNormalizer} from "./PathNormalizer"; import { PathNormalizer } from "./PathNormalizer"
export interface ArtifactGlobber { export interface ArtifactGlobber {
globArtifactString(artifact: string, contentType: string, errorsFailBuild: boolean): Artifact[] globArtifactString(artifact: string, contentType: string, errorsFailBuild: boolean): Artifact[]
@@ -18,14 +18,15 @@ export class FileArtifactGlobber implements ArtifactGlobber {
globArtifactString(artifact: string, contentType: string, errorsFailBuild: boolean): Artifact[] { globArtifactString(artifact: string, contentType: string, errorsFailBuild: boolean): Artifact[] {
const split = /[,\n]/ const split = /[,\n]/
return artifact.split(split) return artifact
.map(path => path.trimStart()) .split(split)
.map(path => PathNormalizer.normalizePath(path)) .map((path) => path.trimStart())
.map(path => FileArtifactGlobber.expandPath(path)) .map((path) => PathNormalizer.normalizePath(path))
.map(pattern => this.globPattern(pattern, errorsFailBuild)) .map((path) => FileArtifactGlobber.expandPath(path))
.map((pattern) => this.globPattern(pattern, errorsFailBuild))
.map((globResult) => FileArtifactGlobber.validatePattern(errorsFailBuild, globResult[1], globResult[0])) .map((globResult) => FileArtifactGlobber.validatePattern(errorsFailBuild, globResult[1], globResult[0]))
.reduce((accumulated, current) => accumulated.concat(current)) .reduce((accumulated, current) => accumulated.concat(current))
.map(path => new Artifact(path, contentType)) .map((path) => new Artifact(path, contentType))
} }
private globPattern(pattern: string, errorsFailBuild: boolean): [string, string[]] { private globPattern(pattern: string, errorsFailBuild: boolean): [string, string[]] {

View File

@@ -1,15 +1,15 @@
import * as core from "@actions/core"; import * as core from "@actions/core"
import {statSync} from "fs"; import { statSync } from "fs"
export class ArtifactPathValidator { export class ArtifactPathValidator {
private readonly errorsFailBuild: boolean; private readonly errorsFailBuild: boolean
private paths: string[]; private paths: string[]
private readonly pattern: string private readonly pattern: string
constructor(errorsFailBuild: boolean, paths: string[], pattern: string) { constructor(errorsFailBuild: boolean, paths: string[], pattern: string) {
this.paths = paths; this.paths = paths
this.pattern = pattern this.pattern = pattern
this.errorsFailBuild = errorsFailBuild; this.errorsFailBuild = errorsFailBuild
} }
validate(): string[] { validate(): string[] {

View File

@@ -1,6 +1,6 @@
import * as core from '@actions/core'; import * as core from "@actions/core"
import {Artifact} from "./Artifact"; import { Artifact } from "./Artifact"
import {Releases} from "./Releases"; import { Releases } from "./Releases"
export interface ArtifactUploader { export interface ArtifactUploader {
uploadArtifacts(artifacts: Artifact[], releaseId: number, uploadUrl: string): Promise<void> uploadArtifacts(artifacts: Artifact[], releaseId: number, uploadUrl: string): Promise<void>
@@ -10,13 +10,10 @@ export class GithubArtifactUploader implements ArtifactUploader {
constructor( constructor(
private releases: Releases, private releases: Releases,
private replacesExistingArtifacts: boolean = true, private replacesExistingArtifacts: boolean = true,
private throwsUploadErrors: boolean = false, private throwsUploadErrors: boolean = false
) { ) {}
}
async uploadArtifacts(artifacts: Artifact[], async uploadArtifacts(artifacts: Artifact[], releaseId: number, uploadUrl: string): Promise<void> {
releaseId: number,
uploadUrl: string): Promise<void> {
if (this.replacesExistingArtifacts) { if (this.replacesExistingArtifacts) {
await this.deleteUpdatedArtifacts(artifacts, releaseId) await this.deleteUpdatedArtifacts(artifacts, releaseId)
} }
@@ -25,18 +22,17 @@ export class GithubArtifactUploader implements ArtifactUploader {
} }
} }
private async uploadArtifact(artifact: Artifact, private async uploadArtifact(artifact: Artifact, releaseId: number, uploadUrl: string, retry = 3) {
releaseId: number,
uploadUrl: string,
retry = 3) {
try { try {
core.debug(`Uploading artifact ${artifact.name}...`) core.debug(`Uploading artifact ${artifact.name}...`)
await this.releases.uploadArtifact(uploadUrl, await this.releases.uploadArtifact(
uploadUrl,
artifact.contentLength, artifact.contentLength,
artifact.contentType, artifact.contentType,
artifact.readFile(), artifact.readFile(),
artifact.name, artifact.name,
releaseId) releaseId
)
} catch (error: any) { } catch (error: any) {
if (error.status >= 500 && retry > 0) { if (error.status >= 500 && retry > 0) {
core.warning(`Failed to upload artifact ${artifact.name}. ${error.message}. Retrying...`) core.warning(`Failed to upload artifact ${artifact.name}. ${error.message}. Retrying...`)
@@ -54,9 +50,9 @@ export class GithubArtifactUploader implements ArtifactUploader {
private async deleteUpdatedArtifacts(artifacts: Artifact[], releaseId: number): Promise<void> { private async deleteUpdatedArtifacts(artifacts: Artifact[], releaseId: number): Promise<void> {
const releaseAssets = await this.releases.listArtifactsForRelease(releaseId) const releaseAssets = await this.releases.listArtifactsForRelease(releaseId)
const assetByName: Record<string, { id: number; name: string }> = {} const assetByName: Record<string, { id: number; name: string }> = {}
releaseAssets.forEach(asset => { releaseAssets.forEach((asset) => {
assetByName[asset.name] = asset assetByName[asset.name] = asset
}); })
for (const artifact of artifacts) { for (const artifact of artifacts) {
const asset = assetByName[artifact.name] const asset = assetByName[artifact.name]
if (asset) { if (asset) {

View File

@@ -1,4 +1,4 @@
import {GithubErrorDetail} from "./GithubErrorDetail" import { GithubErrorDetail } from "./GithubErrorDetail"
export class GithubError { export class GithubError {
private error: any private error: any
@@ -48,4 +48,3 @@ export class GithubError {
return "" return ""
} }
} }

View File

@@ -1,5 +1,5 @@
export class GithubErrorDetail { export class GithubErrorDetail {
private error: any; private error: any
constructor(error: any) { constructor(error: any) {
this.error = error this.error = error
@@ -12,13 +12,13 @@ export class GithubErrorDetail {
toString(): string { toString(): string {
const code = this.error.code const code = this.error.code
switch (code) { switch (code) {
case 'missing': case "missing":
return this.missingResourceMessage() return this.missingResourceMessage()
case 'missing_field': case "missing_field":
return this.missingFieldMessage() return this.missingFieldMessage()
case 'invalid': case "invalid":
return this.invalidFieldMessage() return this.invalidFieldMessage()
case 'already_exists': case "already_exists":
return this.resourceAlreadyExists() return this.resourceAlreadyExists()
default: default:
return this.customErrorMessage() return this.customErrorMessage()
@@ -26,7 +26,7 @@ export class GithubErrorDetail {
} }
private customErrorMessage(): string { private customErrorMessage(): string {
const message = this.error.message; const message = this.error.message
const documentation = this.error.documentation_url const documentation = this.error.documentation_url
let documentationMessage: string let documentationMessage: string

View File

@@ -1,5 +1,4 @@
import {globSync} from "glob"; import { globSync } from "glob"
export interface Globber { export interface Globber {
glob(pattern: string): string[] glob(pattern: string): string[]

View File

@@ -1,8 +1,8 @@
import * as core from '@actions/core'; import * as core from "@actions/core"
import {Context} from "@actions/github/lib/context"; import { Context } from "@actions/github/lib/context"
import {readFileSync} from 'fs'; import { readFileSync } from "fs"
import {ArtifactGlobber} from './ArtifactGlobber'; import { ArtifactGlobber } from "./ArtifactGlobber"
import {Artifact} from './Artifact'; import { Artifact } from "./Artifact"
export interface Inputs { export interface Inputs {
readonly allowUpdates: boolean readonly allowUpdates: boolean
@@ -16,6 +16,7 @@ export interface Inputs {
readonly discussionCategory?: string readonly discussionCategory?: string
readonly generateReleaseNotes: boolean readonly generateReleaseNotes: boolean
readonly makeLatest?: "legacy" | "true" | "false" | undefined readonly makeLatest?: "legacy" | "true" | "false" | undefined
readonly omitBodyDuringUpdate: boolean
readonly owner: string readonly owner: string
readonly removeArtifacts: boolean readonly removeArtifacts: boolean
readonly replacesArtifacts: boolean readonly replacesArtifacts: boolean
@@ -40,53 +41,52 @@ export class CoreInputs implements Inputs {
} }
get allowUpdates(): boolean { get allowUpdates(): boolean {
const allow = core.getInput('allowUpdates') const allow = core.getInput("allowUpdates")
return allow == 'true' return allow == "true"
} }
get artifacts(): Artifact[] { get artifacts(): Artifact[] {
let artifacts = core.getInput('artifacts') let artifacts = core.getInput("artifacts")
if (!artifacts) { if (!artifacts) {
artifacts = core.getInput('artifact') artifacts = core.getInput("artifact")
} }
if (artifacts) { if (artifacts) {
let contentType = core.getInput('artifactContentType') let contentType = core.getInput("artifactContentType")
if (!contentType) { if (!contentType) {
contentType = 'raw' contentType = "raw"
} }
return this.artifactGlobber return this.artifactGlobber.globArtifactString(artifacts, contentType, this.artifactErrorsFailBuild)
.globArtifactString(artifacts, contentType, this.artifactErrorsFailBuild)
} }
return [] return []
} }
get artifactErrorsFailBuild(): boolean { get artifactErrorsFailBuild(): boolean {
const allow = core.getInput('artifactErrorsFailBuild') const allow = core.getInput("artifactErrorsFailBuild")
return allow == 'true' return allow == "true"
} }
private get body(): string | undefined { private get body(): string | undefined {
const body = core.getInput('body') const body = core.getInput("body")
if (body) { if (body) {
return body return body
} }
const bodyFile = core.getInput('bodyFile') const bodyFile = core.getInput("bodyFile")
if (bodyFile) { if (bodyFile) {
return this.stringFromFile(bodyFile) return this.stringFromFile(bodyFile)
} }
return '' return ""
} }
get createdDraft(): boolean { get createdDraft(): boolean {
const draft = core.getInput('draft') const draft = core.getInput("draft")
return draft == 'true' return draft == "true"
} }
get createdPrerelease(): boolean { get createdPrerelease(): boolean {
const preRelease = core.getInput('prerelease') const preRelease = core.getInput("prerelease")
return preRelease == 'true' return preRelease == "true"
} }
get createdReleaseBody(): string | undefined { get createdReleaseBody(): string | undefined {
@@ -95,7 +95,7 @@ export class CoreInputs implements Inputs {
} }
private static get omitBody(): boolean { private static get omitBody(): boolean {
return core.getInput('omitBody') == 'true' return core.getInput("omitBody") == "true"
} }
get createdReleaseName(): string | undefined { get createdReleaseName(): string | undefined {
@@ -104,11 +104,11 @@ export class CoreInputs implements Inputs {
} }
private static get omitName(): boolean { private static get omitName(): boolean {
return core.getInput('omitName') == 'true' return core.getInput("omitName") == "true"
} }
get commit(): string | undefined { get commit(): string | undefined {
const commit = core.getInput('commit') const commit = core.getInput("commit")
if (commit) { if (commit) {
return commit return commit
} }
@@ -116,7 +116,7 @@ export class CoreInputs implements Inputs {
} }
get discussionCategory(): string | undefined { get discussionCategory(): string | undefined {
const category = core.getInput('discussionCategory') const category = core.getInput("discussionCategory")
if (category) { if (category) {
return category return category
} }
@@ -124,7 +124,7 @@ export class CoreInputs implements Inputs {
} }
private get name(): string | undefined { private get name(): string | undefined {
const name = core.getInput('name') const name = core.getInput("name")
if (name) { if (name) {
return name return name
} }
@@ -133,21 +133,21 @@ export class CoreInputs implements Inputs {
} }
get generateReleaseNotes(): boolean { get generateReleaseNotes(): boolean {
const generate = core.getInput('generateReleaseNotes') const generate = core.getInput("generateReleaseNotes")
return generate == 'true' return generate == "true"
} }
get makeLatest(): "legacy" | "true" | "false" | undefined { get makeLatest(): "legacy" | "true" | "false" | undefined {
let latest = core.getInput('makeLatest') let latest = core.getInput("makeLatest")
if (latest == "true" || latest == "false" || latest == "legacy") { if (latest == "true" || latest == "false" || latest == "legacy") {
return latest; return latest
} }
return undefined return undefined
} }
get owner(): string { get owner(): string {
let owner = core.getInput('owner') let owner = core.getInput("owner")
if (owner) { if (owner) {
return owner return owner
} }
@@ -155,17 +155,17 @@ export class CoreInputs implements Inputs {
} }
get removeArtifacts(): boolean { get removeArtifacts(): boolean {
const removes = core.getInput('removeArtifacts') const removes = core.getInput("removeArtifacts")
return removes == 'true' return removes == "true"
} }
get replacesArtifacts(): boolean { get replacesArtifacts(): boolean {
const replaces = core.getInput('replacesArtifacts') const replaces = core.getInput("replacesArtifacts")
return replaces == 'true' return replaces == "true"
} }
get repo(): string { get repo(): string {
let repo = core.getInput('repo') let repo = core.getInput("repo")
if (repo) { if (repo) {
return repo return repo
} }
@@ -177,9 +177,9 @@ export class CoreInputs implements Inputs {
} }
get tag(): string { get tag(): string {
const tag = core.getInput('tag') const tag = core.getInput("tag")
if (tag) { if (tag) {
return tag; return tag
} }
const ref = this.context.ref const ref = this.context.ref
@@ -192,7 +192,7 @@ export class CoreInputs implements Inputs {
} }
get token(): string { get token(): string {
return core.getInput('token', {required: true}) return core.getInput("token", { required: true })
} }
get updatedDraft(): boolean | undefined { get updatedDraft(): boolean | undefined {
@@ -201,7 +201,7 @@ export class CoreInputs implements Inputs {
} }
private static get omitDraftDuringUpdate(): boolean { private static get omitDraftDuringUpdate(): boolean {
return core.getInput('omitDraftDuringUpdate') == 'true' return core.getInput("omitDraftDuringUpdate") == "true"
} }
get updatedPrerelease(): boolean | undefined { get updatedPrerelease(): boolean | undefined {
@@ -210,7 +210,7 @@ export class CoreInputs implements Inputs {
} }
private static get omitPrereleaseDuringUpdate(): boolean { private static get omitPrereleaseDuringUpdate(): boolean {
return core.getInput('omitPrereleaseDuringUpdate') == 'true' return core.getInput("omitPrereleaseDuringUpdate") == "true"
} }
get updatedReleaseBody(): string | undefined { get updatedReleaseBody(): string | undefined {
@@ -218,8 +218,8 @@ export class CoreInputs implements Inputs {
return this.body return this.body
} }
private static get omitBodyDuringUpdate(): boolean { get updateOnlyUnreleased(): boolean {
return core.getInput('omitBodyDuringUpdate') == 'true' return core.getInput("updateOnlyUnreleased") == "true"
} }
get updatedReleaseName(): string | undefined { get updatedReleaseName(): string | undefined {
@@ -227,15 +227,19 @@ export class CoreInputs implements Inputs {
return this.name return this.name
} }
get updateOnlyUnreleased(): boolean { private static get omitBodyDuringUpdate(): boolean {
return core.getInput('updateOnlyUnreleased') == 'true' return core.getInput("omitBodyDuringUpdate") == "true"
}
get omitBodyDuringUpdate(): boolean {
return CoreInputs.omitBodyDuringUpdate
} }
private static get omitNameDuringUpdate(): boolean { private static get omitNameDuringUpdate(): boolean {
return core.getInput('omitNameDuringUpdate') == 'true' return core.getInput("omitNameDuringUpdate") == "true"
} }
stringFromFile(path: string): string { stringFromFile(path: string): string {
return readFileSync(path, 'utf-8') return readFileSync(path, "utf-8")
} }
} }

View File

@@ -1,14 +1,14 @@
import * as github from '@actions/github'; import * as github from "@actions/github"
import * as core from '@actions/core'; import * as core from "@actions/core"
import {CoreInputs} from './Inputs'; import { CoreInputs } from "./Inputs"
import {GithubReleases} from './Releases'; import { GithubReleases } from "./Releases"
import {Action} from './Action'; import { Action } from "./Action"
import {GithubArtifactUploader} from './ArtifactUploader'; import { GithubArtifactUploader } from "./ArtifactUploader"
import {FileArtifactGlobber} from './ArtifactGlobber'; import { FileArtifactGlobber } from "./ArtifactGlobber"
import {GithubError} from './GithubError'; import { GithubError } from "./GithubError"
import {CoreOutputs} from "./Outputs"; import { CoreOutputs } from "./Outputs"
import {GithubArtifactDestroyer} from "./ArtifactDestroyer"; import { GithubArtifactDestroyer } from "./ArtifactDestroyer"
import {ActionSkipper, ReleaseActionSkipper} from "./ActionSkipper"; import { ActionSkipper, ReleaseActionSkipper } from "./ActionSkipper"
async function run() { async function run() {
try { try {
@@ -16,12 +16,12 @@ async function run() {
await action.perform() await action.perform()
} catch (error) { } catch (error) {
const githubError = new GithubError(error) const githubError = new GithubError(error)
core.setFailed(githubError.toString()); core.setFailed(githubError.toString())
} }
} }
function createAction(): Action { function createAction(): Action {
const token = core.getInput('token') const token = core.getInput("token")
const context = github.context const context = github.context
const git = github.getOctokit(token) const git = github.getOctokit(token)
const globber = new FileArtifactGlobber() const globber = new FileArtifactGlobber()
@@ -36,4 +36,4 @@ function createAction(): Action {
return new Action(inputs, outputs, releases, uploader, artifactDestroyer, skipper) return new Action(inputs, outputs, releases, uploader, artifactDestroyer, skipper)
} }
run(); run()

View File

@@ -1,5 +1,5 @@
import * as core from '@actions/core'; import * as core from "@actions/core"
import {ReleaseData} from "./Releases"; import { ReleaseData } from "./Releases"
export interface Outputs { export interface Outputs {
applyReleaseData(releaseData: ReleaseData): void applyReleaseData(releaseData: ReleaseData): void
@@ -7,8 +7,10 @@ export interface Outputs {
export class CoreOutputs implements Outputs { export class CoreOutputs implements Outputs {
applyReleaseData(releaseData: ReleaseData) { applyReleaseData(releaseData: ReleaseData) {
core.setOutput('id', releaseData.id) core.setOutput("id", releaseData.id)
core.setOutput('html_url', releaseData.html_url) core.setOutput("html_url", releaseData.html_url)
core.setOutput('upload_url', releaseData.upload_url) core.setOutput("upload_url", releaseData.upload_url)
core.setOutput("tarball_url", releaseData.tarball_url || "")
core.setOutput("zipball_url", releaseData.zipball_url || "")
} }
} }

View File

@@ -1,4 +1,4 @@
import path from "path"; import path from "path"
export class PathNormalizer { export class PathNormalizer {
static normalizePath(pathString: string): string { static normalizePath(pathString: string): string {

View File

@@ -1,6 +1,5 @@
export class ReleaseValidator { export class ReleaseValidator {
constructor(private updateOnlyUnreleased: boolean) { constructor(private updateOnlyUnreleased: boolean) {}
}
validateReleaseUpdate(releaseResponse: ReleaseStageArguments) { validateReleaseUpdate(releaseResponse: ReleaseStageArguments) {
if (!this.updateOnlyUnreleased) { if (!this.updateOnlyUnreleased) {
@@ -8,7 +7,9 @@ export class ReleaseValidator {
} }
if (!releaseResponse.draft && !releaseResponse.prerelease) { if (!releaseResponse.draft && !releaseResponse.prerelease) {
throw new Error(`Tried to update "${releaseResponse.name ?? "release"}" which is neither a draft or prerelease. (updateOnlyUnreleased is on)`) throw new Error(
`Tried to update "${releaseResponse.name ?? "release"}" which is neither a draft or prerelease. (updateOnlyUnreleased is on)`
)
} }
} }
} }

View File

@@ -1,7 +1,7 @@
import {GitHub} from '@actions/github/lib/utils' import type { GitHub } from "@actions/github/lib/utils"
import {OctokitResponse} from "@octokit/types"; import type { RestEndpointMethodTypes } from "@octokit/plugin-rest-endpoint-methods"
import {RestEndpointMethodTypes} from "@octokit/plugin-rest-endpoint-methods"; import type { OctokitResponse } from "@octokit/types"
import {Inputs} from "./Inputs"; import type { Inputs } from "./Inputs"
export type CreateReleaseResponse = RestEndpointMethodTypes["repos"]["createRelease"]["response"] export type CreateReleaseResponse = RestEndpointMethodTypes["repos"]["createRelease"]["response"]
export type ReleaseByTagResponse = RestEndpointMethodTypes["repos"]["getReleaseByTag"]["response"] export type ReleaseByTagResponse = RestEndpointMethodTypes["repos"]["getReleaseByTag"]["response"]
@@ -10,11 +10,14 @@ export type ListReleaseAssetsResponseData = RestEndpointMethodTypes["repos"]["li
export type UpdateReleaseResponse = RestEndpointMethodTypes["repos"]["updateRelease"]["response"] export type UpdateReleaseResponse = RestEndpointMethodTypes["repos"]["updateRelease"]["response"]
export type UploadArtifactResponse = RestEndpointMethodTypes["repos"]["uploadReleaseAsset"]["response"] export type UploadArtifactResponse = RestEndpointMethodTypes["repos"]["uploadReleaseAsset"]["response"]
export type CreateOrUpdateReleaseResponse = CreateReleaseResponse | UpdateReleaseResponse export type CreateOrUpdateReleaseResponse = CreateReleaseResponse | UpdateReleaseResponse
export type GenerateReleaseNotesResponse = RestEndpointMethodTypes["repos"]["generateReleaseNotes"]["response"]
export type ReleaseData = { export type ReleaseData = {
id: number id: number
html_url: string html_url: string
upload_url: string upload_url: string
tarball_url: string | null
zipball_url: string | null
} }
export interface Releases { export interface Releases {
@@ -34,6 +37,8 @@ export interface Releases {
getByTag(tag: string): Promise<ReleaseByTagResponse> getByTag(tag: string): Promise<ReleaseByTagResponse>
generateReleaseNotes(tag: string): Promise<GenerateReleaseNotesResponse>
listArtifactsForRelease(releaseId: number): Promise<ListReleaseAssetsResponseData> listArtifactsForRelease(releaseId: number): Promise<ListReleaseAssetsResponseData>
listReleases(): Promise<ListReleasesResponse> listReleases(): Promise<ListReleasesResponse>
@@ -56,7 +61,7 @@ export interface Releases {
contentType: string, contentType: string,
file: string | object, file: string | object,
name: string, name: string,
releaseId: number, releaseId: number
): Promise<UploadArtifactResponse> ): Promise<UploadArtifactResponse>
} }
@@ -92,17 +97,23 @@ export class GithubReleases implements Releases {
prerelease: prerelease, prerelease: prerelease,
repo: this.inputs.repo, repo: this.inputs.repo,
target_commitish: commitHash, target_commitish: commitHash,
tag_name: tag tag_name: tag,
}) })
} }
async deleteArtifact( async deleteArtifact(assetId: number): Promise<OctokitResponse<any>> {
assetId: number
): Promise<OctokitResponse<any>> {
return this.git.rest.repos.deleteReleaseAsset({ return this.git.rest.repos.deleteReleaseAsset({
asset_id: assetId, asset_id: assetId,
owner: this.inputs.owner, owner: this.inputs.owner,
repo: this.inputs.repo repo: this.inputs.repo,
})
}
async generateReleaseNotes(tag: string): Promise<GenerateReleaseNotesResponse> {
return this.git.rest.repos.generateReleaseNotes({
owner: this.inputs.owner,
repo: this.inputs.repo,
tag_name: tag,
}) })
} }
@@ -110,24 +121,22 @@ export class GithubReleases implements Releases {
return this.git.rest.repos.getReleaseByTag({ return this.git.rest.repos.getReleaseByTag({
owner: this.inputs.owner, owner: this.inputs.owner,
repo: this.inputs.repo, repo: this.inputs.repo,
tag: tag tag: tag,
}) })
} }
async listArtifactsForRelease( async listArtifactsForRelease(releaseId: number): Promise<ListReleaseAssetsResponseData> {
releaseId: number
): Promise<ListReleaseAssetsResponseData> {
return this.git.paginate(this.git.rest.repos.listReleaseAssets, { return this.git.paginate(this.git.rest.repos.listReleaseAssets, {
owner: this.inputs.owner, owner: this.inputs.owner,
release_id: releaseId, release_id: releaseId,
repo: this.inputs.repo repo: this.inputs.repo,
}) })
} }
async listReleases(): Promise<ListReleasesResponse> { async listReleases(): Promise<ListReleasesResponse> {
return this.git.rest.repos.listReleases({ return this.git.rest.repos.listReleases({
owner: this.inputs.owner, owner: this.inputs.owner,
repo: this.inputs.repo repo: this.inputs.repo,
}) })
} }
@@ -154,7 +163,7 @@ export class GithubReleases implements Releases {
prerelease: prerelease, prerelease: prerelease,
repo: this.inputs.repo, repo: this.inputs.repo,
target_commitish: commitHash, target_commitish: commitHash,
tag_name: tag tag_name: tag,
}) })
} }
@@ -164,19 +173,19 @@ export class GithubReleases implements Releases {
contentType: string, contentType: string,
file: string | object, file: string | object,
name: string, name: string,
releaseId: number, releaseId: number
): Promise<UploadArtifactResponse> { ): Promise<UploadArtifactResponse> {
return this.git.rest.repos.uploadReleaseAsset({ return this.git.rest.repos.uploadReleaseAsset({
url: assetUrl, url: assetUrl,
headers: { headers: {
"content-length": contentLength, "content-length": contentLength,
"content-type": contentType "content-type": contentType,
}, },
data: file as any, data: file as any,
name: name, name: name,
owner: this.inputs.owner, owner: this.inputs.owner,
release_id: releaseId, release_id: releaseId,
repo: this.inputs.repo repo: this.inputs.repo,
}) })
} }
} }

View File

@@ -3,11 +3,11 @@
"target": "ES2022", "target": "ES2022",
"module": "NodeNext", "module": "NodeNext",
"moduleResolution": "NodeNext", "moduleResolution": "NodeNext",
"outDir": "./lib", /* Redirect output structure to the directory. */ "outDir": "./lib",
"strict": true, /* Enable all strict type-checking options. */ "strict": true,
"skipLibCheck": true, "skipLibCheck": true,
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ "noImplicitAny": true,
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ "esModuleInterop": true
}, },
"exclude": ["node_modules", "**/*.test.ts"] "exclude": ["node_modules", "**/*.test.ts"]
} }

219
yarn.lock
View File

@@ -17,15 +17,18 @@
dependencies: dependencies:
"@actions/io" "^1.0.1" "@actions/io" "^1.0.1"
"@actions/github@^6.0.0": "@actions/github@^6.0.1":
version "6.0.0" version "6.0.1"
resolved "https://registry.yarnpkg.com/@actions/github/-/github-6.0.0.tgz#65883433f9d81521b782a64cc1fd45eef2191ea7" resolved "https://registry.yarnpkg.com/@actions/github/-/github-6.0.1.tgz#76e5f96df062c90635a7181ef45ff1c4ac21306e"
integrity sha512-alScpSVnYmjNEXboZjarjukQEzgCRmjMv6Xj47fsdnqGS73bjJNDpiiXmp8jr0UZLdUB6d9jW63IcmddUP+l0g== integrity sha512-xbZVcaqD4XnQAe35qSQqskb3SqIAfRyLBrHMd/8TuL7hJSz2QtbDwnNM8zWx4zO5l2fnGtseNE3MbEvD7BxVMw==
dependencies: dependencies:
"@actions/http-client" "^2.2.0" "@actions/http-client" "^2.2.0"
"@octokit/core" "^5.0.1" "@octokit/core" "^5.0.1"
"@octokit/plugin-paginate-rest" "^9.0.0" "@octokit/plugin-paginate-rest" "^9.2.2"
"@octokit/plugin-rest-endpoint-methods" "^10.0.0" "@octokit/plugin-rest-endpoint-methods" "^10.4.0"
"@octokit/request" "^8.4.1"
"@octokit/request-error" "^5.1.1"
undici "^5.28.5"
"@actions/http-client@^2.0.1", "@actions/http-client@^2.2.0": "@actions/http-client@^2.0.1", "@actions/http-client@^2.2.0":
version "2.2.0" version "2.2.0"
@@ -571,6 +574,60 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@biomejs/biome@1.9.4":
version "1.9.4"
resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-1.9.4.tgz#89766281cbc3a0aae865a7ff13d6aaffea2842bf"
integrity sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==
optionalDependencies:
"@biomejs/cli-darwin-arm64" "1.9.4"
"@biomejs/cli-darwin-x64" "1.9.4"
"@biomejs/cli-linux-arm64" "1.9.4"
"@biomejs/cli-linux-arm64-musl" "1.9.4"
"@biomejs/cli-linux-x64" "1.9.4"
"@biomejs/cli-linux-x64-musl" "1.9.4"
"@biomejs/cli-win32-arm64" "1.9.4"
"@biomejs/cli-win32-x64" "1.9.4"
"@biomejs/cli-darwin-arm64@1.9.4":
version "1.9.4"
resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz#dfa376d23a54a2d8f17133c92f23c1bf2e62509f"
integrity sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==
"@biomejs/cli-darwin-x64@1.9.4":
version "1.9.4"
resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz#eafc2ce3849d385fc02238aad1ca4a73395a64d9"
integrity sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==
"@biomejs/cli-linux-arm64-musl@1.9.4":
version "1.9.4"
resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz#d780c3e01758fc90f3268357e3f19163d1f84fca"
integrity sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==
"@biomejs/cli-linux-arm64@1.9.4":
version "1.9.4"
resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz#8ed1dd0e89419a4b66a47f95aefb8c46ae6041c9"
integrity sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==
"@biomejs/cli-linux-x64-musl@1.9.4":
version "1.9.4"
resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz#f36982b966bd671a36671e1de4417963d7db15fb"
integrity sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==
"@biomejs/cli-linux-x64@1.9.4":
version "1.9.4"
resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz#a0a7f56680c76b8034ddc149dbf398bdd3a462e8"
integrity sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==
"@biomejs/cli-win32-arm64@1.9.4":
version "1.9.4"
resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz#e2ef4e0084e76b7e26f0fc887c5ef1265ea56200"
integrity sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==
"@biomejs/cli-win32-x64@1.9.4":
version "1.9.4"
resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz#4c7afa90e3970213599b4095e62f87e5972b2340"
integrity sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==
"@fastify/busboy@^2.0.0": "@fastify/busboy@^2.0.0":
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.0.tgz#0709e9f4cb252351c609c6e6d8d6779a8d25edff" resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.0.tgz#0709e9f4cb252351c609c6e6d8d6779a8d25edff"
@@ -904,12 +961,12 @@
before-after-hook "^2.2.0" before-after-hook "^2.2.0"
universal-user-agent "^6.0.0" universal-user-agent "^6.0.0"
"@octokit/endpoint@^9.0.0": "@octokit/endpoint@^9.0.6":
version "9.0.4" version "9.0.6"
resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-9.0.4.tgz#8afda5ad1ffc3073d08f2b450964c610b821d1ea" resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-9.0.6.tgz#114d912108fe692d8b139cfe7fc0846dfd11b6c0"
integrity sha512-DWPLtr1Kz3tv8L0UvXTDP1fNwM0S+z6EJpRcvH66orY6Eld4XBMCSYsaWp4xIm61jTWxK68BrR7ibO+vSDnZqw== integrity sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==
dependencies: dependencies:
"@octokit/types" "^12.0.0" "@octokit/types" "^13.1.0"
universal-user-agent "^6.0.0" universal-user-agent "^6.0.0"
"@octokit/graphql@^7.0.0": "@octokit/graphql@^7.0.0":
@@ -926,46 +983,70 @@
resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-19.1.0.tgz#75ec7e64743870fc73e1ab4bc6ec252ecdd624dc" resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-19.1.0.tgz#75ec7e64743870fc73e1ab4bc6ec252ecdd624dc"
integrity sha512-6G+ywGClliGQwRsjvqVYpklIfa7oRPA0vyhPQG/1Feh+B+wU0vGH1JiJ5T25d3g1JZYBHzR2qefLi9x8Gt+cpw== integrity sha512-6G+ywGClliGQwRsjvqVYpklIfa7oRPA0vyhPQG/1Feh+B+wU0vGH1JiJ5T25d3g1JZYBHzR2qefLi9x8Gt+cpw==
"@octokit/plugin-paginate-rest@^9.0.0": "@octokit/openapi-types@^20.0.0":
version "9.1.5" version "20.0.0"
resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.1.5.tgz#1705bcef4dcde1f4015ee58a63dc61b68648f480" resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-20.0.0.tgz#9ec2daa0090eeb865ee147636e0c00f73790c6e5"
integrity sha512-WKTQXxK+bu49qzwv4qKbMMRXej1DU2gq017euWyKVudA6MldaSSQuxtz+vGbhxV4CjxpUxjZu6rM2wfc1FiWVg== integrity sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==
dependencies:
"@octokit/types" "^12.4.0"
"@octokit/plugin-rest-endpoint-methods@^10.0.0": "@octokit/openapi-types@^23.0.1":
version "10.2.0" version "23.0.1"
resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.2.0.tgz#eeaa4de97a2ae26404dea30ce3e17b11928e027c" resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-23.0.1.tgz#3721646ecd36b596ddb12650e0e89d3ebb2dd50e"
integrity sha512-ePbgBMYtGoRNXDyKGvr9cyHjQ163PbwD0y1MkDJCpkO2YH4OeXX40c4wYHKikHGZcpGPbcRLuy0unPUuafco8Q== integrity sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==
dependencies:
"@octokit/types" "^12.3.0"
"@octokit/request-error@^5.0.0": "@octokit/plugin-paginate-rest@^9.2.2":
version "5.0.1" version "9.2.2"
resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-5.0.1.tgz#277e3ce3b540b41525e07ba24c5ef5e868a72db9" resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.2.tgz#c516bc498736bcdaa9095b9a1d10d9d0501ae831"
integrity sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ== integrity sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==
dependencies: dependencies:
"@octokit/types" "^12.0.0" "@octokit/types" "^12.6.0"
"@octokit/plugin-rest-endpoint-methods@^10.4.0":
version "10.4.1"
resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz#41ba478a558b9f554793075b2e20cd2ef973be17"
integrity sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==
dependencies:
"@octokit/types" "^12.6.0"
"@octokit/request-error@^5.0.0", "@octokit/request-error@^5.1.1":
version "5.1.1"
resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-5.1.1.tgz#b9218f9c1166e68bb4d0c89b638edc62c9334805"
integrity sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==
dependencies:
"@octokit/types" "^13.1.0"
deprecation "^2.0.0" deprecation "^2.0.0"
once "^1.4.0" once "^1.4.0"
"@octokit/request@^8.0.1", "@octokit/request@^8.0.2": "@octokit/request@^8.0.1", "@octokit/request@^8.0.2", "@octokit/request@^8.4.1":
version "8.1.6" version "8.4.1"
resolved "https://registry.yarnpkg.com/@octokit/request/-/request-8.1.6.tgz#a76a859c30421737a3918b40973c2ff369009571" resolved "https://registry.yarnpkg.com/@octokit/request/-/request-8.4.1.tgz#715a015ccf993087977ea4365c44791fc4572486"
integrity sha512-YhPaGml3ncZC1NfXpP3WZ7iliL1ap6tLkAp6MvbK2fTTPytzVUyUesBBogcdMm86uRYO5rHaM1xIWxigWZ17MQ== integrity sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==
dependencies: dependencies:
"@octokit/endpoint" "^9.0.0" "@octokit/endpoint" "^9.0.6"
"@octokit/request-error" "^5.0.0" "@octokit/request-error" "^5.1.1"
"@octokit/types" "^12.0.0" "@octokit/types" "^13.1.0"
universal-user-agent "^6.0.0" universal-user-agent "^6.0.0"
"@octokit/types@^12.0.0", "@octokit/types@^12.3.0", "@octokit/types@^12.4.0": "@octokit/types@^12.0.0":
version "12.4.0" version "12.4.0"
resolved "https://registry.yarnpkg.com/@octokit/types/-/types-12.4.0.tgz#8f97b601e91ce6b9776ed8152217e77a71be7aac" resolved "https://registry.yarnpkg.com/@octokit/types/-/types-12.4.0.tgz#8f97b601e91ce6b9776ed8152217e77a71be7aac"
integrity sha512-FLWs/AvZllw/AGVs+nJ+ELCDZZJk+kY0zMen118xhL2zD0s1etIUHm1odgjP7epxYU1ln7SZxEUWYop5bhsdgQ== integrity sha512-FLWs/AvZllw/AGVs+nJ+ELCDZZJk+kY0zMen118xhL2zD0s1etIUHm1odgjP7epxYU1ln7SZxEUWYop5bhsdgQ==
dependencies: dependencies:
"@octokit/openapi-types" "^19.1.0" "@octokit/openapi-types" "^19.1.0"
"@octokit/types@^12.6.0":
version "12.6.0"
resolved "https://registry.yarnpkg.com/@octokit/types/-/types-12.6.0.tgz#8100fb9eeedfe083aae66473bd97b15b62aedcb2"
integrity sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==
dependencies:
"@octokit/openapi-types" "^20.0.0"
"@octokit/types@^13.1.0":
version "13.8.0"
resolved "https://registry.yarnpkg.com/@octokit/types/-/types-13.8.0.tgz#3815885e5abd16ed9ffeea3dced31d37ce3f8a0a"
integrity sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==
dependencies:
"@octokit/openapi-types" "^23.0.1"
"@pkgjs/parseargs@^0.11.0": "@pkgjs/parseargs@^0.11.0":
version "0.11.0" version "0.11.0"
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
@@ -1057,12 +1138,12 @@
expect "^29.0.0" expect "^29.0.0"
pretty-format "^29.0.0" pretty-format "^29.0.0"
"@types/node@*", "@types/node@^22.10.3": "@types/node@*", "@types/node@^22.15.29":
version "22.10.3" version "22.15.29"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.3.tgz#cdc2a89bf6e5d5e593fad08e83f74d7348d5dd10" resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.29.tgz#c75999124a8224a3f79dd8b6ccfb37d74098f678"
integrity sha512-DifAyw4BkrufCILvD3ucnuN8eydUfc/C1GlyrnI+LK6543w5/L3VeVgf05o3B4fqSXP1dKYLOZsKfutpxPzZrw== integrity sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==
dependencies: dependencies:
undici-types "~6.20.0" undici-types "~6.21.0"
"@types/stack-utils@^2.0.0": "@types/stack-utils@^2.0.0":
version "2.0.0" version "2.0.0"
@@ -1626,10 +1707,10 @@ get-stream@^6.0.0:
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
glob@^11.0.0: glob@^11.0.2:
version "11.0.0" version "11.0.2"
resolved "https://registry.yarnpkg.com/glob/-/glob-11.0.0.tgz#6031df0d7b65eaa1ccb9b29b5ced16cea658e77e" resolved "https://registry.yarnpkg.com/glob/-/glob-11.0.2.tgz#3261e3897bbc603030b041fd77ba636022d51ce0"
integrity sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g== integrity sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ==
dependencies: dependencies:
foreground-child "^3.1.0" foreground-child "^3.1.0"
jackspeak "^4.0.1" jackspeak "^4.0.1"
@@ -2532,10 +2613,10 @@ semver@^6.0.0, semver@^6.3.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
semver@^7.5.3, semver@^7.5.4, semver@^7.6.3: semver@^7.5.3, semver@^7.5.4, semver@^7.7.2:
version "7.6.3" version "7.7.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58"
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==
shebang-command@^2.0.0: shebang-command@^2.0.0:
version "2.0.0" version "2.0.0"
@@ -2738,10 +2819,10 @@ to-regex-range@^5.0.1:
dependencies: dependencies:
is-number "^7.0.0" is-number "^7.0.0"
ts-jest@^29.2.5: ts-jest@^29.3.4:
version "29.2.5" version "29.3.4"
resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.2.5.tgz#591a3c108e1f5ebd013d3152142cb5472b399d63" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.3.4.tgz#9354472aceae1d3867a80e8e02014ea5901aee41"
integrity sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA== integrity sha512-Iqbrm8IXOmV+ggWHOTEbjwyCf2xZlUMv5npExksXohL+tk8va4Fjhb+X2+Rt9NBmgO7bJ8WpnMLOwih/DnMlFA==
dependencies: dependencies:
bs-logger "^0.2.6" bs-logger "^0.2.6"
ejs "^3.1.10" ejs "^3.1.10"
@@ -2750,7 +2831,8 @@ ts-jest@^29.2.5:
json5 "^2.2.3" json5 "^2.2.3"
lodash.memoize "^4.1.2" lodash.memoize "^4.1.2"
make-error "^1.3.6" make-error "^1.3.6"
semver "^7.6.3" semver "^7.7.2"
type-fest "^4.41.0"
yargs-parser "^21.1.1" yargs-parser "^21.1.1"
tunnel@^0.0.6: tunnel@^0.0.6:
@@ -2768,20 +2850,25 @@ type-fest@^0.11.0:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1"
integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==
typescript@^5.7.3: type-fest@^4.41.0:
version "5.7.3" version "4.41.0"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.3.tgz#919b44a7dbb8583a9b856d162be24a54bf80073e" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58"
integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw== integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==
undici-types@~6.20.0: typescript@^5.8.3:
version "6.20.0" version "5.8.3"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e"
integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==
undici@^5.25.4: undici-types@~6.21.0:
version "5.28.4" version "6.21.0"
resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.4.tgz#6b280408edb6a1a604a9b20340f45b422e373068" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb"
integrity sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g== integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==
undici@^5.25.4, undici@^5.28.5:
version "5.29.0"
resolved "https://registry.yarnpkg.com/undici/-/undici-5.29.0.tgz#419595449ae3f2cdcba3580a2e8903399bd1f5a3"
integrity sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==
dependencies: dependencies:
"@fastify/busboy" "^2.0.0" "@fastify/busboy" "^2.0.0"