Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
339a81892b | ||
|
|
df17233557 | ||
|
|
813e942459 | ||
|
|
7df3a0e749 | ||
|
|
caacf56b56 | ||
|
|
c074b5e19f | ||
|
|
9e0366240f | ||
|
|
5b4b1954b0 | ||
|
|
89bab4d0a7 | ||
|
|
00cbfdc960 | ||
|
|
3f973430f2 | ||
|
|
5b3313d377 | ||
|
|
7886be8054 | ||
|
|
411d95d6a3 | ||
|
|
72a3e11e23 | ||
|
|
fc196c3dda | ||
|
|
788c17510f | ||
|
|
93682e08f2 | ||
|
|
5f55df31e1 | ||
|
|
be9a4fac3a | ||
|
|
23d2cc4c27 | ||
|
|
c15e4e52f5 | ||
|
|
b9387a1970 | ||
|
|
327ba10342 | ||
|
|
5e58a022dc | ||
|
|
7ffaa93f91 | ||
|
|
c266243d9d | ||
|
|
29d665a1e2 | ||
|
|
8c0975efae | ||
|
|
015a5adb5c | ||
|
|
73edeebfa1 | ||
|
|
cacc3525a9 | ||
|
|
0dda22e486 | ||
|
|
e00665902f | ||
|
|
a0e7822741 | ||
|
|
206b0f552d | ||
|
|
fb82a3ec11 | ||
|
|
4207a7677e | ||
|
|
b7eabc95ff | ||
|
|
e87de4c2e4 | ||
|
|
98d25d4189 | ||
|
|
d3601261f8 | ||
|
|
571fe81601 | ||
|
|
1c89adf398 | ||
|
|
36bf8dd70f | ||
|
|
b12185d71f | ||
|
|
defcf131e4 | ||
|
|
05013d58ed | ||
|
|
20ce211d17 | ||
|
|
a262ce99ce |
19
.github/workflows/build.yml
vendored
19
.github/workflows/build.yml
vendored
@@ -11,22 +11,27 @@ jobs:
|
||||
check_pr:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: "yarn install"
|
||||
run: yarn install
|
||||
- name: "pnpm install"
|
||||
run: pnpm install
|
||||
|
||||
- name: "yarn build"
|
||||
run: yarn build
|
||||
- name: "pnpm build"
|
||||
run: pnpm build
|
||||
|
||||
- name: "check for uncommitted changes"
|
||||
# Ensure no changes, but ignore node_modules dir since dev/fresh ci deps installed.
|
||||
run: |
|
||||
git diff --exit-code --stat -- . ':!node_modules' \
|
||||
|| (echo "##[error] found changed files after build. please 'yarn build && npm run format'" \
|
||||
|| (echo "##[error] found changed files after build. please 'pnpm build && pnpm run format'" \
|
||||
"and check in all changes" \
|
||||
&& exit 1)
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
name: Create Release
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: ncipollo/release-action@v1
|
||||
|
||||
17
.github/workflows/test.yml
vendored
17
.github/workflows/test.yml
vendored
@@ -5,14 +5,19 @@ jobs:
|
||||
check_pr:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: "yarn install"
|
||||
run: yarn install
|
||||
- name: "pnpm install"
|
||||
run: pnpm install
|
||||
|
||||
- name: "yarn test"
|
||||
run: yarn test
|
||||
- name: "pnpm test"
|
||||
run: pnpm test
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -92,7 +92,7 @@ fabric.properties
|
||||
# Markdown Navigator plugin
|
||||
.idea/**/markdown-navigator.xml
|
||||
.idea/**/markdown-navigator/
|
||||
|
||||
.idea/copilot.*
|
||||
# End of https://www.gitignore.io/api/webstorm
|
||||
|
||||
# Coverage
|
||||
@@ -100,3 +100,6 @@ coverage
|
||||
|
||||
# Ignore lib, it contains intermediates
|
||||
/lib
|
||||
|
||||
# Claude
|
||||
.claude/settings.local.json
|
||||
@@ -25,6 +25,8 @@ 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. The default "Announcements" category is not supported via the API and will cause an error if used here. | 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 |
|
||||
| generateReleaseNotesPreviousTag | An optional previous tag to use when generating release notes. This will limit the release notes to changes between the two tags. | false | "" |
|
||||
| immutableCreate | Indicates if immutable release creation should be used. When enabled, the action will first create a draft, upload artifacts, then publish the release. | 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 |
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
| discussionCategory | 当提供该选项时,将生成指定类别的 discussion。指定的类别必须存在,否则将导致 Action 失败。这对草案状态的发布不生效。API 不支持默认的 Announcement 分类,使用这个分类会引发错误。 | false | "" |
|
||||
| draft | 可选择将此版本标记为草稿版本。 设置为 true 以启用。 | false | "" |
|
||||
| generateReleaseNotes | 指示是否应自动生成发行说明。 | false | false |
|
||||
| immutableCreate | 指示是否应使用不可变发布创建。启用时,操作将首先创建草稿,上传产出文件,然后发布版本。 | false | false |
|
||||
| makeLatest | 指示是否将这个发布设置为 latest。使用"lagecy"值则会根据发布时间和语义化版本号自动决定。 | false | "legacy" |
|
||||
| name | 版本的可选名称。 如果省略,将使用标签。 | false | "" |
|
||||
| omitBody | 指示是否应省略发布主体。 | false | false |
|
||||
|
||||
9
__mocks__/@actions/core.ts
Normal file
9
__mocks__/@actions/core.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { vi } from "vitest"
|
||||
|
||||
export const debug = vi.fn()
|
||||
export const getBooleanInput = vi.fn()
|
||||
export const getInput = vi.fn()
|
||||
export const notice = vi.fn()
|
||||
export const setFailed = vi.fn()
|
||||
export const setOutput = vi.fn()
|
||||
export const warning = vi.fn()
|
||||
8
__mocks__/fs.ts
Normal file
8
__mocks__/fs.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { vi } from "vitest"
|
||||
|
||||
export const createReadStream = vi.fn()
|
||||
export const statSync = vi.fn()
|
||||
export const readFileSync = vi.fn()
|
||||
export const writeFileSync = vi.fn()
|
||||
export const existsSync = vi.fn()
|
||||
export const realpathSync = vi.fn()
|
||||
@@ -1,11 +1,12 @@
|
||||
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 { beforeEach, describe, expect, it, vi } from "vitest"
|
||||
import { Action } from "../src/Action.js"
|
||||
import type { ActionSkipper } from "../src/ActionSkipper.js"
|
||||
import { Artifact } from "../src/Artifact.js"
|
||||
import type { ArtifactDestroyer } from "../src/ArtifactDestroyer.js"
|
||||
import type { ArtifactUploader } from "../src/ArtifactUploader.js"
|
||||
import type { Inputs } from "../src/Inputs.js"
|
||||
import type { Outputs } from "../src/Outputs.js"
|
||||
import type { Releases } from "../src/Releases.js"
|
||||
|
||||
const TEST_URLS = {
|
||||
UPLOAD_URL: "http://api.example.com",
|
||||
@@ -14,18 +15,18 @@ const TEST_URLS = {
|
||||
ZIPBALL_URL: "https://api.github.com/repos/owner/repo/zipball/v1.0.0",
|
||||
} as const
|
||||
|
||||
const applyReleaseDataMock = jest.fn()
|
||||
const applyAssetUrlsMock = 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 genReleaseNotesMock = jest.fn()
|
||||
const applyReleaseDataMock = vi.fn()
|
||||
const applyAssetUrlsMock = vi.fn()
|
||||
const artifactDestroyMock = vi.fn()
|
||||
const createMock = vi.fn()
|
||||
const deleteMock = vi.fn()
|
||||
const getMock = vi.fn()
|
||||
const listArtifactsMock = vi.fn()
|
||||
const listMock = vi.fn()
|
||||
const shouldSkipMock = vi.fn()
|
||||
const updateMock = vi.fn()
|
||||
const uploadMock = vi.fn()
|
||||
const genReleaseNotesMock = vi.fn()
|
||||
|
||||
const artifacts = [new Artifact("a/art1"), new Artifact("b/art2")]
|
||||
|
||||
@@ -34,7 +35,7 @@ const createDraft = true
|
||||
const createName = "createName"
|
||||
const commit = "commit"
|
||||
const discussionCategory = "discussionCategory"
|
||||
const generateReleaseNotes = true
|
||||
const _generateReleaseNotes = true
|
||||
const id = 100
|
||||
const createPrerelease = true
|
||||
const releaseId = 101
|
||||
@@ -49,12 +50,15 @@ const updateOnlyUnreleased = false
|
||||
const url = TEST_URLS.UPLOAD_URL
|
||||
const makeLatest = "legacy"
|
||||
const generatedReleaseBody = "test release notes"
|
||||
const previousTag = "v1.0.0"
|
||||
|
||||
describe("Action", () => {
|
||||
beforeEach(() => {
|
||||
applyReleaseDataMock.mockClear()
|
||||
applyAssetUrlsMock.mockClear()
|
||||
artifactDestroyMock.mockClear()
|
||||
createMock.mockClear()
|
||||
genReleaseNotesMock.mockClear()
|
||||
getMock.mockClear()
|
||||
listMock.mockClear()
|
||||
shouldSkipMock.mockClear()
|
||||
@@ -62,18 +66,132 @@ describe("Action", () => {
|
||||
uploadMock.mockClear()
|
||||
})
|
||||
|
||||
it("creates release but does not upload if no artifact", async () => {
|
||||
const action = createAction(false, false)
|
||||
it("creates release with generated release notes when no body provided", async () => {
|
||||
const action = createAction(false, false, false, true, false, "")
|
||||
|
||||
await action.perform()
|
||||
|
||||
expect(genReleaseNotesMock).toHaveBeenCalledWith(tag, undefined, commit)
|
||||
expect(createMock).toHaveBeenCalledWith(
|
||||
tag,
|
||||
createBody,
|
||||
generatedReleaseBody,
|
||||
commit,
|
||||
discussionCategory,
|
||||
createDraft,
|
||||
makeLatest,
|
||||
createName,
|
||||
createPrerelease
|
||||
)
|
||||
expect(uploadMock).not.toHaveBeenCalled()
|
||||
assertOutputApplied()
|
||||
assertAssetUrlsApplied({})
|
||||
})
|
||||
|
||||
it("creates release with generated release notes that override existing body", async () => {
|
||||
const action = createAction(false, false, false, true, false, "")
|
||||
|
||||
await action.perform()
|
||||
|
||||
expect(genReleaseNotesMock).toHaveBeenCalledWith(tag, undefined, commit)
|
||||
expect(createMock).toHaveBeenCalledWith(
|
||||
tag,
|
||||
generatedReleaseBody,
|
||||
commit,
|
||||
discussionCategory,
|
||||
createDraft,
|
||||
makeLatest,
|
||||
createName,
|
||||
createPrerelease
|
||||
)
|
||||
expect(uploadMock).not.toHaveBeenCalled()
|
||||
assertOutputApplied()
|
||||
assertAssetUrlsApplied({})
|
||||
})
|
||||
|
||||
it("creates release with static body when generateReleaseNotes is false", async () => {
|
||||
const action = createAction(false, false, false, false, false, "static body")
|
||||
|
||||
await action.perform()
|
||||
|
||||
expect(genReleaseNotesMock).not.toHaveBeenCalled()
|
||||
expect(createMock).toHaveBeenCalledWith(
|
||||
tag,
|
||||
"static body",
|
||||
commit,
|
||||
discussionCategory,
|
||||
createDraft,
|
||||
makeLatest,
|
||||
createName,
|
||||
createPrerelease
|
||||
)
|
||||
expect(uploadMock).not.toHaveBeenCalled()
|
||||
assertOutputApplied()
|
||||
assertAssetUrlsApplied({})
|
||||
})
|
||||
|
||||
it("creates release with combined body and generated release notes", async () => {
|
||||
const action = createAction(false, false, false, true, false, createBody)
|
||||
|
||||
await action.perform()
|
||||
|
||||
expect(genReleaseNotesMock).toHaveBeenCalledWith(tag, undefined, commit)
|
||||
expect(createMock).toHaveBeenCalledWith(
|
||||
tag,
|
||||
`${createBody}\n${generatedReleaseBody}`,
|
||||
commit,
|
||||
discussionCategory,
|
||||
createDraft,
|
||||
makeLatest,
|
||||
createName,
|
||||
createPrerelease
|
||||
)
|
||||
expect(uploadMock).not.toHaveBeenCalled()
|
||||
assertOutputApplied()
|
||||
assertAssetUrlsApplied({})
|
||||
})
|
||||
|
||||
it("creates release with combined body and generated release notes using previous tag", async () => {
|
||||
const action = createAction(
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
createBody,
|
||||
true,
|
||||
createDraft,
|
||||
updateBody,
|
||||
previousTag
|
||||
)
|
||||
|
||||
await action.perform()
|
||||
|
||||
expect(genReleaseNotesMock).toHaveBeenCalledWith(tag, previousTag, commit)
|
||||
expect(createMock).toHaveBeenCalledWith(
|
||||
tag,
|
||||
`${createBody}\n${generatedReleaseBody}`,
|
||||
commit,
|
||||
discussionCategory,
|
||||
createDraft,
|
||||
makeLatest,
|
||||
createName,
|
||||
createPrerelease
|
||||
)
|
||||
expect(uploadMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("creates release but does not upload if no artifact", async () => {
|
||||
const action = createAction(false, false, false, true, false, "")
|
||||
|
||||
await action.perform()
|
||||
|
||||
expect(genReleaseNotesMock).toHaveBeenCalledWith(tag, undefined, commit)
|
||||
expect(createMock).toHaveBeenCalledWith(
|
||||
tag,
|
||||
generatedReleaseBody,
|
||||
commit,
|
||||
discussionCategory,
|
||||
createDraft,
|
||||
generateReleaseNotes,
|
||||
makeLatest,
|
||||
createName,
|
||||
createPrerelease
|
||||
@@ -84,19 +202,19 @@ describe("Action", () => {
|
||||
})
|
||||
|
||||
it("creates release if no release exists to update", async () => {
|
||||
const action = createAction(true, true)
|
||||
const action = createAction(true, true, false, true, false, "")
|
||||
const error = { status: 404 }
|
||||
getMock.mockRejectedValue(error)
|
||||
|
||||
await action.perform()
|
||||
|
||||
expect(genReleaseNotesMock).toHaveBeenCalledWith(tag, undefined, commit)
|
||||
expect(createMock).toHaveBeenCalledWith(
|
||||
tag,
|
||||
createBody,
|
||||
generatedReleaseBody,
|
||||
commit,
|
||||
discussionCategory,
|
||||
createDraft,
|
||||
generateReleaseNotes,
|
||||
makeLatest,
|
||||
createName,
|
||||
createPrerelease
|
||||
@@ -104,13 +222,13 @@ describe("Action", () => {
|
||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||
assertOutputApplied()
|
||||
assertAssetUrlsApplied({
|
||||
"art1": "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
"art2": "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
art1: "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
art2: "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
})
|
||||
})
|
||||
|
||||
it("creates release if no draft releases", async () => {
|
||||
const action = createAction(true, true)
|
||||
const action = createAction(true, true, false, true, false, "")
|
||||
const error = { status: 404 }
|
||||
getMock.mockRejectedValue(error)
|
||||
listMock.mockResolvedValue({
|
||||
@@ -119,13 +237,13 @@ describe("Action", () => {
|
||||
|
||||
await action.perform()
|
||||
|
||||
expect(genReleaseNotesMock).toHaveBeenCalledWith(tag, undefined, commit)
|
||||
expect(createMock).toHaveBeenCalledWith(
|
||||
tag,
|
||||
createBody,
|
||||
generatedReleaseBody,
|
||||
commit,
|
||||
discussionCategory,
|
||||
createDraft,
|
||||
generateReleaseNotes,
|
||||
makeLatest,
|
||||
createName,
|
||||
createPrerelease
|
||||
@@ -133,23 +251,23 @@ describe("Action", () => {
|
||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||
assertOutputApplied()
|
||||
assertAssetUrlsApplied({
|
||||
"art1": "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
"art2": "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
art1: "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
art2: "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
})
|
||||
})
|
||||
|
||||
it("creates release then uploads artifact", async () => {
|
||||
const action = createAction(false, true)
|
||||
const action = createAction(false, true, false, true, false, "")
|
||||
|
||||
await action.perform()
|
||||
|
||||
expect(genReleaseNotesMock).toHaveBeenCalledWith(tag, undefined, commit)
|
||||
expect(createMock).toHaveBeenCalledWith(
|
||||
tag,
|
||||
createBody,
|
||||
generatedReleaseBody,
|
||||
commit,
|
||||
discussionCategory,
|
||||
createDraft,
|
||||
generateReleaseNotes,
|
||||
makeLatest,
|
||||
createName,
|
||||
createPrerelease
|
||||
@@ -157,8 +275,8 @@ describe("Action", () => {
|
||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||
assertOutputApplied()
|
||||
assertAssetUrlsApplied({
|
||||
"art1": "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
"art2": "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
art1: "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
art2: "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
})
|
||||
})
|
||||
|
||||
@@ -170,8 +288,8 @@ describe("Action", () => {
|
||||
expect(artifactDestroyMock).toHaveBeenCalledWith(releaseId)
|
||||
assertOutputApplied()
|
||||
assertAssetUrlsApplied({
|
||||
"art1": "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
"art2": "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
art1: "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
art2: "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
})
|
||||
})
|
||||
|
||||
@@ -183,8 +301,8 @@ describe("Action", () => {
|
||||
expect(artifactDestroyMock).not.toHaveBeenCalled()
|
||||
assertOutputApplied()
|
||||
assertAssetUrlsApplied({
|
||||
"art1": "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
"art2": "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
art1: "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
art2: "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
})
|
||||
})
|
||||
|
||||
@@ -199,7 +317,7 @@ describe("Action", () => {
|
||||
})
|
||||
|
||||
it("throws error when create fails", async () => {
|
||||
const action = createAction(false, true)
|
||||
const action = createAction(false, true, false, true, false, "")
|
||||
createMock.mockRejectedValue("error")
|
||||
|
||||
expect.hasAssertions()
|
||||
@@ -209,13 +327,13 @@ describe("Action", () => {
|
||||
expect(error).toEqual("error")
|
||||
}
|
||||
|
||||
expect(genReleaseNotesMock).toHaveBeenCalledWith(tag, undefined, commit)
|
||||
expect(createMock).toHaveBeenCalledWith(
|
||||
tag,
|
||||
createBody,
|
||||
generatedReleaseBody,
|
||||
commit,
|
||||
discussionCategory,
|
||||
createDraft,
|
||||
generateReleaseNotes,
|
||||
makeLatest,
|
||||
createName,
|
||||
createPrerelease
|
||||
@@ -273,7 +391,7 @@ describe("Action", () => {
|
||||
})
|
||||
|
||||
it("throws error when update fails", async () => {
|
||||
const action = createAction(true, true)
|
||||
const action = createAction(true, true, false, true, false, "", true, createDraft, "")
|
||||
|
||||
updateMock.mockRejectedValue("error")
|
||||
|
||||
@@ -299,7 +417,7 @@ describe("Action", () => {
|
||||
})
|
||||
|
||||
it("throws error when upload fails", async () => {
|
||||
const action = createAction(false, true)
|
||||
const action = createAction(false, true, false, true, false, "")
|
||||
const expectedError = { status: 404 }
|
||||
uploadMock.mockRejectedValue(expectedError)
|
||||
|
||||
@@ -310,13 +428,13 @@ describe("Action", () => {
|
||||
expect(error).toEqual(expectedError)
|
||||
}
|
||||
|
||||
expect(genReleaseNotesMock).toHaveBeenCalledWith(tag, undefined, commit)
|
||||
expect(createMock).toHaveBeenCalledWith(
|
||||
tag,
|
||||
createBody,
|
||||
generatedReleaseBody,
|
||||
commit,
|
||||
discussionCategory,
|
||||
createDraft,
|
||||
generateReleaseNotes,
|
||||
makeLatest,
|
||||
createName,
|
||||
createPrerelease
|
||||
@@ -325,7 +443,7 @@ describe("Action", () => {
|
||||
})
|
||||
|
||||
it("updates draft release", async () => {
|
||||
const action = createAction(true, true)
|
||||
const action = createAction(true, true, false, true, false, "", true, createDraft, "")
|
||||
const error = { status: 404 }
|
||||
getMock.mockRejectedValue(error)
|
||||
listMock.mockResolvedValue({
|
||||
@@ -351,8 +469,8 @@ describe("Action", () => {
|
||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||
assertOutputApplied()
|
||||
assertAssetUrlsApplied({
|
||||
"art1": "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
"art2": "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
art1: "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
art2: "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
})
|
||||
})
|
||||
|
||||
@@ -383,13 +501,58 @@ describe("Action", () => {
|
||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||
assertOutputApplied()
|
||||
assertAssetUrlsApplied({
|
||||
"art1": "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
"art2": "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
art1: "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
art2: "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
})
|
||||
})
|
||||
|
||||
it("updates release with combined body and generated release notes", async () => {
|
||||
const action = createAction(true, true, false, true, false, "", true, createDraft, updateBody)
|
||||
|
||||
await action.perform()
|
||||
|
||||
expect(genReleaseNotesMock).toHaveBeenCalledWith(tag, undefined, commit)
|
||||
expect(updateMock).toHaveBeenCalledWith(
|
||||
id,
|
||||
tag,
|
||||
`${updateBody}\n${generatedReleaseBody}`,
|
||||
commit,
|
||||
discussionCategory,
|
||||
updateDraft,
|
||||
makeLatest,
|
||||
updateName,
|
||||
updatePrerelease
|
||||
)
|
||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||
assertOutputApplied()
|
||||
assertAssetUrlsApplied({
|
||||
art1: "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
art2: "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
})
|
||||
})
|
||||
|
||||
it("updates release with combined body and generated release notes using previous tag", async () => {
|
||||
const action = createAction(true, true, false, true, false, "", true, createDraft, updateBody, previousTag)
|
||||
|
||||
await action.perform()
|
||||
|
||||
expect(genReleaseNotesMock).toHaveBeenCalledWith(tag, previousTag, commit)
|
||||
expect(updateMock).toHaveBeenCalledWith(
|
||||
id,
|
||||
tag,
|
||||
`${updateBody}\n${generatedReleaseBody}`,
|
||||
commit,
|
||||
discussionCategory,
|
||||
updateDraft,
|
||||
makeLatest,
|
||||
updateName,
|
||||
updatePrerelease
|
||||
)
|
||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||
})
|
||||
|
||||
it("updates release but does not upload if no artifact", async () => {
|
||||
const action = createAction(true, false)
|
||||
const action = createAction(true, false, false, true, false, "", true, createDraft, "")
|
||||
|
||||
await action.perform()
|
||||
|
||||
@@ -410,7 +573,7 @@ describe("Action", () => {
|
||||
})
|
||||
|
||||
it("updates release then uploads artifact", async () => {
|
||||
const action = createAction(true, true)
|
||||
const action = createAction(true, true, false, true, false, "", true, createDraft, "")
|
||||
|
||||
await action.perform()
|
||||
|
||||
@@ -428,8 +591,8 @@ describe("Action", () => {
|
||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||
assertOutputApplied()
|
||||
assertAssetUrlsApplied({
|
||||
"art1": "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
"art2": "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
art1: "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
art2: "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
})
|
||||
})
|
||||
|
||||
@@ -460,8 +623,147 @@ describe("Action", () => {
|
||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||
assertOutputApplied()
|
||||
assertAssetUrlsApplied({
|
||||
"art1": "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
"art2": "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
art1: "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
art2: "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
})
|
||||
})
|
||||
|
||||
it("does not publish immutable release when immutableCreate is false", async () => {
|
||||
const action = createAction(false, true, false, true, false, "", false, false)
|
||||
|
||||
await action.perform()
|
||||
|
||||
expect(createMock).toHaveBeenCalledWith(
|
||||
tag,
|
||||
generatedReleaseBody,
|
||||
commit,
|
||||
discussionCategory,
|
||||
false, // draft should be false when createdDraft is false
|
||||
makeLatest,
|
||||
createName,
|
||||
createPrerelease
|
||||
)
|
||||
// Should only call update once for regular create, not for publishImmutableRelease
|
||||
expect(updateMock).not.toHaveBeenCalled()
|
||||
assertOutputApplied()
|
||||
})
|
||||
|
||||
it("does not publish immutable release when createdDraft is true", async () => {
|
||||
const action = createAction(false, true, false, true, false, "", true, true)
|
||||
|
||||
await action.perform()
|
||||
|
||||
expect(createMock).toHaveBeenCalledWith(
|
||||
tag,
|
||||
generatedReleaseBody,
|
||||
commit,
|
||||
discussionCategory,
|
||||
true, // draft should be true when createdDraft is true or immutableCreate is true
|
||||
makeLatest,
|
||||
createName,
|
||||
createPrerelease
|
||||
)
|
||||
// Should only call update once for regular create, not for publishImmutableRelease
|
||||
expect(updateMock).not.toHaveBeenCalled()
|
||||
assertOutputApplied()
|
||||
})
|
||||
|
||||
it("publishes immutable release when immutableCreate is true and createdDraft is false", async () => {
|
||||
const action = createAction(false, true, false, true, false, "", true, false)
|
||||
const immutableReleaseResponse = {
|
||||
data: {
|
||||
id: 999,
|
||||
upload_url: "http://immutable.example.com",
|
||||
html_url: "https://github.com/owner/repo/releases/tag/v1.0.0-immutable",
|
||||
tarball_url: "https://api.github.com/repos/owner/repo/tarball/v1.0.0-immutable",
|
||||
zipball_url: "https://api.github.com/repos/owner/repo/zipball/v1.0.0-immutable",
|
||||
},
|
||||
}
|
||||
updateMock.mockResolvedValueOnce(immutableReleaseResponse)
|
||||
|
||||
await action.perform()
|
||||
|
||||
expect(createMock).toHaveBeenCalledWith(
|
||||
tag,
|
||||
generatedReleaseBody,
|
||||
commit,
|
||||
discussionCategory,
|
||||
true, // draft should be true when immutableCreate is true
|
||||
makeLatest,
|
||||
createName,
|
||||
createPrerelease
|
||||
)
|
||||
// Should call update for publishImmutableRelease
|
||||
expect(updateMock).toHaveBeenCalledWith(
|
||||
releaseId,
|
||||
tag,
|
||||
undefined, // body is omitted
|
||||
undefined, // commit is omitted
|
||||
discussionCategory,
|
||||
false, // draft is set to false to publish the release
|
||||
makeLatest,
|
||||
createName,
|
||||
createPrerelease
|
||||
)
|
||||
// Should apply the immutable release data instead of the original
|
||||
expect(applyReleaseDataMock).toHaveBeenCalledWith(immutableReleaseResponse.data)
|
||||
assertAssetUrlsApplied({
|
||||
art1: "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
art2: "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
})
|
||||
})
|
||||
|
||||
it("publishes immutable release when allowUpdates is true but release does not exist", async () => {
|
||||
const action = createAction(true, true, false, true, false, "", true, false)
|
||||
const error = { status: 404 }
|
||||
getMock.mockRejectedValue(error)
|
||||
listMock.mockResolvedValue({ data: [] }) // No draft releases found
|
||||
|
||||
const immutableReleaseResponse = {
|
||||
data: {
|
||||
id: 888,
|
||||
upload_url: "http://immutable-update.example.com",
|
||||
html_url: "https://github.com/owner/repo/releases/tag/v1.0.0-immutable-update",
|
||||
tarball_url: "https://api.github.com/repos/owner/repo/tarball/v1.0.0-immutable-update",
|
||||
zipball_url: "https://api.github.com/repos/owner/repo/zipball/v1.0.0-immutable-update",
|
||||
},
|
||||
}
|
||||
updateMock.mockResolvedValueOnce(immutableReleaseResponse)
|
||||
|
||||
await action.perform()
|
||||
|
||||
// Should try to get the release first (allowUpdates=true)
|
||||
expect(getMock).toHaveBeenCalledWith(tag)
|
||||
// Should check for draft releases when get fails with 404
|
||||
expect(listMock).toHaveBeenCalled()
|
||||
// Should create a new release when no drafts found
|
||||
expect(createMock).toHaveBeenCalledWith(
|
||||
tag,
|
||||
generatedReleaseBody,
|
||||
commit,
|
||||
discussionCategory,
|
||||
true, // draft should be true when immutableCreate is true
|
||||
makeLatest,
|
||||
createName,
|
||||
createPrerelease
|
||||
)
|
||||
// Should call update for publishImmutableRelease
|
||||
expect(updateMock).toHaveBeenCalledWith(
|
||||
releaseId,
|
||||
tag,
|
||||
undefined, // body is omitted
|
||||
undefined, // commit is omitted
|
||||
discussionCategory,
|
||||
false, // draft is set to false to publish the release
|
||||
makeLatest,
|
||||
createName,
|
||||
createPrerelease
|
||||
)
|
||||
// Should apply the immutable release data instead of the original
|
||||
expect(applyReleaseDataMock).toHaveBeenCalledWith(immutableReleaseResponse.data)
|
||||
assertAssetUrlsApplied({
|
||||
art1: "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
art2: "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
})
|
||||
})
|
||||
|
||||
@@ -484,7 +786,12 @@ describe("Action", () => {
|
||||
hasArtifact: boolean,
|
||||
removeArtifacts = false,
|
||||
generateReleaseNotes = true,
|
||||
omitBodyDuringUpdate = false
|
||||
omitBodyDuringUpdate = false,
|
||||
createdReleaseBody = createBody,
|
||||
immutableCreate = true,
|
||||
createdDraft = createDraft,
|
||||
updatedReleaseBody = updateBody,
|
||||
generateReleaseNotesPreviousTag: string | undefined = undefined
|
||||
): Action {
|
||||
let inputArtifact: Artifact[]
|
||||
|
||||
@@ -494,7 +801,7 @@ describe("Action", () => {
|
||||
inputArtifact = []
|
||||
}
|
||||
|
||||
const MockReleases = jest.fn<Releases, any>(() => {
|
||||
const MockReleases = vi.fn<() => Releases>(() => {
|
||||
return {
|
||||
create: createMock,
|
||||
deleteArtifact: deleteMock,
|
||||
@@ -502,7 +809,7 @@ describe("Action", () => {
|
||||
listArtifactsForRelease: listArtifactsMock,
|
||||
listReleases: listMock,
|
||||
update: updateMock,
|
||||
uploadArtifact: jest.fn(),
|
||||
uploadArtifact: vi.fn(),
|
||||
generateReleaseNotes: genReleaseNotesMock,
|
||||
}
|
||||
})
|
||||
@@ -541,21 +848,23 @@ describe("Action", () => {
|
||||
},
|
||||
})
|
||||
uploadMock.mockResolvedValue({
|
||||
"art1": "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
"art2": "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
art1: "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||
art2: "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||
})
|
||||
|
||||
const MockInputs = jest.fn<Inputs, any>(() => {
|
||||
const MockInputs = vi.fn<() => Inputs>(() => {
|
||||
return {
|
||||
allowUpdates,
|
||||
artifactErrorsFailBuild: true,
|
||||
artifacts: inputArtifact,
|
||||
createdDraft: createDraft,
|
||||
createdReleaseBody: createBody,
|
||||
createdDraft: createdDraft,
|
||||
createdReleaseBody: createdReleaseBody,
|
||||
createdReleaseName: createName,
|
||||
commit,
|
||||
discussionCategory,
|
||||
generateReleaseNotes,
|
||||
generateReleaseNotesPreviousTag: generateReleaseNotesPreviousTag,
|
||||
immutableCreate: immutableCreate,
|
||||
makeLatest: makeLatest,
|
||||
owner: "owner",
|
||||
createdPrerelease: createPrerelease,
|
||||
@@ -566,42 +875,42 @@ describe("Action", () => {
|
||||
tag,
|
||||
token,
|
||||
updatedDraft: updateDraft,
|
||||
updatedReleaseBody: updateBody,
|
||||
updatedReleaseBody: updatedReleaseBody,
|
||||
updatedReleaseName: updateName,
|
||||
updatedPrerelease: updatePrerelease,
|
||||
updateOnlyUnreleased: updateOnlyUnreleased,
|
||||
omitBodyDuringUpdate,
|
||||
}
|
||||
})
|
||||
const MockOutputs = jest.fn<Outputs, any>(() => {
|
||||
const MockOutputs = vi.fn<() => Outputs>(() => {
|
||||
return {
|
||||
applyReleaseData: applyReleaseDataMock,
|
||||
applyAssetUrls: applyAssetUrlsMock,
|
||||
}
|
||||
})
|
||||
const MockUploader = jest.fn<ArtifactUploader, any>(() => {
|
||||
const MockUploader = vi.fn<() => ArtifactUploader>(() => {
|
||||
return {
|
||||
uploadArtifacts: uploadMock,
|
||||
}
|
||||
})
|
||||
const MockArtifactDestroyer = jest.fn<ArtifactDestroyer, any>(() => {
|
||||
const MockArtifactDestroyer = vi.fn<() => ArtifactDestroyer>(() => {
|
||||
return {
|
||||
destroyArtifacts: artifactDestroyMock,
|
||||
}
|
||||
})
|
||||
|
||||
const MockActionSkipper = jest.fn<ActionSkipper, any>(() => {
|
||||
const MockActionSkipper = vi.fn<() => ActionSkipper>(() => {
|
||||
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()
|
||||
const inputs = MockInputs()
|
||||
const outputs = MockOutputs()
|
||||
const releases = MockReleases()
|
||||
const uploader = MockUploader()
|
||||
const artifactDestroyer = MockArtifactDestroyer()
|
||||
const actionSkipper = MockActionSkipper()
|
||||
|
||||
return new Action(inputs, outputs, releases, uploader, artifactDestroyer, actionSkipper)
|
||||
}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import { ReleaseActionSkipper } from "../src/ActionSkipper"
|
||||
import type { Releases } from "../src/Releases"
|
||||
import { describe, expect, it, vi } from "vitest"
|
||||
import { ReleaseActionSkipper } from "../src/ActionSkipper.js"
|
||||
import type { Releases } from "../src/Releases.js"
|
||||
|
||||
describe("shouldSkip", () => {
|
||||
const getMock = jest.fn()
|
||||
const getMock = vi.fn()
|
||||
const tag = "tag"
|
||||
const MockReleases = jest.fn<Releases, any>(() => {
|
||||
const MockReleases = vi.fn<() => Releases>(() => {
|
||||
return {
|
||||
create: jest.fn(),
|
||||
deleteArtifact: jest.fn(),
|
||||
create: vi.fn(),
|
||||
deleteArtifact: vi.fn(),
|
||||
getByTag: getMock,
|
||||
listArtifactsForRelease: jest.fn(),
|
||||
listReleases: jest.fn(),
|
||||
update: jest.fn(),
|
||||
uploadArtifact: jest.fn(),
|
||||
generateReleaseNotes: jest.fn(),
|
||||
listArtifactsForRelease: vi.fn(),
|
||||
listReleases: vi.fn(),
|
||||
update: vi.fn(),
|
||||
uploadArtifact: vi.fn(),
|
||||
generateReleaseNotes: vi.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import { Artifact } from "../src/Artifact"
|
||||
import * as fs from "node:fs"
|
||||
import { describe, expect, it, vi } from "vitest"
|
||||
import { Artifact } from "../src/Artifact.js"
|
||||
|
||||
vi.mock("fs")
|
||||
|
||||
const contentLength = 42
|
||||
const fakeReadStream = {}
|
||||
|
||||
jest.mock("fs", () => {
|
||||
return {
|
||||
createReadStream: () => fakeReadStream,
|
||||
statSync: () => {
|
||||
return { size: contentLength }
|
||||
},
|
||||
}
|
||||
})
|
||||
const mockCreateReadStream = vi.mocked(fs.createReadStream)
|
||||
const mockStatSync = vi.mocked(fs.statSync)
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Mock object for testing
|
||||
mockCreateReadStream.mockReturnValue(fakeReadStream as any)
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Partial Stats object for testing
|
||||
mockStatSync.mockReturnValue({ size: contentLength } as any)
|
||||
|
||||
describe("Artifact", () => {
|
||||
it("defaults contentType to raw", () => {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { GithubArtifactDestroyer } from "../src/ArtifactDestroyer"
|
||||
import type { Releases } from "../src/Releases"
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest"
|
||||
import { GithubArtifactDestroyer } from "../src/ArtifactDestroyer.js"
|
||||
import type { Releases } from "../src/Releases.js"
|
||||
|
||||
const releaseId = 100
|
||||
|
||||
const deleteMock = jest.fn()
|
||||
const listArtifactsMock = jest.fn()
|
||||
const deleteMock = vi.fn()
|
||||
const listArtifactsMock = vi.fn()
|
||||
|
||||
describe("ArtifactDestroyer", () => {
|
||||
beforeEach(() => {
|
||||
@@ -45,26 +46,26 @@ describe("ArtifactDestroyer", () => {
|
||||
})
|
||||
|
||||
function createDestroyer(): GithubArtifactDestroyer {
|
||||
const MockReleases = jest.fn<Releases, any>(() => {
|
||||
const MockReleases = vi.fn<() => Releases>(() => {
|
||||
return {
|
||||
create: jest.fn(),
|
||||
create: vi.fn(),
|
||||
deleteArtifact: deleteMock,
|
||||
getByTag: jest.fn(),
|
||||
getByTag: vi.fn(),
|
||||
listArtifactsForRelease: listArtifactsMock,
|
||||
listReleases: jest.fn(),
|
||||
update: jest.fn(),
|
||||
uploadArtifact: jest.fn(),
|
||||
generateReleaseNotes: jest.fn(),
|
||||
listReleases: vi.fn(),
|
||||
update: vi.fn(),
|
||||
uploadArtifact: vi.fn(),
|
||||
generateReleaseNotes: vi.fn(),
|
||||
}
|
||||
})
|
||||
return new GithubArtifactDestroyer(new MockReleases())
|
||||
return new GithubArtifactDestroyer(MockReleases())
|
||||
}
|
||||
|
||||
function mockDeleteError(): any {
|
||||
function mockDeleteError(): void {
|
||||
deleteMock.mockRejectedValue("error")
|
||||
}
|
||||
|
||||
function mockDeleteSuccess(): any {
|
||||
function mockDeleteSuccess(): void {
|
||||
deleteMock.mockResolvedValue({})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,32 +1,33 @@
|
||||
const warnMock = jest.fn()
|
||||
import * as fs from "node:fs"
|
||||
import * as core from "@actions/core"
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest"
|
||||
|
||||
import { FileArtifactGlobber } from "../src/ArtifactGlobber"
|
||||
import { Globber } from "../src/Globber"
|
||||
import { Artifact } from "../src/Artifact"
|
||||
import untildify = require("untildify")
|
||||
vi.mock("@actions/core")
|
||||
vi.mock("fs")
|
||||
|
||||
import { Artifact } from "../src/Artifact.js"
|
||||
import { FileArtifactGlobber } from "../src/ArtifactGlobber.js"
|
||||
import type { Globber } from "../src/Globber.js"
|
||||
import { expandTilde } from "../src/PathExpander.js"
|
||||
|
||||
const warnMock = vi.mocked(core.warning)
|
||||
const mockStatSync = vi.mocked(fs.statSync)
|
||||
// biome-ignore lint/suspicious/noExplicitAny: fs.realpathSync has overloads that are difficult to type
|
||||
const mockRealpathSync = vi.mocked(fs.realpathSync as any)
|
||||
|
||||
const contentType = "raw"
|
||||
const globMock = jest.fn()
|
||||
const globMock = vi.fn()
|
||||
const globResults = ["file1", "file2"]
|
||||
|
||||
jest.mock("@actions/core", () => {
|
||||
return { warning: warnMock }
|
||||
})
|
||||
mockStatSync.mockReturnValue({
|
||||
isDirectory(): boolean {
|
||||
return false
|
||||
},
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Partial Stats object for testing
|
||||
} as any)
|
||||
|
||||
jest.mock("fs", () => {
|
||||
return {
|
||||
statSync: () => {
|
||||
return {
|
||||
isDirectory(): boolean {
|
||||
return false
|
||||
},
|
||||
}
|
||||
},
|
||||
realpathSync: () => {
|
||||
return false
|
||||
},
|
||||
}
|
||||
})
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Mock return value for testing
|
||||
mockRealpathSync.mockReturnValue(false as any)
|
||||
|
||||
describe("ArtifactGlobber", () => {
|
||||
beforeEach(() => {
|
||||
@@ -39,7 +40,7 @@ describe("ArtifactGlobber", () => {
|
||||
const expectedArtifacts = globResults.map((path) => new Artifact(path, contentType))
|
||||
|
||||
expect(globber.globArtifactString("~/path", "raw", false)).toEqual(expectedArtifacts)
|
||||
expect(globMock).toHaveBeenCalledWith(untildify("~/path"))
|
||||
expect(globMock).toHaveBeenCalledWith(expandTilde("~/path"))
|
||||
expect(warnMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -90,12 +91,12 @@ describe("ArtifactGlobber", () => {
|
||||
})
|
||||
|
||||
function createArtifactGlobber(results: string[] = globResults): FileArtifactGlobber {
|
||||
const MockGlobber = jest.fn<Globber, any>(() => {
|
||||
const MockGlobber = vi.fn<() => Globber>(() => {
|
||||
return {
|
||||
glob: globMock,
|
||||
}
|
||||
})
|
||||
globMock.mockReturnValue(results)
|
||||
return new FileArtifactGlobber(new MockGlobber())
|
||||
return new FileArtifactGlobber(MockGlobber())
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
const directoryMock = jest.fn()
|
||||
const warnMock = jest.fn()
|
||||
import * as fs from "node:fs"
|
||||
import * as core from "@actions/core"
|
||||
import { describe, expect, it, vi } from "vitest"
|
||||
|
||||
import { ArtifactPathValidator } from "../src/ArtifactPathValidator"
|
||||
vi.mock("@actions/core")
|
||||
vi.mock("fs")
|
||||
|
||||
import { ArtifactPathValidator } from "../src/ArtifactPathValidator.js"
|
||||
|
||||
const warnMock = vi.mocked(core.warning)
|
||||
const mockStatSync = vi.mocked(fs.statSync)
|
||||
const directoryMock = vi.fn()
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Partial Stats object for testing
|
||||
mockStatSync.mockReturnValue({ isDirectory: directoryMock } as any)
|
||||
|
||||
const pattern = "pattern"
|
||||
|
||||
jest.mock("@actions/core", () => {
|
||||
return { warning: warnMock }
|
||||
})
|
||||
|
||||
jest.mock("fs", () => {
|
||||
return {
|
||||
statSync: () => {
|
||||
return { isDirectory: directoryMock }
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe("ArtifactPathValidator", () => {
|
||||
beforeEach(() => {
|
||||
warnMock.mockClear()
|
||||
@@ -36,7 +35,7 @@ describe("ArtifactPathValidator", () => {
|
||||
|
||||
it("warns when no glob results are produced and empty results shouldn't throw", () => {
|
||||
const validator = new ArtifactPathValidator(false, [], pattern)
|
||||
const result = validator.validate()
|
||||
const _result = validator.validate()
|
||||
expect(warnMock).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { RequestError } from "@octokit/request-error"
|
||||
import { Artifact } from "../src/Artifact"
|
||||
import { GithubArtifactUploader } from "../src/ArtifactUploader"
|
||||
import type { Releases } from "../src/Releases"
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest"
|
||||
import { Artifact } from "../src/Artifact.js"
|
||||
import { GithubArtifactUploader } from "../src/ArtifactUploader.js"
|
||||
import type { Releases } from "../src/Releases.js"
|
||||
|
||||
const artifacts = [new Artifact("a/art1"), new Artifact("b/art2")]
|
||||
const fakeReadStream = {}
|
||||
@@ -9,9 +10,9 @@ const contentLength = 42
|
||||
const releaseId = 100
|
||||
const url = "http://api.example.com"
|
||||
|
||||
const deleteMock = jest.fn()
|
||||
const listArtifactsMock = jest.fn()
|
||||
const uploadMock = jest.fn()
|
||||
const deleteMock = vi.fn()
|
||||
const listArtifactsMock = vi.fn()
|
||||
const uploadMock = vi.fn()
|
||||
|
||||
// Mock response with browser_download_url
|
||||
const mockUploadResponse = (name: string) => ({
|
||||
@@ -19,11 +20,11 @@ const mockUploadResponse = (name: string) => ({
|
||||
browser_download_url: `https://github.com/octocat/Hello-World/releases/download/v1.0.0/${name}`,
|
||||
name: name,
|
||||
id: 1,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
jest.mock("fs", () => {
|
||||
const originalFs = jest.requireActual("fs")
|
||||
vi.mock("fs", async () => {
|
||||
const originalFs = await vi.importActual<typeof import("fs")>("fs")
|
||||
return {
|
||||
...originalFs,
|
||||
promises: {},
|
||||
@@ -49,8 +50,8 @@ describe("ArtifactUploader", () => {
|
||||
const result = await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||
|
||||
expect(result).toEqual({
|
||||
"art1": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art1",
|
||||
"art2": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art2",
|
||||
art1: "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art1",
|
||||
art2: "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art2",
|
||||
})
|
||||
expect(uploadMock).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
@@ -117,8 +118,8 @@ describe("ArtifactUploader", () => {
|
||||
const result = await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||
|
||||
expect(result).toEqual({
|
||||
"art1": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art1",
|
||||
"art2": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art2",
|
||||
art1: "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art1",
|
||||
art2: "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art2",
|
||||
})
|
||||
expect(uploadMock).toHaveBeenCalledTimes(2)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
||||
@@ -138,8 +139,8 @@ describe("ArtifactUploader", () => {
|
||||
const result = await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||
|
||||
expect(result).toEqual({
|
||||
"art1": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art1",
|
||||
"art2": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art2",
|
||||
art1: "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art1",
|
||||
art2: "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art2",
|
||||
})
|
||||
expect(uploadMock).toHaveBeenCalledTimes(2)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
||||
@@ -201,8 +202,8 @@ describe("ArtifactUploader", () => {
|
||||
const result = await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||
|
||||
expect(result).toEqual({
|
||||
"art1": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art1",
|
||||
"art2": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art2",
|
||||
art1: "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art1",
|
||||
art2: "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art2",
|
||||
})
|
||||
expect(uploadMock).toHaveBeenCalledTimes(2)
|
||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
||||
@@ -212,26 +213,26 @@ describe("ArtifactUploader", () => {
|
||||
})
|
||||
|
||||
function createUploader(replaces: boolean, throws = false): GithubArtifactUploader {
|
||||
const MockReleases = jest.fn<Releases, any>(() => {
|
||||
const MockReleases = vi.fn<() => Releases>(() => {
|
||||
return {
|
||||
create: jest.fn(),
|
||||
create: vi.fn(),
|
||||
deleteArtifact: deleteMock,
|
||||
getByTag: jest.fn(),
|
||||
getByTag: vi.fn(),
|
||||
listArtifactsForRelease: listArtifactsMock,
|
||||
listReleases: jest.fn(),
|
||||
update: jest.fn(),
|
||||
listReleases: vi.fn(),
|
||||
update: vi.fn(),
|
||||
uploadArtifact: uploadMock,
|
||||
generateReleaseNotes: jest.fn(),
|
||||
generateReleaseNotes: vi.fn(),
|
||||
}
|
||||
})
|
||||
return new GithubArtifactUploader(new MockReleases(), replaces, throws)
|
||||
return new GithubArtifactUploader(MockReleases(), replaces, throws)
|
||||
}
|
||||
|
||||
function mockDeleteError(): any {
|
||||
function mockDeleteError(): void {
|
||||
deleteMock.mockRejectedValue("error")
|
||||
}
|
||||
|
||||
function mockDeleteSuccess(): any {
|
||||
function mockDeleteSuccess(): void {
|
||||
deleteMock.mockResolvedValue({})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { GithubError } from "../src/GithubError"
|
||||
import { describe, expect, it } from "vitest"
|
||||
import { GithubError } from "../src/GithubError.js"
|
||||
|
||||
describe("ErrorMessage", () => {
|
||||
describe("has error with code", () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { GithubErrorDetail } from "../src/GithubErrorDetail"
|
||||
import { describe, expect, it } from "vitest"
|
||||
import { GithubErrorDetail } from "../src/GithubErrorDetail.js"
|
||||
|
||||
describe("GithubErrorDetail", () => {
|
||||
it("provides error code", () => {
|
||||
|
||||
@@ -1,41 +1,56 @@
|
||||
const mockGetInput = jest.fn()
|
||||
const mockGetBooleanInput = jest.fn()
|
||||
const mockGlob = jest.fn()
|
||||
const mockReadFileSync = jest.fn()
|
||||
const mockStatSync = jest.fn()
|
||||
import * as fs from "node:fs"
|
||||
import * as core from "@actions/core"
|
||||
import type * as github from "@actions/github"
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest"
|
||||
|
||||
import { Artifact } from "../src/Artifact"
|
||||
import { ArtifactGlobber } from "../src/ArtifactGlobber"
|
||||
import { Context } from "@actions/github/lib/context"
|
||||
import { Inputs, CoreInputs } from "../src/Inputs"
|
||||
vi.mock("@actions/core")
|
||||
vi.mock("fs")
|
||||
|
||||
import { Artifact } from "../src/Artifact.js"
|
||||
import type { ArtifactGlobber } from "../src/ArtifactGlobber.js"
|
||||
import { CoreInputs, type Inputs } from "../src/Inputs.js"
|
||||
|
||||
const mockGetInput = vi.mocked(core.getInput)
|
||||
const mockGetBooleanInput = vi.mocked(core.getBooleanInput)
|
||||
const mockReadFileSync = vi.mocked(fs.readFileSync)
|
||||
const _mockStatSync = vi.mocked(fs.statSync)
|
||||
const mockExistsSync = vi.mocked(fs.existsSync)
|
||||
const mockGlob = vi.fn()
|
||||
|
||||
// existsSync is used by Context's constructor
|
||||
mockExistsSync.mockReturnValue(false)
|
||||
|
||||
const artifacts = [new Artifact("a/art1"), new Artifact("b/art2")]
|
||||
|
||||
jest.mock("@actions/core", () => {
|
||||
return {
|
||||
getInput: mockGetInput,
|
||||
getBooleanInput: mockGetBooleanInput,
|
||||
}
|
||||
})
|
||||
|
||||
jest.mock("fs", () => {
|
||||
// existsSync is used by Context's constructor
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
return {
|
||||
existsSync: () => {
|
||||
return false
|
||||
},
|
||||
readFileSync: mockReadFileSync,
|
||||
statSync: mockStatSync,
|
||||
}
|
||||
})
|
||||
|
||||
describe("Inputs", () => {
|
||||
let context: Context
|
||||
let context: typeof github.context
|
||||
let inputs: Inputs
|
||||
beforeEach(() => {
|
||||
mockGetInput.mockReset()
|
||||
context = new Context()
|
||||
mockGlob.mockClear()
|
||||
context = {
|
||||
payload: {},
|
||||
eventName: "",
|
||||
sha: "",
|
||||
ref: "",
|
||||
workflow: "",
|
||||
action: "",
|
||||
actor: "",
|
||||
job: "",
|
||||
runNumber: 0,
|
||||
runId: 0,
|
||||
runAttempt: 0,
|
||||
apiUrl: "",
|
||||
serverUrl: "",
|
||||
graphqlUrl: "",
|
||||
get repo() {
|
||||
const repo = process.env.GITHUB_REPOSITORY || "/"
|
||||
const [owner, repoName] = repo.split("/")
|
||||
return { owner: owner || "", repo: repoName || "" }
|
||||
},
|
||||
issue: { owner: "", repo: "", number: 0 },
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Partial Context object for testing
|
||||
} as any
|
||||
inputs = new CoreInputs(createGlobber(), context)
|
||||
})
|
||||
|
||||
@@ -189,17 +204,46 @@ describe("Inputs", () => {
|
||||
})
|
||||
|
||||
describe("generateReleaseNotes", () => {
|
||||
it("returns returns true", function () {
|
||||
it("returns returns true", () => {
|
||||
mockGetInput.mockReturnValue("true")
|
||||
expect(inputs.generateReleaseNotes).toBe(true)
|
||||
})
|
||||
|
||||
it("returns false when omitted", function () {
|
||||
it("returns false when omitted", () => {
|
||||
mockGetInput.mockReturnValue("")
|
||||
expect(inputs.generateReleaseNotes).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("generateReleaseNotesPreviousTag", () => {
|
||||
it("returns the previous tag when provided", () => {
|
||||
mockGetInput.mockReturnValue("v1.0.0")
|
||||
expect(inputs.generateReleaseNotesPreviousTag).toBe("v1.0.0")
|
||||
})
|
||||
|
||||
it("returns undefined when omitted", () => {
|
||||
mockGetInput.mockReturnValue("")
|
||||
expect(inputs.generateReleaseNotesPreviousTag).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe("immutableCreate", () => {
|
||||
it("returns false by default", () => {
|
||||
mockGetInput.mockReturnValue("")
|
||||
expect(inputs.immutableCreate).toBe(false)
|
||||
})
|
||||
|
||||
it("returns true when explicitly set", () => {
|
||||
mockGetInput.mockReturnValue("true")
|
||||
expect(inputs.immutableCreate).toBe(true)
|
||||
})
|
||||
|
||||
it("returns false when explicitly disabled", () => {
|
||||
mockGetInput.mockReturnValue("false")
|
||||
expect(inputs.immutableCreate).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("makeLatest", () => {
|
||||
it("returns legacy", () => {
|
||||
mockGetInput.mockReturnValueOnce("legacy")
|
||||
@@ -223,12 +267,12 @@ describe("Inputs", () => {
|
||||
})
|
||||
|
||||
describe("owner", () => {
|
||||
it("returns owner from context", function () {
|
||||
it("returns owner from context", () => {
|
||||
process.env.GITHUB_REPOSITORY = "owner/repo"
|
||||
mockGetInput.mockReturnValue("")
|
||||
expect(inputs.owner).toBe("owner")
|
||||
})
|
||||
it("returns owner from inputs", function () {
|
||||
it("returns owner from inputs", () => {
|
||||
mockGetInput.mockReturnValue("owner")
|
||||
expect(inputs.owner).toBe("owner")
|
||||
})
|
||||
@@ -268,12 +312,12 @@ describe("Inputs", () => {
|
||||
})
|
||||
|
||||
describe("repo", () => {
|
||||
it("returns repo from context", function () {
|
||||
it("returns repo from context", () => {
|
||||
process.env.GITHUB_REPOSITORY = "owner/repo"
|
||||
mockGetInput.mockReturnValue("")
|
||||
expect(inputs.repo).toBe("repo")
|
||||
})
|
||||
it("returns repo from inputs", function () {
|
||||
it("returns repo from inputs", () => {
|
||||
mockGetInput.mockReturnValue("repo")
|
||||
expect(inputs.repo).toBe("repo")
|
||||
})
|
||||
@@ -301,11 +345,6 @@ describe("Inputs", () => {
|
||||
context.ref = "refs/tags/sha-tag"
|
||||
expect(inputs.tag).toBe("sha-tag")
|
||||
})
|
||||
it("returns context sha when input is null", () => {
|
||||
mockGetInput.mockReturnValue(null)
|
||||
context.ref = "refs/tags/sha-tag"
|
||||
expect(inputs.tag).toBe("sha-tag")
|
||||
})
|
||||
it("throws if no tag", () => {
|
||||
context.ref = ""
|
||||
expect(() => inputs.tag).toThrow()
|
||||
@@ -433,12 +472,12 @@ describe("Inputs", () => {
|
||||
})
|
||||
|
||||
function createGlobber(): ArtifactGlobber {
|
||||
const MockGlobber = jest.fn<ArtifactGlobber, any>(() => {
|
||||
const MockGlobber = vi.fn<() => ArtifactGlobber>(() => {
|
||||
return {
|
||||
globArtifactString: mockGlob,
|
||||
}
|
||||
})
|
||||
mockGlob.mockImplementation(() => artifacts)
|
||||
return new MockGlobber()
|
||||
return MockGlobber()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { Action } from "../src/Action"
|
||||
import * as path from "node:path"
|
||||
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 { beforeEach, describe, it, vi } from "vitest"
|
||||
import { Action } from "../src/Action.js"
|
||||
import { ReleaseActionSkipper } from "../src/ActionSkipper.js"
|
||||
import { GithubArtifactDestroyer } from "../src/ArtifactDestroyer.js"
|
||||
import { FileArtifactGlobber } from "../src/ArtifactGlobber.js"
|
||||
import { GithubArtifactUploader } from "../src/ArtifactUploader.js"
|
||||
import type { Inputs } from "../src/Inputs.js"
|
||||
import type { Outputs } from "../src/Outputs.js"
|
||||
import { GithubReleases, type ReleaseData } from "../src/Releases.js"
|
||||
|
||||
// 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
|
||||
@@ -34,7 +35,7 @@ describe.skip("Integration Test", () => {
|
||||
})
|
||||
|
||||
function getInputs(): Inputs {
|
||||
const MockInputs = jest.fn<Inputs, any>(() => {
|
||||
const MockInputs = vi.fn<() => Inputs>(() => {
|
||||
return {
|
||||
allowUpdates: true,
|
||||
artifactErrorsFailBuild: false,
|
||||
@@ -45,6 +46,7 @@ describe.skip("Integration Test", () => {
|
||||
commit: undefined,
|
||||
discussionCategory: "Release",
|
||||
generateReleaseNotes: true,
|
||||
immutableCreate: true,
|
||||
omitBodyDuringUpdate: false,
|
||||
owner: "ncipollo",
|
||||
createdPrerelease: false,
|
||||
@@ -61,11 +63,11 @@ describe.skip("Integration Test", () => {
|
||||
updateOnlyUnreleased: false,
|
||||
}
|
||||
})
|
||||
return new MockInputs()
|
||||
return MockInputs()
|
||||
}
|
||||
|
||||
function getOutputs(): Outputs {
|
||||
const MockOutputs = jest.fn<Outputs, any>(() => {
|
||||
const MockOutputs = vi.fn<() => Outputs>(() => {
|
||||
return {
|
||||
applyReleaseData(releaseData: ReleaseData) {
|
||||
console.log(`Release Data: ${releaseData}`)
|
||||
@@ -75,7 +77,7 @@ describe.skip("Integration Test", () => {
|
||||
},
|
||||
}
|
||||
})
|
||||
return new MockOutputs()
|
||||
return MockOutputs()
|
||||
}
|
||||
|
||||
function artifacts() {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { CoreOutputs, type Outputs } from "../src/Outputs"
|
||||
import type { ReleaseData } from "../src/Releases"
|
||||
import * as core from "@actions/core"
|
||||
import { describe, expect, it, vi } from "vitest"
|
||||
import { CoreOutputs, type Outputs } from "../src/Outputs.js"
|
||||
import type { ReleaseData } from "../src/Releases.js"
|
||||
|
||||
jest.mock("@actions/core")
|
||||
const { setOutput: mockSetOutput } = jest.mocked(require("@actions/core"))
|
||||
vi.mock("@actions/core")
|
||||
const mockSetOutput = vi.mocked(core.setOutput)
|
||||
|
||||
const TEST_URLS = {
|
||||
HTML_URL: "https://api.example.com/assets",
|
||||
|
||||
42
__tests__/PathExpander.test.ts
Normal file
42
__tests__/PathExpander.test.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import os from "node:os"
|
||||
import { describe, expect, it } from "vitest"
|
||||
import { expandTilde } from "../src/PathExpander.js"
|
||||
|
||||
describe("PathExpander", () => {
|
||||
describe("expandTilde", () => {
|
||||
it("expands ~ at the start of a path", () => {
|
||||
const result = expandTilde("~/documents")
|
||||
expect(result).toBe(`${os.homedir()}/documents`)
|
||||
})
|
||||
|
||||
it("expands ~ with backslash separator", () => {
|
||||
const result = expandTilde("~\\documents")
|
||||
expect(result).toBe(`${os.homedir()}\\documents`)
|
||||
})
|
||||
|
||||
it("expands standalone ~", () => {
|
||||
const result = expandTilde("~")
|
||||
expect(result).toBe(os.homedir())
|
||||
})
|
||||
|
||||
it("does not expand ~ in the middle of a path", () => {
|
||||
const result = expandTilde("/home/~user/documents")
|
||||
expect(result).toBe("/home/~user/documents")
|
||||
})
|
||||
|
||||
it("does not expand ~username patterns", () => {
|
||||
const result = expandTilde("~username/documents")
|
||||
expect(result).toBe("~username/documents")
|
||||
})
|
||||
|
||||
it("returns path unchanged when no tilde present", () => {
|
||||
const result = expandTilde("/absolute/path")
|
||||
expect(result).toBe("/absolute/path")
|
||||
})
|
||||
|
||||
it("returns relative path unchanged", () => {
|
||||
const result = expandTilde("relative/path")
|
||||
expect(result).toBe("relative/path")
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ReleaseValidator } from "../src/ReleaseValidator"
|
||||
import { describe, expect, it } from "vitest"
|
||||
import { ReleaseValidator } from "../src/ReleaseValidator.js"
|
||||
|
||||
describe("validateReleaseUpdate", () => {
|
||||
describe("updateOnlyUnreleased is disabled", () => {
|
||||
|
||||
10
action.yml
10
action.yml
@@ -47,6 +47,14 @@ inputs:
|
||||
description: 'Indicates if release notes should be automatically generated.'
|
||||
required: false
|
||||
default: 'false'
|
||||
generateReleaseNotesPreviousTag:
|
||||
description: 'An optional previous tag to use when generating release notes. This will limit the release notes to changes between the two tags.'
|
||||
required: false
|
||||
default: ''
|
||||
immutableCreate:
|
||||
description: 'Indicates if immutable release creation should be used. When enabled, the action will first create a draft, upload artifacts, then publish the release.'
|
||||
required: false
|
||||
default: 'false'
|
||||
makeLatest:
|
||||
description: 'Indicates if the release should be the "latest" release or not.'
|
||||
required: false
|
||||
@@ -129,7 +137,7 @@ outputs:
|
||||
assets:
|
||||
description: 'JSON string containing a map of asset names to download URLs for uploaded assets.'
|
||||
runs:
|
||||
using: 'node20'
|
||||
using: 'node24'
|
||||
main: 'dist/index.js'
|
||||
branding:
|
||||
icon: 'tag'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
||||
"$schema": "https://biomejs.dev/schemas/2.3.13/schema.json",
|
||||
"vcs": {
|
||||
"enabled": false,
|
||||
"clientKind": "git",
|
||||
@@ -7,7 +7,7 @@
|
||||
},
|
||||
"files": {
|
||||
"ignoreUnknown": false,
|
||||
"ignore": ["node_modules/**/*", "dist/"]
|
||||
"includes": ["**", "!**/node_modules/**/*", "!**/dist/"]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
@@ -17,9 +17,9 @@
|
||||
"indentWidth": 4,
|
||||
"lineWidth": 120
|
||||
},
|
||||
"organizeImports": { "enabled": true },
|
||||
"assist": { "actions": { "source": { "organizeImports": "on" } } },
|
||||
"linter": {
|
||||
"enabled": false,
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true
|
||||
}
|
||||
|
||||
80491
dist/index.js
vendored
80491
dist/index.js
vendored
File diff suppressed because one or more lines are too long
2
dist/index.js.map
vendored
2
dist/index.js.map
vendored
File diff suppressed because one or more lines are too long
211
dist/licenses.txt
vendored
211
dist/licenses.txt
vendored
@@ -71,28 +71,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.
|
||||
|
||||
@fastify/busboy
|
||||
MIT
|
||||
Copyright Brian White. All rights reserved.
|
||||
|
||||
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.
|
||||
|
||||
@octokit/auth-token
|
||||
MIT
|
||||
The MIT License
|
||||
@@ -265,31 +243,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
balanced-match
|
||||
MIT
|
||||
(MIT)
|
||||
|
||||
Copyright (c) 2013 Julian Gruber <julian@juliangruber.com>
|
||||
|
||||
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.
|
||||
|
||||
|
||||
before-after-hook
|
||||
Apache-2.0
|
||||
Apache License
|
||||
@@ -495,11 +448,14 @@ Apache-2.0
|
||||
limitations under the License.
|
||||
|
||||
|
||||
brace-expansion
|
||||
fast-content-type-parse
|
||||
MIT
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2013 Julian Gruber <julian@juliangruber.com>
|
||||
Copyright (c) 2023 The Fastify Team
|
||||
|
||||
The Fastify team members are listed at https://github.com/fastify/fastify#team
|
||||
and in the README file.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -519,123 +475,16 @@ 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.
|
||||
|
||||
|
||||
deprecation
|
||||
ISC
|
||||
The ISC License
|
||||
|
||||
Copyright (c) Gregor Martynus 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.
|
||||
|
||||
|
||||
glob
|
||||
ISC
|
||||
The ISC License
|
||||
|
||||
Copyright (c) 2009-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.
|
||||
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
|
||||
once
|
||||
ISC
|
||||
The ISC License
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
|
||||
path-scurry
|
||||
BlueOak-1.0.0
|
||||
All packages under `src/` are licensed according to the terms in
|
||||
their respective `LICENSE` or `LICENSE.md` files.
|
||||
|
||||
The remainder of this project is licensed under the Blue Oak
|
||||
Model License, as follows:
|
||||
|
||||
-----
|
||||
|
||||
# Blue Oak Model License
|
||||
|
||||
Version 1.0.0
|
||||
@@ -747,40 +596,8 @@ universal-user-agent
|
||||
ISC
|
||||
# [ISC License](https://spdx.org/licenses/ISC)
|
||||
|
||||
Copyright (c) 2018, Gregor Martynus (https://github.com/gr2m)
|
||||
Copyright (c) 2018-2021, Gregor Martynus (https://github.com/gr2m)
|
||||
|
||||
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.
|
||||
|
||||
|
||||
untildify
|
||||
MIT
|
||||
MIT License
|
||||
|
||||
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
3
dist/package.json
vendored
Normal file
3
dist/package.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "module"
|
||||
}
|
||||
1
dist/sourcemap-register.cjs
vendored
Normal file
1
dist/sourcemap-register.cjs
vendored
Normal file
File diff suppressed because one or more lines are too long
71
package.json
71
package.json
@@ -1,66 +1,53 @@
|
||||
{
|
||||
"name": "release-action",
|
||||
"version": "1.1.0",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"description": "An action which manages a github release",
|
||||
"main": "lib/main.js",
|
||||
"main": "lib/src/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 .",
|
||||
"coverage": "vitest run --coverage",
|
||||
"debug": "pnpm clean && pnpm install && pnpm build && pnpm package",
|
||||
"format": "pnpm biome format --write .",
|
||||
"package": "ncc build --source-map --license licenses.txt",
|
||||
"release": "yarn clean && yarn install --production && yarn build && yarn package",
|
||||
"test": "jest"
|
||||
"release": "pnpm clean && pnpm install && pnpm build && pnpm package",
|
||||
"test": "vitest run"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/ncipollo/release-action.git"
|
||||
},
|
||||
"keywords": ["actions", "node", "setup"],
|
||||
"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.1",
|
||||
"@types/node": "^22.15.29",
|
||||
"glob": "^11.0.2",
|
||||
"untildify": "^4.0.0"
|
||||
"@actions/core": "^3.0.0",
|
||||
"@actions/github": "^9.0.0",
|
||||
"@types/node": "^25.1.0",
|
||||
"glob": "^13.0.6"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"undici": ">=6.24.0"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.9.4",
|
||||
"@types/jest": "^29.5.14",
|
||||
"jest": "^29.7.0",
|
||||
"jest-circus": "^29.7.0",
|
||||
"ts-jest": "^29.3.4",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"resolutions": {
|
||||
"jest-cli/yargs": "^17.3.1"
|
||||
"@biomejs/biome": "2.4.4",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^10.4.1",
|
||||
"@octokit/request-error": "^7.1.0",
|
||||
"@octokit/types": "^16.0.0",
|
||||
"@vercel/ncc": "^0.38.4",
|
||||
"@vitest/coverage-v8": "^4.1.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
1441
pnpm-lock.yaml
generated
Normal file
1441
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3,4 +3,4 @@ enable_commit = true
|
||||
tag_pattern = "v{version}"
|
||||
|
||||
[scripts]
|
||||
before_commit = "yarn release"
|
||||
before_commit = "pnpm release"
|
||||
178
src/Action.ts
178
src/Action.ts
@@ -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 { ActionSkipper } from "./ActionSkipper.js"
|
||||
import type { ArtifactDestroyer } from "./ArtifactDestroyer.js"
|
||||
import type { ArtifactUploader } from "./ArtifactUploader.js"
|
||||
import { GithubError } from "./GithubError.js"
|
||||
import type { Inputs } from "./Inputs.js"
|
||||
import type { Outputs } from "./Outputs.js"
|
||||
import { ReleaseValidator } from "./ReleaseValidator.js"
|
||||
import type {
|
||||
CreateOrUpdateReleaseResponse,
|
||||
CreateReleaseResponse,
|
||||
ReleaseByTagResponse,
|
||||
Releases,
|
||||
UpdateReleaseResponse,
|
||||
} from "./Releases"
|
||||
} from "./Releases.js"
|
||||
|
||||
export class Action {
|
||||
private inputs: Inputs
|
||||
@@ -47,83 +47,47 @@ export class Action {
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
let assetUrls: Record<string, string> = {}
|
||||
if (artifacts.length > 0) {
|
||||
assetUrls = await this.uploader.uploadArtifacts(artifacts, releaseId, uploadUrl)
|
||||
}
|
||||
|
||||
this.outputs.applyReleaseData(releaseData)
|
||||
this.outputs.applyAssetUrls(assetUrls)
|
||||
await this.createOrUpdateRelease()
|
||||
}
|
||||
|
||||
private async createOrUpdateRelease(): Promise<CreateOrUpdateReleaseResponse> {
|
||||
private async createOrUpdateRelease() {
|
||||
if (this.inputs.allowUpdates) {
|
||||
let getResponse: ReleaseByTagResponse
|
||||
try {
|
||||
getResponse = await this.releases.getByTag(this.inputs.tag)
|
||||
} catch (error: any) {
|
||||
return await this.checkForMissingReleaseError(error)
|
||||
await this.checkForMissingReleaseError(error)
|
||||
return
|
||||
}
|
||||
|
||||
// Fail if this isn't an unreleased release & updateOnlyUnreleased is enabled.
|
||||
this.releaseValidator.validateReleaseUpdate(getResponse.data)
|
||||
|
||||
return await this.updateRelease(getResponse.data.id)
|
||||
await this.updateRelease(getResponse.data.id)
|
||||
} else {
|
||||
return await this.createRelease()
|
||||
await this.createRelease()
|
||||
}
|
||||
}
|
||||
|
||||
private async checkForMissingReleaseError(error: Error): Promise<CreateOrUpdateReleaseResponse> {
|
||||
private async checkForMissingReleaseError(error: Error): Promise<void> {
|
||||
if (Action.noPublishedRelease(error)) {
|
||||
return await this.updateDraftOrCreateRelease()
|
||||
await this.updateDraftOrCreateRelease()
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
private async updateRelease(id: number): Promise<UpdateReleaseResponse> {
|
||||
let releaseBody = this.inputs.updatedReleaseBody
|
||||
|
||||
if (this.inputs.generateReleaseNotes && !this.inputs.omitBodyDuringUpdate) {
|
||||
const response = await this.releases.generateReleaseNotes(this.inputs.tag)
|
||||
releaseBody = response.data.body
|
||||
}
|
||||
|
||||
return await this.releases.update(
|
||||
id,
|
||||
this.inputs.tag,
|
||||
releaseBody,
|
||||
this.inputs.commit,
|
||||
this.inputs.discussionCategory,
|
||||
this.inputs.updatedDraft,
|
||||
this.inputs.makeLatest,
|
||||
this.inputs.updatedReleaseName,
|
||||
this.inputs.updatedPrerelease
|
||||
)
|
||||
}
|
||||
|
||||
private static noPublishedRelease(error: any): boolean {
|
||||
const githubError = new GithubError(error)
|
||||
return githubError.status == 404
|
||||
}
|
||||
|
||||
private async updateDraftOrCreateRelease(): Promise<CreateReleaseResponse | UpdateReleaseResponse> {
|
||||
private async updateDraftOrCreateRelease(): Promise<void> {
|
||||
const draftReleaseId = await this.findMatchingDraftReleaseId()
|
||||
if (draftReleaseId) {
|
||||
return await this.updateRelease(draftReleaseId)
|
||||
await this.updateRelease(draftReleaseId)
|
||||
} else {
|
||||
return await this.createRelease()
|
||||
await this.createRelease()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,17 +104,111 @@ export class Action {
|
||||
return draftRelease?.id
|
||||
}
|
||||
|
||||
private async createRelease(): Promise<CreateReleaseResponse> {
|
||||
return await this.releases.create(
|
||||
private async updateRelease(id: number) {
|
||||
const releaseBody = await this.combineBodyWithReleaseNotes(this.inputs.updatedReleaseBody, true)
|
||||
|
||||
const releaseResponse = await this.releases.update(
|
||||
id,
|
||||
this.inputs.tag,
|
||||
this.inputs.createdReleaseBody,
|
||||
releaseBody,
|
||||
this.inputs.commit,
|
||||
this.inputs.discussionCategory,
|
||||
this.inputs.createdDraft,
|
||||
this.inputs.generateReleaseNotes,
|
||||
this.inputs.updatedDraft,
|
||||
this.inputs.makeLatest,
|
||||
this.inputs.updatedReleaseName,
|
||||
this.inputs.updatedPrerelease
|
||||
)
|
||||
|
||||
await this.processReleaseArtifactsAndOutputs(releaseResponse, false)
|
||||
}
|
||||
|
||||
private async createRelease() {
|
||||
const releaseBody = await this.combineBodyWithReleaseNotes(this.inputs.createdReleaseBody, false)
|
||||
|
||||
// If immutableCreate is enabled we need to start with a draft release
|
||||
const draft = this.inputs.createdDraft || this.inputs.immutableCreate
|
||||
|
||||
const releaseResponse = await this.releases.create(
|
||||
this.inputs.tag,
|
||||
releaseBody,
|
||||
this.inputs.commit,
|
||||
this.inputs.discussionCategory,
|
||||
draft,
|
||||
this.inputs.makeLatest,
|
||||
this.inputs.createdReleaseName,
|
||||
this.inputs.createdPrerelease
|
||||
)
|
||||
|
||||
await this.processReleaseArtifactsAndOutputs(releaseResponse, true)
|
||||
}
|
||||
|
||||
private async processReleaseArtifactsAndOutputs(releaseResponse: CreateOrUpdateReleaseResponse, wasCreated: boolean) {
|
||||
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
|
||||
let assetUrls: Record<string, string> = {}
|
||||
if (artifacts.length > 0) {
|
||||
assetUrls = await this.uploader.uploadArtifacts(artifacts, releaseId, uploadUrl)
|
||||
}
|
||||
|
||||
if (wasCreated) {
|
||||
const immutableRelease = await this.publishImmutableRelease(releaseId)
|
||||
if (immutableRelease) {
|
||||
this.setOutputs(immutableRelease.data, assetUrls)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.setOutputs(releaseData, assetUrls)
|
||||
}
|
||||
|
||||
private async publishImmutableRelease(releaseId: number): Promise<CreateOrUpdateReleaseResponse | undefined> {
|
||||
// Check if immutableCreate is on and createdDraft is off
|
||||
if (!this.inputs.immutableCreate || this.inputs.createdDraft) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return await this.releases.update(
|
||||
releaseId,
|
||||
this.inputs.tag,
|
||||
undefined, // body is omitted
|
||||
undefined, // commit is omitted
|
||||
this.inputs.discussionCategory,
|
||||
false, // We want to publish the release, set draft to false
|
||||
this.inputs.makeLatest,
|
||||
this.inputs.createdReleaseName,
|
||||
this.inputs.createdPrerelease
|
||||
)
|
||||
}
|
||||
|
||||
private async combineBodyWithReleaseNotes(body: string | undefined, isUpdate: boolean): Promise<string | undefined> {
|
||||
// Determine if we should generate release notes based on operation type
|
||||
const shouldGenerateReleaseNotes = isUpdate
|
||||
? this.inputs.generateReleaseNotes && !this.inputs.omitBodyDuringUpdate
|
||||
: this.inputs.generateReleaseNotes
|
||||
|
||||
if (!shouldGenerateReleaseNotes) {
|
||||
return body
|
||||
}
|
||||
|
||||
const response = await this.releases.generateReleaseNotes(this.inputs.tag, this.inputs.generateReleaseNotesPreviousTag, this.inputs.commit)
|
||||
const releaseNotes = response.data.body
|
||||
|
||||
if (!body || body.trim() === "") {
|
||||
return releaseNotes
|
||||
}
|
||||
|
||||
return `${body}\n${releaseNotes}`
|
||||
}
|
||||
|
||||
private setOutputs(releaseData: any, assetUrls: Record<string, string>): void {
|
||||
this.outputs.applyReleaseData(releaseData)
|
||||
this.outputs.applyAssetUrls(assetUrls)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Releases } from "./Releases"
|
||||
import { Releases } from "./Releases.js"
|
||||
|
||||
export interface ActionSkipper {
|
||||
shouldSkip(): Promise<boolean>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Releases } from "./Releases"
|
||||
import { Releases } from "./Releases.js"
|
||||
import * as core from "@actions/core"
|
||||
|
||||
export interface ArtifactDestroyer {
|
||||
|
||||
@@ -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 { Globber, FileGlobber } from "./Globber.js"
|
||||
import { Artifact } from "./Artifact.js"
|
||||
import { expandTilde } from "./PathExpander.js"
|
||||
import { ArtifactPathValidator } from "./ArtifactPathValidator.js"
|
||||
import { PathNormalizer } from "./PathNormalizer.js"
|
||||
|
||||
export interface ArtifactGlobber {
|
||||
globArtifactString(artifact: string, contentType: string, errorsFailBuild: boolean): Artifact[]
|
||||
@@ -55,6 +55,6 @@ export class FileArtifactGlobber implements ArtifactGlobber {
|
||||
}
|
||||
|
||||
private static expandPath(path: string): string {
|
||||
return untildify(path)
|
||||
return expandTilde(path)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as core from "@actions/core"
|
||||
import { Artifact } from "./Artifact"
|
||||
import { Releases } from "./Releases"
|
||||
import { Artifact } from "./Artifact.js"
|
||||
import { Releases } from "./Releases.js"
|
||||
|
||||
export interface ArtifactUploader {
|
||||
uploadArtifacts(artifacts: Artifact[], releaseId: number, uploadUrl: string): Promise<Record<string, string>>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { GithubErrorDetail } from "./GithubErrorDetail"
|
||||
import { GithubErrorDetail } from "./GithubErrorDetail.js"
|
||||
|
||||
export class GithubError {
|
||||
private error: any
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { readFileSync } from "node:fs"
|
||||
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 type * as github from "@actions/github"
|
||||
import type { Artifact } from "./Artifact.js"
|
||||
import type { ArtifactGlobber } from "./ArtifactGlobber.js"
|
||||
|
||||
export interface Inputs {
|
||||
readonly allowUpdates: boolean
|
||||
@@ -15,6 +15,8 @@ export interface Inputs {
|
||||
readonly createdReleaseName?: string
|
||||
readonly discussionCategory?: string
|
||||
readonly generateReleaseNotes: boolean
|
||||
readonly generateReleaseNotesPreviousTag?: string
|
||||
readonly immutableCreate: boolean
|
||||
readonly makeLatest?: "legacy" | "true" | "false" | undefined
|
||||
readonly omitBodyDuringUpdate: boolean
|
||||
readonly owner: string
|
||||
@@ -33,16 +35,16 @@ export interface Inputs {
|
||||
|
||||
export class CoreInputs implements Inputs {
|
||||
private artifactGlobber: ArtifactGlobber
|
||||
private context: Context
|
||||
private context: typeof github.context
|
||||
|
||||
constructor(artifactGlobber: ArtifactGlobber, context: Context) {
|
||||
constructor(artifactGlobber: ArtifactGlobber, context: typeof github.context) {
|
||||
this.artifactGlobber = artifactGlobber
|
||||
this.context = context
|
||||
}
|
||||
|
||||
get allowUpdates(): boolean {
|
||||
const allow = core.getInput("allowUpdates")
|
||||
return allow == "true"
|
||||
return allow === "true"
|
||||
}
|
||||
|
||||
get artifacts(): Artifact[] {
|
||||
@@ -62,7 +64,7 @@ export class CoreInputs implements Inputs {
|
||||
|
||||
get artifactErrorsFailBuild(): boolean {
|
||||
const allow = core.getInput("artifactErrorsFailBuild")
|
||||
return allow == "true"
|
||||
return allow === "true"
|
||||
}
|
||||
|
||||
private get body(): string | undefined {
|
||||
@@ -81,12 +83,12 @@ export class CoreInputs implements Inputs {
|
||||
|
||||
get createdDraft(): boolean {
|
||||
const draft = core.getInput("draft")
|
||||
return draft == "true"
|
||||
return draft === "true"
|
||||
}
|
||||
|
||||
get createdPrerelease(): boolean {
|
||||
const preRelease = core.getInput("prerelease")
|
||||
return preRelease == "true"
|
||||
return preRelease === "true"
|
||||
}
|
||||
|
||||
get createdReleaseBody(): string | undefined {
|
||||
@@ -95,7 +97,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 {
|
||||
@@ -104,7 +106,7 @@ export class CoreInputs implements Inputs {
|
||||
}
|
||||
|
||||
private static get omitName(): boolean {
|
||||
return core.getInput("omitName") == "true"
|
||||
return core.getInput("omitName") === "true"
|
||||
}
|
||||
|
||||
get commit(): string | undefined {
|
||||
@@ -134,12 +136,22 @@ export class CoreInputs implements Inputs {
|
||||
|
||||
get generateReleaseNotes(): boolean {
|
||||
const generate = core.getInput("generateReleaseNotes")
|
||||
return generate == "true"
|
||||
return generate === "true"
|
||||
}
|
||||
|
||||
get generateReleaseNotesPreviousTag(): string | undefined {
|
||||
const previousTag = core.getInput("generateReleaseNotesPreviousTag")
|
||||
return previousTag || undefined
|
||||
}
|
||||
|
||||
get immutableCreate(): boolean {
|
||||
const immutable = core.getInput("immutableCreate")
|
||||
return immutable === "true"
|
||||
}
|
||||
|
||||
get makeLatest(): "legacy" | "true" | "false" | undefined {
|
||||
let latest = core.getInput("makeLatest")
|
||||
if (latest == "true" || latest == "false" || latest == "legacy") {
|
||||
const latest = core.getInput("makeLatest")
|
||||
if (latest === "true" || latest === "false" || latest === "legacy") {
|
||||
return latest
|
||||
}
|
||||
|
||||
@@ -147,7 +159,7 @@ export class CoreInputs implements Inputs {
|
||||
}
|
||||
|
||||
get owner(): string {
|
||||
let owner = core.getInput("owner")
|
||||
const owner = core.getInput("owner")
|
||||
if (owner) {
|
||||
return owner
|
||||
}
|
||||
@@ -156,16 +168,16 @@ export class CoreInputs implements Inputs {
|
||||
|
||||
get removeArtifacts(): boolean {
|
||||
const removes = core.getInput("removeArtifacts")
|
||||
return removes == "true"
|
||||
return removes === "true"
|
||||
}
|
||||
|
||||
get replacesArtifacts(): boolean {
|
||||
const replaces = core.getInput("replacesArtifacts")
|
||||
return replaces == "true"
|
||||
return replaces === "true"
|
||||
}
|
||||
|
||||
get repo(): string {
|
||||
let repo = core.getInput("repo")
|
||||
const repo = core.getInput("repo")
|
||||
if (repo) {
|
||||
return repo
|
||||
}
|
||||
@@ -184,7 +196,7 @@ export class CoreInputs implements Inputs {
|
||||
|
||||
const ref = this.context.ref
|
||||
const tagPath = "refs/tags/"
|
||||
if (ref && ref.startsWith(tagPath)) {
|
||||
if (ref?.startsWith(tagPath)) {
|
||||
return ref.substr(tagPath.length, ref.length)
|
||||
}
|
||||
|
||||
@@ -201,7 +213,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 {
|
||||
@@ -210,7 +222,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 {
|
||||
@@ -219,7 +231,7 @@ export class CoreInputs implements Inputs {
|
||||
}
|
||||
|
||||
get updateOnlyUnreleased(): boolean {
|
||||
return core.getInput("updateOnlyUnreleased") == "true"
|
||||
return core.getInput("updateOnlyUnreleased") === "true"
|
||||
}
|
||||
|
||||
get updatedReleaseName(): string | undefined {
|
||||
@@ -228,7 +240,7 @@ export class CoreInputs implements Inputs {
|
||||
}
|
||||
|
||||
private static get omitBodyDuringUpdate(): boolean {
|
||||
return core.getInput("omitBodyDuringUpdate") == "true"
|
||||
return core.getInput("omitBodyDuringUpdate") === "true"
|
||||
}
|
||||
|
||||
get omitBodyDuringUpdate(): boolean {
|
||||
@@ -236,7 +248,7 @@ export class CoreInputs implements Inputs {
|
||||
}
|
||||
|
||||
private static get omitNameDuringUpdate(): boolean {
|
||||
return core.getInput("omitNameDuringUpdate") == "true"
|
||||
return core.getInput("omitNameDuringUpdate") === "true"
|
||||
}
|
||||
|
||||
stringFromFile(path: string): string {
|
||||
|
||||
18
src/Main.ts
18
src/Main.ts
@@ -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 { CoreInputs } from "./Inputs.js"
|
||||
import { GithubReleases } from "./Releases.js"
|
||||
import { Action } from "./Action.js"
|
||||
import { GithubArtifactUploader } from "./ArtifactUploader.js"
|
||||
import { FileArtifactGlobber } from "./ArtifactGlobber.js"
|
||||
import { GithubError } from "./GithubError.js"
|
||||
import { CoreOutputs } from "./Outputs.js"
|
||||
import { GithubArtifactDestroyer } from "./ArtifactDestroyer.js"
|
||||
import { ActionSkipper, ReleaseActionSkipper } from "./ActionSkipper.js"
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as core from "@actions/core"
|
||||
import { ReleaseData } from "./Releases"
|
||||
import { ReleaseData } from "./Releases.js"
|
||||
|
||||
export interface Outputs {
|
||||
applyReleaseData(releaseData: ReleaseData): void
|
||||
|
||||
6
src/PathExpander.ts
Normal file
6
src/PathExpander.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import os from "os"
|
||||
|
||||
export function expandTilde(path: string): string {
|
||||
return path.replace(/^~(?=$|\/|\\)/, os.homedir())
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { GitHub } from "@actions/github/lib/utils"
|
||||
import * as github from "@actions/github"
|
||||
import type { RestEndpointMethodTypes } from "@octokit/plugin-rest-endpoint-methods"
|
||||
import type { OctokitResponse } from "@octokit/types"
|
||||
import type { Inputs } from "./Inputs"
|
||||
import type { Inputs } from "./Inputs.js"
|
||||
|
||||
export type CreateReleaseResponse = RestEndpointMethodTypes["repos"]["createRelease"]["response"]
|
||||
export type ReleaseByTagResponse = RestEndpointMethodTypes["repos"]["getReleaseByTag"]["response"]
|
||||
@@ -27,7 +27,6 @@ export interface Releases {
|
||||
commitHash?: string,
|
||||
discussionCategory?: string,
|
||||
draft?: boolean,
|
||||
generateReleaseNotes?: boolean,
|
||||
makeLatest?: "legacy" | "true" | "false" | undefined,
|
||||
name?: string,
|
||||
prerelease?: boolean
|
||||
@@ -37,7 +36,7 @@ export interface Releases {
|
||||
|
||||
getByTag(tag: string): Promise<ReleaseByTagResponse>
|
||||
|
||||
generateReleaseNotes(tag: string): Promise<GenerateReleaseNotesResponse>
|
||||
generateReleaseNotes(tag: string, previousTag?: string, targetCommitish?: string): Promise<GenerateReleaseNotesResponse>
|
||||
|
||||
listArtifactsForRelease(releaseId: number): Promise<ListReleaseAssetsResponseData>
|
||||
|
||||
@@ -66,10 +65,10 @@ export interface Releases {
|
||||
}
|
||||
|
||||
export class GithubReleases implements Releases {
|
||||
git: InstanceType<typeof GitHub>
|
||||
git: ReturnType<typeof github.getOctokit>
|
||||
inputs: Inputs
|
||||
|
||||
constructor(inputs: Inputs, git: InstanceType<typeof GitHub>) {
|
||||
constructor(inputs: Inputs, git: ReturnType<typeof github.getOctokit>) {
|
||||
this.inputs = inputs
|
||||
this.git = git
|
||||
}
|
||||
@@ -80,7 +79,6 @@ export class GithubReleases implements Releases {
|
||||
commitHash?: string,
|
||||
discussionCategory?: string,
|
||||
draft?: boolean,
|
||||
generateReleaseNotes?: boolean,
|
||||
makeLatest?: "legacy" | "true" | "false" | undefined,
|
||||
name?: string,
|
||||
prerelease?: boolean
|
||||
@@ -91,7 +89,6 @@ export class GithubReleases implements Releases {
|
||||
name: name,
|
||||
discussion_category_name: discussionCategory,
|
||||
draft: draft,
|
||||
generate_release_notes: generateReleaseNotes,
|
||||
make_latest: makeLatest,
|
||||
owner: this.inputs.owner,
|
||||
prerelease: prerelease,
|
||||
@@ -109,12 +106,22 @@ export class GithubReleases implements Releases {
|
||||
})
|
||||
}
|
||||
|
||||
async generateReleaseNotes(tag: string): Promise<GenerateReleaseNotesResponse> {
|
||||
return this.git.rest.repos.generateReleaseNotes({
|
||||
async generateReleaseNotes(tag: string, previousTag?: string, targetCommitish?: string): Promise<GenerateReleaseNotesResponse> {
|
||||
const params: any = {
|
||||
owner: this.inputs.owner,
|
||||
repo: this.inputs.repo,
|
||||
tag_name: tag,
|
||||
})
|
||||
}
|
||||
|
||||
if (previousTag) {
|
||||
params.previous_tag_name = previousTag
|
||||
}
|
||||
|
||||
if (targetCommitish) {
|
||||
params.target_commitish = targetCommitish
|
||||
}
|
||||
|
||||
return this.git.rest.repos.generateReleaseNotes(params)
|
||||
}
|
||||
|
||||
async getByTag(tag: string): Promise<ReleaseByTagResponse> {
|
||||
|
||||
@@ -4,10 +4,13 @@
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"outDir": "./lib",
|
||||
"rootDir": ".",
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noImplicitAny": true,
|
||||
"esModuleInterop": true
|
||||
"esModuleInterop": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"exclude": ["node_modules", "**/*.test.ts"]
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "**/*.test.ts", "__mocks__/**/*", "vitest.config.ts"]
|
||||
}
|
||||
|
||||
7
tsconfig.test.json
Normal file
7
tsconfig.test.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"isolatedModules": true
|
||||
},
|
||||
"include": ["src/**/*", "__tests__/**/*"]
|
||||
}
|
||||
21
vitest.config.ts
Normal file
21
vitest.config.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'node',
|
||||
include: ['**/*.test.ts'],
|
||||
coverage: {
|
||||
provider: 'v8',
|
||||
reporter: ['text', 'lcov'],
|
||||
include: ['src/**/*.ts'],
|
||||
exclude: ['src/Globber.ts', 'src/Releases.ts'],
|
||||
thresholds: {
|
||||
lines: 100,
|
||||
functions: 100,
|
||||
branches: 95,
|
||||
statements: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user