1 Commits

Author SHA1 Message Date
Nick Cipollo
2c591bcc8e Prepare 1.14.0
Some checks failed
Test / check_pr (push) Has been cancelled
2024-02-05 18:42:24 -05:00
43 changed files with 4109 additions and 5513 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -1,22 +0,0 @@
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

3
.gitignore vendored
View File

@@ -1,6 +1,5 @@
node_modules/
__tests__/runner/*
.DS_Store
# Created by https://www.gitignore.io/api/webstorm
# Edit at https://www.gitignore.io/?templates=webstorm
@@ -99,4 +98,4 @@ fabric.properties
coverage
# Ignore lib, it contains intermediates
/lib
/lib

6
.idea/biome.xml generated
View File

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

View File

@@ -19,7 +19,7 @@ This action will create a GitHub release and optionally upload an artifact to it
| artifactErrorsFailBuild | An optional flag which indicates if artifact read or upload errors should fail the build. | false | "" |
| artifacts | An optional set of paths representing artifacts to upload to the release. This may be a single path or a comma delimited list of paths (or globs) | false | "" |
| artifactContentType | The content type of the artifact. Defaults to raw | false | "" |
| body | An optional body for the release. Note: This input will have white space trimmed before and after it. Use `bodyFile` if you need a non-trivial markdown body. | false | "" |
| body | An optional body for the release. | false | "" |
| bodyFile | An optional body file for the release. This should be the path to the file. | false | "" |
| commit | An optional commit reference. This will be used to create the tag if it does not exist. | false | "" |
| discussionCategory | When provided this will generate a discussion of the specified category. The category must exist otherwise this will cause the action to fail. This isn't used with draft releases. The default "Announcements" category is not supported via the API and will cause an error if used here. | false | "" |
@@ -68,7 +68,7 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- uses: ncipollo/release-action@v1
with:
artifacts: "release.tar.gz,foo/*.txt"

View File

@@ -20,13 +20,12 @@
| artifactErrorsFailBuild | 一个可选标志,表示读取或上传产出文件错误时是否应该使构建失败。 | false | "" |
| artifacts | 一组可选的路径,表示要上传到版本的产出文件。 这可能是单个路径或以逗号分隔的路径列表(或 globs | false | "" |
| artifactContentType | 产出文件的内容类型。 默认为 raw | false | "" |
| body | 发布的可选主体。注意:此处输入的首尾空格会被去除。如果您需要特殊的 markdown 内容,请使用 `bodyFile` | false | "" |
| body | 发布的可选主体。 | false | "" |
| bodyFile | 发布的可选正文文件。 这应该是文件的路径。 | false | "" |
| commit | 一个可选的提交 ref。 如果标签不存在,将用于创建标签。 | false | "" |
| discussionCategory | 当提供该选项时,将生成指定类别的 discussion。指定的类别必须存在,否则将导致 Action 失败。这草案状态的发布不生效。API 不支持默认的 Announcement 分类,使用这个分类会引发错误。 | false | "" |
| discussionCategory | 当提供该选项时,将生成指定类别的 discussion。类别必须存在否则将导致 Action 失败。这草案发布中没有使用 | false | "" |
| draft | 可选择将此版本标记为草稿版本。 设置为 true 以启用。 | false | "" |
| generateReleaseNotes | 指示是否应自动生成发行说明。 | false | false |
| makeLatest | 指示是否将这个发布设置为 latest。使用"lagecy"值则会根据发布时间和语义化版本号自动决定。 | false | "legacy" |
| name | 版本的可选名称。 如果省略,将使用标签。 | false | "" |
| omitBody | 指示是否应省略发布主体。 | false | false |
| omitBodyDuringUpdate | 指示在更新期间是否应省略发布主体。 正文仍将应用于新创建的版本。 这将在更新期间保留现有正文。 | false | false |
@@ -39,7 +38,6 @@
| removeArtifacts | 指示是否应删除现有的发布产出文件。 | false | false |
| replacesArtifacts | 指示是否应替换现有的发布产出文件。 | false | true |
| repo | (可选)指定应在其中生成版本的存储库。 | false | current repo |
| skipIfReleaseExists | 当该选项启用时,如果提供的标签已存在一个对应的非草案状态的发布,则 action 将会被跳过。 | false | false |
| tag | 发布的可选标签。 如果省略,将使用 git ref (如果它是标签)。 | false | "" |
| token | GitHub 令牌。 这将默认为 GitHub 应用程序令牌。 如果您想使用您的个人令牌(用于定位其他存储库等),这主要是有用的。 如果您使用的是个人访问令牌,它应该可以访问 `repo` 范围。 | false | github.token |
| updateOnlyUnreleased | 启用 allowUpdates 后,如果它正在更新的版本不是草稿或预发布,则该操作将失败。 | false | false |
@@ -62,20 +60,20 @@ name: Releases
on:
push:
tags:
- '*'
- "*"
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: ncipollo/release-action@v1
with:
artifacts: "release.tar.gz,foo/*.txt"
bodyFile: "body.md"
- uses: actions/checkout@v2
- uses: ncipollo/release-action@v1
with:
artifacts: "release.tar.gz,foo/*.txt"
bodyFile: "body.md"
token: ${{ secrets.YOUR_GITHUB_TOKEN }}
```
## 注意
@@ -83,4 +81,3 @@ jobs:
- 您必须通过 Action 输入或 git ref 提供一个标签(即推送/创建标签。如果不提供标签Action 将会失败。
- 如果您正在创建的版本的标签不存在,您应该同时设置标签和提交 Action 输入。 commit 可以指向提交 Hash 或分支名称(例如 - main
- 在上面的示例中,只需要指定操作的权限(即 contents: write。 如果您将其他操作添加到同一工作流程,则应相应地扩展权限。
- 有关于此 action 无法支持 discussion 的 Announcement 分类的原因的更多信息请参见此处https://github.com/goreleaser/goreleaser/issues/2304

View File

@@ -1,11 +1,11 @@
import { Action } from "../src/Action"
import type { ActionSkipper } from "../src/ActionSkipper"
import { Artifact } from "../src/Artifact"
import type { ArtifactDestroyer } from "../src/ArtifactDestroyer"
import type { ArtifactUploader } from "../src/ArtifactUploader"
import type { Inputs } from "../src/Inputs"
import type { Outputs } from "../src/Outputs"
import type { Releases } from "../src/Releases"
import {Action} from "../src/Action";
import {Artifact} from "../src/Artifact";
import {Inputs} from "../src/Inputs";
import {Releases} from "../src/Releases";
import {ArtifactUploader} from "../src/ArtifactUploader";
import {Outputs} from "../src/Outputs";
import {ArtifactDestroyer} from "../src/ArtifactDestroyer";
import {ActionSkipper} from "../src/ActionSkipper";
const applyReleaseDataMock = jest.fn()
const artifactDestroyMock = jest.fn()
@@ -17,30 +17,31 @@ const listMock = jest.fn()
const shouldSkipMock = jest.fn()
const updateMock = jest.fn()
const uploadMock = jest.fn()
const genReleaseNotesMock = jest.fn()
const artifacts = [new Artifact("a/art1"), new Artifact("b/art2")]
const artifacts = [
new Artifact('a/art1'),
new Artifact('b/art2')
]
const createBody = "createBody"
const createBody = 'createBody'
const createDraft = true
const createName = "createName"
const commit = "commit"
const discussionCategory = "discussionCategory"
const createName = 'createName'
const commit = 'commit'
const discussionCategory = 'discussionCategory'
const generateReleaseNotes = true
const id = 100
const createPrerelease = true
const releaseId = 101
const replacesArtifacts = true
const tag = "tag"
const token = "token"
const updateBody = "updateBody"
const tag = 'tag'
const token = 'token'
const updateBody = 'updateBody'
const updateDraft = false
const updateName = "updateName"
const updateName = 'updateName'
const updatePrerelease = false
const updateOnlyUnreleased = false
const url = "http://api.example.com"
const makeLatest = "legacy"
const generatedReleaseBody = "test release notes"
const url = 'http://api.example.com'
const makeLatest = 'legacy'
describe("Action", () => {
beforeEach(() => {
@@ -52,13 +53,12 @@ describe("Action", () => {
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)
await action.perform()
expect(createMock).toHaveBeenCalledWith(
tag,
expect(createMock).toBeCalledWith(tag,
createBody,
commit,
discussionCategory,
@@ -66,20 +66,19 @@ describe("Action", () => {
generateReleaseNotes,
makeLatest,
createName,
createPrerelease
)
expect(uploadMock).not.toHaveBeenCalled()
createPrerelease)
expect(uploadMock).not.toBeCalled()
assertOutputApplied()
})
it("creates release if no release exists to update", async () => {
it('creates release if no release exists to update', async () => {
const action = createAction(true, true)
const error = { status: 404 }
const error = {status: 404}
getMock.mockRejectedValue(error)
await action.perform()
expect(createMock).toHaveBeenCalledWith(
expect(createMock).toBeCalledWith(
tag,
createBody,
commit,
@@ -88,23 +87,24 @@ describe("Action", () => {
generateReleaseNotes,
makeLatest,
createName,
createPrerelease
)
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
createPrerelease)
expect(uploadMock).toBeCalledWith(artifacts, releaseId, url)
assertOutputApplied()
})
it("creates release if no draft releases", async () => {
it('creates release if no draft releases', async () => {
const action = createAction(true, true)
const error = { status: 404 }
const error = {status: 404}
getMock.mockRejectedValue(error)
listMock.mockResolvedValue({
data: [{ id: id, draft: false, tag_name: tag }],
data: [
{id: id, draft: false, tag_name: tag}
]
})
await action.perform()
expect(createMock).toHaveBeenCalledWith(
expect(createMock).toBeCalledWith(
tag,
createBody,
commit,
@@ -115,16 +115,17 @@ describe("Action", () => {
createName,
createPrerelease
)
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
expect(uploadMock).toBeCalledWith(artifacts, releaseId, url)
assertOutputApplied()
})
it("creates release then uploads artifact", async () => {
it('creates release then uploads artifact', async () => {
const action = createAction(false, true)
await action.perform()
expect(createMock).toHaveBeenCalledWith(
expect(createMock).toBeCalledWith(
tag,
createBody,
commit,
@@ -135,39 +136,39 @@ describe("Action", () => {
createName,
createPrerelease
)
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
expect(uploadMock).toBeCalledWith(artifacts, releaseId, url)
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)
await action.perform()
expect(artifactDestroyMock).toHaveBeenCalledWith(releaseId)
expect(artifactDestroyMock).toBeCalledWith(releaseId)
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)
await action.perform()
expect(artifactDestroyMock).not.toHaveBeenCalled()
expect(artifactDestroyMock).not.toBeCalled()
assertOutputApplied()
})
it("skips action", async () => {
it('skips action', async () => {
const action = createAction(false, false, false)
shouldSkipMock.mockResolvedValue(true)
await action.perform()
expect(createMock).not.toHaveBeenCalled()
expect(updateMock).not.toHaveBeenCalled()
expect(createMock).not.toBeCalled()
expect(updateMock).not.toBeCalled()
})
it("throws error when create fails", async () => {
it('throws error when create fails', async () => {
const action = createAction(false, true)
createMock.mockRejectedValue("error")
@@ -178,7 +179,7 @@ describe("Action", () => {
expect(error).toEqual("error")
}
expect(createMock).toHaveBeenCalledWith(
expect(createMock).toBeCalledWith(
tag,
createBody,
commit,
@@ -189,17 +190,17 @@ describe("Action", () => {
createName,
createPrerelease
)
expect(uploadMock).not.toHaveBeenCalled()
expect(uploadMock).not.toBeCalled()
})
it("throws error when get fails", async () => {
it('throws error when get fails', async () => {
const action = createAction(true, true)
const error = {
errors: [
{
code: "already_exists",
},
],
code: 'already_exists'
}
]
}
createMock.mockRejectedValue(error)
@@ -211,37 +212,13 @@ describe("Action", () => {
expect(error).toEqual("error")
}
expect(getMock).toHaveBeenCalledWith(tag)
expect(updateMock).not.toHaveBeenCalled()
expect(uploadMock).not.toHaveBeenCalled()
expect(getMock).toBeCalledWith(tag)
expect(updateMock).not.toBeCalled()
expect(uploadMock).not.toBeCalled()
})
it("throws error when list has no data", async () => {
const action = createAction(true, true)
getMock.mockRejectedValue({ status: 404 })
const error = {
errors: [
{
code: "already_exists",
},
],
}
createMock.mockRejectedValue(error)
listMock.mockResolvedValue({})
expect.hasAssertions()
try {
await action.perform()
} catch (error) {
expect(error).toEqual(Error("No releases found. Response: {}"))
}
expect(listMock).toHaveBeenCalled()
expect(createMock).not.toHaveBeenCalled()
expect(updateMock).not.toHaveBeenCalled()
})
it("throws error when update fails", async () => {
it('throws error when update fails', async () => {
const action = createAction(true, true)
updateMock.mockRejectedValue("error")
@@ -253,88 +230,7 @@ describe("Action", () => {
expect(error).toEqual("error")
}
expect(updateMock).toHaveBeenCalledWith(
id,
tag,
generatedReleaseBody,
commit,
discussionCategory,
updateDraft,
makeLatest,
updateName,
updatePrerelease
)
expect(uploadMock).not.toHaveBeenCalled()
})
it("throws error when upload fails", async () => {
const action = createAction(false, true)
const expectedError = { status: 404 }
uploadMock.mockRejectedValue(expectedError)
expect.hasAssertions()
try {
await action.perform()
} catch (error) {
expect(error).toEqual(expectedError)
}
expect(createMock).toHaveBeenCalledWith(
tag,
createBody,
commit,
discussionCategory,
createDraft,
generateReleaseNotes,
makeLatest,
createName,
createPrerelease
)
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
})
it("updates draft release", async () => {
const action = createAction(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,
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(
expect(updateMock).toBeCalledWith(
id,
tag,
updateBody,
@@ -345,19 +241,72 @@ describe("Action", () => {
updateName,
updatePrerelease
)
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
expect(uploadMock).not.toBeCalled()
})
it('throws error when upload fails', async () => {
const action = createAction(false, true)
const expectedError = {status: 404}
uploadMock.mockRejectedValue(expectedError)
expect.hasAssertions()
try {
await action.perform()
} catch (error) {
expect(error).toEqual(expectedError)
}
expect(createMock).toBeCalledWith(
tag,
createBody,
commit,
discussionCategory,
createDraft,
generateReleaseNotes,
makeLatest,
createName,
createPrerelease
)
expect(uploadMock).toBeCalledWith(artifacts, releaseId, url)
})
it('updates draft release', async () => {
const action = createAction(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).toBeCalledWith(
id,
tag,
updateBody,
commit,
discussionCategory,
updateDraft,
makeLatest,
updateName,
updatePrerelease
)
expect(uploadMock).toBeCalledWith(artifacts, releaseId, url)
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)
await action.perform()
expect(updateMock).toHaveBeenCalledWith(
expect(updateMock).toBeCalledWith(
id,
tag,
generatedReleaseBody,
updateBody,
commit,
discussionCategory,
updateDraft,
@@ -365,19 +314,19 @@ describe("Action", () => {
updateName,
updatePrerelease
)
expect(uploadMock).not.toHaveBeenCalled()
expect(uploadMock).not.toBeCalled()
assertOutputApplied()
})
it("updates release then uploads artifact", async () => {
it('updates release then uploads artifact', async () => {
const action = createAction(true, true)
await action.perform()
expect(updateMock).toHaveBeenCalledWith(
expect(updateMock).toBeCalledWith(
id,
tag,
generatedReleaseBody,
updateBody,
commit,
discussionCategory,
updateDraft,
@@ -385,31 +334,23 @@ describe("Action", () => {
updateName,
updatePrerelease
)
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
expect(uploadMock).toBeCalledWith(artifacts, releaseId, url)
assertOutputApplied()
})
function assertOutputApplied() {
expect(applyReleaseDataMock).toHaveBeenCalledWith({
id: releaseId,
upload_url: url,
})
expect(applyReleaseDataMock).toBeCalledWith({id: releaseId, upload_url: url})
}
function createAction(
allowUpdates: boolean,
hasArtifact: boolean,
removeArtifacts = false,
generateReleaseNotes = true
): Action {
function createAction(allowUpdates: boolean,
hasArtifact: boolean,
removeArtifacts: boolean = false): Action {
let inputArtifact: Artifact[]
if (hasArtifact) {
inputArtifact = artifacts
} else {
inputArtifact = []
}
const MockReleases = jest.fn<Releases, any>(() => {
return {
create: createMock,
@@ -418,86 +359,79 @@ describe("Action", () => {
listArtifactsForRelease: listArtifactsMock,
listReleases: listMock,
update: updateMock,
uploadArtifact: jest.fn(),
generateReleaseNotes: genReleaseNotesMock,
uploadArtifact: jest.fn()
}
})
createMock.mockResolvedValue({
data: {
id: releaseId,
upload_url: url,
},
})
genReleaseNotesMock.mockResolvedValue({
data: {
body: generatedReleaseBody,
},
upload_url: url
}
})
getMock.mockResolvedValue({
data: {
id: id,
},
id: id
}
})
listMock.mockResolvedValue({
data: [],
data: []
})
shouldSkipMock.mockResolvedValue(false)
updateMock.mockResolvedValue({
data: {
id: releaseId,
upload_url: url,
},
upload_url: url
}
})
uploadMock.mockResolvedValue({})
const MockInputs = jest.fn<Inputs, any>(() => {
return {
allowUpdates,
allowUpdates: allowUpdates,
artifactErrorsFailBuild: true,
artifacts: inputArtifact,
createdDraft: createDraft,
createdReleaseBody: createBody,
createdReleaseName: createName,
commit,
discussionCategory,
generateReleaseNotes,
commit: commit,
discussionCategory: discussionCategory,
generateReleaseNotes: true,
makeLatest: makeLatest,
owner: "owner",
createdPrerelease: createPrerelease,
replacesArtifacts,
removeArtifacts,
replacesArtifacts: replacesArtifacts,
removeArtifacts: removeArtifacts,
repo: "repo",
skipIfReleaseExists: false,
tag,
token,
tag: tag,
token: token,
updatedDraft: updateDraft,
updatedReleaseBody: updateBody,
updatedReleaseName: updateName,
updatedPrerelease: updatePrerelease,
updateOnlyUnreleased: updateOnlyUnreleased,
updateOnlyUnreleased: updateOnlyUnreleased
}
})
const MockOutputs = jest.fn<Outputs, any>(() => {
return {
applyReleaseData: applyReleaseDataMock,
applyReleaseData: applyReleaseDataMock
}
})
const MockUploader = jest.fn<ArtifactUploader, any>(() => {
return {
uploadArtifacts: uploadMock,
uploadArtifacts: uploadMock
}
})
const MockArtifactDestroyer = jest.fn<ArtifactDestroyer, any>(() => {
return {
destroyArtifacts: artifactDestroyMock,
destroyArtifacts: artifactDestroyMock
}
})
const MockActionSkipper = jest.fn<ActionSkipper, any>(() => {
return {
shouldSkip: shouldSkipMock,
shouldSkip: shouldSkipMock
}
})

View File

@@ -1,5 +1,5 @@
import { ReleaseActionSkipper } from "../src/ActionSkipper"
import type { Releases } from "../src/Releases"
import {ActionSkipper, ReleaseActionSkipper} from "../src/ActionSkipper";
import {Releases} from "../src/Releases";
describe("shouldSkip", () => {
const getMock = jest.fn()
@@ -12,34 +12,33 @@ describe("shouldSkip", () => {
listArtifactsForRelease: jest.fn(),
listReleases: jest.fn(),
update: jest.fn(),
uploadArtifact: jest.fn(),
generateReleaseNotes: jest.fn(),
uploadArtifact: 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)
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())
const actionSkipper = new ReleaseActionSkipper(true, MockReleases(), tag)
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({})
const actionSkipper = new ReleaseActionSkipper(true, MockReleases(), tag)
expect(await actionSkipper.shouldSkip()).toBe(false)
})
it("should return true when release does exist", async () => {
getMock.mockResolvedValue({ data: {} })
it('should return true when release does exist', async () => {
getMock.mockResolvedValue({data: {}})
const actionSkipper = new ReleaseActionSkipper(true, MockReleases(), tag)
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 fakeReadStream = {}
jest.mock("fs", () => {
jest.mock('fs', () => {
return {
createReadStream: () => fakeReadStream,
statSync: () => {
return { size: contentLength }
},
}
return {size: contentLength}
}
};
})
describe("Artifact", () => {
it("defaults contentType to raw", () => {
const artifact = new Artifact("")
expect(artifact.contentType).toBe("raw")
it('defaults contentType to raw', () => {
const artifact = new Artifact('')
expect(artifact.contentType).toBe('raw')
})
it("generates name from path", () => {
const artifact = new Artifact("some/artifact")
expect(artifact.name).toBe("artifact")
it('generates name from path', () => {
const artifact = new Artifact('some/artifact')
expect(artifact.name).toBe('artifact')
})
it("provides contentLength", () => {
const artifact = new Artifact("some/artifact")
it('provides contentLength', () => {
const artifact = new Artifact('some/artifact')
expect(artifact.contentLength).toBe(contentLength)
})
it("provides path", () => {
const artifact = new Artifact("some/artifact")
expect(artifact.path).toBe("some/artifact")
it('provides path', () => {
const artifact = new Artifact('some/artifact')
expect(artifact.path).toBe('some/artifact')
})
it("reads artifact", () => {
const artifact = new Artifact("some/artifact")
it('reads artifact', () => {
const artifact = new Artifact('some/artifact')
expect(artifact.readFile()).toBe(fakeReadStream)
})
})
})

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,18 +1,18 @@
import { Action } from "../src/Action"
import * as github from "@actions/github"
import { Inputs } from "../src/Inputs"
import { GithubReleases, ReleaseData } from "../src/Releases"
import { GithubArtifactUploader } from "../src/ArtifactUploader"
import * as path from "path"
import { FileArtifactGlobber } from "../src/ArtifactGlobber"
import { Outputs } from "../src/Outputs"
import { GithubArtifactDestroyer } from "../src/ArtifactDestroyer"
import { ReleaseActionSkipper } from "../src/ActionSkipper"
import {Action} from "../src/Action";
import * as github from "@actions/github";
import {Inputs} from "../src/Inputs";
import {GithubReleases, ReleaseData} from "../src/Releases";
import {GithubArtifactUploader} from "../src/ArtifactUploader";
import * as path from "path";
import {FileArtifactGlobber} from "../src/ArtifactGlobber";
import {Outputs} from "../src/Outputs";
import {GithubArtifactDestroyer} from "../src/ArtifactDestroyer";
import {ReleaseActionSkipper} from "../src/ActionSkipper";
// 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
// - Remove skip from the test below
describe.skip("Integration Test", () => {
describe.skip('Integration Test', () => {
let action: Action
beforeEach(() => {
@@ -22,14 +22,18 @@ describe.skip("Integration Test", () => {
const inputs = getInputs()
const outputs = getOutputs()
const releases = new GithubReleases(inputs, git)
const uploader = new GithubArtifactUploader(releases, inputs.replacesArtifacts, inputs.artifactErrorsFailBuild)
const uploader = new GithubArtifactUploader(
releases,
inputs.replacesArtifacts,
inputs.artifactErrorsFailBuild,
)
const artifactDestroyer = new GithubArtifactDestroyer(releases)
const actionSkipper = new ReleaseActionSkipper(inputs.skipIfReleaseExists, releases, inputs.tag)
action = new Action(inputs, outputs, releases, uploader, artifactDestroyer, actionSkipper)
})
it("Performs action", async () => {
it('Performs action', async () => {
await action.perform()
})
@@ -43,7 +47,7 @@ describe.skip("Integration Test", () => {
createdReleaseBody: "This release was generated by release-action's integration test",
createdReleaseName: "Releases Action Integration Test",
commit: undefined,
discussionCategory: "Release",
discussionCategory: 'Release',
generateReleaseNotes: true,
owner: "ncipollo",
createdPrerelease: false,
@@ -57,10 +61,10 @@ describe.skip("Integration Test", () => {
updatedReleaseBody: "This release was updated by release-action's integration test",
updatedReleaseName: "Releases Action Integration Test",
updatedPrerelease: false,
updateOnlyUnreleased: false,
updateOnlyUnreleased: false
}
})
return new MockInputs()
return new MockInputs();
}
function getOutputs(): Outputs {
@@ -68,7 +72,7 @@ describe.skip("Integration Test", () => {
return {
applyReleaseData(releaseData: ReleaseData) {
console.log(`Release Data: ${releaseData}`)
},
}
}
})
return new MockOutputs()
@@ -76,7 +80,7 @@ describe.skip("Integration Test", () => {
function artifacts() {
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}`
return globber.globArtifactString(artifactString, "raw", false)
}
@@ -84,4 +88,5 @@ describe.skip("Integration Test", () => {
function getToken(): string {
return process.env.GITHUB_TOKEN ?? ""
}
})

View File

@@ -1,29 +1,29 @@
const mockSetOutput = jest.fn()
const mockSetOutput = jest.fn();
import { CoreOutputs, Outputs } from "../src/Outputs"
import { ReleaseData } from "../src/Releases"
import {CoreOutputs, Outputs} from "../src/Outputs";
import {ReleaseData} from "../src/Releases";
jest.mock("@actions/core", () => {
return { setOutput: mockSetOutput }
jest.mock('@actions/core', () => {
return {setOutput: mockSetOutput};
})
describe("Outputs", () => {
let outputs: Outputs
describe('Outputs', () => {
let outputs: Outputs;
let releaseData: ReleaseData
beforeEach(() => {
outputs = new CoreOutputs()
releaseData = {
id: 1,
html_url: "https://api.example.com/assets",
upload_url: "https://api.example.com",
html_url: 'https://api.example.com/assets',
upload_url: 'https://api.example.com'
}
})
it("Applies the release data to the action output", () => {
it('Applies the release data to the action output', () => {
outputs.applyReleaseData(releaseData)
expect(mockSetOutput).toBeCalledWith("id", releaseData.id)
expect(mockSetOutput).toBeCalledWith("html_url", releaseData.html_url)
expect(mockSetOutput).toBeCalledWith("upload_url", releaseData.upload_url)
expect(mockSetOutput).toBeCalledWith('id', releaseData.id)
expect(mockSetOutput).toBeCalledWith('html_url', releaseData.html_url)
expect(mockSetOutput).toBeCalledWith('upload_url', releaseData.upload_url)
})
})

View File

@@ -1,13 +1,13 @@
import { ReleaseValidator } from "../src/ReleaseValidator"
import {ReleaseValidator} from "../src/ReleaseValidator";
describe("validateReleaseUpdate", () => {
describe("updateOnlyUnreleased is disabled", () => {
const validator = new ReleaseValidator(false)
it("should not throw", () => {
it('should not throw', () => {
const releaseResponse = {
draft: false,
prerelease: false,
name: "Name",
name: "Name"
}
expect(() => {
validator.validateReleaseUpdate(releaseResponse)
@@ -16,59 +16,59 @@ describe("validateReleaseUpdate", () => {
})
describe("updateOnlyUnreleased is enabled", () => {
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 = {
draft: false,
prerelease: false,
name: "Name",
name: "Name"
}
expect(() => {
validator.validateReleaseUpdate(releaseResponse)
}).toThrow()
})
it("should not throw if draft is enabled", () => {
it('should not throw if draft is enabled', () => {
const releaseResponse = {
draft: true,
prerelease: false,
name: "Name",
name: "Name"
}
expect(() => {
validator.validateReleaseUpdate(releaseResponse)
}).not.toThrow()
})
it("should not throw if prerelease is enabled", () => {
it('should not throw if prerelease is enabled', () => {
const releaseResponse = {
draft: false,
prerelease: true,
name: "Name",
name: "Name"
}
expect(() => {
validator.validateReleaseUpdate(releaseResponse)
}).not.toThrow()
})
it("should not throw if draft & prerelease is enabled", () => {
it('should not throw if draft & prerelease is enabled', () => {
const releaseResponse = {
draft: true,
prerelease: true,
name: "Name",
name: "Name"
}
expect(() => {
validator.validateReleaseUpdate(releaseResponse)
}).not.toThrow()
})
it("should default error message release name to release", () => {
it('should default error message release name to release', () => {
const releaseResponse = {
draft: false,
prerelease: false,
name: null,
name: null
}
expect(() => {
validator.validateReleaseUpdate(releaseResponse)
}).toThrow(`Tried to update "release" which is neither a draft or prerelease. (updateOnlyUnreleased is on)`)
})
})
})
})

View File

@@ -1,35 +0,0 @@
{
"$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
}
}
}

7209
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

37
dist/licenses.txt vendored
View File

@@ -10,18 +10,6 @@ The above copyright notice and this permission notice shall be included in all c
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@actions/exec
MIT
The MIT License (MIT)
Copyright 2019 GitHub
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@actions/github
MIT
The MIT License (MIT)
@@ -59,18 +47,6 @@ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@actions/io
MIT
The MIT License (MIT)
Copyright 2019 GitHub
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@fastify/busboy
MIT
Copyright Brian White. All rights reserved.
@@ -767,6 +743,19 @@ The above copyright notice and this permission notice shall be included in all c
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
uuid
MIT
The MIT License (MIT)
Copyright (c) 2010-2020 Robert Kieffer and other contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
wrappy
ISC
The ISC License

View File

@@ -1,66 +1,76 @@
{
"name": "release-action",
"version": "1.1.0",
"private": true,
"description": "An action which manages a github release",
"main": "lib/main.js",
"scripts": {
"build": "tsc",
"clean": "rm -rf lib/*",
"coverage": "jest --coverage",
"debug": "yarn clean && yarn install && yarn build && yarn package",
"format": "yarn biome format --write .",
"package": "ncc build --source-map --license licenses.txt",
"release": "yarn clean && yarn install --production && yarn build && yarn package",
"test": "jest"
"name": "release-action",
"version": "1.1.0",
"private": true,
"description": "An action which manages a github release",
"main": "lib/main.js",
"scripts": {
"build": "tsc",
"clean": "rm -rf lib/*",
"coverage": "jest --coverage",
"debug": "yarn clean && yarn install && yarn build && yarn package",
"package": "ncc build --source-map --license licenses.txt",
"release": "yarn clean && yarn install --production && yarn build && yarn package",
"test": "jest"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ncipollo/release-action.git"
},
"keywords": [
"actions",
"node",
"setup"
],
"author": "GitHub",
"license": "MIT",
"engines": {
"node": ">=20"
},
"jest": {
"clearMocks": true,
"collectCoverage": true,
"coveragePathIgnorePatterns": [
"src/Globber.ts",
"src/Releases.ts"
],
"coverageThreshold": {
"global": {
"branches": 95,
"functions": 100,
"lines": 100,
"statements": 100
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/ncipollo/release-action.git"
"moduleFileExtensions": [
"js",
"ts"
],
"testEnvironment": "node",
"testMatch": [
"**/*.test.ts"
],
"testRunner": "jest-circus/runner",
"transform": {
"^.+\\.ts$": "ts-jest"
},
"keywords": ["actions", "node", "setup"],
"author": "GitHub",
"license": "MIT",
"engines": {
"node": ">=20"
},
"jest": {
"clearMocks": true,
"collectCoverage": true,
"coveragePathIgnorePatterns": ["src/Globber.ts", "src/Releases.ts"],
"coverageThreshold": {
"global": {
"branches": 95,
"functions": 100,
"lines": 100,
"statements": 100
}
},
"moduleFileExtensions": ["js", "ts"],
"testEnvironment": "node",
"testMatch": ["**/*.test.ts"],
"testRunner": "jest-circus/runner",
"transform": {
"^.+\\.ts$": "ts-jest"
},
"verbose": true
},
"dependencies": {
"@actions/core": "^1.11.1",
"@actions/github": "^6.0.0",
"@types/node": "^22.13.4",
"glob": "^11.0.1",
"untildify": "^4.0.0"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@types/jest": "^29.5.14",
"jest": "^29.7.0",
"jest-circus": "^29.7.0",
"ts-jest": "^29.2.5",
"typescript": "^5.7.3"
},
"resolutions": {
"jest-cli/yargs": "^17.3.1"
}
"verbose": true
},
"dependencies": {
"@actions/core": "^1.10.1",
"@actions/github": "^6.0.0",
"@types/node": "^20.10.6",
"glob": "^10.3.10",
"untildify": "^4.0.0"
},
"devDependencies": {
"@types/jest": "^29.5.11",
"jest": "^29.7.0",
"jest-circus": "^29.7.0",
"ts-jest": "^29.1.1",
"typescript": "^5.3.3"
},
"resolutions": {
"jest-cli/yargs": "^17.3.1"
}
}

