Bump to core-3.0.0, move to vitest, support ESM modules (#587)

This commit is contained in:
Nick Cipollo
2026-02-01 17:51:43 -05:00
committed by GitHub
parent 5b3313d377
commit 3f973430f2
36 changed files with 36195 additions and 11507 deletions

5
.gitignore vendored
View File

@@ -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

View 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
View 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()

View File

@@ -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
@@ -55,6 +56,7 @@ describe("Action", () => {
beforeEach(() => {
applyReleaseDataMock.mockClear()
applyAssetUrlsMock.mockClear()
artifactDestroyMock.mockClear()
createMock.mockClear()
genReleaseNotesMock.mockClear()
getMock.mockClear()
@@ -149,7 +151,18 @@ describe("Action", () => {
})
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)
const action = createAction(
false,
false,
false,
true,
false,
createBody,
true,
createDraft,
updateBody,
previousTag
)
await action.perform()
@@ -209,8 +222,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",
})
})
@@ -238,8 +251,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",
})
})
@@ -262,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",
})
})
@@ -275,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",
})
})
@@ -288,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",
})
})
@@ -456,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",
})
})
@@ -488,8 +501,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",
})
})
@@ -513,8 +526,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",
})
})
@@ -578,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",
})
})
@@ -610,8 +623,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",
})
})
@@ -695,8 +708,8 @@ describe("Action", () => {
// 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",
art1: "https://github.com/owner/repo/releases/download/v1.0.0/art1",
art2: "https://github.com/owner/repo/releases/download/v1.0.0/art2",
})
})
@@ -749,8 +762,8 @@ describe("Action", () => {
// 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",
art1: "https://github.com/owner/repo/releases/download/v1.0.0/art1",
art2: "https://github.com/owner/repo/releases/download/v1.0.0/art2",
})
})
@@ -788,7 +801,7 @@ describe("Action", () => {
inputArtifact = []
}
const MockReleases = jest.fn<Releases, any>(() => {
const MockReleases = vi.fn<() => Releases>(() => {
return {
create: createMock,
deleteArtifact: deleteMock,
@@ -796,7 +809,7 @@ describe("Action", () => {
listArtifactsForRelease: listArtifactsMock,
listReleases: listMock,
update: updateMock,
uploadArtifact: jest.fn(),
uploadArtifact: vi.fn(),
generateReleaseNotes: genReleaseNotesMock,
}
})
@@ -835,11 +848,11 @@ 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,
@@ -869,35 +882,35 @@ describe("Action", () => {
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)
}

View File

@@ -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(),
}
})

View File

@@ -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", () => {

View File

@@ -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({})
}

View File

@@ -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 { expandTilde } from "../src/PathExpander"
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(() => {
@@ -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())
}
})

View File

@@ -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()
})

View File

@@ -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({})
}

View File

@@ -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", () => {

View File

@@ -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", () => {

View File

