1 Commits

Author SHA1 Message Date
Nick Cipollo
2792aea870 Prepate 1.10.1 release
Some checks failed
Test / check_pr (push) Has been cancelled
2022-10-02 15:18:23 -04:00
45 changed files with 5098 additions and 8885 deletions

BIN
.DS_Store vendored

Binary file not shown.

5
.gitignore vendored
View File

@@ -95,7 +95,4 @@ fabric.properties
# End of https://www.gitignore.io/api/webstorm
# Coverage
coverage
# Ignore lib, it contains intermediates
/lib
coverage

View File

@@ -2,16 +2,6 @@
This action will create a GitHub release and optionally upload an artifact to it.
<div align="center">
<strong>
<samp>
[English](README.md) · [简体中文](README.zh-Hans.md)
</samp>
</strong>
</div>
## Action Inputs
| Input name | Description | Required | Default Value |
|----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------|----------------------|
@@ -25,7 +15,6 @@ This action will create a GitHub release and optionally upload an artifact to it
| 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 | false | "" |
| draft | Optionally marks this release as a draft release. Set to true to enable. | false | "" |
| generateReleaseNotes | Indicates if release notes should be automatically generated. | false | false |
| makeLatest | Indicates if the release should be the "latest" release or not. legacy specifies that the latest release should be determined based on the release creation date and higher semantic version. | false | "legacy" |
| name | An optional name for the release. If this is omitted the tag will be used. | false | "" |
| omitBody | Indicates if the release body should be omitted. | false | false |
| omitBodyDuringUpdate | Indicates if the release body should be omitted during updates. The body will still be applied for newly created releases. This will preserve the existing body during updates. | false | false |
@@ -33,15 +22,13 @@ This action will create a GitHub release and optionally upload an artifact to it
| omitName | Indicates if the release name should be omitted. | false | false |
| omitNameDuringUpdate | Indicates if the release name should be omitted during updates. The name will still be applied for newly created releases. This will preserve the existing name during updates. | false | false |
| omitPrereleaseDuringUpdate | Indicates if the prerelease flag should be omitted during updates. The prerelease flag will still be applied for newly created releases. This will preserve the existing prerelease state during updates. | false | false |
| owner | Optionally specify the owner of the repo where the release should be generated. Defaults to current repo's owner. | false | "current repo owner" |
| owner | Optionally specify the owner of the repo where the release should be generated. Defaults to current repo's owner. | false | "current repo owner" |
| prerelease | Optionally marks this release as prerelease. Set to true to enable. | false | "" |
| removeArtifacts | Indicates if existing release artifacts should be removed. | false | false |
| replacesArtifacts | Indicates if existing release artifacts should be replaced. | false | true |
| repo | Optionally specify the repo where the release should be generated. | false | current repo |
| skipIfReleaseExists | When skipIfReleaseExists is enabled the action will be skipped if a non-draft release already exists for the provided tag. | false | false |
| tag | An optional tag for the release. If this is omitted the git ref will be used (if it is a tag). | false | "" |
| token | The GitHub token. This will default to the GitHub app token. This is primarily useful if you want to use your personal token (for targeting other repos, etc). If you are using a personal access token it should have access to the `repo` scope. | false | github.token |
| updateOnlyUnreleased | When allowUpdates is enabled, this will fail the action if the release it is updating is not a draft or a prerelease. | false | false |
## Action Outputs
| Output name | Description |
@@ -68,11 +55,13 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- uses: ncipollo/release-action@v1
with:
artifacts: "release.tar.gz,foo/*.txt"
bodyFile: "body.md"
token: ${{ secrets.YOUR_GITHUB_TOKEN }}
```
## Notes

View File

@@ -1,83 +0,0 @@
# Release Action
此操作将创建一个 GitHub Release并可选择将产出文件上传到其中。
<div align="center">
<strong>
<samp>
[English](README.md) · [简体中文](README.zh-Hans.md)
</samp>
</strong>
</div>
## Action 输入
| 输入名称 | 描述 | 必选 | 默认值 |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | -------------------- |
| allowUpdates | 一个可选标志,表示如果版本已经存在,我们是否应该更新它。默认值为 false。 | false | "" |
| artifactErrorsFailBuild | 一个可选标志,表示读取或上传产出文件错误时是否应该使构建失败。 | false | "" |
| artifacts | 一组可选的路径,表示要上传到版本的产出文件。 这可能是单个路径或以逗号分隔的路径列表(或 globs | false | "" |
| artifactContentType | 产出文件的内容类型。 默认为 raw | false | "" |
| body | 发布的可选主体。 | false | "" |
| bodyFile | 发布的可选正文文件。 这应该是文件的路径。 | false | "" |
| commit | 一个可选的提交 ref。 如果标签不存在,将用于创建标签。 | false | "" |
| discussionCategory | 当提供该选项时,将生成对指定类别的 discussion。类别必须存在否则将导致 Action 失败。这在草案发布中没有使用 | false | "" |
| draft | 可选择将此版本标记为草稿版本。 设置为 true 以启用。 | false | "" |
| generateReleaseNotes | 指示是否应自动生成发行说明。 | false | false |
| name | 版本的可选名称。 如果省略,将使用标签。 | false | "" |
| omitBody | 指示是否应省略发布主体。 | false | false |
| omitBodyDuringUpdate | 指示在更新期间是否应省略发布主体。 正文仍将应用于新创建的版本。 这将在更新期间保留现有正文。 | false | false |
| omitDraftDuringUpdate | 指示是否应在更新期间省略草稿标志。 草稿标志仍将应用于新创建的版本。 这将在更新期间保留现有的草稿状态。 | false | false |
| omitName | 指示是否应省略版本名称。 | false | false |
| omitNameDuringUpdate | 指示在更新期间是否应省略版本名称。 该名称仍将应用于新创建的版本。 这将在更新期间保留现有名称。 | false | false |
| omitPrereleaseDuringUpdate | 指示在更新期间是否应省略预发布标志。 预发布标志仍将应用于新创建的版本。 这将在更新期间保留现有的预发布状态。 | false | false |
| owner | (可选)指定应在其中生成版本的存储库的所有者。 默认为当前存储库的所有者。 | false | "current repo owner" |
| prerelease | 可选择将此版本标记为预发布。 设置为 true 以启用。 | false | "" |
| removeArtifacts | 指示是否应删除现有的发布产出文件。 | false | false |
| replacesArtifacts | 指示是否应替换现有的发布产出文件。 | false | true |
| repo | (可选)指定应在其中生成版本的存储库。 | false | current repo |
| tag | 发布的可选标签。 如果省略,将使用 git ref (如果它是标签)。 | false | "" |
| token | GitHub 令牌。 这将默认为 GitHub 应用程序令牌。 如果您想使用您的个人令牌(用于定位其他存储库等),这主要是有用的。 如果您使用的是个人访问令牌,它应该可以访问 `repo` 范围。 | false | github.token |
| updateOnlyUnreleased | 启用 allowUpdates 后,如果它正在更新的版本不是草稿或预发布,则该操作将失败。 | false | false |
## Action 输出
| 输出名称 | 描述 |
| ---------- | ------------------------ |
| id | 创建的版本的标识符。 |
| html_url | 版本的 HTML URL。 |
| upload_url | 将资产上传到版本的 URL。 |
## 示例
此示例将在推送一个标签时创建一个 Release
```yml
name: Releases
on:
push:
tags:
- "*"
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v2
- uses: ncipollo/release-action@v1
with:
artifacts: "release.tar.gz,foo/*.txt"
bodyFile: "body.md"
token: ${{ secrets.YOUR_GITHUB_TOKEN }}
```
## 注意
- 您必须通过 Action 输入或 git ref 提供一个标签(即推送/创建标签。如果不提供标签Action 将会失败。
- 如果您正在创建的版本的标签不存在,您应该同时设置标签和提交 Action 输入。 commit 可以指向提交 Hash 或分支名称(例如 - main
- 在上面的示例中,只需要指定操作的权限(即 contents: write。 如果您将其他操作添加到同一工作流程,则应相应地扩展权限。

View File

@@ -5,18 +5,16 @@ 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()
const createMock = jest.fn()
const deleteMock = jest.fn()
const getMock = jest.fn()
const listArtifactsMock = jest.fn()
const listMock = jest.fn()
const shouldSkipMock = jest.fn()
const updateMock = jest.fn()
const uploadMock = jest.fn()
const artifactDestroyMock = jest.fn()
const artifacts = [
new Artifact('a/art1'),
@@ -39,16 +37,13 @@ const updateBody = 'updateBody'
const updateDraft = false
const updateName = 'updateName'
const updatePrerelease = false
const updateOnlyUnreleased = false
const url = 'http://api.example.com'
const makeLatest = 'legacy'
describe("Action", () => {
beforeEach(() => {
createMock.mockClear()
getMock.mockClear()
listMock.mockClear()
shouldSkipMock.mockClear()
updateMock.mockClear()
uploadMock.mockClear()
})
@@ -64,7 +59,6 @@ describe("Action", () => {
discussionCategory,
createDraft,
generateReleaseNotes,
makeLatest,
createName,
createPrerelease)
expect(uploadMock).not.toBeCalled()
@@ -85,7 +79,6 @@ describe("Action", () => {
discussionCategory,
createDraft,
generateReleaseNotes,
makeLatest,
createName,
createPrerelease)
expect(uploadMock).toBeCalledWith(artifacts, releaseId, url)
@@ -111,7 +104,6 @@ describe("Action", () => {
discussionCategory,
createDraft,
generateReleaseNotes,
makeLatest,
createName,
createPrerelease
)
@@ -132,7 +124,6 @@ describe("Action", () => {
discussionCategory,
createDraft,
generateReleaseNotes,
makeLatest,
createName,
createPrerelease
)
@@ -158,16 +149,6 @@ describe("Action", () => {
assertOutputApplied()
})
it('skips action', async () => {
const action = createAction(false, false, false)
shouldSkipMock.mockResolvedValue(true)
await action.perform()
expect(createMock).not.toBeCalled()
expect(updateMock).not.toBeCalled()
})
it('throws error when create fails', async () => {
const action = createAction(false, true)
createMock.mockRejectedValue("error")
@@ -186,7 +167,6 @@ describe("Action", () => {
discussionCategory,
createDraft,
generateReleaseNotes,
makeLatest,
createName,
createPrerelease
)
@@ -237,7 +217,6 @@ describe("Action", () => {
commit,
discussionCategory,
updateDraft,
makeLatest,
updateName,
updatePrerelease
)
@@ -263,7 +242,6 @@ describe("Action", () => {
discussionCategory,
createDraft,
generateReleaseNotes,
makeLatest,
createName,
createPrerelease
)
@@ -290,7 +268,6 @@ describe("Action", () => {
commit,
discussionCategory,
updateDraft,
makeLatest,
updateName,
updatePrerelease
)
@@ -310,7 +287,6 @@ describe("Action", () => {
commit,
discussionCategory,
updateDraft,
makeLatest,
updateName,
updatePrerelease
)
@@ -330,7 +306,6 @@ describe("Action", () => {
commit,
discussionCategory,
updateDraft,
makeLatest,
updateName,
updatePrerelease
)
@@ -342,9 +317,7 @@ describe("Action", () => {
expect(applyReleaseDataMock).toBeCalledWith({id: releaseId, upload_url: url})
}
function createAction(allowUpdates: boolean,
hasArtifact: boolean,
removeArtifacts: boolean = false): Action {
function createAction(allowUpdates: boolean, hasArtifact: boolean, removeArtifacts: boolean = false): Action {
let inputArtifact: Artifact[]
if (hasArtifact) {
inputArtifact = artifacts
@@ -377,7 +350,6 @@ describe("Action", () => {
listMock.mockResolvedValue({
data: []
})
shouldSkipMock.mockResolvedValue(false)
updateMock.mockResolvedValue({
data: {
id: releaseId,
@@ -397,20 +369,17 @@ describe("Action", () => {
commit: commit,
discussionCategory: discussionCategory,
generateReleaseNotes: true,
makeLatest: makeLatest,
owner: "owner",
createdPrerelease: createPrerelease,
replacesArtifacts: replacesArtifacts,
removeArtifacts: removeArtifacts,
repo: "repo",
skipIfReleaseExists: false,
tag: tag,
token: token,
updatedDraft: updateDraft,
updatedReleaseBody: updateBody,
updatedReleaseName: updateName,
updatedPrerelease: updatePrerelease,
updateOnlyUnreleased: updateOnlyUnreleased
updatedPrerelease: updatePrerelease
}
})
const MockOutputs = jest.fn<Outputs, any>(() => {
@@ -428,20 +397,13 @@ describe("Action", () => {
destroyArtifacts: artifactDestroyMock
}
})
const MockActionSkipper = jest.fn<ActionSkipper, any>(() => {
return {
shouldSkip: shouldSkipMock
}
})
const inputs = new MockInputs()
const outputs = new MockOutputs()
const releases = new MockReleases()
const uploader = new MockUploader()
const artifactDestroyer = new MockArtifactDestroyer()
const actionSkipper = new MockActionSkipper()
return new Action(inputs, outputs, releases, uploader, artifactDestroyer, actionSkipper)
return new Action(inputs, outputs, releases, uploader, artifactDestroyer)
}
})

View File

@@ -1,44 +0,0 @@
import {ActionSkipper, ReleaseActionSkipper} from "../src/ActionSkipper";
import {Releases} from "../src/Releases";
describe("shouldSkip", () => {
const getMock = jest.fn()
const tag = "tag"
const MockReleases = jest.fn<Releases, any>(() => {
return {
create: jest.fn(),
deleteArtifact: jest.fn(),
getByTag: getMock,
listArtifactsForRelease: jest.fn(),
listReleases: jest.fn(),
update: jest.fn(),
uploadArtifact: jest.fn()
}
})
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 () => {
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 () => {
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: {}})
const actionSkipper = new ReleaseActionSkipper(true, MockReleases(), tag)
expect(await actionSkipper.shouldSkip()).toBe(true)
})
})

View File

@@ -1,14 +1,12 @@
import {Artifact} from "../src/Artifact";
import { Artifact } from "../src/Artifact";
const fileContents = Buffer.from('artful facts', 'utf-8')
const contentLength = 42
const fakeReadStream = {}
jest.mock('fs', () => {
return {
createReadStream: () => fakeReadStream,
statSync: () => {
return {size: contentLength}
}
readFileSync: () => fileContents,
statSync: () => { return { size: contentLength } }
};
})
@@ -35,6 +33,6 @@ describe("Artifact", () => {
it('reads artifact', () => {
const artifact = new Artifact('some/artifact')
expect(artifact.readFile()).toBe(fakeReadStream)
expect(artifact.readFile()).toBe(fileContents)
})
})

View File

@@ -21,9 +21,6 @@ jest.mock('fs', () => {
return false
}
}
},
realpathSync: () => {
return false
}
};
})

View File

@@ -7,7 +7,7 @@ const artifacts = [
new Artifact('a/art1'),
new Artifact('b/art2')
]
const fakeReadStream = {}
const fileContents = Buffer.from('artful facts', 'utf-8')
const contentLength = 42
const releaseId = 100
const url = 'http://api.example.com'
@@ -19,7 +19,7 @@ const uploadMock = jest.fn()
jest.mock('fs', () => {
return {
promises: {},
createReadStream: () => fakeReadStream,
readFileSync: () => fileContents,
statSync: () => {
return {size: contentLength}
}
@@ -42,9 +42,9 @@ describe('ArtifactUploader', () => {
expect(uploadMock).toBeCalledTimes(2)
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art1', releaseId)
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art1', releaseId)
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art2', releaseId)
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art2', releaseId)
expect(deleteMock).toBeCalledTimes(0)
})
@@ -58,15 +58,15 @@ describe('ArtifactUploader', () => {
expect(uploadMock).toBeCalledTimes(5)
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art1', releaseId)
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art1', releaseId)
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art1', releaseId)
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art1', releaseId)
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art1', releaseId)
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art1', releaseId)
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art2', releaseId)
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art2', releaseId)
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art2', releaseId)
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art2', releaseId)
expect(deleteMock).toBeCalledTimes(0)
})
@@ -81,9 +81,9 @@ describe('ArtifactUploader', () => {
expect(uploadMock).toBeCalledTimes(2)
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art1', releaseId)
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art1', releaseId)
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art2', releaseId)
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art2', releaseId)
expect(deleteMock).toBeCalledTimes(2)
expect(deleteMock).toBeCalledWith(1)
@@ -100,9 +100,9 @@ describe('ArtifactUploader', () => {
expect(uploadMock).toBeCalledTimes(2)
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art1', releaseId)
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art1', releaseId)
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art2', releaseId)
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art2', releaseId)
expect(deleteMock).toBeCalledTimes(0)
})
@@ -116,13 +116,13 @@ describe('ArtifactUploader', () => {
expect(uploadMock).toBeCalledTimes(4)
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art1', releaseId)
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art1', releaseId)
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art1', releaseId)
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art1', releaseId)
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art1', releaseId)
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art1', releaseId)
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art2', releaseId)
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art2', releaseId)
expect(deleteMock).toBeCalledTimes(0)
})
@@ -164,9 +164,9 @@ describe('ArtifactUploader', () => {
expect(uploadMock).toBeCalledTimes(2)
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art1', releaseId)
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art1', releaseId)
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fakeReadStream, 'art2', releaseId)
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art2', releaseId)
expect(deleteMock).toBeCalledTimes(0)
})

View File

@@ -29,31 +29,6 @@ describe('ErrorMessage', () => {
})
})
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')
})
it('provides remediation with 404 with errors', () => {
const error = {
message: 'message',
errors: [
{
code: 'missing',
resource: 'release'
}
],
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')
})
})
it('generates message with errors', () => {
const error = {
message: 'something bad happened',

View File

@@ -1,5 +1,4 @@
const mockGetInput = jest.fn();
const mockGetBooleanInput = jest.fn();
const mockGlob = jest.fn()
const mockReadFileSync = jest.fn();
const mockStatSync = jest.fn();
@@ -15,10 +14,7 @@ const artifacts = [
]
jest.mock('@actions/core', () => {
return {
getInput: mockGetInput,
getBooleanInput: mockGetBooleanInput
};
return {getInput: mockGetInput};
})
jest.mock('fs', () => {
@@ -91,7 +87,7 @@ describe('Inputs', () => {
expect(mockGlob).toBeCalledTimes(1)
expect(mockGlob).toBeCalledWith('art1', 'contentType', true)
})
it('returns empty artifacts', () => {
mockGetInput.mockReturnValueOnce('')
.mockReturnValueOnce('')
@@ -206,7 +202,7 @@ describe('Inputs', () => {
mockGetInput.mockReturnValue('Release')
expect(inputs.discussionCategory).toBe('Release')
})
it('returns undefined', () => {
mockGetInput.mockReturnValue('')
expect(inputs.discussionCategory).toBe(undefined)
@@ -224,24 +220,7 @@ describe('Inputs', () => {
expect(inputs.generateReleaseNotes).toBe(false)
});
})
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 true', () => {
mockGetInput.mockReturnValueOnce('true')
expect(inputs.makeLatest).toBe('true')
})
})
describe('owner', () => {
it('returns owner from context', function () {
process.env.GITHUB_REPOSITORY = "owner/repo"
@@ -299,18 +278,6 @@ describe('Inputs', () => {
});
})
describe('skipIfReleaseExists', () => {
it('returns false', () => {
mockGetBooleanInput.mockReturnValue(false)
expect(inputs.skipIfReleaseExists).toBe(false)
})
it('returns true', () => {
mockGetBooleanInput.mockReturnValue(true)
expect(inputs.skipIfReleaseExists).toBe(true)
})
})
describe('tag', () => {
it('returns input tag', () => {
mockGetInput.mockReturnValue('tag')
@@ -463,17 +430,6 @@ describe('Inputs', () => {
})
})
describe('updateOnlyUnreleased', () => {
it('returns false', () => {
expect(inputs.updateOnlyUnreleased).toBe(false)
})
it('returns true', () => {
mockGetInput.mockReturnValueOnce('true')
expect(inputs.updateOnlyUnreleased).toBe(true)
})
})
function createGlobber(): ArtifactGlobber {
const MockGlobber = jest.fn<ArtifactGlobber, any>(() => {
return {

View File

@@ -7,7 +7,6 @@ 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
@@ -28,9 +27,8 @@ describe.skip('Integration Test', () => {
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)
action = new Action(inputs, outputs, releases, uploader, artifactDestroyer)
})
it('Performs action', async () => {
@@ -54,14 +52,12 @@ describe.skip('Integration Test', () => {
replacesArtifacts: true,
removeArtifacts: false,
repo: "actions-playground",
skipIfReleaseExists: false,
tag: "release-action-test",
token: getToken(),
updatedDraft: false,
updatedReleaseBody: "This release was updated by release-action's integration test",
updatedReleaseBody: "This release was generated by release-action's integration test",
updatedReleaseName: "Releases Action Integration Test",
updatedPrerelease: false,
updateOnlyUnreleased: false
updatedPrerelease: false
}
})
return new MockInputs();

View File

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

View File

@@ -47,10 +47,6 @@ inputs:
description: 'Indicates if release notes should be automatically generated.'
required: false
default: 'false'
makeLatest:
description: 'Indicates if the release should be the "latest" release or not.'
required: false
default: 'legacy'
name:
description: 'An optional name for the release. If this is omitted the tag will be used.'
required: false
@@ -98,11 +94,7 @@ inputs:
repo:
description: "Optionally specify the repo where the release should be generated. Defaults to current repo"
required: false
default: ''
skipIfReleaseExists:
description: "When skipIfReleaseExists is enabled the action will be skipped if a non-draft release already exists for the provided tag."
required: false
default: 'false'
default: ''
tag:
description: 'An optional tag for the release. If this is omitted the git ref will be used (if it is a tag).'
required: false
@@ -111,10 +103,6 @@ inputs:
description: 'The Github token.'
required: false
default: ${{ github.token }}
updateOnlyUnreleased:
description: "When allowUpdates is enabled, this will fail the action if the release it is updating is not a draft or a prerelease."
required: false
default: 'false'
outputs:
id:
description: 'The identifier of the created release.'

10906
dist/index.js vendored

File diff suppressed because it is too large Load Diff

2
dist/index.js.map vendored

File diff suppressed because one or more lines are too long

189
dist/licenses.txt vendored
View File

@@ -503,11 +503,11 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
glob
fs.realpath
ISC
The ISC License
Copyright (c) 2009-2023 Isaac Z. Schlueter and Contributors
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -521,6 +521,92 @@ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
----
This library bundles a version of the `fs.realpath` and `fs.realpathSync`
methods from Node.js v0.10 under the terms of the Node.js MIT license.
Node's license follows, also included at the header of `old.js` which contains
the licensed code:
Copyright Joyent, Inc. and other Node 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.
glob
ISC
The ISC License
Copyright (c) 2009-2022 Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
inflight
ISC
The ISC License
Copyright (c) Isaac Z. Schlueter
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
inherits
ISC
The ISC License
Copyright (c) Isaac Z. Schlueter
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
is-plain-object
MIT
@@ -547,49 +633,11 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
lru-cache
ISC
The ISC License
Copyright (c) 2010-2023 Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
minimatch
ISC
The ISC License
Copyright (c) 2011-2023 Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
minipass
ISC
The ISC License
Copyright (c) 2017-2023 npm, Inc., Isaac Z. Schlueter, and Contributors
Copyright (c) 2011-2022 Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -649,65 +697,6 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
path-scurry
BlueOak-1.0.0
# Blue Oak Model License
Version 1.0.0
## Purpose
This license gives everyone as much permission to work with
this software as possible, while protecting contributors
from liability.
## Acceptance
In order to receive this license, you must agree to its
rules. The rules of this license are both obligations
under that agreement and conditions to your license.
You must not do anything with this software that triggers
a rule that you cannot or will not follow.
## Copyright
Each contributor licenses you to do everything with this
software that would otherwise infringe that contributor's
copyright in it.
## Notices
You must ensure that everyone who gets a copy of
any part of this software from you, with or without
changes, also gets the text of this license or a link to
<https://blueoakcouncil.org/license/1.0.0>.
## Excuse
If anyone notifies you in writing that you have not
complied with [Notices](#notices), you can keep your
license by taking all practical steps to comply within 30
days after the notice. If you do not do so, your license
ends immediately.
## Patent
Each contributor licenses you to do everything with this
software that would otherwise infringe any patent claims
they can license or become able to license.
## Reliability
No contributor can revoke this license.
## No Liability
***As far as the law allows, this software comes as is,
without any warranty or condition, and no contributor
will be liable to anyone for any damages related to this
software or this license, under any kind of legal claim.***
tr46
MIT

File diff suppressed because one or more lines are too long

100
lib/Action.js Normal file
View File

@@ -0,0 +1,100 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Action = void 0;
const GithubError_1 = require("./GithubError");
class Action {
constructor(inputs, outputs, releases, uploader, artifactDestroyer) {
this.inputs = inputs;
this.outputs = outputs;
this.releases = releases;
this.uploader = uploader;
this.artifactDestroyer = artifactDestroyer;
}
perform() {
return __awaiter(this, void 0, void 0, function* () {
const releaseResponse = yield this.createOrUpdateRelease();
const releaseData = releaseResponse.data;
const releaseId = releaseData.id;
const uploadUrl = releaseData.upload_url;
if (this.inputs.removeArtifacts) {
yield this.artifactDestroyer.destroyArtifacts(releaseId);
}
const artifacts = this.inputs.artifacts;
if (artifacts.length > 0) {
yield this.uploader.uploadArtifacts(artifacts, releaseId, uploadUrl);
}
this.outputs.applyReleaseData(releaseData);
});
}
createOrUpdateRelease() {
return __awaiter(this, void 0, void 0, function* () {
if (this.inputs.allowUpdates) {
let getResponse;
try {
getResponse = yield this.releases.getByTag(this.inputs.tag);
}
catch (error) {
return yield this.checkForMissingReleaseError(error);
}
return yield this.updateRelease(getResponse.data.id);
}
else {
return yield this.createRelease();
}
});
}
checkForMissingReleaseError(error) {
return __awaiter(this, void 0, void 0, function* () {
if (Action.noPublishedRelease(error)) {
return yield this.updateDraftOrCreateRelease();
}
else {
throw error;
}
});
}
updateRelease(id) {
return __awaiter(this, void 0, void 0, function* () {
return yield this.releases.update(id, this.inputs.tag, this.inputs.updatedReleaseBody, this.inputs.commit, this.inputs.discussionCategory, this.inputs.updatedDraft, this.inputs.updatedReleaseName, this.inputs.updatedPrerelease);
});
}
static noPublishedRelease(error) {
const githubError = new GithubError_1.GithubError(error);
return githubError.status == 404;
}
updateDraftOrCreateRelease() {
return __awaiter(this, void 0, void 0, function* () {
const draftReleaseId = yield this.findMatchingDraftReleaseId();
if (draftReleaseId) {
return yield this.updateRelease(draftReleaseId);
}
else {
return yield this.createRelease();
}
});
}
findMatchingDraftReleaseId() {
return __awaiter(this, void 0, void 0, function* () {
const tag = this.inputs.tag;
const response = yield this.releases.listReleases();
const releases = response.data;
const draftRelease = releases.find(release => release.draft && release.tag_name == tag);
return draftRelease === null || draftRelease === void 0 ? void 0 : draftRelease.id;
});
}
createRelease() {
return __awaiter(this, void 0, void 0, function* () {
return yield this.releases.create(this.inputs.tag, this.inputs.createdReleaseBody, this.inputs.commit, this.inputs.discussionCategory, this.inputs.createdDraft, this.inputs.generateReleaseNotes, this.inputs.createdReleaseName, this.inputs.createdPrerelease);
});
}
}
exports.Action = Action;

19
lib/Artifact.js Normal file
View File

@@ -0,0 +1,19 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Artifact = void 0;
const path_1 = require("path");
const fs_1 = require("fs");
class Artifact {
constructor(path, contentType = "raw") {
this.path = path;
this.name = path_1.basename(path);
this.contentType = contentType;
}
get contentLength() {
return fs_1.statSync(this.path).size;
}
readFile() {
return fs_1.readFileSync(this.path);
}
}
exports.Artifact = Artifact;

48
lib/ArtifactDestroyer.js Normal file
View File

@@ -0,0 +1,48 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GithubArtifactDestroyer = void 0;
const core = __importStar(require("@actions/core"));
class GithubArtifactDestroyer {
constructor(releases) {
this.releases = releases;
}
destroyArtifacts(releaseId) {
return __awaiter(this, void 0, void 0, function* () {
const releaseAssets = yield this.releases.listArtifactsForRelease(releaseId);
for (const artifact of releaseAssets) {
const asset = artifact;
core.debug(`Deleting existing artifact ${artifact.name}...`);
yield this.releases.deleteArtifact(asset.id);
}
});
}
}
exports.GithubArtifactDestroyer = GithubArtifactDestroyer;

71
lib/ArtifactGlobber.js Normal file
View File

@@ -0,0 +1,71 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.FileArtifactGlobber = void 0;
const core = __importStar(require("@actions/core"));
const Globber_1 = require("./Globber");
const Artifact_1 = require("./Artifact");
const untildify_1 = __importDefault(require("untildify"));
const ArtifactPathValidator_1 = require("./ArtifactPathValidator");
class FileArtifactGlobber {
constructor(globber = new Globber_1.FileGlobber()) {
this.globber = globber;
}
globArtifactString(artifact, contentType, errorsFailBuild) {
const split = /[,\n]/;
return artifact.split(split)
.map(path => path.trimStart())
.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_1.Artifact(path, contentType));
}
globPattern(pattern, errorsFailBuild) {
const paths = this.globber.glob(pattern);
if (paths.length == 0) {
if (errorsFailBuild) {
FileArtifactGlobber.throwGlobError(pattern);
}
else {
FileArtifactGlobber.reportGlobWarning(pattern);
}
}
return [pattern, paths];
}
static validatePattern(errorsFailBuild, paths, pattern) {
const validator = new ArtifactPathValidator_1.ArtifactPathValidator(errorsFailBuild, paths, pattern);
return validator.validate();
}
static reportGlobWarning(pattern) {
core.warning(`Artifact pattern :${pattern} did not match any files`);
}
static throwGlobError(pattern) {
throw Error(`Artifact pattern :${pattern} did not match any files`);
}
static expandPath(path) {
return untildify_1.default(path);
}
}
exports.FileArtifactGlobber = FileArtifactGlobber;

View File

@@ -0,0 +1,58 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ArtifactPathValidator = void 0;
const core = __importStar(require("@actions/core"));
const fs_1 = require("fs");
class ArtifactPathValidator {
constructor(errorsFailBuild, paths, pattern) {
this.paths = paths;
this.pattern = pattern;
this.errorsFailBuild = errorsFailBuild;
}
validate() {
this.verifyPathsNotEmpty();
return this.paths.filter((path) => this.verifyNotDirectory(path));
}
verifyPathsNotEmpty() {
if (this.paths.length == 0) {
const message = `Artifact pattern:${this.pattern} did not match any files`;
this.reportError(message);
}
}
verifyNotDirectory(path) {
const isDir = fs_1.statSync(path).isDirectory();
if (isDir) {
const message = `Artifact is a directory:${path}. Directories can not be uploaded to a release.`;
this.reportError(message);
}
return !isDir;
}
reportError(message) {
if (this.errorsFailBuild) {
throw Error(message);
}
else {
core.warning(message);
}
}
}
exports.ArtifactPathValidator = ArtifactPathValidator;

88
lib/ArtifactUploader.js Normal file
View File

@@ -0,0 +1,88 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GithubArtifactUploader = void 0;
const core = __importStar(require("@actions/core"));
class GithubArtifactUploader {
constructor(releases, replacesExistingArtifacts = true, throwsUploadErrors = false) {
this.releases = releases;
this.replacesExistingArtifacts = replacesExistingArtifacts;
this.throwsUploadErrors = throwsUploadErrors;
}
uploadArtifacts(artifacts, releaseId, uploadUrl) {
return __awaiter(this, void 0, void 0, function* () {
if (this.replacesExistingArtifacts) {
yield this.deleteUpdatedArtifacts(artifacts, releaseId);
}
for (const artifact of artifacts) {
yield this.uploadArtifact(artifact, releaseId, uploadUrl);
}
});
}
uploadArtifact(artifact, releaseId, uploadUrl, retry = 3) {
return __awaiter(this, void 0, void 0, function* () {
try {
core.debug(`Uploading artifact ${artifact.name}...`);
yield this.releases.uploadArtifact(uploadUrl, artifact.contentLength, artifact.contentType, artifact.readFile(), artifact.name, releaseId);
}
catch (error) {
if (error.status >= 500 && retry > 0) {
core.warning(`Failed to upload artifact ${artifact.name}. ${error.message}. Retrying...`);
yield this.uploadArtifact(artifact, releaseId, uploadUrl, retry - 1);
}
else {
if (this.throwsUploadErrors) {
throw Error(`Failed to upload artifact ${artifact.name}. ${error.message}.`);
}
else {
core.warning(`Failed to upload artifact ${artifact.name}. ${error.message}.`);
}
}
}
});
}
deleteUpdatedArtifacts(artifacts, releaseId) {
return __awaiter(this, void 0, void 0, function* () {
const releaseAssets = yield this.releases.listArtifactsForRelease(releaseId);
const assetByName = {};
releaseAssets.forEach(asset => {
assetByName[asset.name] = asset;
});
for (const artifact of artifacts) {
const asset = assetByName[artifact.name];
if (asset) {
core.debug(`Deleting existing artifact ${artifact.name}...`);
yield this.releases.deleteArtifact(asset.id);
}
}
});
}
}
exports.GithubArtifactUploader = GithubArtifactUploader;

40
lib/GithubError.js Normal file
View File

@@ -0,0 +1,40 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.GithubError = void 0;
const GithubErrorDetail_1 = require("./GithubErrorDetail");
class GithubError {
constructor(error) {
this.error = error;
this.githubErrors = this.generateGithubErrors();
}
generateGithubErrors() {
const errors = this.error.errors;
if (errors instanceof Array) {
return errors.map((err) => new GithubErrorDetail_1.GithubErrorDetail(err));
}
else {
return [];
}
}
get status() {
return this.error.status;
}
hasErrorWithCode(code) {
return this.githubErrors.some((err) => err.code == code);
}
toString() {
const message = this.error.message;
const errors = this.githubErrors;
const status = this.status;
if (errors.length > 0) {
return `Error ${status}: ${message}\nErrors:\n${this.errorBulletedList(errors)}`;
}
else {
return `Error ${status}: ${message}`;
}
}
errorBulletedList(errors) {
return errors.map((err) => `- ${err}`).join("\n");
}
}
exports.GithubError = GithubError;

57
lib/GithubErrorDetail.js Normal file
View File

@@ -0,0 +1,57 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.GithubErrorDetail = void 0;
class GithubErrorDetail {
constructor(error) {
this.error = error;
}
get code() {
return this.error.code;
}
toString() {
const code = this.error.code;
switch (code) {
case 'missing':
return this.missingResourceMessage();
case 'missing_field':
return this.missingFieldMessage();
case 'invalid':
return this.invalidFieldMessage();
case 'already_exists':
return this.resourceAlreadyExists();
default:
return this.customErrorMessage();
}
}
customErrorMessage() {
const message = this.error.message;
const documentation = this.error.documentation_url;
let documentationMessage;
if (documentation) {
documentationMessage = `\nPlease see ${documentation}.`;
}
else {
documentationMessage = "";
}
return `${message}${documentationMessage}`;
}
invalidFieldMessage() {
const resource = this.error.resource;
const field = this.error.field;
return `The ${field} field on ${resource} is an invalid format.`;
}
missingResourceMessage() {
const resource = this.error.resource;
return `${resource} does not exist.`;
}
missingFieldMessage() {
const resource = this.error.resource;
const field = this.error.field;
return `The ${field} field on ${resource} is missing.`;
}
resourceAlreadyExists() {
const resource = this.error.resource;
return `${resource} already exists.`;
}
}
exports.GithubErrorDetail = GithubErrorDetail;

10
lib/Globber.js Normal file
View File

@@ -0,0 +1,10 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FileGlobber = void 0;
const glob_1 = require("glob");
class FileGlobber {
glob(pattern) {
return new glob_1.GlobSync(pattern, { mark: true }).found;
}
}
exports.FileGlobber = FileGlobber;

186
lib/Inputs.js Normal file
View File

@@ -0,0 +1,186 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CoreInputs = void 0;
const core = __importStar(require("@actions/core"));
const fs_1 = require("fs");
class CoreInputs {
constructor(artifactGlobber, context) {
this.artifactGlobber = artifactGlobber;
this.context = context;
}
get allowUpdates() {
const allow = core.getInput('allowUpdates');
return allow == 'true';
}
get artifacts() {
let artifacts = core.getInput('artifacts');
if (!artifacts) {
artifacts = core.getInput('artifact');
}
if (artifacts) {
let contentType = core.getInput('artifactContentType');
if (!contentType) {
contentType = 'raw';
}
return this.artifactGlobber
.globArtifactString(artifacts, contentType, this.artifactErrorsFailBuild);
}
return [];
}
get artifactErrorsFailBuild() {
const allow = core.getInput('artifactErrorsFailBuild');
return allow == 'true';
}
get body() {
const body = core.getInput('body');
if (body) {
return body;
}
const bodyFile = core.getInput('bodyFile');
if (bodyFile) {
return this.stringFromFile(bodyFile);
}
return '';
}
get createdDraft() {
const draft = core.getInput('draft');
return draft == 'true';
}
get createdPrerelease() {
const preRelease = core.getInput('prerelease');
return preRelease == 'true';
}
get createdReleaseBody() {
if (CoreInputs.omitBody)
return undefined;
return this.body;
}
static get omitBody() {
return core.getInput('omitBody') == 'true';
}
get createdReleaseName() {
if (CoreInputs.omitName)
return undefined;
return this.name;
}
static get omitName() {
return core.getInput('omitName') == 'true';
}
get commit() {
const commit = core.getInput('commit');
if (commit) {
return commit;
}
return undefined;
}
get discussionCategory() {
const category = core.getInput('discussionCategory');
if (category) {
return category;
}
return undefined;
}
get name() {
const name = core.getInput('name');
if (name) {
return name;
}
return this.tag;
}
get generateReleaseNotes() {
const generate = core.getInput('generateReleaseNotes');
return generate == 'true';
}
get owner() {
let owner = core.getInput('owner');
if (owner) {
return owner;
}
return this.context.repo.owner;
}
get removeArtifacts() {
const removes = core.getInput('removeArtifacts');
return removes == 'true';
}
get replacesArtifacts() {
const replaces = core.getInput('replacesArtifacts');
return replaces == 'true';
}
get repo() {
let repo = core.getInput('repo');
if (repo) {
return repo;
}
return this.context.repo.repo;
}
get tag() {
const tag = core.getInput('tag');
if (tag) {
return tag;
}
const ref = this.context.ref;
const tagPath = "refs/tags/";
if (ref && ref.startsWith(tagPath)) {
return ref.substr(tagPath.length, ref.length);
}
throw Error("No tag found in ref or input!");
}
get token() {
return core.getInput('token', { required: true });
}
get updatedDraft() {
if (CoreInputs.omitDraftDuringUpdate)
return undefined;
return this.createdDraft;
}
static get omitDraftDuringUpdate() {
return core.getInput('omitDraftDuringUpdate') == 'true';
}
get updatedPrerelease() {
if (CoreInputs.omitPrereleaseDuringUpdate)
return undefined;
return this.createdPrerelease;
}
static get omitPrereleaseDuringUpdate() {
return core.getInput('omitPrereleaseDuringUpdate') == 'true';
}
get updatedReleaseBody() {
if (CoreInputs.omitBody || CoreInputs.omitBodyDuringUpdate)
return undefined;
return this.body;
}
static get omitBodyDuringUpdate() {
return core.getInput('omitBodyDuringUpdate') == 'true';
}
get updatedReleaseName() {
if (CoreInputs.omitName || CoreInputs.omitNameDuringUpdate)
return undefined;
return this.name;
}
static get omitNameDuringUpdate() {
return core.getInput('omitNameDuringUpdate') == 'true';
}
stringFromFile(path) {
return fs_1.readFileSync(path, 'utf-8');
}
}
exports.CoreInputs = CoreInputs;

65
lib/Main.js Normal file
View File

@@ -0,0 +1,65 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const github = __importStar(require("@actions/github"));
const core = __importStar(require("@actions/core"));
const Inputs_1 = require("./Inputs");
const Releases_1 = require("./Releases");
const Action_1 = require("./Action");
const ArtifactUploader_1 = require("./ArtifactUploader");
const ArtifactGlobber_1 = require("./ArtifactGlobber");
const GithubError_1 = require("./GithubError");
const Outputs_1 = require("./Outputs");
const ArtifactDestroyer_1 = require("./ArtifactDestroyer");
function run() {
return __awaiter(this, void 0, void 0, function* () {
try {
const action = createAction();
yield action.perform();
}
catch (error) {
const githubError = new GithubError_1.GithubError(error);
core.setFailed(githubError.toString());
}
});
}
function createAction() {
const token = core.getInput('token');
const context = github.context;
const git = github.getOctokit(token);
const globber = new ArtifactGlobber_1.FileArtifactGlobber();
const inputs = new Inputs_1.CoreInputs(globber, context);
const outputs = new Outputs_1.CoreOutputs();
const releases = new Releases_1.GithubReleases(inputs, git);
const uploader = new ArtifactUploader_1.GithubArtifactUploader(releases, inputs.replacesArtifacts, inputs.artifactErrorsFailBuild);
const artifactDestroyer = new ArtifactDestroyer_1.GithubArtifactDestroyer(releases);
return new Action_1.Action(inputs, outputs, releases, uploader, artifactDestroyer);
}
run();

31
lib/Outputs.js Normal file
View File

@@ -0,0 +1,31 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CoreOutputs = void 0;
const core = __importStar(require("@actions/core"));
class CoreOutputs {
applyReleaseData(releaseData) {
core.setOutput('id', releaseData.id);
core.setOutput('html_url', releaseData.html_url);
core.setOutput('upload_url', releaseData.upload_url);
}
}
exports.CoreOutputs = CoreOutputs;

104
lib/Releases.js Normal file
View File

@@ -0,0 +1,104 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GithubReleases = void 0;
class GithubReleases {
constructor(inputs, git) {
this.inputs = inputs;
this.git = git;
}
create(tag, body, commitHash, discussionCategory, draft, generateReleaseNotes, name, prerelease) {
return __awaiter(this, void 0, void 0, function* () {
// noinspection TypeScriptValidateJSTypes
return this.git.rest.repos.createRelease({
body: body,
name: name,
discussion_category_name: discussionCategory,
draft: draft,
generate_release_notes: generateReleaseNotes,
owner: this.inputs.owner,
prerelease: prerelease,
repo: this.inputs.repo,
target_commitish: commitHash,
tag_name: tag
});
});
}
deleteArtifact(assetId) {
return __awaiter(this, void 0, void 0, function* () {
return this.git.rest.repos.deleteReleaseAsset({
asset_id: assetId,
owner: this.inputs.owner,
repo: this.inputs.repo
});
});
}
getByTag(tag) {
return __awaiter(this, void 0, void 0, function* () {
return this.git.rest.repos.getReleaseByTag({
owner: this.inputs.owner,
repo: this.inputs.repo,
tag: tag
});
});
}
listArtifactsForRelease(releaseId) {
return __awaiter(this, void 0, void 0, function* () {
return this.git.paginate(this.git.rest.repos.listReleaseAssets, {
owner: this.inputs.owner,
release_id: releaseId,
repo: this.inputs.repo
});
});
}
listReleases() {
return __awaiter(this, void 0, void 0, function* () {
return this.git.rest.repos.listReleases({
owner: this.inputs.owner,
repo: this.inputs.repo
});
});
}
update(id, tag, body, commitHash, discussionCategory, draft, name, prerelease) {
return __awaiter(this, void 0, void 0, function* () {
// noinspection TypeScriptValidateJSTypes
return this.git.rest.repos.updateRelease({
release_id: id,
body: body,
name: name,
discussion_category_name: discussionCategory,
draft: draft,
owner: this.inputs.owner,
prerelease: prerelease,
repo: this.inputs.repo,
target_commitish: commitHash,
tag_name: tag
});
});
}
uploadArtifact(assetUrl, contentLength, contentType, file, name, releaseId) {
return __awaiter(this, void 0, void 0, function* () {
return this.git.rest.repos.uploadReleaseAsset({
url: assetUrl,
headers: {
"content-length": contentLength,
"content-type": contentType
},
data: file,
name: name,
owner: this.inputs.owner,
release_id: releaseId,
repo: this.inputs.repo
});
});
}
}
exports.GithubReleases = GithubReleases;

View File

@@ -56,15 +56,16 @@
"dependencies": {
"@actions/core": "^1.10.0",
"@actions/github": "^5.1.1",
"glob": "^10.3.1",
"@types/glob": "^8.0.0",
"glob": "^8.0.3",
"untildify": "^4.0.0"
},
"devDependencies": {
"@types/jest": "^29.5.2",
"@types/node": "^20.3.3",
"jest": "^29.5.0",
"jest-circus": "^29.5.0",
"ts-jest": "^29.1.1",
"typescript": "^5.1.6"
"@types/jest": "^28.1.1",
"@types/node": "^18.7.23",
"jest": "^28.1.1",
"jest-circus": "^29.1.2",
"ts-jest": "^28.0.5",
"typescript": "^4.8.4"
}
}

View File

@@ -1,4 +1,3 @@
import * as core from '@actions/core';
import {Inputs} from "./Inputs";
import {
CreateOrUpdateReleaseResponse,
@@ -11,40 +10,27 @@ 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
private outputs: Outputs
private releases: Releases
private uploader: ArtifactUploader
private artifactDestroyer: ArtifactDestroyer
private skipper: ActionSkipper
private releaseValidator: ReleaseValidator
private uploader: ArtifactUploader
constructor(inputs: Inputs,
outputs: Outputs,
releases: Releases,
uploader: ArtifactUploader,
artifactDestroyer: ArtifactDestroyer,
skipper: ActionSkipper) {
artifactDestroyer: ArtifactDestroyer) {
this.inputs = inputs
this.outputs = outputs
this.releases = releases
this.uploader = uploader
this.artifactDestroyer = artifactDestroyer
this.skipper = skipper
this.releaseValidator = new ReleaseValidator(inputs.updateOnlyUnreleased)
}
async perform() {
if (await this.skipper.shouldSkip()) {
core.notice("Skipping action, release already exists and skipIfReleaseExists is enabled.")
return
}
const releaseResponse = await this.createOrUpdateRelease();
const releaseData = releaseResponse.data
const releaseId = releaseData.id
@@ -70,9 +56,6 @@ 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)
return await this.updateRelease(getResponse.data.id)
} else {
@@ -96,7 +79,6 @@ export class Action {
this.inputs.commit,
this.inputs.discussionCategory,
this.inputs.updatedDraft,
this.inputs.makeLatest,
this.inputs.updatedReleaseName,
this.inputs.updatedPrerelease
)
@@ -133,7 +115,6 @@ export class Action {
this.inputs.discussionCategory,
this.inputs.createdDraft,
this.inputs.generateReleaseNotes,
this.inputs.makeLatest,
this.inputs.createdReleaseName,
this.inputs.createdPrerelease
)

View File

@@ -1,27 +0,0 @@
import {Releases} from "./Releases";
export interface ActionSkipper {
shouldSkip(): Promise<boolean>
}
export class ReleaseActionSkipper {
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;
}
try {
const getResponse = await this.releases.getByTag(this.tag)
return getResponse.data != null
} catch (error: any) {
// There is either no release or something else went wrong. Either way, run the action.
return false;
}
}
}

View File

@@ -1,5 +1,5 @@
import { basename } from "path";
import {createReadStream, readFileSync, ReadStream, statSync} from "fs";
import { readFileSync, statSync } from "fs";
export class Artifact {
readonly contentType: string
@@ -16,7 +16,7 @@ export class Artifact {
return statSync(this.path).size
}
readFile(): ReadStream {
return createReadStream(this.path)
readFile(): Buffer {
return readFileSync(this.path)
}
}

View File

@@ -3,7 +3,6 @@ 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[]
@@ -20,7 +19,6 @@ export class FileArtifactGlobber implements ArtifactGlobber {
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))
.map((globResult) => FileArtifactGlobber.validatePattern(errorsFailBuild, globResult[1], globResult[0]))

View File

@@ -31,21 +31,14 @@ export class GithubError {
const errors = this.githubErrors
const status = this.status
if (errors.length > 0) {
return `Error ${status}: ${message}\nErrors:\n${this.errorBulletedList(errors)}${this.remediation()}`
return `Error ${status}: ${message}\nErrors:\n${this.errorBulletedList(errors)}`
} else {
return `Error ${status}: ${message}${this.remediation()}`
return `Error ${status}: ${message}`
}
}
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"
}
return ""
}
}

View File

@@ -1,5 +1,4 @@
import {globSync} from "glob";
import { GlobSync } from "glob";
export interface Globber {
glob(pattern: string): string[]
@@ -7,6 +6,6 @@ export interface Globber {
export class FileGlobber implements Globber {
glob(pattern: string): string[] {
return globSync(pattern, { mark: true })
return new GlobSync(pattern, { mark: true }).found
}
}

View File

@@ -15,19 +15,16 @@ export interface Inputs {
readonly createdReleaseName?: string
readonly discussionCategory?: string
readonly generateReleaseNotes: boolean
readonly makeLatest?: string
readonly owner: string
readonly removeArtifacts: boolean
readonly replacesArtifacts: boolean
readonly repo: string
readonly skipIfReleaseExists: boolean
readonly tag: string
readonly token: string
readonly updatedDraft?: boolean
readonly updatedReleaseBody?: string
readonly updatedReleaseName?: string
readonly updatedPrerelease?: boolean
readonly updateOnlyUnreleased: boolean
}
export class CoreInputs implements Inputs {
@@ -137,10 +134,6 @@ export class CoreInputs implements Inputs {
return generate == 'true'
}
get makeLatest(): string {
return core.getInput('makeLatest')
}
get owner(): string {
let owner = core.getInput('owner')
if (owner) {
@@ -166,10 +159,6 @@ export class CoreInputs implements Inputs {
return this.context.repo.repo
}
get skipIfReleaseExists(): boolean {
return core.getBooleanInput("skipIfReleaseExists")
}
get tag(): string {
const tag = core.getInput('tag')
if (tag) {
@@ -220,10 +209,6 @@ export class CoreInputs implements Inputs {
if (CoreInputs.omitName || CoreInputs.omitNameDuringUpdate) return undefined
return this.name
}
get updateOnlyUnreleased(): boolean {
return core.getInput('updateOnlyUnreleased') == 'true'
}
private static get omitNameDuringUpdate(): boolean {
return core.getInput('omitNameDuringUpdate') == 'true'

View File

@@ -8,7 +8,6 @@ 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 {
@@ -29,11 +28,10 @@ function createAction(): Action {
const inputs = new CoreInputs(globber, context)
const outputs = new CoreOutputs()
const releases = new GithubReleases(inputs, git)
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)
return new Action(inputs, outputs, releases, uploader, artifactDestroyer)
}
run();

View File

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

View File

@@ -1,20 +0,0 @@
export class ReleaseValidator {
constructor(private updateOnlyUnreleased: boolean) {
}
validateReleaseUpdate(releaseResponse: ReleaseStageArguments) {
if (!this.updateOnlyUnreleased) {
return
}
if (!releaseResponse.draft && !releaseResponse.prerelease) {
throw new Error(`Tried to update "${releaseResponse.name ?? "release"}" which is neither a draft or prerelease. (updateOnlyUnreleased is on)`)
}
}
}
export type ReleaseStageArguments = {
draft: boolean
name: string | null
prerelease: boolean
}

View File

@@ -25,7 +25,6 @@ export interface Releases {
discussionCategory?: string,
draft?: boolean,
generateReleaseNotes?: boolean,
makeLatest?: string,
name?: string,
prerelease?: boolean
): Promise<CreateReleaseResponse>
@@ -45,7 +44,6 @@ export interface Releases {
commitHash?: string,
discussionCategory?: string,
draft?: boolean,
makeLatest?: string,
name?: string,
prerelease?: boolean
): Promise<UpdateReleaseResponse>
@@ -76,7 +74,6 @@ export class GithubReleases implements Releases {
discussionCategory?: string,
draft?: boolean,
generateReleaseNotes?: boolean,
makeLatest?: string,
name?: string,
prerelease?: boolean
): Promise<CreateReleaseResponse> {
@@ -87,7 +84,6 @@ export class GithubReleases implements Releases {
discussion_category_name: discussionCategory,
draft: draft,
generate_release_notes: generateReleaseNotes,
make_latest: makeLatest,
owner: this.inputs.owner,
prerelease: prerelease,
repo: this.inputs.repo,
@@ -138,7 +134,6 @@ export class GithubReleases implements Releases {
commitHash?: string,
discussionCategory?: string,
draft?: boolean,
makeLatest?: string,
name?: string,
prerelease?: boolean
): Promise<UpdateReleaseResponse> {
@@ -149,7 +144,6 @@ export class GithubReleases implements Releases {
name: name,
discussion_category_name: discussionCategory,
draft: draft,
make_latest: makeLatest,
owner: this.inputs.owner,
prerelease: prerelease,
repo: this.inputs.repo,

View File

@@ -4,6 +4,7 @@
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"moduleResolution": "node",
"outDir": "./lib", /* Redirect output structure to the directory. */
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
"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'. */

1440
yarn.lock

File diff suppressed because it is too large Load Diff