View File

@@ -1,6 +0,0 @@
[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 type { ActionSkipper } from "./ActionSkipper"
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 {
import * as core from '@actions/core';
import {Inputs} from "./Inputs";
import {
CreateOrUpdateReleaseResponse,
CreateReleaseResponse,
ReleaseByTagResponse,
Releases,
UpdateReleaseResponse,
} from "./Releases"
UpdateReleaseResponse
} 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 {
private inputs: Inputs
@@ -21,17 +21,15 @@ export class Action {
private uploader: ArtifactUploader
private artifactDestroyer: ArtifactDestroyer
private skipper: ActionSkipper
private releaseValidator: ReleaseValidator
constructor(
inputs: Inputs,
outputs: Outputs,
releases: Releases,
uploader: ArtifactUploader,
artifactDestroyer: ArtifactDestroyer,
skipper: ActionSkipper
) {
constructor(inputs: Inputs,
outputs: Outputs,
releases: Releases,
uploader: ArtifactUploader,
artifactDestroyer: ArtifactDestroyer,
skipper: ActionSkipper) {
this.inputs = inputs
this.outputs = outputs
this.releases = releases
@@ -46,16 +44,16 @@ export class Action {
core.notice("Skipping action, release already exists and skipIfReleaseExists is enabled.")
return
}
const releaseResponse = await this.createOrUpdateRelease()
const releaseResponse = await this.createOrUpdateRelease();
const releaseData = releaseResponse.data
const releaseId = releaseData.id
const uploadUrl = releaseData.upload_url
if (this.inputs.removeArtifacts) {
await this.artifactDestroyer.destroyArtifacts(releaseId)
}
const artifacts = this.inputs.artifacts
if (artifacts.length > 0) {
await this.uploader.uploadArtifacts(artifacts, releaseId, uploadUrl)
@@ -72,7 +70,7 @@ export class Action {
} catch (error: any) {
return await this.checkForMissingReleaseError(error)
}
// Fail if this isn't an unreleased release & updateOnlyUnreleased is enabled.
this.releaseValidator.validateReleaseUpdate(getResponse.data)
@@ -91,17 +89,10 @@ export class Action {
}
private async updateRelease(id: number): Promise<UpdateReleaseResponse> {
let releaseBody = this.inputs.updatedReleaseBody
if (this.inputs.generateReleaseNotes) {
const response = await this.releases.generateReleaseNotes(this.inputs.tag)
releaseBody = response.data.body
}
return await this.releases.update(
id,
this.inputs.tag,
releaseBody,
this.inputs.updatedReleaseBody,
this.inputs.commit,
this.inputs.discussionCategory,
this.inputs.updatedDraft,
@@ -129,11 +120,7 @@ export class Action {
const tag = this.inputs.tag
const response = await this.releases.listReleases()
const releases = response.data
if (!releases) {
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
}

View File

@@ -1,20 +1,19 @@
import { Releases } from "./Releases"
import {Releases} from "./Releases";
export interface ActionSkipper {
shouldSkip(): Promise<boolean>
}
export class ReleaseActionSkipper {
constructor(
private skipIfReleaseExists: boolean,
private releases: Releases,
private tag: string
) {}
constructor(private skipIfReleaseExists: boolean,
private releases: Releases,
private tag: string) {
}
async shouldSkip(): Promise<boolean> {
if (!this.skipIfReleaseExists) {
// Bail if skip flag isn't set.
return false
return false;
}
try {
@@ -22,7 +21,7 @@ export class ReleaseActionSkipper {
return getResponse.data != null
} catch (error: any) {
// 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 { createReadStream, readFileSync, ReadStream, statSync } from "fs"
import { basename } from "path";
import {createReadStream, readFileSync, ReadStream, statSync} from "fs";
export class Artifact {
readonly contentType: string
@@ -9,7 +9,7 @@ export class Artifact {
constructor(path: string, contentType: string = "raw") {
this.path = path
this.name = basename(path)
this.contentType = contentType
this.contentType = contentType;
}
get contentLength(): number {

View File

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

View File

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

View File

@@ -1,29 +1,29 @@
import * as core from "@actions/core"
import { statSync } from "fs"
import * as core from "@actions/core";
import {statSync} from "fs";
export class ArtifactPathValidator {
private readonly errorsFailBuild: boolean
private paths: string[]
private readonly errorsFailBuild: boolean;
private paths: string[];
private readonly pattern: string
constructor(errorsFailBuild: boolean, paths: string[], pattern: string) {
this.paths = paths
this.paths = paths;
this.pattern = pattern
this.errorsFailBuild = errorsFailBuild
this.errorsFailBuild = errorsFailBuild;
}
validate(): string[] {
this.verifyPathsNotEmpty()
return this.paths.filter((path) => this.verifyNotDirectory(path))
}
private verifyPathsNotEmpty() {
if (this.paths.length == 0) {
const message = `Artifact pattern:${this.pattern} did not match any files`
this.reportError(message)
}
}
private verifyNotDirectory(path: string): boolean {
const isDir = statSync(path).isDirectory()
if (isDir) {
@@ -32,7 +32,7 @@ export class ArtifactPathValidator {
}
return !isDir
}
private reportError(message: string) {
if (this.errorsFailBuild) {
throw Error(message)
@@ -40,4 +40,4 @@ export class ArtifactPathValidator {
core.warning(message)
}
}
}
}

View File

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

View File

@@ -1,4 +1,4 @@
import { GithubErrorDetail } from "./GithubErrorDetail"
import {GithubErrorDetail} from "./GithubErrorDetail"
export class GithubError {
private error: any
@@ -40,7 +40,7 @@ export class GithubError {
private errorBulletedList(errors: GithubErrorDetail[]): string {
return errors.map((err) => `- ${err}`).join("\n")
}
private remediation(): String {
if (this.status == 404) {
return "\nMake sure your github token has access to the repo and has permission to author releases"
@@ -48,3 +48,4 @@ export class GithubError {
return ""
}
}

View File

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

View File

@@ -1,4 +1,5 @@
import { globSync } from "glob"
import {globSync} from "glob";
export interface Globber {
glob(pattern: string): string[]
@@ -8,4 +9,4 @@ export class FileGlobber implements Globber {
glob(pattern: string): string[] {
return globSync(pattern, { mark: true })
}
}
}

View File

@@ -1,8 +1,8 @@
import * as core from "@actions/core"
import { Context } from "@actions/github/lib/context"
import { readFileSync } from "fs"
import { ArtifactGlobber } from "./ArtifactGlobber"
import { Artifact } from "./Artifact"
import * as core from '@actions/core';
import {Context} from "@actions/github/lib/context";
import {readFileSync} from 'fs';
import {ArtifactGlobber} from './ArtifactGlobber';
import {Artifact} from './Artifact';
export interface Inputs {
readonly allowUpdates: boolean
@@ -40,52 +40,53 @@ export class CoreInputs implements Inputs {
}
get allowUpdates(): boolean {
const allow = core.getInput("allowUpdates")
return allow == "true"
const allow = core.getInput('allowUpdates')
return allow == 'true'
}
get artifacts(): Artifact[] {
let artifacts = core.getInput("artifacts")
let artifacts = core.getInput('artifacts')
if (!artifacts) {
artifacts = core.getInput("artifact")
artifacts = core.getInput('artifact')
}
if (artifacts) {
let contentType = core.getInput("artifactContentType")
let contentType = core.getInput('artifactContentType')
if (!contentType) {
contentType = "raw"
contentType = 'raw'
}
return this.artifactGlobber.globArtifactString(artifacts, contentType, this.artifactErrorsFailBuild)
return this.artifactGlobber
.globArtifactString(artifacts, contentType, this.artifactErrorsFailBuild)
}
return []
}
get artifactErrorsFailBuild(): boolean {
const allow = core.getInput("artifactErrorsFailBuild")
return allow == "true"
const allow = core.getInput('artifactErrorsFailBuild')
return allow == 'true'
}
private get body(): string | undefined {
const body = core.getInput("body")
const body = core.getInput('body')
if (body) {
return body
}
const bodyFile = core.getInput("bodyFile")
const bodyFile = core.getInput('bodyFile')
if (bodyFile) {
return this.stringFromFile(bodyFile)
}
return ""
return ''
}
get createdDraft(): boolean {
const draft = core.getInput("draft")
return draft == "true"
const draft = core.getInput('draft')
return draft == 'true'
}
get createdPrerelease(): boolean {
const preRelease = core.getInput("prerelease")
return preRelease == "true"
const preRelease = core.getInput('prerelease')
return preRelease == 'true'
}
get createdReleaseBody(): string | undefined {
@@ -94,7 +95,7 @@ export class CoreInputs implements Inputs {
}
private static get omitBody(): boolean {
return core.getInput("omitBody") == "true"
return core.getInput('omitBody') == 'true'
}
get createdReleaseName(): string | undefined {
@@ -103,11 +104,11 @@ export class CoreInputs implements Inputs {
}
private static get omitName(): boolean {
return core.getInput("omitName") == "true"
return core.getInput('omitName') == 'true'
}
get commit(): string | undefined {
const commit = core.getInput("commit")
const commit = core.getInput('commit')
if (commit) {
return commit
}
@@ -115,7 +116,7 @@ export class CoreInputs implements Inputs {
}
get discussionCategory(): string | undefined {
const category = core.getInput("discussionCategory")
const category = core.getInput('discussionCategory')
if (category) {
return category
}
@@ -123,7 +124,7 @@ export class CoreInputs implements Inputs {
}
private get name(): string | undefined {
const name = core.getInput("name")
const name = core.getInput('name')
if (name) {
return name
}
@@ -132,21 +133,21 @@ export class CoreInputs implements Inputs {
}
get generateReleaseNotes(): boolean {
const generate = core.getInput("generateReleaseNotes")
return generate == "true"
const generate = core.getInput('generateReleaseNotes')
return generate == 'true'
}
get makeLatest(): "legacy" | "true" | "false" | undefined {
let latest = core.getInput("makeLatest")
let latest = core.getInput('makeLatest')
if (latest == "true" || latest == "false" || latest == "legacy") {
return latest
return latest;
}
return undefined
}
get owner(): string {
let owner = core.getInput("owner")
let owner = core.getInput('owner')
if (owner) {
return owner
}
@@ -154,17 +155,17 @@ export class CoreInputs implements Inputs {
}
get removeArtifacts(): boolean {
const removes = core.getInput("removeArtifacts")
return removes == "true"
const removes = core.getInput('removeArtifacts')
return removes == 'true'
}
get replacesArtifacts(): boolean {
const replaces = core.getInput("replacesArtifacts")
return replaces == "true"
const replaces = core.getInput('replacesArtifacts')
return replaces == 'true'
}
get repo(): string {
let repo = core.getInput("repo")
let repo = core.getInput('repo')
if (repo) {
return repo
}
@@ -176,9 +177,9 @@ export class CoreInputs implements Inputs {
}
get tag(): string {
const tag = core.getInput("tag")
const tag = core.getInput('tag')
if (tag) {
return tag
return tag;
}
const ref = this.context.ref
@@ -191,7 +192,7 @@ export class CoreInputs implements Inputs {
}
get token(): string {
return core.getInput("token", { required: true })
return core.getInput('token', {required: true})
}
get updatedDraft(): boolean | undefined {
@@ -200,7 +201,7 @@ export class CoreInputs implements Inputs {
}
private static get omitDraftDuringUpdate(): boolean {
return core.getInput("omitDraftDuringUpdate") == "true"
return core.getInput('omitDraftDuringUpdate') == 'true'
}
get updatedPrerelease(): boolean | undefined {
@@ -209,7 +210,7 @@ export class CoreInputs implements Inputs {
}
private static get omitPrereleaseDuringUpdate(): boolean {
return core.getInput("omitPrereleaseDuringUpdate") == "true"
return core.getInput('omitPrereleaseDuringUpdate') == 'true'
}
get updatedReleaseBody(): string | undefined {
@@ -218,7 +219,7 @@ export class CoreInputs implements Inputs {
}
private static get omitBodyDuringUpdate(): boolean {
return core.getInput("omitBodyDuringUpdate") == "true"
return core.getInput('omitBodyDuringUpdate') == 'true'
}
get updatedReleaseName(): string | undefined {
@@ -227,14 +228,14 @@ export class CoreInputs implements Inputs {
}
get updateOnlyUnreleased(): boolean {
return core.getInput("updateOnlyUnreleased") == "true"
return core.getInput('updateOnlyUnreleased') == 'true'
}
private static get omitNameDuringUpdate(): boolean {
return core.getInput("omitNameDuringUpdate") == "true"
return core.getInput('omitNameDuringUpdate') == 'true'
}
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 core from "@actions/core"
import { CoreInputs } from "./Inputs"
import { GithubReleases } from "./Releases"
import { Action } from "./Action"
import { GithubArtifactUploader } from "./ArtifactUploader"
import { FileArtifactGlobber } from "./ArtifactGlobber"
import { GithubError } from "./GithubError"
import { CoreOutputs } from "./Outputs"
import { GithubArtifactDestroyer } from "./ArtifactDestroyer"
import { ActionSkipper, ReleaseActionSkipper } from "./ActionSkipper"
import * as github from '@actions/github';
import * as core from '@actions/core';
import {CoreInputs} from './Inputs';
import {GithubReleases} from './Releases';
import {Action} from './Action';
import {GithubArtifactUploader} from './ArtifactUploader';
import {FileArtifactGlobber} from './ArtifactGlobber';
import {GithubError} from './GithubError';
import {CoreOutputs} from "./Outputs";
import {GithubArtifactDestroyer} from "./ArtifactDestroyer";
import {ActionSkipper, ReleaseActionSkipper} from "./ActionSkipper";
async function run() {
try {
@@ -16,12 +16,12 @@ async function run() {
await action.perform()
} catch (error) {
const githubError = new GithubError(error)
core.setFailed(githubError.toString())
core.setFailed(githubError.toString());
}
}
function createAction(): Action {
const token = core.getInput("token")
const token = core.getInput('token')
const context = github.context
const git = github.getOctokit(token)
const globber = new FileArtifactGlobber()
@@ -32,8 +32,8 @@ function createAction(): Action {
const skipper = new ReleaseActionSkipper(inputs.skipIfReleaseExists, releases, inputs.tag)
const uploader = new GithubArtifactUploader(releases, inputs.replacesArtifacts, inputs.artifactErrorsFailBuild)
const artifactDestroyer = new GithubArtifactDestroyer(releases)
return new Action(inputs, outputs, releases, uploader, artifactDestroyer, skipper)
}
run()
run();

View File

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

View File

@@ -1,7 +1,7 @@
import path from "path"
import path from "path";
export class PathNormalizer {
static normalizePath(pathString: string): string {
return pathString.split(path.sep).join("/")
}
}
}
}

View File

@@ -1,5 +1,6 @@
export class ReleaseValidator {
constructor(private updateOnlyUnreleased: boolean) {}
constructor(private updateOnlyUnreleased: boolean) {
}
validateReleaseUpdate(releaseResponse: ReleaseStageArguments) {
if (!this.updateOnlyUnreleased) {
@@ -7,9 +8,7 @@ export class ReleaseValidator {
}
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)`)
}
}
}
@@ -18,4 +17,4 @@ export type ReleaseStageArguments = {
draft: boolean
name: string | null
prerelease: boolean
}
}

View File

@@ -1,7 +1,7 @@
import type { GitHub } from "@actions/github/lib/utils"
import type { RestEndpointMethodTypes } from "@octokit/plugin-rest-endpoint-methods"
import type { OctokitResponse } from "@octokit/types"
import type { Inputs } from "./Inputs"
import {GitHub} from '@actions/github/lib/utils'
import {OctokitResponse} from "@octokit/types";
import {RestEndpointMethodTypes} from "@octokit/plugin-rest-endpoint-methods";
import {Inputs} from "./Inputs";
export type CreateReleaseResponse = RestEndpointMethodTypes["repos"]["createRelease"]["response"]
export type ReleaseByTagResponse = RestEndpointMethodTypes["repos"]["getReleaseByTag"]["response"]
@@ -10,7 +10,6 @@ export type ListReleaseAssetsResponseData = RestEndpointMethodTypes["repos"]["li
export type UpdateReleaseResponse = RestEndpointMethodTypes["repos"]["updateRelease"]["response"]
export type UploadArtifactResponse = RestEndpointMethodTypes["repos"]["uploadReleaseAsset"]["response"]
export type CreateOrUpdateReleaseResponse = CreateReleaseResponse | UpdateReleaseResponse
export type GenerateReleaseNotesResponse = RestEndpointMethodTypes["repos"]["generateReleaseNotes"]["response"]
export type ReleaseData = {
id: number
@@ -26,7 +25,7 @@ export interface Releases {
discussionCategory?: string,
draft?: boolean,
generateReleaseNotes?: boolean,
makeLatest?: "legacy" | "true" | "false" | undefined,
makeLatest?: "legacy" | "true" | "false" | undefined,
name?: string,
prerelease?: boolean
): Promise<CreateReleaseResponse>
@@ -35,8 +34,6 @@ export interface Releases {
getByTag(tag: string): Promise<ReleaseByTagResponse>
generateReleaseNotes(tag: string): Promise<GenerateReleaseNotesResponse>
listArtifactsForRelease(releaseId: number): Promise<ListReleaseAssetsResponseData>
listReleases(): Promise<ListReleasesResponse>
@@ -48,7 +45,7 @@ export interface Releases {
commitHash?: string,
discussionCategory?: string,
draft?: boolean,
makeLatest?: "legacy" | "true" | "false" | undefined,
makeLatest?: "legacy" | "true" | "false" | undefined,
name?: string,
prerelease?: boolean
): Promise<UpdateReleaseResponse>
@@ -59,7 +56,7 @@ export interface Releases {
contentType: string,
file: string | object,
name: string,
releaseId: number
releaseId: number,
): Promise<UploadArtifactResponse>
}
@@ -79,7 +76,7 @@ export class GithubReleases implements Releases {
discussionCategory?: string,
draft?: boolean,
generateReleaseNotes?: boolean,
makeLatest?: "legacy" | "true" | "false" | undefined,
makeLatest?: "legacy" | "true" | "false" | undefined,
name?: string,
prerelease?: boolean
): Promise<CreateReleaseResponse> {
@@ -95,23 +92,17 @@ export class GithubReleases implements Releases {
prerelease: prerelease,
repo: this.inputs.repo,
target_commitish: commitHash,
tag_name: tag,
tag_name: tag
})
}
async deleteArtifact(assetId: number): Promise<OctokitResponse<any>> {
async deleteArtifact(
assetId: number
): Promise<OctokitResponse<any>> {
return this.git.rest.repos.deleteReleaseAsset({
asset_id: assetId,
owner: this.inputs.owner,
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,
repo: this.inputs.repo
})
}
@@ -119,22 +110,24 @@ export class GithubReleases implements Releases {
return this.git.rest.repos.getReleaseByTag({
owner: this.inputs.owner,
repo: this.inputs.repo,
tag: tag,
tag: tag
})
}
async listArtifactsForRelease(releaseId: number): Promise<ListReleaseAssetsResponseData> {
async listArtifactsForRelease(
releaseId: number
): Promise<ListReleaseAssetsResponseData> {
return this.git.paginate(this.git.rest.repos.listReleaseAssets, {
owner: this.inputs.owner,
release_id: releaseId,
repo: this.inputs.repo,
repo: this.inputs.repo
})
}
async listReleases(): Promise<ListReleasesResponse> {
return this.git.rest.repos.listReleases({
owner: this.inputs.owner,
repo: this.inputs.repo,
repo: this.inputs.repo
})
}
@@ -145,7 +138,7 @@ export class GithubReleases implements Releases {
commitHash?: string,
discussionCategory?: string,
draft?: boolean,
makeLatest?: "legacy" | "true" | "false" | undefined,
makeLatest?: "legacy" | "true" | "false" | undefined,
name?: string,
prerelease?: boolean
): Promise<UpdateReleaseResponse> {
@@ -161,7 +154,7 @@ export class GithubReleases implements Releases {
prerelease: prerelease,
repo: this.inputs.repo,
target_commitish: commitHash,
tag_name: tag,
tag_name: tag
})
}
@@ -171,19 +164,19 @@ export class GithubReleases implements Releases {
contentType: string,
file: string | object,
name: string,
releaseId: number
releaseId: number,
): Promise<UploadArtifactResponse> {
return this.git.rest.repos.uploadReleaseAsset({
url: assetUrl,
headers: {
"content-length": contentLength,
"content-type": contentType,
"content-type": contentType
},
data: file as any,
name: name,
owner: this.inputs.owner,
release_id: releaseId,
repo: this.inputs.repo,
repo: this.inputs.repo
})
}
}

View File

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

351
yarn.lock
View File

@@ -2,20 +2,13 @@
# yarn lockfile v1
"@actions/core@^1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.11.1.tgz#ae683aac5112438021588030efb53b1adb86f172"
integrity sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==
"@actions/core@^1.10.1":
version "1.10.1"
resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.10.1.tgz#61108e7ac40acae95ee36da074fa5850ca4ced8a"
integrity sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g==
dependencies:
"@actions/exec" "^1.1.1"
"@actions/http-client" "^2.0.1"
"@actions/exec@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@actions/exec/-/exec-1.1.1.tgz#2e43f28c54022537172819a7cf886c844221a611"
integrity sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==
dependencies:
"@actions/io" "^1.0.1"
uuid "^8.3.2"
"@actions/github@^6.0.0":
version "6.0.0"
@@ -35,11 +28,6 @@
tunnel "^0.0.6"
undici "^5.25.4"
"@actions/io@^1.0.1":
version "1.1.3"
resolved "https://registry.yarnpkg.com/@actions/io/-/io-1.1.3.tgz#4cdb6254da7962b07473ff5c335f3da485d94d71"
integrity sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==
"@ampproject/remapping@^2.1.0":
version "2.1.2"
resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.1.2.tgz#4edca94973ded9630d20101cd8559cedb8d8bd34"
@@ -571,60 +559,6 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
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":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.0.tgz#0709e9f4cb252351c609c6e6d8d6779a8d25edff"
@@ -980,11 +914,6 @@
resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-19.1.0.tgz#75ec7e64743870fc73e1ab4bc6ec252ecdd624dc"
integrity sha512-6G+ywGClliGQwRsjvqVYpklIfa7oRPA0vyhPQG/1Feh+B+wU0vGH1JiJ5T25d3g1JZYBHzR2qefLi9x8Gt+cpw==
"@octokit/openapi-types@^23.0.1":
version "23.0.1"
resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-23.0.1.tgz#3721646ecd36b596ddb12650e0e89d3ebb2dd50e"
integrity sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==
"@octokit/plugin-paginate-rest@^9.0.0":
version "9.1.5"
resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.1.5.tgz#1705bcef4dcde1f4015ee58a63dc61b68648f480"
@@ -1000,11 +929,11 @@
"@octokit/types" "^12.3.0"
"@octokit/request-error@^5.0.0":
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==
version "5.0.1"
resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-5.0.1.tgz#277e3ce3b540b41525e07ba24c5ef5e868a72db9"
integrity sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ==
dependencies:
"@octokit/types" "^13.1.0"
"@octokit/types" "^12.0.0"
deprecation "^2.0.0"
once "^1.4.0"
@@ -1025,13 +954,6 @@
dependencies:
"@octokit/openapi-types" "^19.1.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":
version "0.11.0"
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
@@ -1115,20 +1037,20 @@
dependencies:
"@types/istanbul-lib-report" "*"
"@types/jest@^29.5.14":
version "29.5.14"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.14.tgz#2b910912fa1d6856cadcd0c1f95af7df1d6049e5"
integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==
"@types/jest@^29.5.11":
version "29.5.11"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.11.tgz#0c13aa0da7d0929f078ab080ae5d4ced80fa2f2c"
integrity sha512-S2mHmYIVe13vrm6q4kN6fLYYAka15ALQki/vgDC3mIukEOx8WJlv0kQPM+d4w8Gp6u0uSdKND04IlTXBv0rwnQ==
dependencies:
expect "^29.0.0"
pretty-format "^29.0.0"
"@types/node@*", "@types/node@^22.13.4":
version "22.13.4"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.4.tgz#3fe454d77cd4a2d73c214008b3e331bfaaf5038a"
integrity sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==
"@types/node@*", "@types/node@^20.10.6":
version "20.10.6"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.6.tgz#a3ec84c22965802bf763da55b2394424f22bfbb5"
integrity sha512-Vac8H+NlRNNlAmDfGUP7b5h/KA+AtWIzuXy0E6OyP8f1tCLYAtPvKRRDJjAPqhpCb0t6U2j7/xqAuLEebW2kiw==
dependencies:
undici-types "~6.20.0"
undici-types "~5.26.4"
"@types/stack-utils@^2.0.0":
version "2.0.0"
@@ -1203,11 +1125,6 @@ argparse@^1.0.7:
dependencies:
sprintf-js "~1.0.2"
async@^3.2.3:
version "3.2.5"
resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66"
integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==
babel-jest@^29.7.0:
version "29.7.0"
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5"
@@ -1293,12 +1210,12 @@ brace-expansion@^2.0.1:
dependencies:
balanced-match "^1.0.0"
braces@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
braces@^3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
dependencies:
fill-range "^7.1.1"
fill-range "^7.0.1"
browserslist@^4.17.5:
version "4.19.3"
@@ -1322,7 +1239,7 @@ browserslist@^4.20.2:
node-releases "^2.0.3"
picocolors "^1.0.0"
bs-logger@^0.2.6:
bs-logger@0.x:
version "0.2.6"
resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8"
integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==
@@ -1383,14 +1300,6 @@ chalk@^4.0.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chalk@^4.0.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
char-regex@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"
@@ -1480,9 +1389,9 @@ create-jest@^29.7.0:
prompts "^2.0.1"
cross-spawn@^7.0.0, cross-spawn@^7.0.3:
version "7.0.6"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
dependencies:
path-key "^3.1.0"
shebang-command "^2.0.0"
@@ -1525,13 +1434,6 @@ eastasianwidth@^0.2.0:
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
ejs@^3.1.10:
version "3.1.10"
resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b"
integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==
dependencies:
jake "^10.8.5"
electron-to-chromium@^1.4.118:
version "1.4.147"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.147.tgz#1ecf318737b21ba1e5b53319eb1edf8143892270"
@@ -1627,17 +1529,10 @@ fb-watchman@^2.0.0:
dependencies:
bser "2.1.1"
filelist@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5"
integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==
dependencies:
minimatch "^5.0.1"
fill-range@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
dependencies:
to-regex-range "^5.0.1"
@@ -1692,17 +1587,16 @@ get-stream@^6.0.0:
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
glob@^11.0.1:
version "11.0.1"
resolved "https://registry.yarnpkg.com/glob/-/glob-11.0.1.tgz#1c3aef9a59d680e611b53dcd24bb8639cef064d9"
integrity sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==
glob@^10.3.10:
version "10.3.10"
resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.10.tgz#0351ebb809fd187fe421ab96af83d3a70715df4b"
integrity sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==
dependencies:
foreground-child "^3.1.0"
jackspeak "^4.0.1"
minimatch "^10.0.0"
minipass "^7.1.2"
package-json-from-dist "^1.0.0"
path-scurry "^2.0.0"
jackspeak "^2.3.5"
minimatch "^9.0.1"
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
path-scurry "^1.10.1"
glob@^7.1.3, glob@^7.1.4:
version "7.2.0"
@@ -1874,25 +1768,15 @@ istanbul-reports@^3.1.3:
html-escaper "^2.0.0"
istanbul-lib-report "^3.0.0"
jackspeak@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-4.0.1.tgz#9fca4ce961af6083e259c376e9e3541431f5287b"
integrity sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==
jackspeak@^2.3.5:
version "2.3.6"
resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8"
integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==
dependencies:
"@isaacs/cliui" "^8.0.2"
optionalDependencies:
"@pkgjs/parseargs" "^0.11.0"
jake@^10.8.5:
version "10.9.2"
resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f"
integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==
dependencies:
async "^3.2.3"
chalk "^4.0.2"
filelist "^1.0.4"
minimatch "^3.1.2"
jest-changed-files@^29.7.0:
version "29.7.0"
resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a"
@@ -2301,20 +2185,27 @@ locate-path@^5.0.0:
dependencies:
p-locate "^4.1.0"
lodash.memoize@^4.1.2:
lodash.memoize@4.x:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
lodash@^4.17.19:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
lru-cache@^11.0.0:
version "11.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.0.0.tgz#15d93a196f189034d7166caf9fe55e7384c98a21"
integrity sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
dependencies:
yallist "^4.0.0"
"lru-cache@^9.1.1 || ^10.0.0":
version "10.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.0.tgz#b9e2a6a72a129d81ab317202d93c7691df727e61"
integrity sha512-svTf/fzsKHffP42sujkO/Rjs37BCIsQVRCeNYIm9WN8rgT7ffoUnRtZCqU+6BqcSBdv8gwJeTz8knJpgACeQMw==
make-dir@^3.0.0:
version "3.1.0"
@@ -2323,7 +2214,7 @@ make-dir@^3.0.0:
dependencies:
semver "^6.0.0"
make-error@^1.3.6:
make-error@1.x:
version "1.3.6"
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
@@ -2341,43 +2232,36 @@ merge-stream@^2.0.0:
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
micromatch@^4.0.4:
version "4.0.8"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202"
integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
version "4.0.4"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9"
integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==
dependencies:
braces "^3.0.3"
picomatch "^2.3.1"
braces "^3.0.1"
picomatch "^2.2.3"
mimic-fn@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
minimatch@^10.0.0:
version "10.0.1"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.0.1.tgz#ce0521856b453c86e25f2c4c0d03e6ff7ddc440b"
integrity sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==
dependencies:
brace-expansion "^2.0.1"
minimatch@^3.0.4, minimatch@^3.1.2:
minimatch@^3.0.4:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
dependencies:
brace-expansion "^1.1.7"
minimatch@^5.0.1:
version "5.1.6"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96"
integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
minimatch@^9.0.1:
version "9.0.1"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.1.tgz#8a555f541cf976c622daf078bb28f29fb927c253"
integrity sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==
dependencies:
brace-expansion "^2.0.1"
minipass@^7.1.2:
version "7.1.2"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
"minipass@^5.0.0 || ^6.0.2 || ^7.0.0":
version "7.0.3"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.3.tgz#05ea638da44e475037ed94d1c7efcc76a25e1974"
integrity sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==
ms@2.1.2:
version "2.1.2"
@@ -2456,11 +2340,6 @@ p-try@^2.0.0:
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
package-json-from-dist@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz#e501cd3094b278495eb4258d4c9f6d5ac3019f00"
integrity sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==
parse-json@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
@@ -2491,13 +2370,13 @@ path-parse@^1.0.6:
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
path-scurry@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.0.tgz#9f052289f23ad8bf9397a2a0425e7b8615c58580"
integrity sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==
path-scurry@^1.10.1:
version "1.10.1"
resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.1.tgz#9ba6bf5aa8500fe9fd67df4f0d9483b2b0bfc698"
integrity sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==
dependencies:
lru-cache "^11.0.0"
minipass "^7.1.2"
lru-cache "^9.1.1 || ^10.0.0"
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
picocolors@^1.0.0:
version "1.0.0"
@@ -2514,11 +2393,6 @@ picomatch@^2.2.3:
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==
picomatch@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
pirates@^4.0.4:
version "4.0.5"
resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b"
@@ -2598,10 +2472,12 @@ semver@^6.0.0, semver@^6.3.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
semver@^7.5.3, semver@^7.5.4, semver@^7.6.3:
version "7.6.3"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
semver@^7.5.3, semver@^7.5.4:
version "7.5.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
dependencies:
lru-cache "^6.0.0"
shebang-command@^2.0.0:
version "2.0.0"
@@ -2788,20 +2664,19 @@ to-regex-range@^5.0.1:
dependencies:
is-number "^7.0.0"
ts-jest@^29.2.5:
version "29.2.5"
resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.2.5.tgz#591a3c108e1f5ebd013d3152142cb5472b399d63"
integrity sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==
ts-jest@^29.1.1:
version "29.1.1"
resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.1.tgz#f58fe62c63caf7bfcc5cc6472082f79180f0815b"
integrity sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==
dependencies:
bs-logger "^0.2.6"
ejs "^3.1.10"
fast-json-stable-stringify "^2.1.0"
bs-logger "0.x"
fast-json-stable-stringify "2.x"
jest-util "^29.0.0"
json5 "^2.2.3"
lodash.memoize "^4.1.2"
make-error "^1.3.6"
semver "^7.6.3"
yargs-parser "^21.1.1"
lodash.memoize "4.x"
make-error "1.x"
semver "^7.5.3"
yargs-parser "^21.0.1"
tunnel@^0.0.6:
version "0.0.6"
@@ -2818,20 +2693,20 @@ type-fest@^0.11.0:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1"
integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==
typescript@^5.7.3:
version "5.7.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.3.tgz#919b44a7dbb8583a9b856d162be24a54bf80073e"
integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==
typescript@^5.3.3:
version "5.3.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37"
integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==
undici-types@~6.20.0:
version "6.20.0"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433"
integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==
undici-types@~5.26.4:
version "5.26.5"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
undici@^5.25.4:
version "5.28.5"
resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.5.tgz#b2b94b6bf8f1d919bc5a6f31f2c01deb02e54d4b"
integrity sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA==
version "5.28.2"
resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.2.tgz#fea200eac65fc7ecaff80a023d1a0543423b4c91"
integrity sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w==
dependencies:
"@fastify/busboy" "^2.0.0"
@@ -2845,6 +2720,11 @@ untildify@^4.0.0:
resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b"
integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==
uuid@^8.3.2:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
v8-to-istanbul@^9.0.1:
version "9.1.0"
resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz#1b83ed4e397f58c85c266a570fc2558b5feb9265"
@@ -2904,10 +2784,15 @@ y18n@^5.0.5:
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
yargs-parser@^21.0.0, yargs-parser@^21.1.1:
version "21.1.1"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
yallist@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yargs-parser@^21.0.0, yargs-parser@^21.0.1:
version "21.0.1"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.1.tgz#0267f286c877a4f0f728fceb6f8a3e4cb95c6e35"
integrity sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==
yargs@^17.3.1:
version "17.5.1"