Compare commits

..

19 Commits

Author SHA1 Message Date
Meredith Lancaster cef9ee9223 remove comment
Signed-off-by: Meredith Lancaster <malancas@github.com>
2026-02-05 13:18:59 -08:00
Meredith Lancaster 554882dca6 remove unused mocks
Signed-off-by: Meredith Lancaster <malancas@github.com>
2026-02-05 13:15:37 -08:00
Meredith Lancaster bdb27c47d0 tests handle esm
Signed-off-by: Meredith Lancaster <malancas@github.com>
2026-02-05 13:09:33 -08:00
Meredith Lancaster c44ed442f4 update eslint and ts configs to handle esm
Signed-off-by: Meredith Lancaster <malancas@github.com>
2026-02-05 13:09:08 -08:00
Meredith Lancaster 4eb88dc0f0 drop old jest setup
Signed-off-by: Meredith Lancaster <malancas@github.com>
2026-02-05 13:08:11 -08:00
Meredith Lancaster 6f4be31791 move jest config to separate file
Signed-off-by: Meredith Lancaster <malancas@github.com>
2026-02-05 13:07:47 -08:00
Meredith Lancaster 20984daf61 regenerate dist
Signed-off-by: Meredith Lancaster <malancas@github.com>
2026-02-05 11:15:36 -08:00
Meredith Lancaster 858f1cb727 regenerate dist
Signed-off-by: Meredith Lancaster <malancas@github.com>
2026-02-05 11:09:12 -08:00
Meredith Lancaster 0345c893f4 update esm imports
Signed-off-by: Meredith Lancaster <malancas@github.com>
2026-02-05 11:09:06 -08:00
Meredith Lancaster 75dea93e1d include type:module in package
Signed-off-by: Meredith Lancaster <malancas@github.com>
2026-02-05 11:08:57 -08:00
dependabot[bot] a82737a684 Bump @isaacs/brace-expansion from 5.0.0 to 5.0.1 (#342)
* Bump @isaacs/brace-expansion from 5.0.0 to 5.0.1

Bumps @isaacs/brace-expansion from 5.0.0 to 5.0.1.

---
updated-dependencies:
- dependency-name: "@isaacs/brace-expansion"
  dependency-version: 5.0.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* regenerate dist

Signed-off-by: Meredith Lancaster <malancas@github.com>

* regenerate package-lock

Signed-off-by: Meredith Lancaster <malancas@github.com>

* regenerate dist

Signed-off-by: Meredith Lancaster <malancas@github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Meredith Lancaster <malancas@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Meredith Lancaster <malancas@github.com>
Co-authored-by: Meredith Lancaster <malancas@users.noreply.github.com>
2026-02-05 10:03:29 -08:00
dependabot[bot] 9a85e4f48a Bump the npm-development group with 2 updates (#338)
Bumps the npm-development group with 2 updates: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) and [undici](https://github.com/nodejs/undici).


Updates `@types/node` from 25.1.0 to 25.2.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `undici` from 7.19.2 to 7.20.0
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v7.19.2...v7.20.0)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.2.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: undici
  dependency-version: 7.20.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-03 14:50:42 -08:00
dependabot[bot] 615da641f0 Bump tar from 7.4.3 to 7.5.7 (#337)
* Bump tar from 7.4.3 to 7.5.7

Bumps [tar](https://github.com/isaacs/node-tar) from 7.4.3 to 7.5.7.
- [Release notes](https://github.com/isaacs/node-tar/releases)
- [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/isaacs/node-tar/compare/v7.4.3...v7.5.7)

---
updated-dependencies:
- dependency-name: tar
  dependency-version: 7.5.7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* Rebuild dist after dependency updates

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tingting Wang <tingx2wang@github.com>
2026-01-29 15:03:36 -08:00
dependabot[bot] 411f73e40b Bump @actions/attest from 2.1.0 to 2.2.0 (#325)
* Bump @actions/attest from 2.1.0 to 2.2.0

Bumps [@actions/attest](https://github.com/actions/toolkit/tree/HEAD/packages/attest) from 2.1.0 to 2.2.0.
- [Changelog](https://github.com/actions/toolkit/blob/main/packages/attest/RELEASES.md)
- [Commits](https://github.com/actions/toolkit/commits/HEAD/packages/attest)

---
updated-dependencies:
- dependency-name: "@actions/attest"
  dependency-version: 2.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore: update dist/ after build

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tingting Wang <tingx2wang@github.com>
2026-01-29 15:01:54 -08:00
dependabot[bot] 95674aef8a Bump @actions/github from 6.0.1 to 7.0.0 (#324)
Bumps [@actions/github](https://github.com/actions/toolkit/tree/HEAD/packages/github) from 6.0.1 to 7.0.0.
- [Changelog](https://github.com/actions/toolkit/blob/main/packages/github/RELEASES.md)
- [Commits](https://github.com/actions/toolkit/commits/HEAD/packages/github)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tingting Wang <tingx2wang@github.com>
2026-01-28 15:29:26 -08:00
dependabot[bot] 775709ffff Bump the npm-development group across 1 directory with 5 updates (#336)
* Bump the npm-development group across 1 directory with 5 updates

Bumps the npm-development group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `25.0.3` | `25.0.10` |
| [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) | `29.9.0` | `29.12.1` |
| [prettier](https://github.com/prettier/prettier) | `3.7.4` | `3.8.1` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.50.1` | `8.54.0` |
| [undici](https://github.com/nodejs/undici) | `7.18.2` | `7.19.1` |



Updates `@types/node` from 25.0.3 to 25.0.10
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `eslint-plugin-jest` from 29.9.0 to 29.12.1
- [Release notes](https://github.com/jest-community/eslint-plugin-jest/releases)
- [Changelog](https://github.com/jest-community/eslint-plugin-jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jest-community/eslint-plugin-jest/compare/v29.9.0...v29.12.1)

Updates `prettier` from 3.7.4 to 3.8.1
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.7.4...3.8.1)

Updates `typescript-eslint` from 8.50.1 to 8.54.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.54.0/packages/typescript-eslint)

Updates `undici` from 7.18.2 to 7.19.1
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v7.18.2...v7.19.1)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.0.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-development
- dependency-name: eslint-plugin-jest
  dependency-version: 29.12.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: prettier
  dependency-version: 3.8.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: typescript-eslint
  dependency-version: 8.54.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: undici
  dependency-version: 7.19.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore: update dist/ after build

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tingting Wang <tingx2wang@github.com>
2026-01-27 18:41:15 -08:00
dependabot[bot] 6d9cc6edb5 Bump tar from 7.4.3 to 7.5.6 (#333)
* Bump tar from 7.4.3 to 7.5.6

Bumps [tar](https://github.com/isaacs/node-tar) from 7.4.3 to 7.5.6.
- [Release notes](https://github.com/isaacs/node-tar/releases)
- [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/isaacs/node-tar/compare/v7.4.3...v7.5.6)

---
updated-dependencies:
- dependency-name: tar
  dependency-version: 7.5.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore: update dist/ after build

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tingting Wang <tingx2wang@github.com>
2026-01-27 18:40:58 -08:00
dependabot[bot] 792c62d14a Bump @actions/core from 2.0.1 to 2.0.2 in the npm-production group (#323)
Bumps the npm-production group with 1 update: [@actions/core](https://github.com/actions/toolkit/tree/HEAD/packages/core).


Updates `@actions/core` from 2.0.1 to 2.0.2
- [Changelog](https://github.com/actions/toolkit/blob/main/packages/core/RELEASES.md)
- [Commits](https://github.com/actions/toolkit/commits/HEAD/packages/core)

---
updated-dependencies:
- dependency-name: "@actions/core"
  dependency-version: 2.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tingting Wang <tingx2wang@github.com>
2026-01-27 18:40:13 -08:00
dependabot[bot] 65786c7512 Bump the actions-minor group across 1 directory with 2 updates (#335)
Bumps the actions-minor group with 2 updates in the / directory: [actions/checkout](https://github.com/actions/checkout) and [actions/setup-node](https://github.com/actions/setup-node).


Updates `actions/checkout` from 6.0.1 to 6.0.2
- [Release notes](https://github.com/actions/checkout/releases)
- [Commits](https://github.com/actions/checkout/compare/v6.0.1...v6.0.2)

Updates `actions/setup-node` from 6.1.0 to 6.2.0
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v6.1.0...v6.2.0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-minor
- dependency-name: actions/setup-node
  dependency-version: 6.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-27 15:27:59 -08:00
21 changed files with 33423 additions and 56671 deletions
+2 -2
View File
@@ -28,11 +28,11 @@ jobs:
steps:
- name: Checkout
id: checkout
uses: actions/checkout@v6.0.1
uses: actions/checkout@v6.0.2
- name: Setup Node.js
id: setup-node
uses: actions/setup-node@v6.1.0
uses: actions/setup-node@v6.2.0
with:
node-version-file: .node-version
cache: npm
+3 -3
View File
@@ -21,11 +21,11 @@ jobs:
steps:
- name: Checkout
id: checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5.0.1
uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v5.0.1
- name: Setup Node.js
id: setup-node
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: .node-version
cache: npm
@@ -58,7 +58,7 @@ jobs:
steps:
- name: Checkout
id: checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5.0.1
uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v5.0.1
- name: Calculate subject digest
id: subject
env:
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
steps:
- name: Checkout
id: checkout
uses: actions/checkout@v6.0.1
uses: actions/checkout@v6.0.2
- name: Initialize CodeQL
id: initialize
+15 -9
View File
@@ -2,20 +2,26 @@
* Unit tests for the action's entrypoint, src/index.ts
*/
import * as core from '@actions/core'
import * as main from '../src/main'
import { jest, describe, expect, beforeEach } from '@jest/globals'
// Mock the action's entrypoint
const runMock = jest.spyOn(main, 'run').mockImplementation()
const getBooleanInputMock = jest.spyOn(core, 'getBooleanInput')
// Mock modules before importing them
const runMock = jest.fn<() => Promise<void>>()
jest.unstable_mockModule('../src/main', () => ({
run: runMock
}))
jest.unstable_mockModule('@actions/core', () => ({
getInput: jest.fn(() => ''),
getBooleanInput: jest.fn(() => false)
}))
describe('index', () => {
beforeEach(() => {
getBooleanInputMock.mockImplementation(() => false)
jest.clearAllMocks()
})
it('calls run when imported', () => {
// eslint-disable-next-line @typescript-eslint/no-require-imports
require('../src/index')
it('calls run when imported', async () => {
await import('../src/index.js')
expect(runMock).toHaveBeenCalled()
})
+180 -83
View File
@@ -5,41 +5,110 @@
* Specifically, the inputs listed in `action.yml` should be set as environment
* variables following the pattern `INPUT_<INPUT_NAME>`.
*/
import * as core from '@actions/core'
import * as github from '@actions/github'
import { mockFulcio, mockRekor, mockTSA } from '@sigstore/mock'
import * as oci from '@sigstore/oci'
import * as attest from '@actions/attest'
import * as localAttest from '../src/attest'
import fs from 'fs/promises'
import nock from 'nock'
import os from 'os'
import path from 'path'
import { MockAgent, setGlobalDispatcher } from 'undici'
import { SEARCH_PUBLIC_GOOD_URL } from '../src/endpoints'
import * as main from '../src/main'
import {
jest,
describe,
expect,
beforeEach,
afterEach,
it
} from '@jest/globals'
import type { RunInputs } from '../src/main.js'
// Mock the GitHub Actions core library
const infoMock = jest.spyOn(core, 'info')
const warningMock = jest.spyOn(core, 'warning')
const startGroupMock = jest.spyOn(core, 'startGroup')
const setOutputMock = jest.spyOn(core, 'setOutput')
const setFailedMock = jest.spyOn(core, 'setFailed')
// Create mock functions for core
const infoMock = jest.fn()
const warningMock = jest.fn()
const startGroupMock = jest.fn()
const endGroupMock = jest.fn()
const setOutputMock = jest.fn()
const setFailedMock = jest.fn()
const summaryWriteMock = jest.fn<() => Promise<void>>()
// Ensure that setFailed doesn't set an exit code during tests
setFailedMock.mockImplementation(() => {})
// Create a mock summary object
const mockSummary = {
addHeading: jest.fn().mockReturnThis(),
addRaw: jest.fn().mockReturnThis(),
addTable: jest.fn().mockReturnThis(),
addSeparator: jest.fn().mockReturnThis(),
addLink: jest.fn().mockReturnThis(),
addBreak: jest.fn().mockReturnThis(),
addList: jest.fn().mockReturnThis(),
write: summaryWriteMock.mockResolvedValue(undefined)
}
const summaryWriteMock = jest.spyOn(core.summary, 'write')
summaryWriteMock.mockResolvedValue(core.summary)
// Mock @actions/core before importing
jest.unstable_mockModule('@actions/core', () => ({
info: infoMock,
warning: warningMock,
startGroup: startGroupMock,
endGroup: endGroupMock,
setOutput: setOutputMock,
setFailed: setFailedMock,
summary: mockSummary
}))
// Mock the action's main function
const runMock = jest.spyOn(main, 'run')
// Create mocks for OCI and attest modules
/* eslint-disable @typescript-eslint/no-explicit-any */
const getRegistryCredentialsMock = jest.fn<(...args: any[]) => any>()
const attachArtifactToImageMock = jest.fn<(...args: any[]) => any>()
const createStorageRecordMock = jest.fn<(...args: any[]) => any>()
const attestMock = jest.fn<(...args: any[]) => any>()
/* eslint-enable @typescript-eslint/no-explicit-any */
// Mock @sigstore/oci
jest.unstable_mockModule('@sigstore/oci', () => ({
getRegistryCredentials: getRegistryCredentialsMock,
attachArtifactToImage: attachArtifactToImageMock
}))
// Mock @actions/attest
jest.unstable_mockModule('@actions/attest', () => ({
attest: attestMock,
createStorageRecord: createStorageRecordMock
}))
// Create a mutable context object for @actions/github
const mockContext: Record<string, unknown> = {}
// Mock for getOctokit to return a mock octokit client
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mockReposGet = jest.fn<(...args: any[]) => any>()
const mockOctokit = {
rest: {
repos: {
get: mockReposGet
}
}
}
jest.unstable_mockModule('@actions/github', () => ({
context: mockContext,
getOctokit: jest.fn(() => mockOctokit)
}))
// Helper to set the mocked GitHub context
function setGHContext(context: object): void {
Object.keys(mockContext).forEach(key => delete mockContext[key])
Object.assign(mockContext, context)
}
// Now import the modules after mocking
const { mockFulcio, mockRekor, mockTSA } = await import('@sigstore/mock')
const fs = (await import('fs/promises')).default
const nock = (await import('nock')).default
const os = (await import('os')).default
const path = (await import('path')).default
const { MockAgent, setGlobalDispatcher } = await import('undici')
const { SEARCH_PUBLIC_GOOD_URL } = await import('../src/endpoints.js')
const { run } = (await import('../src/main.js')) as {
run: (inputs: RunInputs) => Promise<void>
}
// MockAgent for mocking @actions/github
const mockAgent = new MockAgent()
setGlobalDispatcher(mockAgent)
const defaultInputs: main.RunInputs = {
const defaultInputs: RunInputs = {
predicate: '',
predicateType: '',
predicatePath: '',
@@ -55,10 +124,8 @@ const defaultInputs: main.RunInputs = {
}
describe('action', () => {
// Capture original environment variables and GitHub context so we can restore
// them after each test
// Capture original environment variables so we can restore them after each test
const originalEnv = process.env
const originalContext = { ...github.context }
// Mock OIDC token endpoint
const tokenURL = 'https://token.url'
@@ -82,6 +149,34 @@ describe('action', () => {
beforeEach(() => {
jest.clearAllMocks()
// Set up default GitHub context with empty payload
setGHContext({
payload: {},
repo: { owner: 'test-owner', repo: 'test-repo' }
})
// Set up default return value for attestMock (without tlogID for private/GitHub sigstore)
attestMock.mockResolvedValue({
attestationID,
bundle: {
mediaType: 'application/vnd.dev.sigstore.bundle.v0.3+json',
verificationMaterial: {
certificate: {
rawBytes: Buffer.from(
'-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----'
).toString('base64')
},
tlogEntries: []
},
content: {}
},
certificate:
'-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----'
})
// Set up default return value for createStorageRecordMock (returns array of record IDs)
createStorageRecordMock.mockResolvedValue([storageRecordID])
nock(tokenURL)
.get('/')
.query({ audience: 'sigstore' })
@@ -114,12 +209,12 @@ describe('action', () => {
// Restore the original environment
process.env = originalEnv
// Restore the original github.context
setGHContext(originalContext)
// Clear the github context
setGHContext({ payload: {}, repo: { owner: '', repo: '' } })
})
describe('when ACTIONS_ID_TOKEN_REQUEST_URL is not set', () => {
const inputs: main.RunInputs = {
const inputs: RunInputs = {
...defaultInputs,
subjectDigest,
subjectName,
@@ -134,9 +229,8 @@ describe('action', () => {
})
it('sets a failed status', async () => {
await main.run(inputs)
await run(inputs)
expect(runMock).toHaveReturned()
expect(setFailedMock).toHaveBeenCalledWith(
new Error(
'missing "id-token" permission. Please add "permissions: id-token: write" to your workflow.'
@@ -147,9 +241,8 @@ describe('action', () => {
describe('when no inputs are provided', () => {
it('sets a failed status', async () => {
await main.run(defaultInputs)
await run(defaultInputs)
expect(runMock).toHaveReturned()
expect(setFailedMock).toHaveBeenCalledWith(
new Error(
'One of subject-path, subject-digest, or subject-checksums must be provided'
@@ -159,7 +252,7 @@ describe('action', () => {
})
describe('when the repository is private', () => {
const inputs: main.RunInputs = {
const inputs: RunInputs = {
...defaultInputs,
subjectDigest,
subjectName,
@@ -183,10 +276,9 @@ describe('action', () => {
})
it('invokes the action w/o error', async () => {
await main.run(inputs)
await run(inputs)
expect(runMock).toHaveReturned()
expect(setFailedMock).not.toHaveBeenCalledWith()
expect(setFailedMock).not.toHaveBeenCalled()
expect(infoMock).toHaveBeenNthCalledWith(
1,
expect.stringMatching(
@@ -229,13 +321,7 @@ describe('action', () => {
})
describe('when the repository is public', () => {
const getRegCredsSpy = jest.spyOn(oci, 'getRegistryCredentials')
const attachArtifactSpy = jest.spyOn(oci, 'attachArtifactToImage')
const repoOwnerIsOrgSpy = jest.spyOn(localAttest, 'repoOwnerIsOrg')
const createStorageRecordSpy = jest.spyOn(attest, 'createStorageRecord')
const createAttestationSpy = jest.spyOn(localAttest, 'createAttestation')
const inputs: main.RunInputs = {
const inputs: RunInputs = {
...defaultInputs,
subjectDigest,
subjectName,
@@ -258,28 +344,50 @@ describe('action', () => {
})
await mockRekor({ baseURL: 'https://rekor.sigstore.dev' })
getRegCredsSpy.mockImplementation(() => ({
getRegistryCredentialsMock.mockImplementation(() => ({
username: 'username',
password: 'password'
}))
attachArtifactSpy.mockResolvedValue({
attachArtifactToImageMock.mockResolvedValue({
digest: 'sha256:123456',
mediaType: 'application/vnd.cncf.notary.v2',
size: 123456
})
repoOwnerIsOrgSpy.mockResolvedValue(true)
// Set up attestMock with tlogID for public good sigstore
attestMock.mockResolvedValue({
attestationID,
bundle: {
mediaType: 'application/vnd.dev.sigstore.bundle.v0.3+json',
verificationMaterial: {
certificate: {
rawBytes: Buffer.from(
'-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----'
).toString('base64')
},
tlogEntries: [{ logIndex: '123' }]
},
content: {}
},
certificate:
'-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----',
tlogID: '123'
})
// Mock the repos.get API call for repoOwnerIsOrg check
mockReposGet.mockResolvedValue({
data: { owner: { type: 'Organization' } }
})
})
it('invokes the action w/o error', async () => {
await main.run(inputs)
await run(inputs)
expect(runMock).toHaveReturned()
expect(setFailedMock).not.toHaveBeenCalled()
expect(getRegCredsSpy).toHaveBeenCalledWith(subjectName)
expect(attachArtifactSpy).toHaveBeenCalled()
expect(createAttestationSpy).toHaveBeenCalled()
expect(repoOwnerIsOrgSpy).toHaveBeenCalled()
expect(createStorageRecordSpy).toHaveBeenCalled()
expect(getRegistryCredentialsMock).toHaveBeenCalledWith(subjectName)
expect(attachArtifactToImageMock).toHaveBeenCalled()
expect(attestMock).toHaveBeenCalled()
expect(createStorageRecordMock).toHaveBeenCalled()
expect(warningMock).not.toHaveBeenCalled()
expect(infoMock).toHaveBeenNthCalledWith(
1,
@@ -344,16 +452,14 @@ describe('action', () => {
it('catches error when storage record creation fails and continues', async () => {
// Mock the createStorageRecord function and throw an error
createStorageRecordSpy.mockRejectedValueOnce(
createStorageRecordMock.mockRejectedValueOnce(
new Error('Failed to persist storage record: Not Found')
)
await main.run(inputs)
await run(inputs)
expect(runMock).toHaveReturned()
expect(createAttestationSpy).toHaveBeenCalled()
expect(repoOwnerIsOrgSpy).toHaveBeenCalled()
expect(createStorageRecordSpy).toHaveBeenCalled()
expect(attestMock).toHaveBeenCalled()
expect(createStorageRecordMock).toHaveBeenCalled()
expect(setFailedMock).not.toHaveBeenCalled()
expect(warningMock).toHaveBeenNthCalledWith(
1,
@@ -362,17 +468,16 @@ describe('action', () => {
})
it('does not create a storage record when the repo is owned by a user', async () => {
repoOwnerIsOrgSpy.mockResolvedValueOnce(false)
// Mock the repos.get API to return a user-owned repo
mockReposGet.mockResolvedValueOnce({ data: { owner: { type: 'User' } } })
await main.run(inputs)
await run(inputs)
expect(runMock).toHaveReturned()
expect(setFailedMock).not.toHaveBeenCalled()
expect(getRegCredsSpy).toHaveBeenCalledWith(subjectName)
expect(attachArtifactSpy).toHaveBeenCalled()
expect(createAttestationSpy).toHaveBeenCalled()
expect(repoOwnerIsOrgSpy).toHaveBeenCalled()
expect(createStorageRecordSpy).not.toHaveBeenCalled()
expect(getRegistryCredentialsMock).toHaveBeenCalledWith(subjectName)
expect(attachArtifactToImageMock).toHaveBeenCalled()
expect(attestMock).toHaveBeenCalled()
expect(createStorageRecordMock).not.toHaveBeenCalled()
expect(warningMock).not.toHaveBeenCalled()
expect(infoMock).toHaveBeenCalledWith(
expect.stringMatching(
@@ -436,16 +541,15 @@ describe('action', () => {
})
it('invokes the action w/o error', async () => {
const inputs: main.RunInputs = {
const inputs: RunInputs = {
...defaultInputs,
subjectPath: path.join(dir, `${filename}-*`),
predicateType,
predicate,
githubToken: 'gh-token'
}
await main.run(inputs)
await run(inputs)
expect(runMock).toHaveReturned()
expect(setFailedMock).not.toHaveBeenCalled()
expect(infoMock).toHaveBeenNthCalledWith(
1,
@@ -484,16 +588,15 @@ describe('action', () => {
})
it('sets a failed status', async () => {
const inputs: main.RunInputs = {
const inputs: RunInputs = {
...defaultInputs,
subjectPath: path.join(dir, `${filename}-*`),
predicateType,
predicate,
githubToken: 'gh-token'
}
await main.run(inputs)
await run(inputs)
expect(runMock).toHaveReturned()
expect(setFailedMock).toHaveBeenCalledWith(
new Error(
'Too many subjects specified. The maximum number of subjects is 1024.'
@@ -502,9 +605,3 @@ describe('action', () => {
})
})
})
// Stubbing the GitHub context is a bit tricky. We need to use
// `Object.defineProperty` because `github.context` is read-only.
function setGHContext(context: object): void {
Object.defineProperty(github, 'context', { value: context })
}
+1 -1
View File
@@ -1,7 +1,7 @@
import fs from 'fs/promises'
import os from 'os'
import path from 'path'
import { predicateFromInputs, PredicateInputs } from '../src/predicate'
import { predicateFromInputs, PredicateInputs } from '../src/predicate.js'
describe('subjectFromInputs', () => {
const blankInputs: PredicateInputs = {
+1 -1
View File
@@ -1,4 +1,4 @@
import { highlight, mute } from '../src/style'
import { highlight, mute } from '../src/style.js'
describe('style', () => {
describe('highlight', () => {
+10 -5
View File
@@ -6,7 +6,7 @@ import {
formatSubjectDigest,
subjectFromInputs,
SubjectInputs
} from '../src/subject'
} from '../src/subject.js'
describe('subjectFromInputs', () => {
const blankInputs: SubjectInputs = {
@@ -264,10 +264,15 @@ describe('subjectFromInputs', () => {
expect(subjects).toBeDefined()
expect(subjects).toHaveLength(3)
subjects.forEach((subject, i) => {
expect(subject.name).toEqual(`${filename}-${i}`)
expect(subject.digest).toEqual({ sha256: expectedDigest })
})
subjects.forEach(
(
subject: { name: string; digest: Record<string, string> },
i: number
) => {
expect(subject.name).toEqual(`${filename}-${i}`)
expect(subject.digest).toEqual({ sha256: expectedDigest })
}
)
})
})
Generated Vendored
+26 -14
View File
@@ -1,7 +1,6 @@
"use strict";
exports.id = 606;
exports.ids = [606];
exports.modules = {
export const id = 606;
export const ids = [606];
export const modules = {
/***/ 606:
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
@@ -19,7 +18,7 @@ async function pMap(
signal,
} = {},
) {
return new Promise((resolve, reject_) => {
return new Promise((resolve_, reject_) => {
if (iterable[Symbol.iterator] === undefined && iterable[Symbol.asyncIterator] === undefined) {
throw new TypeError(`Expected \`input\` to be either an \`Iterable\` or \`AsyncIterable\`, got (${typeof iterable})`);
}
@@ -42,10 +41,24 @@ async function pMap(
let currentIndex = 0;
const iterator = iterable[Symbol.iterator] === undefined ? iterable[Symbol.asyncIterator]() : iterable[Symbol.iterator]();
const signalListener = () => {
reject(signal.reason);
};
const cleanup = () => {
signal?.removeEventListener('abort', signalListener);
};
const resolve = value => {
resolve_(value);
cleanup();
};
const reject = reason => {
isRejected = true;
isResolved = true;
reject_(reason);
cleanup();
};
if (signal) {
@@ -53,9 +66,7 @@ async function pMap(
reject(signal.reason);
}
signal.addEventListener('abort', () => {
reject(signal.reason);
});
signal.addEventListener('abort', signalListener, {once: true});
}
const next = async () => {
@@ -203,31 +214,32 @@ function pMapIterable(
const iterator = iterable[Symbol.asyncIterator] === undefined ? iterable[Symbol.iterator]() : iterable[Symbol.asyncIterator]();
const promises = [];
let runningMappersCount = 0;
let pendingPromisesCount = 0;
let isDone = false;
let index = 0;
function trySpawn() {
if (isDone || !(runningMappersCount < concurrency && promises.length < backpressure)) {
if (isDone || !(pendingPromisesCount < concurrency && promises.length < backpressure)) {
return;
}
pendingPromisesCount++;
const promise = (async () => {
const {done, value} = await iterator.next();
if (done) {
pendingPromisesCount--;
return {done: true};
}
runningMappersCount++;
// Spawn if still below concurrency and backpressure limit
trySpawn();
try {
const returnValue = await mapper(await value, index++);
runningMappersCount--;
pendingPromisesCount--;
if (returnValue === pMapSkip) {
const index = promises.indexOf(promise);
@@ -242,6 +254,7 @@ function pMapIterable(
return {done: false, value: returnValue};
} catch (error) {
pendingPromisesCount--;
isDone = true;
return {error};
}
@@ -284,4 +297,3 @@ const pMapSkip = Symbol('skip');
/***/ })
};
;
Generated Vendored
+31102 -48719
View File
File diff suppressed because one or more lines are too long
Generated Vendored
+3
View File
@@ -0,0 +1,3 @@
{
"type": "module"
}
+7
View File
@@ -89,6 +89,13 @@ export default tseslint.config(
allowObject: true
}
]
},
settings: {
'import/resolver': {
typescript: {
project: './tsconfig.lint.json'
}
}
}
}
)
+37
View File
@@ -0,0 +1,37 @@
export default {
preset: "ts-jest",
verbose: true,
clearMocks: true,
testEnvironment: 'node',
moduleFileExtensions: ['js', 'ts'],
testMatch: ['**/*.test.ts'],
testPathIgnorePatterns: [
"/node_modules/",
"/dist/"
],
transform: {
'^.+\\.ts$': [
'ts-jest',
{
useESM: true,
diagnostics: {
ignoreCodes: [151002]
}
}
]
},
coverageReporters: [
"json-summary",
"text",
"lcov"
],
collectCoverage: true,
collectCoverageFrom: [
"./src/**"
],
extensionsToTreatAsEsm: ['.ts'],
transformIgnorePatterns: ['node_modules/(?!(@actions)/)'],
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1'
},
}
-1
View File
@@ -1 +0,0 @@
process.stdout.write = jest.fn()
+2011 -7780
View File
File diff suppressed because it is too large Load Diff
+13 -42
View File
@@ -2,6 +2,7 @@
"name": "actions/attest",
"description": "Generate signed attestations for workflow artifacts",
"version": "3.2.0",
"type": "module",
"author": "",
"private": true,
"homepage": "https://github.com/actions/attest",
@@ -24,7 +25,7 @@
},
"scripts": {
"bundle": "npm run format:write && npm run package",
"ci-test": "jest",
"ci-test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
"format:write": "prettier --write **/*.ts",
"format:check": "prettier --check **/*.ts",
"lint:eslint": "npx eslint",
@@ -32,68 +33,38 @@
"lint": "npm run lint:eslint && npm run lint:markdown",
"package": "ncc build src/index.ts --license licenses.txt",
"package:watch": "npm run package -- --watch",
"test": "jest",
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
"all": "npm run format:write && npm run lint && npm run test && npm run package"
},
"license": "MIT",
"jest": {
"preset": "ts-jest",
"setupFilesAfterEnv": [
"./jest.setup.js"
],
"verbose": true,
"clearMocks": true,
"testEnvironment": "node",
"moduleFileExtensions": [
"js",
"ts"
],
"testMatch": [
"**/*.test.ts"
],
"testPathIgnorePatterns": [
"/node_modules/",
"/dist/"
],
"transform": {
"^.+\\.ts$": "ts-jest"
},
"coverageReporters": [
"json-summary",
"text",
"lcov"
],
"collectCoverage": true,
"collectCoverageFrom": [
"./src/**"
]
},
"dependencies": {
"@actions/attest": "^2.1.0",
"@actions/core": "^2.0.1",
"@actions/github": "^6.0.1",
"@actions/attest": "^2.2.1",
"@actions/core": "^2.0.2",
"@actions/github": "^7.0.0",
"@actions/glob": "^0.5.0",
"@sigstore/oci": "^0.6.0",
"csv-parse": "^5.6.0"
},
"devDependencies": {
"@eslint/js": "^9.39.2",
"@jest/globals": "^30.2.0",
"@sigstore/mock": "^0.11.0",
"@types/jest": "^30.0.0",
"@types/make-fetch-happen": "^10.0.4",
"@types/node": "^25.0.3",
"@types/node": "^25.2.0",
"@vercel/ncc": "^0.38.4",
"eslint": "^9.39.2",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jest": "^29.9.0",
"eslint-plugin-jest": "^29.12.1",
"jest": "^30.2.0",
"js-yaml": "^4.1.1",
"markdownlint-cli": "^0.47.0",
"nock": "^13.5.6",
"prettier": "^3.7.4",
"prettier": "^3.8.1",
"ts-jest": "^29.4.6",
"typescript": "^5.9.3",
"typescript-eslint": "^8.50.1",
"undici": "^7.18.2"
"typescript-eslint": "^8.54.0",
"undici": "^7.20.0"
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ import {
createStorageRecord
} from '@actions/attest'
import { attachArtifactToImage, getRegistryCredentials } from '@sigstore/oci'
import { formatSubjectDigest } from './subject'
import { formatSubjectDigest } from './subject.js'
import * as core from '@actions/core'
import * as github from '@actions/github'
+1 -1
View File
@@ -2,7 +2,7 @@
* The entrypoint for the action.
*/
import * as core from '@actions/core'
import { run, RunInputs } from './main'
import { run, RunInputs } from './main.js'
const inputs: RunInputs = {
subjectPath: core.getInput('subject-path'),
+5 -5
View File
@@ -3,15 +3,15 @@ import * as github from '@actions/github'
import fs from 'fs'
import os from 'os'
import path from 'path'
import { AttestResult, SigstoreInstance, createAttestation } from './attest'
import { SEARCH_PUBLIC_GOOD_URL } from './endpoints'
import { PredicateInputs, predicateFromInputs } from './predicate'
import * as style from './style'
import { AttestResult, SigstoreInstance, createAttestation } from './attest.js'
import { SEARCH_PUBLIC_GOOD_URL } from './endpoints.js'
import { PredicateInputs, predicateFromInputs } from './predicate.js'
import * as style from './style.js'
import {
SubjectInputs,
formatSubjectDigest,
subjectFromInputs
} from './subject'
} from './subject.js'
import type { Subject } from '@actions/attest'
+1 -1
View File
@@ -16,5 +16,5 @@
"skipLibCheck": true,
"newLine": "lf"
},
"exclude": ["./dist", "./node_modules", "./__tests__", "./coverage"]
"exclude": ["./dist", "./node_modules", "./__tests__", "./coverage", "./jest.config.ts"]
}
+3 -2
View File
@@ -2,8 +2,9 @@
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true
"noEmit": true,
"rootDir": "."
},
"include": ["./__tests__/**/*", "./src/**/*"],
"include": ["./__tests__/**/*", "./src/**/*", "./jest.config.ts"],
"exclude": ["./dist", "./node_modules", "./coverage", "*.json"]
}