@@ -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,41 +204,41 @@ 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", function () {
it("returns the previous tag when provided", () => {
mockGetInput.mockReturnValue("v1.0.0")
expect(inputs.generateReleaseNotesPreviousTag).toBe("v1.0.0")
})
it("returns undefined when omitted", function () {
it("returns undefined when omitted", () => {
mockGetInput.mockReturnValue("")
expect(inputs.generateReleaseNotesPreviousTag).toBeUndefined()
})
})
describe("immutableCreate", () => {
it("returns false by default", function () {
it("returns false by default", () => {
mockGetInput.mockReturnValue("")
expect(inputs.immutableCreate).toBe(false)
})
it("returns true when explicitly set", function () {
it("returns true when explicitly set", () => {
mockGetInput.mockReturnValue("true")
expect(inputs.immutableCreate).toBe(true)
})
it("returns false when explicitly disabled", function () {
it("returns false when explicitly disabled", () => {
mockGetInput.mockReturnValue("false")
expect(inputs.immutableCreate).toBe(false)
})
@@ -252,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")
})
@@ -297,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")
})
@@ -330,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()
@@ -462,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()
}
})

View File

@@ -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,
@@ -62,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}`)
@@ -76,7 +77,7 @@ describe.skip("Integration Test", () => {
},
}
})
return new MockOutputs()
return MockOutputs()
}
function artifacts() {

View File

@@ -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",

View File

@@ -1,5 +1,6 @@
import os from "os"
import { expandTilde } from "../src/PathExpander"
import os from "node:os"
import { describe, expect, it } from "vitest"
import { expandTilde } from "../src/PathExpander.js"
describe("PathExpander", () => {
describe("expandTilde", () => {
@@ -39,4 +40,3 @@ describe("PathExpander", () => {
})
})
})

View File

@@ -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", () => {

View File

@@ -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
}

43250
dist/index.js vendored

File diff suppressed because one or more lines are too long

2
dist/index.js.map vendored

File diff suppressed because one or more lines are too long

310
dist/licenses.txt vendored
View File

@@ -93,6 +93,60 @@ 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.
@isaacs/balanced-match
MIT
(MIT)
Original code Copyright Julian Gruber <julian@juliangruber.com>
Port to TypeScript Copyright Isaac Z. Schlueter <i@izs.me>
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.
@isaacs/brace-expansion
MIT
MIT License
Copyright Julian Gruber <julian@juliangruber.com>
TypeScript port Copyright Isaac Z. Schlueter <i@izs.me>
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 +319,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 &lt;julian@juliangruber.com&gt;
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,31 +524,6 @@ Apache-2.0
limitations under the License.
brace-expansion
MIT
MIT License
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.
deprecation
ISC
The ISC License
@@ -540,60 +544,188 @@ IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
glob
ISC
The ISC License
BlueOak-1.0.0
All packages under `src/` are licensed according to the terms in
their respective `LICENSE` or `LICENSE.md` files.
Copyright (c) 2009-2023 Isaac Z. Schlueter and Contributors
The remainder of this project is licensed under the Blue Oak
Model License, as follows:
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.
# Blue Oak Model License
Version 1.0.0
## Purpose
This license gives everyone as much permission to work with
this software as possible, while protecting contributors
from liability.
## Acceptance
In order to receive this license, you must agree to its
rules. The rules of this license are both obligations
under that agreement and conditions to your license.
You must not do anything with this software that triggers
a rule that you cannot or will not follow.
## Copyright
Each contributor licenses you to do everything with this
software that would otherwise infringe that contributor's
copyright in it.
## Notices
You must ensure that everyone who gets a copy of
any part of this software from you, with or without
changes, also gets the text of this license or a link to
<https://blueoakcouncil.org/license/1.0.0>.
## Excuse
If anyone notifies you in writing that you have not
complied with [Notices](#notices), you can keep your
license by taking all practical steps to comply within 30
days after the notice. If you do not do so, your license
ends immediately.
## Patent
Each contributor licenses you to do everything with this
software that would otherwise infringe any patent claims
they can license or become able to license.
## Reliability
No contributor can revoke this license.
## No Liability
***As far as the law allows, this software comes as is,
without any warranty or condition, and no contributor
will be liable to anyone for any damages related to this
software or this license, under any kind of legal claim.***
lru-cache
ISC
The ISC License
BlueOak-1.0.0
# Blue Oak Model License
Copyright (c) 2010-2023 Isaac Z. Schlueter and Contributors
Version 1.0.0
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.
## Purpose
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.
This license gives everyone as much permission to work with
this software as possible, while protecting contributors
from liability.
## Acceptance
In order to receive this license, you must agree to its
rules. The rules of this license are both obligations
under that agreement and conditions to your license.
You must not do anything with this software that triggers
a rule that you cannot or will not follow.
## Copyright
Each contributor licenses you to do everything with this
software that would otherwise infringe that contributor's
copyright in it.
## Notices
You must ensure that everyone who gets a copy of
any part of this software from you, with or without
changes, also gets the text of this license or a link to
<https://blueoakcouncil.org/license/1.0.0>.
## Excuse
If anyone notifies you in writing that you have not
complied with [Notices](#notices), you can keep your
license by taking all practical steps to comply within 30
days after the notice. If you do not do so, your license
ends immediately.
## Patent
Each contributor licenses you to do everything with this
software that would otherwise infringe any patent claims
they can license or become able to license.
## Reliability
No contributor can revoke this license.
## No Liability
***As far as the law allows, this software comes as is,
without any warranty or condition, and no contributor
will be liable to anyone for any damages related to this
software or this license, under any kind of legal claim.***
minimatch
ISC
The ISC License
BlueOak-1.0.0
# Blue Oak Model License
Copyright (c) 2011-2023 Isaac Z. Schlueter and Contributors
Version 1.0.0
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.
## Purpose
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.
This license gives everyone as much permission to work with
this software as possible, while protecting contributors
from liability.
## Acceptance
In order to receive this license, you must agree to its
rules. The rules of this license are both obligations
under that agreement and conditions to your license.
You must not do anything with this software that triggers
a rule that you cannot or will not follow.
## Copyright
Each contributor licenses you to do everything with this
software that would otherwise infringe that contributor's
copyright in it.
## Notices
You must ensure that everyone who gets a copy of
any part of this software from you, with or without
changes, also gets the text of this license or a link to
<https://blueoakcouncil.org/license/1.0.0>.
## Excuse
If anyone notifies you in writing that you have not
complied with [Notices](#notices), you can keep your
license by taking all practical steps to comply within 30
days after the notice. If you do not do so, your license
ends immediately.
## Patent
Each contributor licenses you to do everything with this
software that would otherwise infringe any patent claims
they can license or become able to license.
## Reliability
No contributor can revoke this license.
## No Liability
**_As far as the law allows, this software comes as is,
without any warranty or condition, and no contributor
will be liable to anyone for any damages related to this
software or this license, under any kind of legal claim._**
minipass

3
dist/package.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"type": "module"
}

1
dist/sourcemap-register.cjs vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,54 +1,36 @@
{
"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",
"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": "pnpm clean && pnpm install --prod && pnpm build && pnpm package",
"test": "jest"
"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", {
"tsconfig": "tsconfig.test.json"
}]
},
"verbose": true
},
"dependencies": {
"@actions/core": "^2.0.1",
"@actions/core": "^3.0.0",
"@actions/github": "^6.0.1",
"@types/node": "^25.1.0",
"glob": "^13.0.0"
@@ -56,19 +38,10 @@
"devDependencies": {
"@biomejs/biome": "2.3.13",
"@octokit/plugin-rest-endpoint-methods": "^10.4.1",
"@octokit/request-error": "^7.1.0",
"@octokit/types": "^16.0.0",
"@types/jest": "30.0.0",
"jest": "^30.2.0",
"jest-circus": "^30.2.0",
"ts-jest": "^29.4.5",
"typescript": "^5.9.3"
},
"pnpm": {
"overrides": {
"jest-cli>yargs": "^17.3.1",
"glob": "^10.5.0",
"test-exclude": "^7.0.0",
"babel-plugin-istanbul": "^7.0.0"
}
"@vitest/coverage-v8": "^4.0.18",
"typescript": "^5.9.3",
"vitest": "^4.0.18"
}
}

3411
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,18 @@
import * as core from "@actions/core"
import type { ActionSkipper } from "./ActionSkipper"
import type { ArtifactDestroyer } from "./ArtifactDestroyer"
import type { ArtifactUploader } from "./ArtifactUploader"
import { GithubError } from "./GithubError"
import type { Inputs } from "./Inputs"
import type { Outputs } from "./Outputs"
import { ReleaseValidator } from "./ReleaseValidator"
import type { 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

View File

@@ -1,4 +1,4 @@
import { Releases } from "./Releases"
import { Releases } from "./Releases.js"
export interface ActionSkipper {
shouldSkip(): Promise<boolean>

View File

@@ -1,4 +1,4 @@
import { Releases } from "./Releases"
import { Releases } from "./Releases.js"
import * as core from "@actions/core"
export interface ArtifactDestroyer {

View File

@@ -1,9 +1,9 @@
import * as core from "@actions/core"
import { Globber, FileGlobber } from "./Globber"
import { Artifact } from "./Artifact"
import { expandTilde } from "./PathExpander"
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[]

View File

@@ -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>>

View File

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

View File

@@ -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
@@ -35,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[] {
@@ -64,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 {
@@ -83,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 {
@@ -97,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 {
@@ -106,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 {
@@ -136,7 +136,7 @@ export class CoreInputs implements Inputs {
get generateReleaseNotes(): boolean {
const generate = core.getInput("generateReleaseNotes")
return generate == "true"
return generate === "true"
}
get generateReleaseNotesPreviousTag(): string | undefined {
@@ -146,12 +146,12 @@ export class CoreInputs implements Inputs {
get immutableCreate(): boolean {
const immutable = core.getInput("immutableCreate")
return immutable == "true"
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
}
@@ -159,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
}
@@ -168,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
}
@@ -196,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)
}
@@ -213,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 {
@@ -222,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 {
@@ -231,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 {
@@ -240,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 {
@@ -248,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 {

View File

@@ -1,14 +1,14 @@
import * as github from "@actions/github"
import * as core from "@actions/core"
import { CoreInputs } from "./Inputs"
import { GithubReleases } from "./Releases"
import { Action } from "./Action"
import { GithubArtifactUploader } from "./ArtifactUploader"
import { FileArtifactGlobber } from "./ArtifactGlobber"
import { GithubError } from "./GithubError"
import { CoreOutputs } from "./Outputs"
import { GithubArtifactDestroyer } from "./ArtifactDestroyer"
import { ActionSkipper, ReleaseActionSkipper } from "./ActionSkipper"
import { 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 {

View File

@@ -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

View File

@@ -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"]
@@ -65,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
}

21
vitest.config.ts Normal file
View 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,
},
},
},
})