81 Commits
v2 ... main

Author SHA1 Message Date
dependabot[bot]
2da562a775 Bump the actions-minor group with 2 updates (#400)
Some checks failed
Public-Good Sigstore Prober / prober (push) Has been cancelled
GitHub Sigstore Prober / prober (push) Has been cancelled
Check Transpiled JavaScript / Check dist/ (push) Has been cancelled
Continuous Integration / TypeScript Tests (push) Has been cancelled
Continuous Integration / Test attest action (push) Has been cancelled
CodeQL / Analyze (TypeScript) (push) Has been cancelled
Bumps the actions-minor group with 2 updates: [actions/setup-node](https://github.com/actions/setup-node) and [github/codeql-action](https://github.com/github/codeql-action).


Updates `actions/setup-node` from 6.3.0 to 6.4.0
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](53b83947a5...48b55a011b)

Updates `github/codeql-action` from 4.35.1 to 4.35.2
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](c10b8064de...95e58e9a2c)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 6.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-minor
- dependency-name: github/codeql-action
  dependency-version: 4.35.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  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-04-20 17:31:02 -07:00
dependabot[bot]
96309a74df Bump picomatch from 2.3.1 to 2.3.2 (#398)
Bumps [picomatch](https://github.com/micromatch/picomatch) from 2.3.1 to 2.3.2.
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/picomatch/compare/2.3.1...2.3.2)

---
updated-dependencies:
- dependency-name: picomatch
  dependency-version: 2.3.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-18 17:17:59 -07:00
dependabot[bot]
e287548486 Bump the npm-development group across 1 directory with 6 updates (#396)
Bumps the npm-development group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@sigstore/mock](https://github.com/sigstore/sigstore-js) | `0.11.0` | `0.12.0` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `25.5.0` | `25.6.0` |
| [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) | `29.15.0` | `29.15.2` |
| [prettier](https://github.com/prettier/prettier) | `3.8.1` | `3.8.2` |
| [ts-jest](https://github.com/kulshekhar/ts-jest) | `29.4.6` | `29.4.9` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.57.1` | `8.58.2` |



Updates `@sigstore/mock` from 0.11.0 to 0.12.0
- [Release notes](https://github.com/sigstore/sigstore-js/releases)
- [Commits](https://github.com/sigstore/sigstore-js/compare/@sigstore/mock@0.11.0...@sigstore/mock@0.12.0)

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

Updates `eslint-plugin-jest` from 29.15.0 to 29.15.2
- [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.15.0...v29.15.2)

Updates `prettier` from 3.8.1 to 3.8.2
- [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.8.1...3.8.2)

Updates `ts-jest` from 29.4.6 to 29.4.9
- [Release notes](https://github.com/kulshekhar/ts-jest/releases)
- [Changelog](https://github.com/kulshekhar/ts-jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/kulshekhar/ts-jest/compare/v29.4.6...v29.4.9)

Updates `typescript-eslint` from 8.57.1 to 8.58.2
- [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.58.2/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@sigstore/mock"
  dependency-version: 0.12.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: "@types/node"
  dependency-version: 25.6.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: eslint-plugin-jest
  dependency-version: 29.15.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-development
- dependency-name: prettier
  dependency-version: 3.8.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-development
- dependency-name: ts-jest
  dependency-version: 29.4.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-development
- dependency-name: typescript-eslint
  dependency-version: 8.58.2
  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-04-18 17:08:38 -07:00
dependabot[bot]
fd6b752f2b Bump handlebars from 4.7.8 to 4.7.9 (#388)
Bumps [handlebars](https://github.com/handlebars-lang/handlebars.js) from 4.7.8 to 4.7.9.
- [Release notes](https://github.com/handlebars-lang/handlebars.js/releases)
- [Changelog](https://github.com/handlebars-lang/handlebars.js/blob/v4.7.9/release-notes.md)
- [Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.7.8...v4.7.9)

---
updated-dependencies:
- dependency-name: handlebars
  dependency-version: 4.7.9
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-18 17:03:22 -07:00
dependabot[bot]
c2fbdcca7f Bump the actions-minor group across 1 directory with 3 updates (#395)
Bumps the actions-minor group with 3 updates in the / directory: [actions/upload-artifact](https://github.com/actions/upload-artifact), [github/codeql-action](https://github.com/github/codeql-action) and [masci/datadog](https://github.com/masci/datadog).


Updates `actions/upload-artifact` from 7.0.0 to 7.0.1
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](bbbca2ddaa...043fb46d1a)

Updates `github/codeql-action` from 4.34.1 to 4.35.1
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](3869755554...c10b8064de)

Updates `masci/datadog` from 2.0.0 to 2.0.2
- [Release notes](https://github.com/masci/datadog/releases)
- [Commits](c1b1466047...d7cb6cfc2b)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-minor
- dependency-name: github/codeql-action
  dependency-version: 4.35.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-minor
- dependency-name: masci/datadog
  dependency-version: 2.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  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-04-18 17:01:52 -07:00
Eugene
c65e8d4737 Pin GitHub Actions to commit SHAs for security (#386)
Replace mutable tag references with immutable commit SHAs in
codeql-analysis.yml and check-dist.yml to prevent supply chain attacks.

Actions pinned:
- actions/checkout@v6.0.2
- github/codeql-action/init@v4
- github/codeql-action/autobuild@v4
- github/codeql-action/analyze@v4
- actions/setup-node@v6.3.0
- actions/upload-artifact@v7.0.0

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-25 13:13:15 -04:00
dependabot[bot]
ffbe5726c3 Bump the npm-development group with 5 updates (#379)
Bumps the npm-development group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [@jest/globals](https://github.com/jestjs/jest/tree/HEAD/packages/jest-globals) | `30.2.0` | `30.3.0` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `25.4.0` | `25.5.0` |
| [jest](https://github.com/jestjs/jest/tree/HEAD/packages/jest) | `30.2.0` | `30.3.0` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.57.0` | `8.57.1` |
| [undici](https://github.com/nodejs/undici) | `7.22.0` | `7.24.4` |


Updates `@jest/globals` from 30.2.0 to 30.3.0
- [Release notes](https://github.com/jestjs/jest/releases)
- [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jestjs/jest/commits/v30.3.0/packages/jest-globals)

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

Updates `jest` from 30.2.0 to 30.3.0
- [Release notes](https://github.com/jestjs/jest/releases)
- [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jestjs/jest/commits/v30.3.0/packages/jest)

Updates `typescript-eslint` from 8.57.0 to 8.57.1
- [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.57.1/packages/typescript-eslint)

Updates `undici` from 7.22.0 to 7.24.4
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v7.22.0...v7.24.4)

---
updated-dependencies:
- dependency-name: "@jest/globals"
  dependency-version: 30.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: "@types/node"
  dependency-version: 25.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: jest
  dependency-version: 30.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: typescript-eslint
  dependency-version: 8.57.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-development
- dependency-name: undici
  dependency-version: 7.24.4
  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>
Co-authored-by: Eugene <108841108+ejahnGithub@users.noreply.github.com>
2026-03-23 10:30:09 -04:00
dependabot[bot]
1ddccb76ab Bump masci/datadog from 1.9.3 to 2.0.0 (#378)
Bumps [masci/datadog](https://github.com/masci/datadog) from 1.9.3 to 2.0.0.
- [Release notes](https://github.com/masci/datadog/releases)
- [Commits](a3f481d2ed...c1b1466047)

---
updated-dependencies:
- dependency-name: masci/datadog
  dependency-version: 2.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: Eugene <108841108+ejahnGithub@users.noreply.github.com>
2026-03-23 10:28:55 -04:00
dependabot[bot]
a25a27cab8 Bump flatted from 3.3.3 to 3.4.2 (#380)
Bumps [flatted](https://github.com/WebReflection/flatted) from 3.3.3 to 3.4.2.
- [Commits](https://github.com/WebReflection/flatted/compare/v3.3.3...v3.4.2)

---
updated-dependencies:
- dependency-name: flatted
  dependency-version: 3.4.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-23 10:28:08 -04:00
dependabot[bot]
e18a799f23 Bump tar from 7.5.10 to 7.5.11 (#375)
Bumps [tar](https://github.com/isaacs/node-tar) from 7.5.10 to 7.5.11.
- [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.5.10...v7.5.11)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-11 14:32:46 -07:00
dependabot[bot]
5b44f686c6 Bump the npm-development group with 3 updates (#373)
Bumps the npm-development group with 3 updates: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node), [markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


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

Updates `markdownlint-cli` from 0.47.0 to 0.48.0
- [Release notes](https://github.com/igorshubovych/markdownlint-cli/releases)
- [Commits](https://github.com/igorshubovych/markdownlint-cli/compare/v0.47.0...v0.48.0)

Updates `typescript-eslint` from 8.56.1 to 8.57.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.57.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: markdownlint-cli
  dependency-version: 0.48.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: typescript-eslint
  dependency-version: 8.57.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-03-09 16:55:05 -07:00
dependabot[bot]
9c10929297 Bump actions/setup-node from 6.2.0 to 6.3.0 in the actions-minor group (#372)
Bumps the actions-minor group with 1 update: [actions/setup-node](https://github.com/actions/setup-node).


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

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 6.3.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-03-09 15:38:03 -07:00
dependabot[bot]
3f6ab60ba4 Bump tar from 7.5.9 to 7.5.10 (#371)
Bumps [tar](https://github.com/isaacs/node-tar) from 7.5.9 to 7.5.10.
- [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.5.9...v7.5.10)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-06 07:22:41 -08:00
dependabot[bot]
f7e1c5d786 Bump @types/node from 25.3.0 to 25.3.3 in the npm-development group (#370)
Bumps the npm-development group with 1 update: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node).


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

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.3.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  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-03-02 16:31:03 -08:00
dependabot[bot]
088247d089 Bump actions/upload-artifact from 6.0.0 to 7.0.0 (#369)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6.0.0 to 7.0.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  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>
2026-03-02 16:03:08 -08:00
Brian DeHamer
6bc26cfc5e add e2e tests (#368)
Signed-off-by: Brian DeHamer <bdehamer@github.com>
2026-02-26 14:23:01 -08:00
Brian DeHamer
59d89421af add storage record content to README (#366)
Signed-off-by: Brian DeHamer <bdehamer@github.com>
2026-02-26 12:55:02 -08:00
Brian DeHamer
ec072a1cb2 add new subject-version input (#364)
Signed-off-by: Brian DeHamer <bdehamer@github.com>
2026-02-26 12:38:12 -08:00
Brian DeHamer
8b290b8d86 bump @actions/attest from 3.1.0 to 3.2.0 (#365)
Signed-off-by: Brian DeHamer <bdehamer@github.com>
2026-02-26 12:36:53 -08:00
Brian DeHamer
35cfe2422e bump @actions/attest from 3.0.0 to 3.1.0 (#362)
Signed-off-by: Brian DeHamer <bdehamer@github.com>
2026-02-25 16:03:18 -08:00
Brian DeHamer
c32b4b8b19 bump version in package.json to v4.0.0 (#360)
Signed-off-by: Brian DeHamer <bdehamer@github.com>
2026-02-24 15:48:06 -08:00
dependabot[bot]
1e73be196c Bump typescript-eslint in the npm-development group (#358)
Bumps the npm-development group with 1 update: [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


Updates `typescript-eslint` from 8.56.0 to 8.56.1
- [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.56.1/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: typescript-eslint
  dependency-version: 8.56.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  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-24 15:45:28 -08:00
dependabot[bot]
e1345cbec4 Bump the npm-development group across 1 directory with 3 updates (#357)
Bumps the npm-development group with 3 updates in the / directory: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node), [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


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

Updates `eslint-plugin-jest` from 29.14.0 to 29.15.0
- [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.14.0...v29.15.0)

Updates `typescript-eslint` from 8.55.0 to 8.56.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.56.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: eslint-plugin-jest
  dependency-version: 29.15.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: typescript-eslint
  dependency-version: 8.56.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-20 09:02:14 -08:00
dependabot[bot]
09cd5f66cb Bump tar from 7.5.7 to 7.5.9 (#354)
Bumps [tar](https://github.com/isaacs/node-tar) from 7.5.7 to 7.5.9.
- [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.5.7...v7.5.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-19 10:20:40 -08:00
Brian DeHamer
19ad753d23 test suite re-write (#356)
Signed-off-by: Brian DeHamer <bdehamer@github.com>
2026-02-19 10:14:47 -08:00
Brian DeHamer
7d7ff4475a ESM Conversion (#347)
* initial esm conversion

Signed-off-by: Brian DeHamer <bdehamer@github.com>

* esm'ify jest tests

Signed-off-by: Brian DeHamer <bdehamer@github.com>

* lint issues

Signed-off-by: Brian DeHamer <bdehamer@github.com>

* debug mock

Signed-off-by: Brian DeHamer <bdehamer@github.com>

* glob updated

Signed-off-by: Brian DeHamer <bdehamer@github.com>

* async all file functions

Signed-off-by: Brian DeHamer <bdehamer@github.com>

* update @actions/github

Signed-off-by: Brian DeHamer <bdehamer@github.com>

* update @actions/attest

Signed-off-by: Brian DeHamer <bdehamer@github.com>

* rebuild package-lock.json

Signed-off-by: Brian DeHamer <bdehamer@github.com>

* use experimental flag for jest in ci

Signed-off-by: Brian DeHamer <bdehamer@github.com>

* remove stray istanbul ignore

Signed-off-by: Brian DeHamer <bdehamer@github.com>

* Optimize getSubjectFromPath to avoid concurrent stat calls

Co-authored-by: bdehamer <398027+bdehamer@users.noreply.github.com>

* Fix boundary condition for MAX_SUBJECT_COUNT check

Co-authored-by: bdehamer <398027+bdehamer@users.noreply.github.com>

* Improve error message clarity for subject count limit

Co-authored-by: bdehamer <398027+bdehamer@users.noreply.github.com>

* Update test to match new error message format

Co-authored-by: bdehamer <398027+bdehamer@users.noreply.github.com>

* rebuild dist

Signed-off-by: Brian DeHamer <bdehamer@github.com>

* Fix parseSBOMFromPath to check file size before reading

Co-authored-by: bdehamer <398027+bdehamer@users.noreply.github.com>

* Build package with updated changes

Co-authored-by: bdehamer <398027+bdehamer@users.noreply.github.com>

---------

Signed-off-by: Brian DeHamer <bdehamer@github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: bdehamer <398027+bdehamer@users.noreply.github.com>
2026-02-18 08:52:30 -08:00
Brian DeHamer
dc4ad3cc6c Consolidate attestation actions (#346)
* consolidate attestation actions

Signed-off-by: Brian DeHamer <bdehamer@github.com>

* better errors

Signed-off-by: Brian DeHamer <bdehamer@github.com>

* Update src/sbom.ts

Co-authored-by: Austin Beattie <ajbeattie@github.com>

* clarify dedupe comment

Signed-off-by: Brian DeHamer <bdehamer@github.com>

---------

Signed-off-by: Brian DeHamer <bdehamer@github.com>
Co-authored-by: Austin Beattie <ajbeattie@github.com>
2026-02-13 11:23:24 -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
Meredith Lancaster
e59cbc1ad1 Update version to 3.2.0 (#334)
* update version to 3.2.0

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

* regenerate package-lock

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

---------

Signed-off-by: Meredith Lancaster <malancas@github.com>
2026-01-26 08:59:13 -08:00
Meredith Lancaster
20eb46ce7a Validate repository org-ownership before storage record creation (#328)
* check if the repository is owned by org before attempting storage record creation

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

* linter

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

* generate dist

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

* add fixtures for repoOwnerIsOrg function

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

* formatter

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

* clean up fixtures

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

* more clean up

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

* fix function declaration

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

* clean up fixtures

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

* add test when repo is not owned by org

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

* add more expect statements, clean up mock calls

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

* formatter

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

* add more spy expect statements

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

---------

Signed-off-by: Meredith Lancaster <malancas@github.com>
2026-01-26 08:31:21 -08:00
Meredith Lancaster
7433fa7e7a Update undici development dependency to the latest version (#332)
* update undici dep to the latest version

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

* regenerate dist

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

* update to v7.18.2

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

---------

Signed-off-by: Meredith Lancaster <malancas@github.com>
2026-01-20 16:21:19 -08:00
dependabot[bot]
c03bf4160d Bump the npm-development group with 3 updates (#320)
Bumps the npm-development group with 3 updates: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node), [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


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

Updates `eslint-plugin-jest` from 29.5.0 to 29.9.0
- [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.5.0...v29.9.0)

Updates `typescript-eslint` from 8.50.0 to 8.50.1
- [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.50.1/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.0.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-development
- dependency-name: eslint-plugin-jest
  dependency-version: 29.9.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: typescript-eslint
  dependency-version: 8.50.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  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-01-02 05:03:48 -08:00
Meredith Lancaster
7667f588f2 Create Artifact Metadata Storage Record on registry push (#313)
* first pass at creating storage record

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

* include storage record param in action config

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

* use latest actions/attest version

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

* update storage record params

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

* include storage record id in result

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

* regenerate dist

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

* add documentation on storage records

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

* log storage record creation

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

* add storage record output

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

* add new param

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

* add storage record id output

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

* fix linter errors

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

* return all storage record ids

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

* bump minor version

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

* use expect string match function

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

* add try catch block for storage record creation

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

* fix table column spacing

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

* check for protocol

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

* check for artifact url protocol

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

* only fill registry_url for now

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

* cleanup protocol handling

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

* regenerate dist

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

* handle subject name correctly

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

* move test

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

* add back assert statements

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

* add back output assert statements

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

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* use url for subject name parsing

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

* add missing test setpu

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

* fix storage record fail test

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

* regenerate dist

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

---------

Signed-off-by: Meredith Lancaster <malancas@github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-18 11:30:45 -08:00
dependabot[bot]
0512723b04 Bump @actions/core from 1.11.1 to 2.0.1 (#318)
* Bump @actions/core from 1.11.1 to 2.0.1

Bumps [@actions/core](https://github.com/actions/toolkit/tree/HEAD/packages/core) from 1.11.1 to 2.0.1.
- [Changelog](https://github.com/actions/toolkit/blob/main/packages/core/RELEASES.md)
- [Commits](https://github.com/actions/toolkit/commits/@actions/artifact@2.0.1/packages/core)

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

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

* rebuild dist

Signed-off-by: Brian DeHamer <bdehamer@github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Brian DeHamer <bdehamer@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Brian DeHamer <bdehamer@github.com>
2025-12-15 16:39:30 -08:00
dependabot[bot]
c16e6655b7 Bump the npm-development group with 5 updates (#315)
Bumps the npm-development group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) | `9.39.1` | `9.39.2` |
| [eslint](https://github.com/eslint/eslint) | `9.39.1` | `9.39.2` |
| [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) | `29.2.1` | `29.5.0` |
| [markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli) | `0.46.0` | `0.47.0` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.49.0` | `8.50.0` |


Updates `@eslint/js` from 9.39.1 to 9.39.2
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/commits/v9.39.2/packages/js)

Updates `eslint` from 9.39.1 to 9.39.2
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v9.39.1...v9.39.2)

Updates `eslint-plugin-jest` from 29.2.1 to 29.5.0
- [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.2.1...v29.5.0)

Updates `markdownlint-cli` from 0.46.0 to 0.47.0
- [Release notes](https://github.com/igorshubovych/markdownlint-cli/releases)
- [Commits](https://github.com/igorshubovych/markdownlint-cli/compare/v0.46.0...v0.47.0)

Updates `typescript-eslint` from 8.49.0 to 8.50.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.50.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-version: 9.39.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-development
- dependency-name: eslint
  dependency-version: 9.39.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-development
- dependency-name: eslint-plugin-jest
  dependency-version: 29.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: markdownlint-cli
  dependency-version: 0.47.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: typescript-eslint
  dependency-version: 8.50.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>
2025-12-15 16:27:15 -08:00
dependabot[bot]
3374a04c9f Bump @types/node from 24.10.1 to 25.0.2 (#317)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 24.10.1 to 25.0.2.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.0.2
  dependency-type: direct:development
  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>
2025-12-15 16:23:27 -08:00
dependabot[bot]
8ed7eda47c Bump actions/upload-artifact from 5 to 6 (#314)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  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>
2025-12-15 14:54:32 -08:00
dependabot[bot]
6440a037b1 Bump the npm-development group with 2 updates (#312)
Bumps the npm-development group with 2 updates: [prettier](https://github.com/prettier/prettier) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


Updates `prettier` from 3.7.3 to 3.7.4
- [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.3...3.7.4)

Updates `typescript-eslint` from 8.48.0 to 8.49.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.49.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: prettier
  dependency-version: 3.7.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-development
- dependency-name: typescript-eslint
  dependency-version: 8.49.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>
2025-12-15 08:42:03 -08:00
dependabot[bot]
70b5d87a50 Bump the actions-minor group with 2 updates (#311)
Bumps the actions-minor group with 2 updates: [actions/checkout](https://github.com/actions/checkout) and [actions/setup-node](https://github.com/actions/setup-node).


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

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

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-minor
- dependency-name: actions/setup-node
  dependency-version: 6.1.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>
2025-12-15 08:41:09 -08:00
dependabot[bot]
9902fb2594 Bump the npm-development group with 2 updates (#310)
Bumps the npm-development group with 2 updates: [prettier](https://github.com/prettier/prettier) and [ts-jest](https://github.com/kulshekhar/ts-jest).


Updates `prettier` from 3.6.2 to 3.7.3
- [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.6.2...3.7.3)

Updates `ts-jest` from 29.4.5 to 29.4.6
- [Release notes](https://github.com/kulshekhar/ts-jest/releases)
- [Changelog](https://github.com/kulshekhar/ts-jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/kulshekhar/ts-jest/compare/v29.4.5...v29.4.6)

---
updated-dependencies:
- dependency-name: prettier
  dependency-version: 3.7.3
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: ts-jest
  dependency-version: 29.4.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-01 19:16:19 -08:00
dependabot[bot]
3293874900 Bump actions/checkout from 5.0.1 to 6.0.0 (#308)
Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.1 to 6.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Commits](https://github.com/actions/checkout/compare/v5.0.1...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.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>
2025-11-26 08:44:16 -08:00
dependabot[bot]
88adb86536 Bump the npm-development group with 2 updates (#307)
Bumps the npm-development group with 2 updates: [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


Updates `eslint-plugin-jest` from 29.1.0 to 29.2.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.1.0...v29.2.1)

Updates `typescript-eslint` from 8.47.0 to 8.48.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.48.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: eslint-plugin-jest
  dependency-version: 29.2.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: typescript-eslint
  dependency-version: 8.48.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>
2025-11-24 14:32:05 -08:00
dependabot[bot]
a6ce6d776c Bump actions/checkout from 5 to 6 (#306)
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  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>
2025-11-24 14:31:23 -08:00
dependabot[bot]
2498417848 Bump glob and markdownlint-cli (#305)
* Bump glob and markdownlint-cli

Bumps [glob](https://github.com/isaacs/node-glob) to 10.5.0 and updates ancestor dependencies [glob](https://github.com/isaacs/node-glob) and [markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli). These dependencies need to be updated together.


Updates `glob` from 10.4.5 to 10.5.0
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v10.4.5...v10.5.0)

Updates `glob` from 11.0.3 to 11.1.0
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v10.4.5...v10.5.0)

Updates `markdownlint-cli` from 0.45.0 to 0.46.0
- [Release notes](https://github.com/igorshubovych/markdownlint-cli/releases)
- [Commits](https://github.com/igorshubovych/markdownlint-cli/compare/v0.45.0...v0.46.0)

---
updated-dependencies:
- dependency-name: glob
  dependency-version: 10.5.0
  dependency-type: indirect
- dependency-name: glob
  dependency-version: 11.1.0
  dependency-type: indirect
- dependency-name: markdownlint-cli
  dependency-version: 0.46.0
  dependency-type: direct:development
...

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

* ran npm run bundle and commit /dist changes

---------

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>
2025-11-20 10:17:59 -08:00
dependabot[bot]
498dbf428a Bump the npm-development group across 1 directory with 6 updates (#302)
* Bump the npm-development group across 1 directory with 6 updates

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

| Package | From | To |
| --- | --- | --- |
| [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) | `9.37.0` | `9.39.1` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `24.7.0` | `24.10.0` |
| [eslint](https://github.com/eslint/eslint) | `9.37.0` | `9.39.1` |
| [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) | `29.0.1` | `29.1.0` |
| [ts-jest](https://github.com/kulshekhar/ts-jest) | `29.4.4` | `29.4.5` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.46.0` | `8.46.4` |



Updates `@eslint/js` from 9.37.0 to 9.39.1
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/commits/v9.39.1/packages/js)

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

Updates `eslint` from 9.37.0 to 9.39.1
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v9.37.0...v9.39.1)

Updates `eslint-plugin-jest` from 29.0.1 to 29.1.0
- [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.0.1...v29.1.0)

Updates `ts-jest` from 29.4.4 to 29.4.5
- [Release notes](https://github.com/kulshekhar/ts-jest/releases)
- [Changelog](https://github.com/kulshekhar/ts-jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/kulshekhar/ts-jest/compare/v29.4.4...v29.4.5)

Updates `typescript-eslint` from 8.46.0 to 8.46.4
- [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.46.4/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-version: 9.39.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: "@types/node"
  dependency-version: 24.10.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: eslint
  dependency-version: 9.39.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: eslint-plugin-jest
  dependency-version: 29.1.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: ts-jest
  dependency-version: 29.4.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-development
- dependency-name: typescript-eslint
  dependency-version: 8.46.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-development
...

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

* ran npm run bundle and commit /dist changes

---------

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>
2025-11-20 10:17:12 -08:00
dependabot[bot]
065aa7392a Bump @actions/attest from 1.6.0 to 2.0.0 (#299)
* Bump @actions/attest from 1.6.0 to 2.0.0

Bumps [@actions/attest](https://github.com/actions/toolkit/tree/HEAD/packages/attest) from 1.6.0 to 2.0.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.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

* ran npm run bundle and commit /dist changes

---------

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>
2025-11-20 10:16:34 -08:00
dependabot[bot]
9ad3ee754c Bump actions/setup-node from 5.0.0 to 6.0.0 (#296)
* Bump actions/setup-node from 5.0.0 to 6.0.0

Bumps [actions/setup-node](https://github.com/actions/setup-node) from 5.0.0 to 6.0.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

* Fix actions/setup-node v6.0.0 version comment and commit hash (#304)

* Initial plan

* Fix actions/setup-node v6.0.0 comment and hash

Co-authored-by: tingx2wang <17136661+tingx2wang@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: tingx2wang <17136661+tingx2wang@users.noreply.github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: tingx2wang <17136661+tingx2wang@users.noreply.github.com>
2025-11-20 10:15:59 -08:00
dependabot[bot]
139b0f683f Bump actions/upload-artifact from 4 to 5 (#298)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  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>
2025-11-18 16:09:35 -08:00
dependabot[bot]
faa0536652 Bump js-yaml from 4.1.0 to 4.1.1 (#303)
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.0 to 4.1.1.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.1.1
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-17 10:13:47 -08:00
dependabot[bot]
d59d2680aa Bump github/codeql-action from 3 to 4 (#293)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: '4'
  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>
2025-10-17 10:54:18 -07:00
dependabot[bot]
935b19fceb Bump the npm-development group with 5 updates (#292)
Bumps the npm-development group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) | `9.36.0` | `9.37.0` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `24.6.0` | `24.7.0` |
| [eslint](https://github.com/eslint/eslint) | `9.36.0` | `9.37.0` |
| [typescript](https://github.com/microsoft/TypeScript) | `5.9.2` | `5.9.3` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.45.0` | `8.46.0` |


Updates `@eslint/js` from 9.36.0 to 9.37.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/commits/v9.37.0/packages/js)

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

Updates `eslint` from 9.36.0 to 9.37.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v9.36.0...v9.37.0)

Updates `typescript` from 5.9.2 to 5.9.3
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release-publish.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.9.2...v5.9.3)

Updates `typescript-eslint` from 8.45.0 to 8.46.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.46.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-version: 9.37.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: "@types/node"
  dependency-version: 24.7.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: eslint
  dependency-version: 9.37.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: typescript
  dependency-version: 5.9.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-development
- dependency-name: typescript-eslint
  dependency-version: 8.46.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>
2025-10-07 06:41:54 -07:00
dependabot[bot]
51eca592cc Bump the npm-development group with 3 updates (#291)
Bumps the npm-development group with 3 updates: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node), [jest](https://github.com/jestjs/jest/tree/HEAD/packages/jest) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


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

Updates `jest` from 30.1.3 to 30.2.0
- [Release notes](https://github.com/jestjs/jest/releases)
- [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jestjs/jest/commits/v30.2.0/packages/jest)

Updates `typescript-eslint` from 8.44.1 to 8.45.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.45.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 24.6.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: jest
  dependency-version: 30.2.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: typescript-eslint
  dependency-version: 8.45.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>
2025-10-06 10:04:14 -07:00
dependabot[bot]
419b2d7b05 Bump the npm-development group across 1 directory with 6 updates (#290)
Bumps the npm-development group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) | `9.35.0` | `9.36.0` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `24.3.1` | `24.5.2` |
| [@vercel/ncc](https://github.com/vercel/ncc) | `0.38.3` | `0.38.4` |
| [eslint](https://github.com/eslint/eslint) | `9.35.0` | `9.36.0` |
| [ts-jest](https://github.com/kulshekhar/ts-jest) | `29.4.1` | `29.4.4` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.43.0` | `8.44.1` |



Updates `@eslint/js` from 9.35.0 to 9.36.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/commits/v9.36.0/packages/js)

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

Updates `@vercel/ncc` from 0.38.3 to 0.38.4
- [Release notes](https://github.com/vercel/ncc/releases)
- [Commits](https://github.com/vercel/ncc/compare/0.38.3...0.38.4)

Updates `eslint` from 9.35.0 to 9.36.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.35.0...v9.36.0)

Updates `ts-jest` from 29.4.1 to 29.4.4
- [Release notes](https://github.com/kulshekhar/ts-jest/releases)
- [Changelog](https://github.com/kulshekhar/ts-jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/kulshekhar/ts-jest/compare/v29.4.1...v29.4.4)

Updates `typescript-eslint` from 8.43.0 to 8.44.1
- [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.44.1/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-version: 9.36.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: "@types/node"
  dependency-version: 24.5.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: "@vercel/ncc"
  dependency-version: 0.38.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-development
- dependency-name: eslint
  dependency-version: 9.36.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: ts-jest
  dependency-version: 29.4.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-development
- dependency-name: typescript-eslint
  dependency-version: 8.44.1
  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>
2025-09-29 14:15:27 -04:00
dependabot[bot]
4ed1c73c0c Bump actions/setup-node from 4 to 5 (#286)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 5.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '5'
  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>
2025-09-12 13:06:37 -07:00
dependabot[bot]
8255f5ff67 Bump the npm-development group across 1 directory with 5 updates (#287)
Bumps the npm-development group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) | `9.34.0` | `9.35.0` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `24.3.0` | `24.3.1` |
| [eslint](https://github.com/eslint/eslint) | `9.34.0` | `9.35.0` |
| [jest](https://github.com/jestjs/jest/tree/HEAD/packages/jest) | `30.0.5` | `30.1.3` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.41.0` | `8.43.0` |



Updates `@eslint/js` from 9.34.0 to 9.35.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/commits/v9.35.0/packages/js)

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

Updates `eslint` from 9.34.0 to 9.35.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.34.0...v9.35.0)

Updates `jest` from 30.0.5 to 30.1.3
- [Release notes](https://github.com/jestjs/jest/releases)
- [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jestjs/jest/commits/v30.1.3/packages/jest)

Updates `typescript-eslint` from 8.41.0 to 8.43.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.43.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-version: 9.35.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: "@types/node"
  dependency-version: 24.3.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-development
- dependency-name: eslint
  dependency-version: 9.35.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: jest
  dependency-version: 30.1.3
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: typescript-eslint
  dependency-version: 8.43.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>
2025-09-12 13:06:00 -07:00
Brian DeHamer
daf44fb950 improved checksum parsing (#280)
Signed-off-by: Brian DeHamer <bdehamer@github.com>
2025-08-26 16:07:15 -07:00
Brian DeHamer
eda10f897a Upgrade to Node 24 (#276)
* upgrade to node24

* update .node-version to 24.x

Signed-off-by: Brian DeHamer <bdehamer@github.com>

* delete publish immutable action

Signed-off-by: Brian DeHamer <bdehamer@github.com>

* bump version from 2.4.0 to 3.0.0

Signed-off-by: Brian DeHamer <bdehamer@github.com>

---------

Signed-off-by: Brian DeHamer <bdehamer@github.com>
Co-authored-by: Salman Chishti <salmanmkc@GitHub.com>
2025-08-26 16:06:47 -07:00
Brian DeHamer
1e2321d281 remove super-linter (#283)
Signed-off-by: Brian DeHamer <bdehamer@github.com>
2025-08-26 16:05:21 -07:00
dependabot[bot]
aecfe99586 Bump the npm-development group across 1 directory with 4 updates (#282)
Bumps the npm-development group with 4 updates in the / directory: [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js), [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node), [eslint](https://github.com/eslint/eslint) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


Updates `@eslint/js` from 9.33.0 to 9.34.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/commits/v9.34.0/packages/js)

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

Updates `eslint` from 9.33.0 to 9.34.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.33.0...v9.34.0)

Updates `typescript-eslint` from 8.39.1 to 8.41.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.41.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-version: 9.34.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: "@types/node"
  dependency-version: 24.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: eslint
  dependency-version: 9.34.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: typescript-eslint
  dependency-version: 8.41.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>
2025-08-26 15:47:27 -07:00
dependabot[bot]
03f25d8602 Bump the npm-development group with 4 updates (#273)
Bumps the npm-development group with 4 updates: [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js), [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node), [eslint](https://github.com/eslint/eslint) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


Updates `@eslint/js` from 9.32.0 to 9.33.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/commits/v9.33.0/packages/js)

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

Updates `eslint` from 9.32.0 to 9.33.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.32.0...v9.33.0)

Updates `typescript-eslint` from 8.39.0 to 8.39.1
- [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.39.1/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-version: 9.33.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: "@types/node"
  dependency-version: 24.2.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-development
- dependency-name: eslint
  dependency-version: 9.33.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: typescript-eslint
  dependency-version: 8.39.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Brian DeHamer <bdehamer@github.com>
2025-08-14 14:29:58 -07:00
Brian DeHamer
0fca5a6fa3 use absolute path in linter config (#275)
Signed-off-by: Brian DeHamer <bdehamer@github.com>
2025-08-14 14:27:34 -07:00
dependabot[bot]
238c03f77f Bump actions/checkout from 4 to 5 (#272)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  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>
2025-08-14 14:02:31 -07:00
dependabot[bot]
9c3e2717a6 Bump @sigstore/oci from 0.5.0 to 0.6.0 (#271)
* Bump @sigstore/oci from 0.5.0 to 0.6.0

Bumps [@sigstore/oci](https://github.com/sigstore/sigstore-js) from 0.5.0 to 0.6.0.
- [Release notes](https://github.com/sigstore/sigstore-js/releases)
- [Commits](https://github.com/sigstore/sigstore-js/compare/@sigstore/oci@0.5.0...@sigstore/oci@0.6.0)

---
updated-dependencies:
- dependency-name: "@sigstore/oci"
  dependency-version: 0.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

* Update dist after build

* 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 <twang1225@gmail.com>
2025-08-05 14:10:37 -04:00
dependabot[bot]
b40d9fa17a Bump the npm-development group with 8 updates (#270)
Bumps the npm-development group with 8 updates:

| Package | From | To |
| --- | --- | --- |
| [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) | `9.30.0` | `9.32.0` |
| [@sigstore/mock](https://github.com/sigstore/sigstore-js) | `0.10.0` | `0.11.0` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `24.0.8` | `24.2.0` |
| [eslint](https://github.com/eslint/eslint) | `9.30.0` | `9.32.0` |
| [jest](https://github.com/jestjs/jest/tree/HEAD/packages/jest) | `30.0.3` | `30.0.5` |
| [ts-jest](https://github.com/kulshekhar/ts-jest) | `29.4.0` | `29.4.1` |
| [typescript](https://github.com/microsoft/TypeScript) | `5.8.3` | `5.9.2` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.35.1` | `8.39.0` |


Updates `@eslint/js` from 9.30.0 to 9.32.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/commits/v9.32.0/packages/js)

Updates `@sigstore/mock` from 0.10.0 to 0.11.0
- [Release notes](https://github.com/sigstore/sigstore-js/releases)
- [Commits](https://github.com/sigstore/sigstore-js/compare/@sigstore/mock@0.10.0...@sigstore/mock@0.11.0)

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

Updates `eslint` from 9.30.0 to 9.32.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.30.0...v9.32.0)

Updates `jest` from 30.0.3 to 30.0.5
- [Release notes](https://github.com/jestjs/jest/releases)
- [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jestjs/jest/commits/v30.0.5/packages/jest)

Updates `ts-jest` from 29.4.0 to 29.4.1
- [Release notes](https://github.com/kulshekhar/ts-jest/releases)
- [Changelog](https://github.com/kulshekhar/ts-jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/kulshekhar/ts-jest/compare/v29.4.0...v29.4.1)

Updates `typescript` from 5.8.3 to 5.9.2
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release-publish.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.8.3...v5.9.2)

Updates `typescript-eslint` from 8.35.1 to 8.39.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.39.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-version: 9.32.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: "@sigstore/mock"
  dependency-version: 0.11.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: "@types/node"
  dependency-version: 24.2.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: eslint
  dependency-version: 9.32.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: jest
  dependency-version: 30.0.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-development
- dependency-name: ts-jest
  dependency-version: 29.4.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-development
- dependency-name: typescript
  dependency-version: 5.9.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: typescript-eslint
  dependency-version: 8.39.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>
2025-08-05 09:09:33 -07:00
dependabot[bot]
e831e0e28d Bump form-data from 4.0.0 to 4.0.4 (#266)
Bumps [form-data](https://github.com/form-data/form-data) from 4.0.0 to 4.0.4.
- [Release notes](https://github.com/form-data/form-data/releases)
- [Changelog](https://github.com/form-data/form-data/blob/master/CHANGELOG.md)
- [Commits](https://github.com/form-data/form-data/compare/v4.0.0...v4.0.4)

---
updated-dependencies:
- dependency-name: form-data
  dependency-version: 4.0.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Eugene <108841108+ejahnGithub@users.noreply.github.com>
2025-07-22 10:47:16 -04:00
dependabot[bot]
4a7671d5ce Bump the npm-development group with 6 updates (#261)
Bumps the npm-development group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) | `9.29.0` | `9.30.0` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `24.0.3` | `24.0.8` |
| [eslint](https://github.com/eslint/eslint) | `9.29.0` | `9.30.0` |
| [jest](https://github.com/jestjs/jest/tree/HEAD/packages/jest) | `30.0.2` | `30.0.3` |
| [prettier](https://github.com/prettier/prettier) | `3.6.0` | `3.6.2` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.35.0` | `8.35.1` |


Updates `@eslint/js` from 9.29.0 to 9.30.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/commits/v9.30.0/packages/js)

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

Updates `eslint` from 9.29.0 to 9.30.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.29.0...v9.30.0)

Updates `jest` from 30.0.2 to 30.0.3
- [Release notes](https://github.com/jestjs/jest/releases)
- [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jestjs/jest/commits/v30.0.3/packages/jest)

Updates `prettier` from 3.6.0 to 3.6.2
- [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.6.0...3.6.2)

Updates `typescript-eslint` from 8.35.0 to 8.35.1
- [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.35.1/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-version: 9.30.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: "@types/node"
  dependency-version: 24.0.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-development
- dependency-name: eslint
  dependency-version: 9.30.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: jest
  dependency-version: 30.0.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-development
- dependency-name: prettier
  dependency-version: 3.6.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-development
- dependency-name: typescript-eslint
  dependency-version: 8.35.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Eugene <108841108+ejahnGithub@users.noreply.github.com>
2025-07-22 10:43:47 -04:00
dependabot[bot]
7a96af5af0 Bump super-linter/super-linter from 7.4.0 to 8.0.0 (#265)
Bumps [super-linter/super-linter](https://github.com/super-linter/super-linter) from 7.4.0 to 8.0.0.
- [Release notes](https://github.com/super-linter/super-linter/releases)
- [Changelog](https://github.com/super-linter/super-linter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/super-linter/super-linter/compare/v7.4.0...v8.0.0)

---
updated-dependencies:
- dependency-name: super-linter/super-linter
  dependency-version: 8.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>
2025-07-22 10:39:13 -04:00
dependabot[bot]
0d204b8297 Bump the npm-development group with 4 updates (#258)
Bumps the npm-development group with 4 updates: [eslint-plugin-import](https://github.com/import-js/eslint-plugin-import), [jest](https://github.com/jestjs/jest/tree/HEAD/packages/jest), [prettier](https://github.com/prettier/prettier) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


Updates `eslint-plugin-import` from 2.31.0 to 2.32.0
- [Release notes](https://github.com/import-js/eslint-plugin-import/releases)
- [Changelog](https://github.com/import-js/eslint-plugin-import/blob/main/CHANGELOG.md)
- [Commits](https://github.com/import-js/eslint-plugin-import/compare/v2.31.0...v2.32.0)

Updates `jest` from 30.0.0 to 30.0.2
- [Release notes](https://github.com/jestjs/jest/releases)
- [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jestjs/jest/commits/v30.0.2/packages/jest)

Updates `prettier` from 3.5.3 to 3.6.0
- [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.5.3...3.6.0)

Updates `typescript-eslint` from 8.34.1 to 8.35.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.35.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: eslint-plugin-import
  dependency-version: 2.32.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: jest
  dependency-version: 30.0.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-development
- dependency-name: prettier
  dependency-version: 3.6.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: typescript-eslint
  dependency-version: 8.35.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>
2025-06-24 09:41:11 -07:00
dependabot[bot]
c974bd63c8 Bump eslint-plugin-jest from 28.14.0 to 29.0.1 (#259)
Bumps [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) from 28.14.0 to 29.0.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/v28.14.0...v29.0.1)

---
updated-dependencies:
- dependency-name: eslint-plugin-jest
  dependency-version: 29.0.1
  dependency-type: direct:development
  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>
2025-06-24 09:28:23 -07:00
Brian DeHamer
3640186643 update vulnerable deps (#257)
Signed-off-by: Brian DeHamer <bdehamer@github.com>
2025-06-17 12:13:48 -07:00
dependabot[bot]
03074e1180 Bump jest and @types/jest (#255)
* Bump jest and @types/jest

Bumps [jest](https://github.com/jestjs/jest/tree/HEAD/packages/jest) and [@types/jest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jest). These dependencies needed to be updated together.

Updates `jest` from 29.7.0 to 30.0.0
- [Release notes](https://github.com/jestjs/jest/releases)
- [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jestjs/jest/commits/v30.0.0/packages/jest)

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

---
updated-dependencies:
- dependency-name: jest
  dependency-version: 30.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
- dependency-name: "@types/jest"
  dependency-version: 30.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

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

* rebuild dist

Signed-off-by: Brian DeHamer <bdehamer@github.com>

* test coverage hints

Signed-off-by: Brian DeHamer <bdehamer@github.com>

* enable ts isolated modules

Signed-off-by: Brian DeHamer <bdehamer@github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Brian DeHamer <bdehamer@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Brian DeHamer <bdehamer@github.com>
2025-06-17 09:51:59 -07:00
dependabot[bot]
5dc9c4c392 Bump @types/node from 22.15.30 to 24.0.3 (#256)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.15.30 to 24.0.3.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 24.0.3
  dependency-type: direct:development
  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>
2025-06-17 08:54:51 -07:00
dependabot[bot]
3d2580fcec Bump the npm-development group with 5 updates (#254)
Bumps the npm-development group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) | `9.28.0` | `9.29.0` |
| [eslint](https://github.com/eslint/eslint) | `9.28.0` | `9.29.0` |
| [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) | `28.13.0` | `28.14.0` |
| [ts-jest](https://github.com/kulshekhar/ts-jest) | `29.3.4` | `29.4.0` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.34.0` | `8.34.1` |


Updates `@eslint/js` from 9.28.0 to 9.29.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/commits/v9.29.0/packages/js)

Updates `eslint` from 9.28.0 to 9.29.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.28.0...v9.29.0)

Updates `eslint-plugin-jest` from 28.13.0 to 28.14.0
- [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/v28.13.0...v28.14.0)

Updates `ts-jest` from 29.3.4 to 29.4.0
- [Release notes](https://github.com/kulshekhar/ts-jest/releases)
- [Changelog](https://github.com/kulshekhar/ts-jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/kulshekhar/ts-jest/compare/v29.3.4...v29.4.0)

Updates `typescript-eslint` from 8.34.0 to 8.34.1
- [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.34.1/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-version: 9.29.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: eslint
  dependency-version: 9.29.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: eslint-plugin-jest
  dependency-version: 28.14.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: ts-jest
  dependency-version: 29.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-development
- dependency-name: typescript-eslint
  dependency-version: 8.34.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-17 08:51:20 -07:00
48 changed files with 86783 additions and 55515 deletions

View File

@@ -1,10 +0,0 @@
rules:
document-end: disable
document-start:
level: warning
present: false
line-length:
level: warning
max: 80
allow-non-breakable-words: true
allow-non-breakable-inline-mappings: true

View File

@@ -1,9 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "../../tsconfig.json",
"compilerOptions": {
"noEmit": true
},
"include": ["../../__tests__/**/*", "../../src/**/*"],
"exclude": ["../../dist", "../../node_modules", "../../coverage", "*.json"]
}

View File

@@ -28,11 +28,11 @@ jobs:
steps:
- name: Checkout
id: checkout
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node.js
id: setup-node
uses: actions/setup-node@v4
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: .node-version
cache: npm
@@ -60,7 +60,7 @@ jobs:
- if: ${{ failure() && steps.diff.outcome == 'failure' }}
name: Upload Artifact
id: upload
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: dist
path: dist/

View File

@@ -21,11 +21,11 @@ jobs:
steps:
- name: Checkout
id: checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v5.0.1
- name: Setup Node.js
id: setup-node
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: .node-version
cache: npm
@@ -58,7 +58,7 @@ jobs:
steps:
- name: Checkout
id: checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v5.0.1
- name: Calculate subject digest
id: subject
env:

View File

@@ -32,19 +32,19 @@ jobs:
steps:
- name: Checkout
id: checkout
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Initialize CodeQL
id: initialize
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
with:
languages: ${{ matrix.language }}
source-root: src
- name: Autobuild
id: autobuild
uses: github/codeql-action/autobuild@v3
uses: github/codeql-action/autobuild@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
- name: Perform CodeQL Analysis
id: analyze
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2

View File

@@ -1,54 +0,0 @@
name: Lint Codebase
on:
pull_request:
branches:
- main
push:
branches:
- main
permissions:
contents: read
packages: read
statuses: write
jobs:
lint:
name: Lint Codebase
runs-on: ubuntu-latest
steps:
- name: Checkout
id: checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
id: setup-node
uses: actions/setup-node@v4
with:
node-version-file: .node-version
cache: npm
- name: Install Dependencies
id: install
run: npm ci
- name: Lint Codebase
id: super-linter
uses: super-linter/super-linter/slim@v7.4.0
env:
DEFAULT_BRANCH: main
FILTER_REGEX_EXCLUDE: dist/**/*
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TYPESCRIPT_DEFAULT_STYLE: prettier
VALIDATE_ALL_CODEBASE: true
VALIDATE_JAVASCRIPT_STANDARD: false
VALIDATE_TYPESCRIPT_STANDARD: false
VALIDATE_TYPESCRIPT_ES: false
VALIDATE_JSCPD: false
- name: Run eslint
run: npm run lint:eslint

18
.github/workflows/prober-github.yml vendored Normal file
View File

@@ -0,0 +1,18 @@
name: GitHub Sigstore Prober
on:
workflow_dispatch:
schedule:
# run every 5 minutes, as often as Github Actions allows
- cron: '*/5 * * * *'
jobs:
prober:
if: github.repository_owner == 'actions'
permissions:
attestations: write
id-token: write
secrets: inherit
uses: ./.github/workflows/prober.yml
with:
sigstore: github

View File

@@ -0,0 +1,18 @@
name: Public-Good Sigstore Prober
on:
workflow_dispatch:
schedule:
# run every 5 minutes, as often as Github Actions allows
- cron: '*/5 * * * *'
jobs:
prober:
if: github.repository_owner == 'actions'
permissions:
attestations: write
id-token: write
secrets: inherit
uses: ./.github/workflows/prober.yml
with:
sigstore: public-good

84
.github/workflows/prober.yml vendored Normal file
View File

@@ -0,0 +1,84 @@
name: Prober Workflow
on:
workflow_call:
inputs:
sigstore:
description: 'Which Sigstore instance to use for signing'
required: true
type: string
jobs:
probe:
runs-on: ubuntu-latest
permissions:
attestations: write
id-token: write
steps:
- name: Request OIDC Token
run: |
curl "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=nobody" \
-H "Authorization: bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \
-H "Accept: application/json; api-version=2.0" \
-H "Content-Type: application/json" \
--silent | jq -r '.value' | jq -R 'split(".") | .[0],.[1] | @base64d | fromjson'
- name: Create artifact
run: |
date > artifact
- name: Attest build provenance
uses: actions/attest@main
env:
INPUT_PRIVATE-SIGNING: ${{ inputs.sigstore == 'github' && 'true' || 'false' }}
with:
subject-path: artifact
- name: Verify build artifact
env:
GH_TOKEN: ${{ github.token }}
run: |
gh attestation verify ./artifact --owner "$GITHUB_REPOSITORY_OWNER"
- name: Upload build artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
path: "artifact"
- name: Report attestation prober success
if: ${{ success() }}
uses: masci/datadog@d7cb6cfc2ba13f7c2ae5227a26f2e2f42a7d179e # v2.0.2
with:
api-key: "${{ secrets.DATADOG_API_KEY }}"
service-checks: |
- check: "attestation-integration.actions.prober"
status: 0
host_name: github.com
tags:
- "catalog_service:${{ secrets.CATALOG_SERVICE }}"
- "service:${{ secrets.CATALOG_SERVICE }}"
- "stamp:${{ secrets.STAMP }}"
- "env:production"
- "repo:${{ github.repository }}"
- "team:${{ secrets.TEAM }}"
- "sigstore:${{ inputs.sigstore }}"
- name: Report attestation prober failure
if: ${{ failure() }}
uses: masci/datadog@d7cb6cfc2ba13f7c2ae5227a26f2e2f42a7d179e # v2.0.2
with:
api-key: "${{ secrets.DATADOG_API_KEY }}"
service-checks: |
- check: "attestation-integration.actions.prober"
message: "${{ github.repository_owner }} failed prober check"
status: 2
host_name: github.com
tags:
- "catalog_service:${{ secrets.CATALOG_SERVICE }}"
- "service:${{ secrets.CATALOG_SERVICE }}"
- "stamp:${{ secrets.STAMP }}"
- "env:production"
- "repo:${{ github.repository }}"
- "team:${{ secrets.TEAM }}"
- "sigstore:${{ inputs.sigstore }}"

View File

@@ -1,22 +0,0 @@
name: 'Publish Immutable Action Version'
on:
release:
types: [published]
permissions: {}
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
packages: write
steps:
- name: Checking out
uses: actions/checkout@v4
- name: Publish
id: publish
uses: actions/publish-immutable-action@v0.0.4

View File

@@ -1 +1 @@
20.6.0
24.5.0

137
README.md
View File

@@ -41,6 +41,21 @@ information on artifact attestations.
> Artifact attestations are NOT supported on GitHub Enterprise Server.
<!-- prettier-ignore-end -->
## Attestation Modes
This action supports three attestation modes, automatically detected based on
the inputs you provide:
<!-- markdownlint-disable MD013 -->
| Mode | When Used | Description |
| -------------- | ------------------------------------------------------ | ----------------------------------------------- |
| **Provenance** | No `sbom-path` or predicate inputs | Auto-generates [SLSA build provenance][10] |
| **SBOM** | `sbom-path` is provided | Creates attestation from SPDX or CycloneDX SBOM |
| **Custom** | `predicate-type`/`predicate`/`predicate-path` provided | User-supplied predicate |
<!-- markdownlint-enable MD013 -->
## Usage
Within the GitHub Actions workflow which builds some artifact you would like to
@@ -52,33 +67,32 @@ attest:
permissions:
id-token: write
attestations: write
artifact-metadata: write
```
The `id-token` permission gives the action the ability to mint the OIDC token
necessary to request a Sigstore signing certificate. The `attestations`
permission is necessary to persist the attestation.
permission is necessary to persist the attestation. The `artifact-metadata`
permission is necessary to create the artifact storage record.
1. Add the following to your workflow after your artifact has been built:
```yaml
- uses: actions/attest@v2
- uses: actions/attest@v4
with:
subject-path: '<PATH TO ARTIFACT>'
predicate-type: '<PREDICATE URI>'
predicate-path: '<PATH TO PREDICATE>'
```
The `subject-path` parameter should identify the artifact for which you want
to generate an attestation. The `predicate-type` can be any of the the
[vetted predicate types][3] or a custom value. The `predicate-path`
identifies a file containing the JSON-encoded predicate parameters.
By default, this generates a [SLSA build provenance][10] attestation. For
SBOM or custom attestations, see the [Attestation Modes](#attestation-modes)
section.
### Inputs
See [action.yml](action.yml)
```yaml
- uses: actions/attest@v2
- uses: actions/attest@v4
with:
# Path to the artifact serving as the subject of the attestation. Must
# specify exactly one of "subject-path", "subject-digest", or
@@ -100,17 +114,24 @@ See [action.yml](action.yml)
# or "subject-checksums".
subject-checksums:
# URI identifying the type of the predicate.
# Path to the JSON-formatted SBOM file (SPDX or CycloneDX) to attest.
# File size cannot exceed 16MB. When provided, creates an SBOM attestation.
# Cannot be used together with "predicate-type", "predicate", or
# "predicate-path".
sbom-path:
# URI identifying the type of the predicate. Required when using "predicate"
# or "predicate-path" for custom attestations.
predicate-type:
# String containing the value for the attestation predicate. String length
# cannot exceed 16MB. Must supply exactly one of "predicate-path" or
# "predicate".
# "predicate" when creating custom attestations.
predicate:
# Path to the file which contains the content for the attestation predicate.
# File size cannot exceed 16MB. Must supply exactly one of "predicate-path"
# or "predicate".
# or "predicate" when creating custom attestations.
predicate-path:
# Whether to push the attestation to the image registry. Requires that the
@@ -118,6 +139,12 @@ See [action.yml](action.yml)
# the "subject-digest" parameter be specified. Defaults to false.
push-to-registry:
# Whether to create a storage record for the artifact.
# Requires that push-to-registry is set to true.
# Requires that the "subject-name" parameter specify the fully-qualified
# image name. Defaults to true.
create-storage-record:
# Whether to attach a list of generated attestations to the workflow run
# summary page. Defaults to true.
show-summary:
@@ -131,11 +158,12 @@ See [action.yml](action.yml)
<!-- markdownlint-disable MD013 -->
| Name | Description | Example |
| ----------------- | -------------------------------------------------------------- | ------------------------------------------------ |
| `attestation-id` | GitHub ID for the attestation | `123456` |
| `attestation-url` | URL for the attestation summary | `https://github.com/foo/bar/attestations/123456` |
| `bundle-path` | Absolute path to the file containing the generated attestation | `/tmp/attestation.json` |
| Name | Description | Example |
| -------------------- | -------------------------------------------------------------- | ------------------------------------------------ |
| `attestation-id` | GitHub ID for the attestation | `123456` |
| `attestation-url` | URL for the attestation summary | `https://github.com/foo/bar/attestations/123456` |
| `bundle-path` | Absolute path to the file containing the generated attestation | `/tmp/attestation.json` |
| `storage-record-ids` | GitHub IDs for the storage records | `987654` |
<!-- markdownlint-enable MD013 -->
@@ -157,13 +185,13 @@ string cannot exceed 16MB.
## Examples
### Identify Subject by Path
### Provenance Attestation (Default)
For the basic use case, simply add the `attest` action to your workflow and
supply the path to the artifact for which you want to generate attestation.
The simplest use case - just specify the artifact path and a SLSA build
provenance attestation is automatically generated:
```yaml
name: build-attest
name: build-attest-provenance
on:
workflow_dispatch:
@@ -181,11 +209,36 @@ jobs:
- name: Build artifact
run: make my-app
- name: Attest
uses: actions/attest@v2
uses: actions/attest@v4
with:
subject-path: '${{ github.workspace }}/my-app'
predicate-type: 'https://example.com/predicate/v1'
predicate: '{}'
```
### SBOM Attestation
To create an SBOM attestation, provide the path to an SPDX or CycloneDX JSON
file:
```yaml
- name: Generate SBOM
run: syft . -o spdx-json > sbom.spdx.json
- uses: actions/attest@v4
with:
subject-path: '${{ github.workspace }}/my-app'
sbom-path: '${{ github.workspace }}/sbom.spdx.json'
```
### Custom Attestation
For custom attestations, provide your own predicate type and content:
```yaml
- uses: actions/attest@v4
with:
subject-path: '${{ github.workspace }}/my-app'
predicate-type: 'https://example.com/predicate/v1'
predicate: '{}'
```
### Identify Multiple Subjects
@@ -194,7 +247,7 @@ If you are generating multiple artifacts, you can attest all of them at the same
time by using a wildcard in the `subject-path` input.
```yaml
- uses: actions/attest@v2
- uses: actions/attest@v4
with:
subject-path: 'dist/**/my-bin-*'
predicate-type: 'https://example.com/predicate/v1'
@@ -208,13 +261,13 @@ Alternatively, you can explicitly list multiple subjects with either a comma or
newline delimited list:
```yaml
- uses: actions/attest@v2
- uses: actions/attest@v4
with:
subject-path: 'dist/foo, dist/bar'
```
```yaml
- uses: actions/attest@v2
- uses: actions/attest@v4
with:
subject-path: |
dist/foo
@@ -236,11 +289,9 @@ attestation.
run: |
shasum -a 256 foo_0.0.1_* > subject.checksums.txt
- uses: actions/attest@v2
- uses: actions/attest@v4
with:
subject-checksums: subject.checksums.txt
predicate-type: 'https://example.com/predicate/v1'
predicate: '{}'
```
<!-- markdownlint-disable MD038 -->
@@ -269,6 +320,26 @@ fully-qualified image name (e.g. "ghcr.io/user/app" or
"acme.azurecr.io/user/app"). Do NOT include a tag as part of the image name --
the specific image being attested is identified by the supplied digest.
#### Artifact Metadata Storage Records
When generating a build provenance attestation, if the `push-to-registry` option
is set to true, the Action will also emit an
[Artifact Metadata Storage Record](https://docs.github.com/en/rest/orgs/artifact-metadata?apiVersion=2022-11-28#create-artifact-metadata-storage-record).
Storage records enrich artifact metadata by capturing storage related details,
such as which registry an image is hosted on and whether it's marked as active.
If you do not want to emit a storage record, set `create-storage-record` to
`false`.
> **NOTE**: Storage records can only be created for artifacts built from
> [organization-owned](https://docs.github.com/en/organizations/collaborating-with-groups-in-organizations/about-organizations)
> repositories.
Artifacts associated with a storage record can be viewed by navigating to the
`Linked Artifacts` page in your organization:
`https://github.com/orgs/YOUR_ORG/artifacts` (replace `YOUR_ORG` with your
organization name).
> **NOTE**: When pushing to Docker Hub, please use "docker.io" as the registry
> portion of the image name.
@@ -287,6 +358,7 @@ jobs:
packages: write
contents: read
attestations: write
artifact-metadata: write
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
@@ -308,13 +380,11 @@ jobs:
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- name: Attest
uses: actions/attest@v2
uses: actions/attest@v4
id: attest
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
subject-digest: ${{ steps.push.outputs.digest }}
predicate-type: 'https://in-toto.io/attestation/release/v0.1'
predicate: '{"purl":"pkg:oci/..."}'
push-to-registry: true
```
@@ -329,3 +399,4 @@ jobs:
[8]: https://github.com/actions/toolkit/tree/main/packages/glob#patterns
[9]:
https://docs.github.com/en/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds
[10]: https://slsa.dev/spec/v1.0/provenance

253
__tests__/fixtures/mocks.ts Normal file
View File

@@ -0,0 +1,253 @@
import type { Attestation, Predicate, Subject } from '@actions/attest'
import { jest } from '@jest/globals'
import type { RestEndpointMethodTypes } from '@octokit/plugin-rest-endpoint-methods'
import type { Descriptor } from '@sigstore/oci'
// =============================================================================
// @actions/core mock factory
// =============================================================================
export type CoreMock = {
info: jest.Mock
warning: jest.Mock
debug: jest.Mock
startGroup: jest.Mock
endGroup: jest.Mock
setOutput: jest.Mock
setFailed: jest.Mock
summary: SummaryMock
}
export type SummaryMock = {
write: jest.Mock
addRaw: jest.Mock
addHeading: jest.Mock
addLink: jest.Mock
addTable: jest.Mock
addBreak: jest.Mock
addSeparator: jest.Mock
addQuote: jest.Mock
addCodeBlock: jest.Mock
addList: jest.Mock
addImage: jest.Mock
addDetails: jest.Mock
addEOL: jest.Mock
emptyBuffer: jest.Mock
stringify: jest.Mock
isEmptyBuffer: jest.Mock
clear: jest.Mock
}
export const createSummaryMock = (): SummaryMock => {
const mock: SummaryMock = {
write: jest.fn(),
addRaw: jest.fn(),
addHeading: jest.fn(),
addLink: jest.fn(),
addTable: jest.fn(),
addBreak: jest.fn(),
addSeparator: jest.fn(),
addQuote: jest.fn(),
addCodeBlock: jest.fn(),
addList: jest.fn(),
addImage: jest.fn(),
addDetails: jest.fn(),
addEOL: jest.fn(),
emptyBuffer: jest.fn(),
stringify: jest.fn().mockReturnValue(''),
isEmptyBuffer: jest.fn().mockReturnValue(true),
clear: jest.fn()
}
// Make chainable
for (const key of Object.keys(mock) as (keyof SummaryMock)[]) {
if (key !== 'stringify' && key !== 'isEmptyBuffer') {
mock[key].mockReturnThis()
}
}
return mock
}
export const createCoreMock = (): CoreMock => ({
info: jest.fn(),
warning: jest.fn(),
debug: jest.fn(),
startGroup: jest.fn(),
endGroup: jest.fn(),
setOutput: jest.fn(),
setFailed: jest.fn(),
summary: createSummaryMock()
})
// =============================================================================
// @actions/github mock factory
// =============================================================================
export type GitHubContextMock = {
repo: { owner: string; repo: string }
payload: { repository?: { visibility: string } }
serverUrl: string
}
export const createGitHubContextMock = (
overrides: Partial<GitHubContextMock> = {}
): GitHubContextMock => ({
repo: { owner: 'test-owner', repo: 'test-repo' },
payload: { repository: { visibility: 'public' } },
serverUrl: 'https://github.com',
...overrides
})
export type OctokitMock = {
rest: {
repos: {
get: jest.Mock
}
}
}
export const createOctokitMock = (
ownerType: 'Organization' | 'User' = 'Organization'
): OctokitMock => ({
rest: {
repos: {
get: jest
.fn<RestEndpointMethodTypes['repos']['get']['response']>()
.mockResolvedValue({
data: { owner: { type: ownerType } }
})
}
}
})
// =============================================================================
// @actions/attest mock factory
// =============================================================================
export type AttestMock = {
attest: jest.Mock
buildSLSAProvenancePredicate: jest.Mock
createStorageRecord: jest.Mock
}
export const createAttestMock = (): AttestMock => ({
attest: jest.fn(),
buildSLSAProvenancePredicate: jest.fn(),
createStorageRecord: jest.fn()
})
export const createAttestationResult = (
overrides: Partial<Attestation> = {}
): Attestation => ({
bundle: {
mediaType: 'application/vnd.dev.sigstore.bundle.v0.3+json' as const,
verificationMaterial: {
certificate: { rawBytes: '' },
publicKey: undefined,
x509CertificateChain: undefined,
tlogEntries: [],
timestampVerificationData: undefined
},
dsseEnvelope: {
payload: '',
payloadType: '',
signatures: []
},
messageSignature: undefined
},
certificate: '-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----',
tlogID: 'tlog-123',
attestationID: 'att-123',
...overrides
})
// =============================================================================
// @sigstore/oci mock factory
// =============================================================================
export type OciMock = {
getRegistryCredentials: jest.Mock
attachArtifactToImage: jest.Mock
}
export const createOciMock = (): OciMock => ({
getRegistryCredentials: jest.fn().mockReturnValue({
username: 'test-user',
password: 'test-pass'
}),
attachArtifactToImage: jest
.fn<() => Promise<Descriptor>>()
.mockResolvedValue({
digest: 'sha256:abc123def456',
mediaType: 'application/vnd.dev.sigstore.bundle.v0.3+json',
size: 1234
})
})
// =============================================================================
// Common test data
// =============================================================================
export const TEST_SUBJECT: Subject = {
name: 'test-artifact',
digest: {
sha256: '7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32'
}
}
export const TEST_SUBJECT_WITH_REGISTRY: Subject = {
name: 'ghcr.io/test-owner/test-repo',
digest: {
sha256: '7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32'
}
}
export const TEST_PREDICATE: Predicate = {
type: 'https://example.com/predicate/v1',
params: { foo: 'bar' }
}
export const TEST_PROVENANCE_PREDICATE: Predicate = {
type: 'https://slsa.dev/provenance/v1',
params: {
buildDefinition: {
buildType: 'https://actions.github.io/buildtypes/workflow/v1'
},
runDetails: {
builder: { id: 'https://github.com/actions/runner' }
}
}
}
// =============================================================================
// Environment helpers
// =============================================================================
export const setupTestEnvironment = (
env: Record<string, string> = {}
): (() => void) => {
const originalEnv = { ...process.env }
process.env = {
...process.env,
ACTIONS_ID_TOKEN_REQUEST_URL: 'https://token.url',
ACTIONS_ID_TOKEN_REQUEST_TOKEN: 'test-token',
RUNNER_TEMP: '/tmp',
...env
}
return () => {
process.env = originalEnv
}
}
// =============================================================================
// OIDC token helpers
// =============================================================================
export const createOidcToken = (subject = 'test@example.com'): string => {
const payload = {
sub: subject,
iss: 'https://token.actions.githubusercontent.com'
}
return `.${Buffer.from(JSON.stringify(payload)).toString('base64')}.`
}

View File

@@ -0,0 +1,13 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.4",
"serialNumber": "urn:uuid:12345678-1234-1234-1234-123456789012",
"version": 1,
"components": [
{
"type": "library",
"name": "test-component",
"version": "1.0.0"
}
]
}

View File

@@ -0,0 +1,9 @@
{
"buildType": "https://example.com/build/v1",
"builder": {
"id": "https://github.com/actions/runner"
},
"metadata": {
"buildStartedOn": "2024-01-01T00:00:00Z"
}
}

View File

@@ -0,0 +1,15 @@
{
"spdxVersion": "SPDX-2.3",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "test-package",
"dataLicense": "CC0-1.0",
"documentNamespace": "https://example.com/test-package",
"packages": [
{
"SPDXID": "SPDXRef-Package",
"name": "test-package",
"versionInfo": "1.0.0",
"downloadLocation": "https://example.com/test-package-1.0.0.tar.gz"
}
]
}

View File

@@ -1,22 +1,73 @@
/**
* Unit tests for the action's entrypoint, src/index.ts
*/
import { jest } from '@jest/globals'
import * as core from '@actions/core'
import * as main from '../src/main'
// Mock functions
const mockRun = jest.fn()
const mockGetInput = jest.fn()
const mockGetBooleanInput = jest.fn()
// Mock the action's entrypoint
const runMock = jest.spyOn(main, 'run').mockImplementation()
const getBooleanInputMock = jest.spyOn(core, 'getBooleanInput')
// Mock @actions/core
jest.unstable_mockModule('@actions/core', () => ({
getInput: mockGetInput,
getBooleanInput: mockGetBooleanInput
}))
// Mock ../src/main
jest.unstable_mockModule('../src/main', () => ({
run: mockRun
}))
describe('index', () => {
beforeEach(() => {
getBooleanInputMock.mockImplementation(() => false)
jest.clearAllMocks()
mockGetInput.mockReturnValue('')
mockGetBooleanInput.mockReturnValue(false)
})
it('calls run when imported', () => {
// eslint-disable-next-line @typescript-eslint/no-require-imports
require('../src/index')
expect(runMock).toHaveBeenCalled()
it('should call run with inputs from core.getInput', async () => {
mockGetInput.mockImplementation((name: string) => {
const inputs: Record<string, string> = {
'subject-path': '/path/to/subject',
'subject-name': 'my-artifact',
'subject-digest': '',
'subject-checksums': '',
'subject-version': '',
'predicate-type': 'https://example.com/predicate',
predicate: '{}',
'predicate-path': '',
'sbom-path': '',
'github-token': 'test-token'
}
return inputs[name] || ''
})
mockGetBooleanInput.mockImplementation((name: string) => {
const inputs: Record<string, boolean> = {
'push-to-registry': false,
'create-storage-record': true,
'show-summary': true,
'private-signing': false
}
return inputs[name] || false
})
// Dynamic import triggers the module
await import('../src/index')
expect(mockRun).toHaveBeenCalledWith({
subjectPath: '/path/to/subject',
subjectName: 'my-artifact',
subjectDigest: '',
subjectChecksums: '',
subjectVersion: '',
predicateType: 'https://example.com/predicate',
predicate: '{}',
predicatePath: '',
sbomPath: '',
githubToken: 'test-token',
pushToRegistry: false,
createStorageRecord: true,
showSummary: true,
privateSigning: false
})
})
})

View File

@@ -0,0 +1,246 @@
import { jest } from '@jest/globals'
import {
createAttestationResult,
createGitHubContextMock,
createOctokitMock,
TEST_PREDICATE,
TEST_SUBJECT_WITH_REGISTRY
} from '../fixtures/mocks'
import type { Attestation } from '@actions/attest'
import type { Descriptor } from '@sigstore/oci'
// Mock functions
const mockGetOctokit = jest.fn()
const mockAttest = jest.fn<() => Promise<Attestation>>()
const mockCreateStorageRecord = jest.fn<() => Promise<number[]>>()
const mockGetRegistryCredentials = jest.fn()
const mockAttachArtifactToImage = jest.fn<() => Promise<Descriptor>>()
// Mutable context for tests
const mockContext = createGitHubContextMock()
// Mock @actions/github
jest.unstable_mockModule('@actions/github', () => ({
getOctokit: mockGetOctokit,
context: mockContext
}))
// Mock @actions/attest
jest.unstable_mockModule('@actions/attest', () => ({
attest: mockAttest,
createStorageRecord: mockCreateStorageRecord
}))
// Mock @sigstore/oci
jest.unstable_mockModule('@sigstore/oci', () => ({
getRegistryCredentials: mockGetRegistryCredentials,
attachArtifactToImage: mockAttachArtifactToImage
}))
// Dynamic imports after mocking
const { createAttestation, repoOwnerIsOrg } = await import('../../src/attest')
describe('repoOwnerIsOrg', () => {
beforeEach(() => {
jest.clearAllMocks()
})
it('should return true when repo owner is an Organization', async () => {
mockGetOctokit.mockReturnValue(createOctokitMock('Organization'))
const result = await repoOwnerIsOrg('test-token')
expect(result).toBe(true)
expect(mockGetOctokit).toHaveBeenCalledWith('test-token')
})
it('should return false when repo owner is a User', async () => {
mockGetOctokit.mockReturnValue(createOctokitMock('User'))
const result = await repoOwnerIsOrg('test-token')
expect(result).toBe(false)
})
})
describe('createAttestation', () => {
const defaultOpts = {
sigstoreInstance: 'github' as const,
pushToRegistry: false,
createStorageRecord: false,
githubToken: 'test-token'
}
beforeEach(() => {
jest.clearAllMocks()
mockAttest.mockResolvedValue(createAttestationResult())
mockGetRegistryCredentials.mockReturnValue({
username: 'test-user',
password: 'test-pass'
})
mockAttachArtifactToImage.mockResolvedValue({
digest: 'sha256:attestation-digest',
mediaType: 'application/vnd.dev.sigstore.bundle.v0.3+json',
size: 1234
})
mockCreateStorageRecord.mockResolvedValue([12345])
mockGetOctokit.mockReturnValue(createOctokitMock('Organization'))
})
describe('basic attestation', () => {
it('should call attest with correct parameters', async () => {
const subjects = [TEST_SUBJECT_WITH_REGISTRY]
await createAttestation(subjects, TEST_PREDICATE, defaultOpts)
expect(mockAttest).toHaveBeenCalledWith({
subjects,
predicateType: TEST_PREDICATE.type,
predicate: TEST_PREDICATE.params,
sigstore: 'github',
token: 'test-token'
})
})
it('should return attestation result', async () => {
const subjects = [TEST_SUBJECT_WITH_REGISTRY]
const result = await createAttestation(
subjects,
TEST_PREDICATE,
defaultOpts
)
expect(result.attestationID).toBe('att-123')
expect(result.certificate).toContain('BEGIN CERTIFICATE')
expect(result.tlogID).toBe('tlog-123')
})
})
describe('registry push', () => {
const pushOpts = { ...defaultOpts, pushToRegistry: true }
it('should push attestation to registry when enabled', async () => {
const subjects = [TEST_SUBJECT_WITH_REGISTRY]
const result = await createAttestation(subjects, TEST_PREDICATE, pushOpts)
expect(mockGetRegistryCredentials).toHaveBeenCalledWith(subjects[0].name)
expect(mockAttachArtifactToImage).toHaveBeenCalled()
expect(result.attestationDigest).toBe('sha256:attestation-digest')
})
it('should skip registry push for multiple subjects', async () => {
const subjects = [TEST_SUBJECT_WITH_REGISTRY, TEST_SUBJECT_WITH_REGISTRY]
await createAttestation(subjects, TEST_PREDICATE, pushOpts)
expect(mockAttachArtifactToImage).not.toHaveBeenCalled()
})
})
describe('storage record creation', () => {
const storageOpts = {
...defaultOpts,
pushToRegistry: true,
createStorageRecord: true,
subjectVersion: '1.2.3'
}
it('should create storage record when enabled and owner is org', async () => {
const subjects = [TEST_SUBJECT_WITH_REGISTRY]
const result = await createAttestation(
subjects,
TEST_PREDICATE,
storageOpts
)
expect(mockCreateStorageRecord).toHaveBeenCalledWith(
expect.objectContaining({ version: '1.2.3' }),
expect.anything(),
expect.anything()
)
expect(result.storageRecordIds).toEqual([12345])
})
it('should omit version from storage record when subjectVersion is empty', async () => {
const subjects = [TEST_SUBJECT_WITH_REGISTRY]
const opts = { ...storageOpts, subjectVersion: '' }
await createAttestation(subjects, TEST_PREDICATE, opts)
expect(mockCreateStorageRecord).toHaveBeenCalledWith(
expect.objectContaining({ version: undefined }),
expect.anything(),
expect.anything()
)
})
it('should skip storage record when owner is User', async () => {
mockGetOctokit.mockReturnValue(createOctokitMock('User'))
const subjects = [TEST_SUBJECT_WITH_REGISTRY]
const result = await createAttestation(
subjects,
TEST_PREDICATE,
storageOpts
)
expect(mockCreateStorageRecord).not.toHaveBeenCalled()
expect(result.storageRecordIds).toBeUndefined()
})
it('should skip storage record when createStorageRecord is false', async () => {
const subjects = [TEST_SUBJECT_WITH_REGISTRY]
const opts = { ...storageOpts, createStorageRecord: false }
await createAttestation(subjects, TEST_PREDICATE, opts)
expect(mockCreateStorageRecord).not.toHaveBeenCalled()
})
it('should handle empty storage records gracefully', async () => {
mockCreateStorageRecord.mockResolvedValue([])
const subjects = [TEST_SUBJECT_WITH_REGISTRY]
const result = await createAttestation(
subjects,
TEST_PREDICATE,
storageOpts
)
expect(result.storageRecordIds).toEqual([])
})
it('should continue when storage record creation fails', async () => {
mockCreateStorageRecord.mockRejectedValue(new Error('Permission denied'))
const subjects = [TEST_SUBJECT_WITH_REGISTRY]
// Should not throw
const result = await createAttestation(
subjects,
TEST_PREDICATE,
storageOpts
)
expect(result.attestationID).toBe('att-123')
expect(result.storageRecordIds).toBeUndefined()
})
})
describe('sigstore instance selection', () => {
it('should use public-good sigstore instance when specified', async () => {
const subjects = [TEST_SUBJECT_WITH_REGISTRY]
const opts = { ...defaultOpts, sigstoreInstance: 'public-good' as const }
await createAttestation(subjects, TEST_PREDICATE, opts)
expect(mockAttest).toHaveBeenCalledWith(
expect.objectContaining({ sigstore: 'public-good' })
)
})
})
})

View File

@@ -0,0 +1,455 @@
import { jest } from '@jest/globals'
import fs from 'fs/promises'
import os from 'os'
import path from 'path'
import {
createAttestationResult,
createOctokitMock,
TEST_PROVENANCE_PREDICATE
} from '../fixtures/mocks'
import type { Attestation, Predicate } from '@actions/attest'
import type { Descriptor } from '@sigstore/oci'
import type { RunInputs } from '../../src/main'
// Create persistent mock functions
const infoMock = jest.fn()
const warningMock = jest.fn()
const debugMock = jest.fn()
const startGroupMock = jest.fn()
const endGroupMock = jest.fn()
const setOutputMock = jest.fn()
const setFailedMock = jest.fn()
// Create chainable summary mock
const summaryMock = {
write: jest.fn().mockReturnThis(),
addRaw: jest.fn().mockReturnThis(),
addHeading: jest.fn().mockReturnThis(),
addLink: jest.fn().mockReturnThis(),
addTable: jest.fn().mockReturnThis(),
addBreak: jest.fn().mockReturnThis(),
addSeparator: jest.fn().mockReturnThis(),
addQuote: jest.fn().mockReturnThis(),
addCodeBlock: jest.fn().mockReturnThis(),
addList: jest.fn().mockReturnThis(),
addImage: jest.fn().mockReturnThis(),
addDetails: jest.fn().mockReturnThis(),
addEOL: jest.fn().mockReturnThis(),
emptyBuffer: jest.fn().mockReturnThis(),
stringify: jest.fn().mockReturnValue(''),
isEmptyBuffer: jest.fn().mockReturnValue(true),
clear: jest.fn().mockReturnThis()
}
const mockGetOctokit = jest.fn()
const mockAttest = jest.fn<() => Promise<Attestation>>()
const mockBuildSLSAProvenancePredicate = jest.fn<() => Promise<Predicate>>()
const mockCreateStorageRecord = jest.fn<() => Promise<number[]>>()
const mockGetRegistryCredentials = jest.fn()
const mockAttachArtifactToImage = jest.fn<() => Promise<Descriptor>>()
// Mutable context for tests
const mockContext = {
repo: { owner: 'test-owner', repo: 'test-repo' },
payload: { repository: { visibility: 'private' } },
serverUrl: 'https://github.com'
}
// Mock @actions/core
jest.unstable_mockModule('@actions/core', () => ({
info: infoMock,
warning: warningMock,
debug: debugMock,
startGroup: startGroupMock,
endGroup: endGroupMock,
setOutput: setOutputMock,
setFailed: setFailedMock,
summary: summaryMock
}))
// Mock @actions/github
jest.unstable_mockModule('@actions/github', () => ({
getOctokit: mockGetOctokit,
context: mockContext
}))
// Mock @actions/attest
jest.unstable_mockModule('@actions/attest', () => ({
attest: mockAttest,
buildSLSAProvenancePredicate: mockBuildSLSAProvenancePredicate,
createStorageRecord: mockCreateStorageRecord
}))
// Mock @sigstore/oci
jest.unstable_mockModule('@sigstore/oci', () => ({
getRegistryCredentials: mockGetRegistryCredentials,
attachArtifactToImage: mockAttachArtifactToImage
}))
// Dynamic import after mocking
const { run } = await import('../../src/main')
const defaultInputs: RunInputs = {
predicate: '',
predicateType: '',
predicatePath: '',
sbomPath: '',
subjectName: '',
subjectDigest: '',
subjectPath: '',
subjectChecksums: '',
pushToRegistry: false,
createStorageRecord: false,
subjectVersion: '',
showSummary: false,
githubToken: 'test-token',
privateSigning: false
}
describe('run', () => {
let tempDir: string
const originalEnv = { ...process.env }
beforeEach(async () => {
jest.clearAllMocks()
// Reset chainable summary mocks
for (const key of Object.keys(summaryMock)) {
if (key !== 'stringify' && key !== 'isEmptyBuffer') {
;(
summaryMock[key as keyof typeof summaryMock] as jest.Mock
).mockReturnThis()
}
}
mockAttest.mockResolvedValue(createAttestationResult())
mockBuildSLSAProvenancePredicate.mockResolvedValue(
TEST_PROVENANCE_PREDICATE
)
mockCreateStorageRecord.mockResolvedValue([12345])
mockGetOctokit.mockReturnValue(createOctokitMock('Organization'))
mockGetRegistryCredentials.mockReturnValue({ username: 'u', password: 'p' })
mockAttachArtifactToImage.mockResolvedValue({
digest: 'sha256:abc',
mediaType: 'application/vnd.dev.sigstore.bundle.v0.3+json',
size: 100
})
// Reset context
mockContext.repo = { owner: 'test-owner', repo: 'test-repo' }
mockContext.payload = { repository: { visibility: 'private' } }
// Create temp directory
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'main-test-'))
// Set required environment
process.env.ACTIONS_ID_TOKEN_REQUEST_URL = 'https://token.url'
process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'test-token'
process.env.RUNNER_TEMP = tempDir
})
afterEach(async () => {
process.env = { ...originalEnv }
await fs.rm(tempDir, { recursive: true, force: true })
})
describe('environment validation', () => {
it('should fail when ACTIONS_ID_TOKEN_REQUEST_URL is not set', async () => {
delete process.env.ACTIONS_ID_TOKEN_REQUEST_URL
await run({
...defaultInputs,
subjectName: 'artifact',
subjectDigest:
'sha256:7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32',
predicateType: 'https://example.com/predicate',
predicate: '{}'
})
expect(setFailedMock).toHaveBeenCalledWith(
expect.objectContaining({
message: expect.stringContaining('id-token')
})
)
})
})
describe('subject validation', () => {
it('should fail when no subject inputs are provided', async () => {
await run(defaultInputs)
expect(setFailedMock).toHaveBeenCalledWith(
expect.objectContaining({
message: expect.stringContaining('subject-path')
})
)
})
})
describe('attestation type detection', () => {
it('should detect provenance attestation when no predicate inputs provided', async () => {
await run({
...defaultInputs,
subjectName: 'artifact',
subjectDigest:
'sha256:7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32'
})
expect(infoMock).toHaveBeenCalledWith(
'Attestation type: Build Provenance'
)
expect(mockBuildSLSAProvenancePredicate).toHaveBeenCalled()
})
it('should detect custom attestation when predicate inputs provided', async () => {
await run({
...defaultInputs,
subjectName: 'artifact',
subjectDigest:
'sha256:7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32',
predicateType: 'https://example.com/predicate',
predicate: '{}'
})
expect(infoMock).toHaveBeenCalledWith('Attestation type: Custom')
expect(mockBuildSLSAProvenancePredicate).not.toHaveBeenCalled()
})
it('should detect SBOM attestation when sbom-path provided', async () => {
const sbomPath = path.join(tempDir, 'sbom.json')
await fs.writeFile(
sbomPath,
JSON.stringify({
spdxVersion: 'SPDX-2.3',
SPDXID: 'SPDXRef-DOCUMENT',
name: 'test'
})
)
await run({
...defaultInputs,
subjectName: 'artifact',
subjectDigest:
'sha256:7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32',
sbomPath
})
expect(infoMock).toHaveBeenCalledWith('Attestation type: SBOM')
})
it('should fail when sbom-path is combined with predicate inputs', async () => {
await run({
...defaultInputs,
subjectName: 'artifact',
subjectDigest:
'sha256:7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32',
sbomPath: '/path/to/sbom.json',
predicateType: 'https://example.com/predicate'
})
expect(setFailedMock).toHaveBeenCalledWith(
expect.objectContaining({
message: expect.stringContaining(
'Cannot specify sbom-path together with'
)
})
)
})
})
describe('successful attestation', () => {
const validInputs: RunInputs = {
...defaultInputs,
subjectName: 'artifact',
subjectDigest:
'sha256:7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32',
predicateType: 'https://example.com/predicate',
predicate: '{}'
}
it('should create attestation successfully', async () => {
await run(validInputs)
expect(setFailedMock).not.toHaveBeenCalled()
expect(mockAttest).toHaveBeenCalled()
})
it('should set output for attestation-id', async () => {
await run(validInputs)
expect(setOutputMock).toHaveBeenCalledWith('attestation-id', 'att-123')
})
it('should set output for attestation-url', async () => {
await run(validInputs)
expect(setOutputMock).toHaveBeenCalledWith(
'attestation-url',
'https://github.com/test-owner/test-repo/attestations/att-123'
)
})
it('should set output for bundle-path', async () => {
await run(validInputs)
expect(setOutputMock).toHaveBeenCalledWith(
'bundle-path',
expect.stringContaining('attestation.json')
)
})
it('should write attestation bundle to file', async () => {
await run(validInputs)
const bundlePath = setOutputMock.mock.calls.find(
(call: unknown[]) => call[0] === 'bundle-path'
)?.[1] as string
const content = await fs.readFile(bundlePath, 'utf-8')
expect(content).toContain('mediaType')
})
})
describe('sigstore instance selection', () => {
const validInputs: RunInputs = {
...defaultInputs,
subjectName: 'artifact',
subjectDigest:
'sha256:7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32',
predicateType: 'https://example.com/predicate',
predicate: '{}'
}
it('should use github sigstore for private repos', async () => {
mockContext.payload = { repository: { visibility: 'private' } }
await run(validInputs)
expect(mockAttest).toHaveBeenCalledWith(
expect.objectContaining({ sigstore: 'github' })
)
})
it('should use public-good sigstore for public repos', async () => {
mockContext.payload = { repository: { visibility: 'public' } }
await run(validInputs)
expect(mockAttest).toHaveBeenCalledWith(
expect.objectContaining({ sigstore: 'public-good' })
)
})
it('should use github sigstore when privateSigning is true', async () => {
mockContext.payload = { repository: { visibility: 'public' } }
await run({ ...validInputs, privateSigning: true })
expect(mockAttest).toHaveBeenCalledWith(
expect.objectContaining({ sigstore: 'github' })
)
})
})
describe('multiple subjects', () => {
it('should handle multiple subjects from glob pattern', async () => {
// Create test files
for (let i = 0; i < 3; i++) {
await fs.writeFile(path.join(tempDir, `file-${i}.txt`), `content-${i}`)
}
await run({
...defaultInputs,
subjectPath: path.join(tempDir, 'file-*.txt'),
predicateType: 'https://example.com/predicate',
predicate: '{}'
})
expect(setFailedMock).not.toHaveBeenCalled()
expect(infoMock).toHaveBeenCalledWith(
expect.stringContaining('3 subjects')
)
})
it('should fail when subject count exceeds maximum', async () => {
// Create too many files
for (let i = 0; i < 1025; i++) {
await fs.writeFile(path.join(tempDir, `file-${i}.txt`), `content-${i}`)
}
await run({
...defaultInputs,
subjectPath: path.join(tempDir, 'file-*.txt'),
predicateType: 'https://example.com/predicate',
predicate: '{}'
})
expect(setFailedMock).toHaveBeenCalledWith(
expect.objectContaining({
message: expect.stringContaining('Too many subjects')
})
)
})
})
describe('summary output', () => {
const validInputs: RunInputs = {
...defaultInputs,
subjectName: 'artifact',
subjectDigest:
'sha256:7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32',
predicateType: 'https://example.com/predicate',
predicate: '{}',
showSummary: true
}
it('should write summary when showSummary is true', async () => {
await run(validInputs)
expect(summaryMock.addHeading).toHaveBeenCalled()
expect(summaryMock.write).toHaveBeenCalled()
})
it('should not write summary when showSummary is false', async () => {
await run({ ...validInputs, showSummary: false })
expect(summaryMock.write).not.toHaveBeenCalled()
})
})
describe('registry push', () => {
const registryInputs: RunInputs = {
...defaultInputs,
subjectName: 'ghcr.io/test-owner/test-repo',
subjectDigest:
'sha256:7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32',
predicateType: 'https://example.com/predicate',
predicate: '{}',
pushToRegistry: true
}
it('should push attestation to registry when enabled', async () => {
await run(registryInputs)
expect(mockAttachArtifactToImage).toHaveBeenCalled()
})
it('should lowercase subject name for registry push', async () => {
await run({
...registryInputs,
subjectName: 'ghcr.io/TEST-OWNER/Test-Repo'
})
expect(mockAttest).toHaveBeenCalledWith(
expect.objectContaining({
subjects: [
expect.objectContaining({
name: 'ghcr.io/test-owner/test-repo'
})
]
})
)
})
})
})

View File

@@ -0,0 +1,37 @@
import type { Predicate } from '@actions/attest'
import { jest } from '@jest/globals'
import { TEST_PROVENANCE_PREDICATE } from '../fixtures/mocks'
// Mock function
const mockBuildSLSAProvenancePredicate = jest.fn<() => Promise<Predicate>>()
// Mock @actions/attest
jest.unstable_mockModule('@actions/attest', () => ({
buildSLSAProvenancePredicate: mockBuildSLSAProvenancePredicate
}))
// Dynamic import after mocking
const { generateProvenancePredicate } = await import('../../src/provenance')
describe('generateProvenancePredicate', () => {
beforeEach(() => {
jest.clearAllMocks()
mockBuildSLSAProvenancePredicate.mockResolvedValue(TEST_PROVENANCE_PREDICATE)
})
it('should delegate to buildSLSAProvenancePredicate', async () => {
const result = await generateProvenancePredicate()
expect(mockBuildSLSAProvenancePredicate).toHaveBeenCalledTimes(1)
expect(result).toEqual(TEST_PROVENANCE_PREDICATE)
})
it('should propagate errors from the underlying function', async () => {
const error = new Error('Failed to build provenance predicate')
mockBuildSLSAProvenancePredicate.mockRejectedValue(error)
await expect(generateProvenancePredicate()).rejects.toThrow(
'Failed to build provenance predicate'
)
})
})

View File

@@ -1,425 +0,0 @@
/**
* Unit tests for the action's main functionality, src/main.ts
*
* These should be run as if the action was called from a workflow.
* 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 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'
// Mock the GitHub Actions core library
const infoMock = jest.spyOn(core, 'info')
const startGroupMock = jest.spyOn(core, 'startGroup')
const setOutputMock = jest.spyOn(core, 'setOutput')
const setFailedMock = jest.spyOn(core, 'setFailed')
// Ensure that setFailed doesn't set an exit code during tests
setFailedMock.mockImplementation(() => {})
const summaryWriteMock = jest.spyOn(core.summary, 'write')
summaryWriteMock.mockImplementation(async () => Promise.resolve(core.summary))
// Mock the action's main function
const runMock = jest.spyOn(main, 'run')
// MockAgent for mocking @actions/github
const mockAgent = new MockAgent()
setGlobalDispatcher(mockAgent)
const defaultInputs: main.RunInputs = {
predicate: '',
predicateType: '',
predicatePath: '',
subjectName: '',
subjectDigest: '',
subjectPath: '',
subjectChecksums: '',
pushToRegistry: false,
showSummary: true,
githubToken: '',
privateSigning: false
}
describe('action', () => {
// Capture original environment variables and GitHub context 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'
// Fake an OIDC token
const oidcSubject = 'foo@bar.com'
const oidcPayload = { sub: oidcSubject, iss: '' }
const oidcToken = `.${Buffer.from(JSON.stringify(oidcPayload)).toString(
'base64'
)}.}`
const subjectName = 'registry/foo/bar'
const subjectDigest =
'sha256:7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32'
const predicate = '{}'
const predicateType = 'https://in-toto.io/attestation/release/v0.1'
const attestationID = '1234567890'
beforeEach(() => {
jest.clearAllMocks()
nock(tokenURL)
.get('/')
.query({ audience: 'sigstore' })
.reply(200, { value: oidcToken })
mockAgent
.get('https://api.github.com')
.intercept({
path: /^\/repos\/.*\/.*\/attestations$/,
method: 'post'
})
.reply(201, { id: attestationID })
process.env = {
...originalEnv,
ACTIONS_ID_TOKEN_REQUEST_URL: tokenURL,
ACTIONS_ID_TOKEN_REQUEST_TOKEN: 'token',
RUNNER_TEMP: process.env.RUNNER_TEMP || '/tmp'
}
})
afterEach(() => {
// Restore the original environment
process.env = originalEnv
// Restore the original github.context
setGHContext(originalContext)
})
describe('when ACTIONS_ID_TOKEN_REQUEST_URL is not set', () => {
const inputs: main.RunInputs = {
...defaultInputs,
subjectDigest,
subjectName,
predicateType,
predicate,
githubToken: 'gh-token'
}
beforeEach(() => {
// Nullify the OIDC token URL
process.env.ACTIONS_ID_TOKEN_REQUEST_URL = ''
})
it('sets a failed status', async () => {
await main.run(inputs)
expect(runMock).toHaveReturned()
expect(setFailedMock).toHaveBeenCalledWith(
new Error(
'missing "id-token" permission. Please add "permissions: id-token: write" to your workflow.'
)
)
})
})
describe('when no inputs are provided', () => {
it('sets a failed status', async () => {
await main.run(defaultInputs)
expect(runMock).toHaveReturned()
expect(setFailedMock).toHaveBeenCalledWith(
new Error(
'One of subject-path, subject-digest, or subject-checksums must be provided'
)
)
})
})
describe('when the repository is private', () => {
const inputs: main.RunInputs = {
...defaultInputs,
subjectDigest,
subjectName,
predicateType,
predicate,
githubToken: 'gh-token'
}
beforeEach(async () => {
// Set the GH context with private repository visibility and a repo owner.
setGHContext({
payload: { repository: { visibility: 'private' } },
repo: { owner: 'foo', repo: 'bar' }
})
await mockFulcio({
baseURL: 'https://fulcio.githubapp.com',
strict: false
})
await mockTSA({ baseURL: 'https://timestamp.githubapp.com' })
})
it('invokes the action w/o error', async () => {
await main.run(inputs)
expect(runMock).toHaveReturned()
expect(setFailedMock).not.toHaveBeenCalledWith()
expect(infoMock).toHaveBeenNthCalledWith(
1,
expect.stringMatching(
`Attestation created for ${subjectName}@${subjectDigest}`
)
)
expect(startGroupMock).toHaveBeenNthCalledWith(
1,
expect.stringMatching('GitHub Sigstore')
)
expect(infoMock).toHaveBeenNthCalledWith(
2,
expect.stringMatching('-----BEGIN CERTIFICATE-----')
)
expect(infoMock).toHaveBeenNthCalledWith(
3,
expect.stringMatching(/attestation uploaded/i)
)
expect(infoMock).toHaveBeenNthCalledWith(
4,
expect.stringMatching(attestationID)
)
expect(setOutputMock).toHaveBeenNthCalledWith(
1,
'bundle-path',
expect.stringMatching('attestation.json')
)
expect(setOutputMock).toHaveBeenNthCalledWith(
2,
'attestation-id',
expect.stringMatching(attestationID)
)
expect(setOutputMock).toHaveBeenNthCalledWith(
3,
'attestation-url',
expect.stringContaining(`foo/bar/attestations/${attestationID}`)
)
expect(setFailedMock).not.toHaveBeenCalled()
})
})
describe('when the repository is public', () => {
const getRegCredsSpy = jest.spyOn(oci, 'getRegistryCredentials')
const attachArtifactSpy = jest.spyOn(oci, 'attachArtifactToImage')
const inputs: main.RunInputs = {
...defaultInputs,
subjectDigest,
subjectName,
predicateType,
predicate,
githubToken: 'gh-token',
pushToRegistry: true
}
beforeEach(async () => {
// Set the GH context with public repository visibility and a repo owner.
setGHContext({
payload: { repository: { visibility: 'public' } },
repo: { owner: 'foo', repo: 'bar' }
})
await mockFulcio({
baseURL: 'https://fulcio.sigstore.dev',
strict: false
})
await mockRekor({ baseURL: 'https://rekor.sigstore.dev' })
getRegCredsSpy.mockImplementation(() => ({
username: 'username',
password: 'password'
}))
attachArtifactSpy.mockImplementation(async () =>
Promise.resolve({
digest: 'sha256:123456',
mediaType: 'application/vnd.cncf.notary.v2',
size: 123456
})
)
})
it('invokes the action w/o error', async () => {
await main.run(inputs)
expect(runMock).toHaveReturned()
expect(setFailedMock).not.toHaveBeenCalled()
expect(getRegCredsSpy).toHaveBeenCalledWith(subjectName)
expect(attachArtifactSpy).toHaveBeenCalled()
expect(infoMock).toHaveBeenNthCalledWith(
1,
expect.stringMatching(
`Attestation created for ${subjectName}@${subjectDigest}`
)
)
expect(startGroupMock).toHaveBeenNthCalledWith(
1,
expect.stringMatching('Public Good Sigstore')
)
expect(infoMock).toHaveBeenNthCalledWith(
2,
expect.stringMatching('-----BEGIN CERTIFICATE-----')
)
expect(infoMock).toHaveBeenNthCalledWith(
3,
expect.stringMatching(/signature uploaded/i)
)
expect(infoMock).toHaveBeenNthCalledWith(
4,
expect.stringMatching(SEARCH_PUBLIC_GOOD_URL)
)
expect(infoMock).toHaveBeenNthCalledWith(
5,
expect.stringMatching(/attestation uploaded/i)
)
expect(infoMock).toHaveBeenNthCalledWith(
6,
expect.stringMatching(attestationID)
)
expect(setOutputMock).toHaveBeenNthCalledWith(
1,
'bundle-path',
expect.stringMatching('attestation.json')
)
expect(setOutputMock).toHaveBeenNthCalledWith(
2,
'attestation-id',
expect.stringMatching(attestationID)
)
expect(setOutputMock).toHaveBeenNthCalledWith(
3,
'attestation-url',
expect.stringContaining(`foo/bar/attestations/${attestationID}`)
)
expect(setFailedMock).not.toHaveBeenCalled()
})
})
describe('when the subject count is greater than 1', () => {
let dir = ''
const filename = 'subject'
beforeEach(async () => {
const subjectCount = 5
const content = 'file content'
// Set-up temp directory
const tmpDir = await fs.realpath(os.tmpdir())
dir = await fs.mkdtemp(tmpDir + path.sep)
// Add files for glob testing
for (let i = 0; i < subjectCount; i++) {
await fs.writeFile(path.join(dir, `${filename}-${i}`), content)
}
// Set the GH context with private repository visibility and a repo owner.
setGHContext({
payload: { repository: { visibility: 'private' } },
repo: { owner: 'foo', repo: 'bar' }
})
// Set-up a Fulcio mock for each subject
await mockFulcio({
baseURL: 'https://fulcio.githubapp.com',
strict: false
})
// Set-up a TSA mock for each subject
await mockTSA({ baseURL: 'https://timestamp.githubapp.com' })
})
afterEach(async () => {
// Clean-up temp directory
await fs.rm(dir, { recursive: true })
})
it('invokes the action w/o error', async () => {
const inputs: main.RunInputs = {
...defaultInputs,
subjectPath: path.join(dir, `${filename}-*`),
predicateType,
predicate,
githubToken: 'gh-token'
}
await main.run(inputs)
expect(runMock).toHaveReturned()
expect(setFailedMock).not.toHaveBeenCalled()
expect(infoMock).toHaveBeenNthCalledWith(
1,
expect.stringMatching('Attestation created for 5 subjects')
)
})
})
describe('when the subject count exceeds the max', () => {
let dir = ''
const filename = 'subject'
beforeEach(async () => {
const subjectCount = 1025
const content = 'file content'
// Set-up temp directory
const tmpDir = await fs.realpath(os.tmpdir())
dir = await fs.mkdtemp(tmpDir + path.sep)
// Add files for glob testing
for (let i = 0; i < subjectCount; i++) {
await fs.writeFile(path.join(dir, `${filename}-${i}`), content)
}
// Set the GH context with private repository visibility and a repo owner.
setGHContext({
payload: { repository: { visibility: 'private' } },
repo: { owner: 'foo', repo: 'bar' }
})
})
afterEach(async () => {
// Clean-up temp directory
await fs.rm(dir, { recursive: true })
})
it('sets a failed status', async () => {
const inputs: main.RunInputs = {
...defaultInputs,
subjectPath: path.join(dir, `${filename}-*`),
predicateType,
predicate,
githubToken: 'gh-token'
}
await main.run(inputs)
expect(runMock).toHaveReturned()
expect(setFailedMock).toHaveBeenCalledWith(
new Error(
'Too many subjects specified. The maximum number of subjects is 1024.'
)
)
})
})
})
// 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 })
}

View File

@@ -1,129 +0,0 @@
import fs from 'fs/promises'
import os from 'os'
import path from 'path'
import { predicateFromInputs, PredicateInputs } from '../src/predicate'
describe('subjectFromInputs', () => {
const blankInputs: PredicateInputs = {
predicateType: '',
predicate: '',
predicatePath: ''
}
describe('when no inputs are provided', () => {
it('throws an error', () => {
expect(() => predicateFromInputs(blankInputs)).toThrow(/predicate-type/i)
})
})
describe('when neither predicate path nor predicate are provided', () => {
it('throws an error', () => {
const inputs: PredicateInputs = {
...blankInputs,
predicateType: 'https://example.com/predicate'
}
expect(() => predicateFromInputs(inputs)).toThrow(
/one of predicate-path or predicate must be provided/i
)
})
})
describe('when both predicate path and predicate are provided', () => {
it('throws an error', () => {
const inputs: PredicateInputs = {
predicateType: 'https://example.com/predicate',
predicate: '{}',
predicatePath: 'path/to/predicate'
}
expect(() => predicateFromInputs(inputs)).toThrow(
/only one of predicate-path or predicate may be provided/i
)
})
})
describe('when specifying a predicate path', () => {
const predicateType = 'https://example.com/predicate'
const content = '{}'
let predicatePath = ''
beforeEach(async () => {
// Set-up temp directory
const tmpDir = await fs.realpath(os.tmpdir())
const dir = await fs.mkdtemp(tmpDir + path.sep)
const filename = 'subject'
predicatePath = path.join(dir, filename)
// Write file to temp directory
await fs.writeFile(predicatePath, content)
})
afterEach(async () => {
// Clean-up temp directory
await fs.rm(path.parse(predicatePath).dir, { recursive: true })
})
it('returns the predicate', () => {
const inputs: PredicateInputs = {
...blankInputs,
predicateType,
predicatePath
}
expect(predicateFromInputs(inputs)).toEqual({
type: predicateType,
params: JSON.parse(content)
})
})
})
describe('when specifying a predicate path that does not exist', () => {
const predicateType = 'https://example.com/predicate'
const predicatePath = 'foo'
it('returns the predicate', () => {
const inputs: PredicateInputs = {
...blankInputs,
predicateType,
predicatePath
}
expect(() => predicateFromInputs(inputs)).toThrow(/file not found/)
})
})
describe('when specifying a predicate value', () => {
const predicateType = 'https://example.com/predicate'
const content = '{}'
it('returns the predicate', () => {
const inputs: PredicateInputs = {
...blankInputs,
predicateType,
predicate: content
}
expect(predicateFromInputs(inputs)).toEqual({
type: predicateType,
params: JSON.parse(content)
})
})
})
describe('when specifying a predicate value exceeding the max size', () => {
const predicateType = 'https://example.com/predicate'
const content = JSON.stringify({ a: 'a'.repeat(16 * 1024 * 1024) })
it('throws an error', () => {
const inputs: PredicateInputs = {
...blankInputs,
predicateType,
predicate: content
}
expect(() => predicateFromInputs(inputs)).toThrow(
/predicate string exceeds maximum/
)
})
})
})

View File

@@ -1,15 +0,0 @@
import { highlight, mute } from '../src/style'
describe('style', () => {
describe('highlight', () => {
it('adds cyan color to the string', () => {
expect(highlight('foo')).toBe('\x1B[36mfoo\x1B[39m')
})
})
describe('mute', () => {
it('adds gray color to the string', () => {
expect(mute('foo')).toBe('\x1B[38;5;244mfoo\x1B[39m')
})
})
})

View File

@@ -1,546 +0,0 @@
import crypto from 'crypto'
import fs from 'fs/promises'
import os from 'os'
import path from 'path'
import {
formatSubjectDigest,
subjectFromInputs,
SubjectInputs
} from '../src/subject'
describe('subjectFromInputs', () => {
const blankInputs: SubjectInputs = {
subjectPath: '',
subjectName: '',
subjectDigest: '',
subjectChecksums: ''
}
describe('when no inputs are provided', () => {
it('throws an error', async () => {
await expect(subjectFromInputs(blankInputs)).rejects.toThrow(
/one of subject-path, subject-digest, or subject-checksums must be provided/i
)
})
})
describe('when both subject path and subject digest are provided', () => {
it('throws an error', async () => {
const inputs: SubjectInputs = {
subjectName: 'foo',
subjectPath: 'path/to/subject',
subjectDigest: 'digest',
subjectChecksums: ''
}
await expect(subjectFromInputs(inputs)).rejects.toThrow(
/only one of subject-path, subject-digest, or subject-checksums may be provided/i
)
})
})
describe('when both subject path and subject checksums are provided', () => {
it('throws an error', async () => {
const inputs: SubjectInputs = {
subjectName: '',
subjectPath: 'path/to/subject',
subjectDigest: '',
subjectChecksums: 'path/to/checksums'
}
await expect(subjectFromInputs(inputs)).rejects.toThrow(
/only one of subject-path, subject-digest, or subject-checksums may be provided/i
)
})
})
describe('when both subject digest and subject checksums are provided', () => {
it('throws an error', async () => {
const inputs: SubjectInputs = {
subjectName: 'foo',
subjectPath: '',
subjectDigest: 'digest',
subjectChecksums: 'path/to/checksums'
}
await expect(subjectFromInputs(inputs)).rejects.toThrow(
/only one of subject-path, subject-digest, or subject-checksums may be provided/i
)
})
})
describe('when subject digest is provided but not the name', () => {
it('throws an error', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectDigest: 'digest'
}
await expect(subjectFromInputs(inputs)).rejects.toThrow(
/subject-name must be provided when using subject-digest/i
)
})
})
describe('when specifying a subject digest', () => {
const name = 'Subject'
describe('when the digest is malformed', () => {
it('throws an error', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectDigest: 'digest',
subjectName: name
}
await expect(subjectFromInputs(inputs)).rejects.toThrow(
/subject-digest must be in the format "sha256:<hex-digest>"/i
)
})
})
describe('when the algorithm is not supported', () => {
it('throws an error', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectDigest: 'md5:deadbeef',
subjectName: name
}
await expect(subjectFromInputs(inputs)).rejects.toThrow(
/subject-digest must be in the format "sha256:<hex-digest>"/i
)
})
})
describe('when the sha256 digest is malformed', () => {
it('throws an error', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectDigest: 'sha256:deadbeef',
subjectName: name
}
await expect(subjectFromInputs(inputs)).rejects.toThrow(
/subject-digest must be in the format "sha256:<hex-digest>"/i
)
})
})
describe('when the sha256 digest is valid', () => {
const alg = 'sha256'
const digest =
'7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32'
it('returns the subject', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectDigest: `${alg}:${digest}`,
subjectName: name
}
const subject = await subjectFromInputs(inputs)
expect(subject).toBeDefined()
expect(subject).toHaveLength(1)
expect(subject[0].name).toEqual(name)
expect(subject[0].digest).toEqual({ [alg]: digest })
})
})
describe('when the downcaseName is true', () => {
const imageName = 'ghcr.io/FOO/bar'
const alg = 'sha256'
const digest =
'7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32'
it('returns the subject (with name downcased)', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectDigest: `${alg}:${digest}`,
subjectName: imageName,
downcaseName: true
}
const subject = await subjectFromInputs(inputs)
expect(subject).toBeDefined()
expect(subject).toHaveLength(1)
expect(subject[0].name).toEqual(imageName.toLowerCase())
expect(subject[0].digest).toEqual({ [alg]: digest })
})
})
})
describe('when specifying a subject path', () => {
describe('when the file does NOT exist', () => {
it('throws an error', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectPath: '/f/a/k/e'
}
await expect(subjectFromInputs(inputs)).rejects.toThrow(
/could not find subject at path/i
)
})
})
})
describe('when the file exists', () => {
let dir = ''
const filename = 'subject'
const content = 'file content'
const expectedDigest = crypto
.createHash('sha256')
.update(content)
.digest('hex')
beforeEach(async () => {
// Set-up temp directory
const tmpDir = await fs.realpath(os.tmpdir())
dir = await fs.mkdtemp(tmpDir + path.sep)
// Write file to temp directory
await fs.writeFile(path.join(dir, filename), content)
// Add files for glob testing
for (let i = 0; i < 3; i++) {
await fs.writeFile(path.join(dir, `${filename}-${i}`), content)
await fs.writeFile(path.join(dir, `other-${i}`), content)
}
})
afterEach(async () => {
// Clean-up temp directory
await fs.rm(dir, { recursive: true })
})
describe('when no name is provided', () => {
it('returns the subject', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectPath: path.join(dir, filename)
}
const subject = await subjectFromInputs(inputs)
expect(subject).toBeDefined()
expect(subject).toHaveLength(1)
expect(subject[0].name).toEqual(filename)
expect(subject[0].digest).toEqual({ sha256: expectedDigest })
})
})
describe('when a name is provided', () => {
const name = 'mysubject'
it('returns the subject', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectPath: path.join(dir, filename),
subjectName: name
}
const subject = await subjectFromInputs(inputs)
expect(subject).toBeDefined()
expect(subject).toHaveLength(1)
expect(subject[0].name).toEqual(name)
expect(subject[0].digest).toEqual({ sha256: expectedDigest })
})
})
describe('when a file glob is supplied', () => {
it('returns the multiple subjects', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectPath: path.join(dir, 'subject-*')
}
const subjects = await subjectFromInputs(inputs)
expect(subjects).toBeDefined()
expect(subjects).toHaveLength(3)
subjects.forEach((subject, i) => {
expect(subject.name).toEqual(`${filename}-${i}`)
expect(subject.digest).toEqual({ sha256: expectedDigest })
})
})
})
describe('when a file glob is supplied which also matches non-files', () => {
it('returns the subjects (excluding non-files)', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectPath: `${dir}*`
}
const subjects = await subjectFromInputs(inputs)
expect(subjects).toBeDefined()
expect(subjects).toHaveLength(7)
})
})
describe('when a comma-separated list is supplied', () => {
it('returns the multiple subjects', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectPath: `${path.join(dir, 'subject-1')},${path.join(dir, 'subject-2')}`
}
const subjects = await subjectFromInputs(inputs)
expect(subjects).toBeDefined()
expect(subjects).toHaveLength(2)
expect(subjects).toContainEqual({
name: 'subject-1',
digest: { sha256: expectedDigest }
})
expect(subjects).toContainEqual({
name: 'subject-2',
digest: { sha256: expectedDigest }
})
})
})
describe('when a multi-line list is supplied', () => {
it('returns the multiple subjects', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectPath: `${path.join(dir, 'subject-0')}\n${path.join(dir, 'subject-2')}`
}
const subjects = await subjectFromInputs(inputs)
expect(subjects).toBeDefined()
expect(subjects).toHaveLength(2)
expect(subjects).toContainEqual({
name: 'subject-0',
digest: { sha256: expectedDigest }
})
expect(subjects).toContainEqual({
name: 'subject-2',
digest: { sha256: expectedDigest }
})
})
})
describe('when an excluding glob is supplied', () => {
it('returns the multiple subjects', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectPath: `${path.join(dir, 'subject-*')},!${path.join(dir, 'subject-1')}`
}
const subjects = await subjectFromInputs(inputs)
expect(subjects).toBeDefined()
expect(subjects).toHaveLength(2)
expect(subjects).toContainEqual({
name: 'subject-0',
digest: { sha256: expectedDigest }
})
expect(subjects).toContainEqual({
name: 'subject-2',
digest: { sha256: expectedDigest }
})
})
})
describe('when a multi-line glob list is supplied', () => {
it('returns the multiple subjects', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectPath: `${path.join(dir, 'subject-*')}\n ${path.join(dir, 'other-*')} `
}
const subjects = await subjectFromInputs(inputs)
expect(subjects).toBeDefined()
expect(subjects).toHaveLength(6)
expect(subjects).toContainEqual({
name: 'subject-0',
digest: { sha256: expectedDigest }
})
expect(subjects).toContainEqual({
name: 'subject-1',
digest: { sha256: expectedDigest }
})
expect(subjects).toContainEqual({
name: 'subject-2',
digest: { sha256: expectedDigest }
})
expect(subjects).toContainEqual({
name: 'other-0',
digest: { sha256: expectedDigest }
})
expect(subjects).toContainEqual({
name: 'other-1',
digest: { sha256: expectedDigest }
})
expect(subjects).toContainEqual({
name: 'other-2',
digest: { sha256: expectedDigest }
})
})
})
describe('when duplicate subjects are supplied', () => {
let otherDir = ''
// Add duplicate subject in alternate directory
beforeEach(async () => {
// Set-up temp directory
const tmpDir = await fs.realpath(os.tmpdir())
otherDir = await fs.mkdtemp(tmpDir + path.sep)
// Write file to temp directory
await fs.writeFile(path.join(otherDir, filename), content)
})
it('returns de-duplicated subjects', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectPath: `${path.join(dir, 'subject')}, ${path.join(otherDir, 'subject')} `
}
const subjects = await subjectFromInputs(inputs)
expect(subjects).toBeDefined()
expect(subjects).toHaveLength(1)
})
})
})
describe('when specifying a subject checksums file', () => {
const checksums = `
187dcd1506a170337415589ff00c8743f19d41cc31fca246c2739dfd450d0b9d demo_0.0.1_linux_amd64
badline
5d8b4751ef31f9440d843fcfa4e53ca2e25b1cb1f13fd355fdc7c24b41fe645293291ea9297ba3989078abb77ebbaac66be073618a9e4974dbd0361881d4c718 demo_0.0.1_darwin_arm64`
let dir = ''
const filename = 'checksums'
beforeEach(async () => {
// Set-up temp directory
const tmpDir = await fs.realpath(os.tmpdir())
dir = await fs.mkdtemp(tmpDir + path.sep)
// Write file to temp directory
await fs.writeFile(path.join(dir, filename), checksums)
})
afterEach(async () => {
// Clean-up temp directory
await fs.rm(dir, { recursive: true })
})
describe('when the specified path is NOT a file', () => {
it('throws an error', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectChecksums: dir
}
await expect(subjectFromInputs(inputs)).rejects.toThrow(
/subject checksums file not found/i
)
})
})
describe('when the specific path is a file', () => {
it('returns the multiple subjects', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectChecksums: path.join(dir, filename)
}
const subjects = await subjectFromInputs(inputs)
expect(subjects).toBeDefined()
expect(subjects).toHaveLength(2)
expect(subjects).toContainEqual({
name: 'demo_0.0.1_linux_amd64',
digest: {
sha256:
'187dcd1506a170337415589ff00c8743f19d41cc31fca246c2739dfd450d0b9d'
}
})
})
})
})
describe('when specifying a subject checksums string', () => {
const checksums = `
f861e68a080799ca83104630b56abb90d8dbcc5f8b5a8639cb691e269838f29e demo_0.0.1_linux_386
187dcd1506a170337415589ff00c8743f19d41cc31fca246c2739dfd450d0b9d demo_0.0.1_linux_amd64
9ecbf449e286a8a8748c161c52aa28b6b2fc64ab86f94161c5d1b3abc18156c5 demo_0.0.1_linux_arm64`
it('returns the multiple subjects', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectChecksums: checksums
}
const subjects = await subjectFromInputs(inputs)
expect(subjects).toBeDefined()
expect(subjects).toHaveLength(3)
expect(subjects).toContainEqual({
name: 'demo_0.0.1_linux_386',
digest: {
sha256:
'f861e68a080799ca83104630b56abb90d8dbcc5f8b5a8639cb691e269838f29e'
}
})
})
})
describe('when specifying a subject checksums string with an unrecognized digest', () => {
const checksums = `f861e demo_0.0.1_linux_386`
it('throws an error', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectChecksums: checksums
}
await expect(subjectFromInputs(inputs)).rejects.toThrow(
/unknown digest algorithm/i
)
})
})
describe('when specifying a subject checksums string with an invalid digest', () => {
const checksums =
'!!!!e68a080799ca83104630b56abb90d8dbcc5f8b5a8639cb691e269838f29e demo_0.0.1_linux_386'
it('throws an error', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectChecksums: checksums
}
await expect(subjectFromInputs(inputs)).rejects.toThrow(/invalid digest/i)
})
})
})
describe('subjectDigest', () => {
it('returns the digest', () => {
const subject = {
name: 'foo',
digest: { sha1: 'deadbeef' }
}
const digest = formatSubjectDigest(subject)
expect(digest).toEqual('sha1:deadbeef')
})
})

View File

@@ -0,0 +1,168 @@
import {
detectAttestationType,
validateAttestationInputs,
DetectionInputs
} from '../../src/detect'
describe('detectAttestationType', () => {
const blankInputs: DetectionInputs = {
sbomPath: '',
predicateType: '',
predicate: '',
predicatePath: ''
}
it('should return provenance when no inputs are provided', () => {
expect(detectAttestationType(blankInputs)).toBe('provenance')
})
describe('SBOM detection', () => {
it('should return sbom when sbom-path is provided', () => {
const inputs: DetectionInputs = {
...blankInputs,
sbomPath: '/path/to/sbom.json'
}
expect(detectAttestationType(inputs)).toBe('sbom')
})
it('should prioritize sbom over custom predicate inputs', () => {
const inputs: DetectionInputs = {
...blankInputs,
sbomPath: '/path/to/sbom.json',
predicateType: 'https://example.com/predicate'
}
expect(detectAttestationType(inputs)).toBe('sbom')
})
})
describe('custom detection', () => {
it('should return custom when predicate-type is provided', () => {
const inputs: DetectionInputs = {
...blankInputs,
predicateType: 'https://example.com/predicate'
}
expect(detectAttestationType(inputs)).toBe('custom')
})
it('should return custom when predicate is provided', () => {
const inputs: DetectionInputs = {
...blankInputs,
predicate: '{}'
}
expect(detectAttestationType(inputs)).toBe('custom')
})
it('should return custom when predicate-path is provided', () => {
const inputs: DetectionInputs = {
...blankInputs,
predicatePath: '/path/to/predicate.json'
}
expect(detectAttestationType(inputs)).toBe('custom')
})
it('should return custom when predicate-type and predicate are both provided', () => {
const inputs: DetectionInputs = {
...blankInputs,
predicateType: 'https://example.com/predicate',
predicate: '{}'
}
expect(detectAttestationType(inputs)).toBe('custom')
})
})
})
describe('validateAttestationInputs', () => {
const blankInputs: DetectionInputs = {
sbomPath: '',
predicateType: '',
predicate: '',
predicatePath: ''
}
it('should not throw when no inputs are provided', () => {
expect(() => validateAttestationInputs(blankInputs)).not.toThrow()
})
it('should not throw when sbom-path is provided alone', () => {
const inputs: DetectionInputs = {
...blankInputs,
sbomPath: '/path/to/sbom.json'
}
expect(() => validateAttestationInputs(inputs)).not.toThrow()
})
describe('sbom-path conflicts', () => {
it('should throw when sbom-path is combined with predicate-type', () => {
const inputs: DetectionInputs = {
...blankInputs,
sbomPath: '/path/to/sbom.json',
predicateType: 'https://example.com/predicate'
}
expect(() => validateAttestationInputs(inputs)).toThrow(
/Cannot specify sbom-path together with/
)
})
it('should throw when sbom-path is combined with predicate', () => {
const inputs: DetectionInputs = {
...blankInputs,
sbomPath: '/path/to/sbom.json',
predicate: '{}'
}
expect(() => validateAttestationInputs(inputs)).toThrow(
/Cannot specify sbom-path together with/
)
})
it('should throw when sbom-path is combined with predicate-path', () => {
const inputs: DetectionInputs = {
...blankInputs,
sbomPath: '/path/to/sbom.json',
predicatePath: '/path/to/predicate.json'
}
expect(() => validateAttestationInputs(inputs)).toThrow(
/Cannot specify sbom-path together with/
)
})
})
describe('predicate-type requirements', () => {
it('should throw when predicate is provided without predicate-type', () => {
const inputs: DetectionInputs = {
...blankInputs,
predicate: '{}'
}
expect(() => validateAttestationInputs(inputs)).toThrow(
/predicate-type is required/
)
})
it('should throw when predicate-path is provided without predicate-type', () => {
const inputs: DetectionInputs = {
...blankInputs,
predicatePath: '/path/to/predicate.json'
}
expect(() => validateAttestationInputs(inputs)).toThrow(
/predicate-type is required/
)
})
it('should not throw when predicate-type and predicate are provided', () => {
const inputs: DetectionInputs = {
...blankInputs,
predicateType: 'https://example.com/predicate',
predicate: '{}'
}
expect(() => validateAttestationInputs(inputs)).not.toThrow()
})
it('should not throw when predicate-type and predicate-path are provided', () => {
const inputs: DetectionInputs = {
...blankInputs,
predicateType: 'https://example.com/predicate',
predicatePath: '/path/to/predicate.json'
}
expect(() => validateAttestationInputs(inputs)).not.toThrow()
})
})
})

View File

@@ -0,0 +1,142 @@
import fs from 'fs/promises'
import os from 'os'
import path from 'path'
import { predicateFromInputs, PredicateInputs } from '../../src/predicate'
describe('predicateFromInputs', () => {
const blankInputs: PredicateInputs = {
predicateType: '',
predicate: '',
predicatePath: ''
}
let tempDir: string
beforeEach(async () => {
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'predicate-test-'))
})
afterEach(async () => {
await fs.rm(tempDir, { recursive: true, force: true })
})
describe('input validation', () => {
it('should throw when predicate-type is not provided', async () => {
await expect(predicateFromInputs(blankInputs)).rejects.toThrow(
/predicate-type must be provided/
)
})
it('should throw when neither predicate nor predicate-path is provided', async () => {
const inputs: PredicateInputs = {
...blankInputs,
predicateType: 'https://example.com/predicate'
}
await expect(predicateFromInputs(inputs)).rejects.toThrow(
/one of predicate-path or predicate must be provided/i
)
})
it('should throw when both predicate and predicate-path are provided', async () => {
const inputs: PredicateInputs = {
predicateType: 'https://example.com/predicate',
predicate: '{}',
predicatePath: '/path/to/predicate.json'
}
await expect(predicateFromInputs(inputs)).rejects.toThrow(
/only one of predicate-path or predicate may be provided/i
)
})
})
describe('with predicate string', () => {
it('should parse and return the predicate', async () => {
const predicateType = 'https://example.com/predicate'
const predicateContent = { foo: 'bar', nested: { value: 123 } }
const inputs: PredicateInputs = {
...blankInputs,
predicateType,
predicate: JSON.stringify(predicateContent)
}
const result = await predicateFromInputs(inputs)
expect(result).toEqual({
type: predicateType,
params: predicateContent
})
})
it('should throw when predicate string exceeds max size', async () => {
const predicateType = 'https://example.com/predicate'
const largeContent = JSON.stringify({ data: 'x'.repeat(16 * 1024 * 1024) })
const inputs: PredicateInputs = {
...blankInputs,
predicateType,
predicate: largeContent
}
await expect(predicateFromInputs(inputs)).rejects.toThrow(
/predicate string exceeds maximum/
)
})
it('should throw when predicate is invalid JSON', async () => {
const inputs: PredicateInputs = {
...blankInputs,
predicateType: 'https://example.com/predicate',
predicate: 'not valid json'
}
await expect(predicateFromInputs(inputs)).rejects.toThrow(/JSON/)
})
})
describe('with predicate path', () => {
it('should read and parse predicate from file', async () => {
const predicateType = 'https://example.com/predicate'
const predicateContent = { buildType: 'test', metadata: { version: '1.0' } }
const filePath = path.join(tempDir, 'predicate.json')
await fs.writeFile(filePath, JSON.stringify(predicateContent))
const inputs: PredicateInputs = {
...blankInputs,
predicateType,
predicatePath: filePath
}
const result = await predicateFromInputs(inputs)
expect(result).toEqual({
type: predicateType,
params: predicateContent
})
})
it('should throw when predicate file does not exist', async () => {
const inputs: PredicateInputs = {
...blankInputs,
predicateType: 'https://example.com/predicate',
predicatePath: '/nonexistent/file.json'
}
await expect(predicateFromInputs(inputs)).rejects.toThrow(/file not found/)
})
it('should throw when predicate file contains invalid JSON', async () => {
const filePath = path.join(tempDir, 'invalid.json')
await fs.writeFile(filePath, 'not valid json')
const inputs: PredicateInputs = {
...blankInputs,
predicateType: 'https://example.com/predicate',
predicatePath: filePath
}
await expect(predicateFromInputs(inputs)).rejects.toThrow(/JSON/)
})
})
})

181
__tests__/unit/sbom.test.ts Normal file
View File

@@ -0,0 +1,181 @@
import { jest } from '@jest/globals'
import fs from 'fs/promises'
import os from 'os'
import path from 'path'
import { parseSBOMFromPath, generateSBOMPredicate, SBOM } from '../../src/sbom'
describe('parseSBOMFromPath', () => {
let tempDir: string
beforeEach(async () => {
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'sbom-test-'))
})
afterEach(async () => {
await fs.rm(tempDir, { recursive: true, force: true })
})
describe('file handling', () => {
it('should throw when file does not exist', async () => {
await expect(parseSBOMFromPath('/nonexistent/file.json')).rejects.toThrow(
/SBOM file not found/
)
})
it('should rethrow non-ENOENT errors', async () => {
const statSpy = jest.spyOn(fs, 'stat').mockRejectedValueOnce(
Object.assign(new Error('Permission denied'), { code: 'EACCES' })
)
await expect(parseSBOMFromPath('/some/file.json')).rejects.toThrow(
/Permission denied/
)
statSpy.mockRestore()
})
it('should throw when file contains invalid JSON', async () => {
const filePath = path.join(tempDir, 'invalid.json')
await fs.writeFile(filePath, 'not valid json')
await expect(parseSBOMFromPath(filePath)).rejects.toThrow(/JSON/)
})
it('should throw when file exceeds maximum size', async () => {
const filePath = path.join(tempDir, 'large.json')
const largeContent = 'x'.repeat(17 * 1024 * 1024)
await fs.writeFile(filePath, largeContent)
await expect(parseSBOMFromPath(filePath)).rejects.toThrow(
/SBOM file exceeds maximum allowed size/
)
})
})
describe('SPDX format', () => {
const spdxSBOM = {
spdxVersion: 'SPDX-2.3',
SPDXID: 'SPDXRef-DOCUMENT',
name: 'test-package',
packages: []
}
it('should parse valid SPDX SBOM', async () => {
const filePath = path.join(tempDir, 'sbom.spdx.json')
await fs.writeFile(filePath, JSON.stringify(spdxSBOM))
const result = await parseSBOMFromPath(filePath)
expect(result.type).toBe('spdx')
expect(result.object).toEqual(spdxSBOM)
})
})
describe('CycloneDX format', () => {
const cyclonedxSBOM = {
bomFormat: 'CycloneDX',
specVersion: '1.4',
serialNumber: 'urn:uuid:12345',
components: []
}
it('should parse valid CycloneDX SBOM', async () => {
const filePath = path.join(tempDir, 'sbom.cdx.json')
await fs.writeFile(filePath, JSON.stringify(cyclonedxSBOM))
const result = await parseSBOMFromPath(filePath)
expect(result.type).toBe('cyclonedx')
expect(result.object).toEqual(cyclonedxSBOM)
})
})
describe('unsupported formats', () => {
it('should throw for unrecognized SBOM format', async () => {
const filePath = path.join(tempDir, 'invalid-sbom.json')
await fs.writeFile(filePath, JSON.stringify({ random: 'data' }))
await expect(parseSBOMFromPath(filePath)).rejects.toThrow(
/Unsupported SBOM format/
)
})
it('should throw for SPDX missing SPDXID', async () => {
const filePath = path.join(tempDir, 'partial-spdx.json')
await fs.writeFile(filePath, JSON.stringify({ spdxVersion: 'SPDX-2.3' }))
await expect(parseSBOMFromPath(filePath)).rejects.toThrow(
/Unsupported SBOM format/
)
})
it('should throw for CycloneDX missing required fields', async () => {
const filePath = path.join(tempDir, 'partial-cdx.json')
await fs.writeFile(filePath, JSON.stringify({ bomFormat: 'CycloneDX' }))
await expect(parseSBOMFromPath(filePath)).rejects.toThrow(
/Unsupported SBOM format/
)
})
})
})
describe('generateSBOMPredicate', () => {
describe('SPDX predicates', () => {
it('should generate predicate with correct SPDX type URL', () => {
const sbom: SBOM = {
type: 'spdx',
object: {
spdxVersion: 'SPDX-2.3',
SPDXID: 'SPDXRef-DOCUMENT',
name: 'test-package'
}
}
const predicate = generateSBOMPredicate(sbom)
expect(predicate.type).toBe('https://spdx.dev/Document/v2.3')
expect(predicate.params).toEqual(sbom.object)
})
it('should throw when spdxVersion is missing', () => {
const sbom: SBOM = {
type: 'spdx',
object: { SPDXID: 'SPDXRef-DOCUMENT' }
}
expect(() => generateSBOMPredicate(sbom)).toThrow(
/Cannot find spdxVersion/
)
})
})
describe('CycloneDX predicates', () => {
it('should generate predicate with correct CycloneDX type URL', () => {
const sbom: SBOM = {
type: 'cyclonedx',
object: {
bomFormat: 'CycloneDX',
specVersion: '1.4',
serialNumber: 'urn:uuid:12345'
}
}
const predicate = generateSBOMPredicate(sbom)
expect(predicate.type).toBe('https://cyclonedx.org/bom')
expect(predicate.params).toEqual(sbom.object)
})
})
describe('unsupported types', () => {
it('should throw for unsupported SBOM type', () => {
const sbom = {
type: 'unknown' as SBOM['type'],
object: { foo: 'bar' }
}
expect(() => generateSBOMPredicate(sbom)).toThrow(/Unsupported SBOM format/)
})
})
})

View File

@@ -0,0 +1,27 @@
import { highlight, mute } from '../../src/style'
describe('style', () => {
describe('highlight', () => {
it('should wrap text with cyan ANSI color codes', () => {
const result = highlight('test message')
expect(result).toBe('\x1B[36mtest message\x1B[39m')
})
it('should handle empty strings', () => {
const result = highlight('')
expect(result).toBe('\x1B[36m\x1B[39m')
})
})
describe('mute', () => {
it('should wrap text with gray ANSI color codes', () => {
const result = mute('test message')
expect(result).toBe('\x1B[38;5;244mtest message\x1B[39m')
})
it('should handle empty strings', () => {
const result = mute('')
expect(result).toBe('\x1B[38;5;244m\x1B[39m')
})
})
})

View File

@@ -0,0 +1,462 @@
import crypto from 'crypto'
import fs from 'fs/promises'
import os from 'os'
import path from 'path'
import {
subjectFromInputs,
formatSubjectDigest,
SubjectInputs
} from '../../src/subject'
describe('subjectFromInputs', () => {
const blankInputs: SubjectInputs = {
subjectPath: '',
subjectName: '',
subjectDigest: '',
subjectChecksums: ''
}
let tempDir: string
beforeEach(async () => {
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'subject-test-'))
})
afterEach(async () => {
await fs.rm(tempDir, { recursive: true, force: true })
})
describe('input validation', () => {
it('should throw when no inputs are provided', async () => {
await expect(subjectFromInputs(blankInputs)).rejects.toThrow(
/one of subject-path, subject-digest, or subject-checksums must be provided/i
)
})
it('should throw when multiple subject inputs are provided', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectPath: '/some/path',
subjectDigest: 'sha256:abc123'
}
await expect(subjectFromInputs(inputs)).rejects.toThrow(
/only one of subject-path, subject-digest, or subject-checksums may be provided/i
)
})
it('should throw when subject-digest is provided without subject-name', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectDigest: 'sha256:7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32'
}
await expect(subjectFromInputs(inputs)).rejects.toThrow(
/subject-name must be provided when using subject-digest/i
)
})
})
describe('with subject-digest', () => {
const validDigest = 'sha256:7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32'
it('should return subject with provided name and digest', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectName: 'my-artifact',
subjectDigest: validDigest
}
const subjects = await subjectFromInputs(inputs)
expect(subjects).toHaveLength(1)
expect(subjects[0].name).toBe('my-artifact')
expect(subjects[0].digest).toEqual({
sha256: '7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32'
})
})
it('should lowercase name when downcaseName is true', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectName: 'ghcr.io/FOO/Bar',
subjectDigest: validDigest,
downcaseName: true
}
const subjects = await subjectFromInputs(inputs)
expect(subjects[0].name).toBe('ghcr.io/foo/bar')
})
it('should throw for malformed digest format', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectName: 'artifact',
subjectDigest: 'invalid-digest'
}
await expect(subjectFromInputs(inputs)).rejects.toThrow(
/subject-digest must be in the format/
)
})
it('should throw for unsupported hash algorithm', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectName: 'artifact',
subjectDigest: 'md5:d41d8cd98f00b204e9800998ecf8427e'
}
await expect(subjectFromInputs(inputs)).rejects.toThrow(
/subject-digest must be in the format/
)
})
it('should throw for incorrect sha256 digest length', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectName: 'artifact',
subjectDigest: 'sha256:deadbeef'
}
await expect(subjectFromInputs(inputs)).rejects.toThrow(
/subject-digest must be in the format/
)
})
})
describe('with subject-path', () => {
const fileContent = 'test file content'
const expectedDigest = crypto.createHash('sha256').update(fileContent).digest('hex')
it('should calculate digest from file', async () => {
const filePath = path.join(tempDir, 'artifact.bin')
await fs.writeFile(filePath, fileContent)
const inputs: SubjectInputs = {
...blankInputs,
subjectPath: filePath
}
const subjects = await subjectFromInputs(inputs)
expect(subjects).toHaveLength(1)
expect(subjects[0].name).toBe('artifact.bin')
expect(subjects[0].digest).toEqual({ sha256: expectedDigest })
})
it('should use provided name instead of filename', async () => {
const filePath = path.join(tempDir, 'artifact.bin')
await fs.writeFile(filePath, fileContent)
const inputs: SubjectInputs = {
...blankInputs,
subjectPath: filePath,
subjectName: 'custom-name'
}
const subjects = await subjectFromInputs(inputs)
expect(subjects[0].name).toBe('custom-name')
})
it('should throw when file does not exist', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectPath: '/nonexistent/file'
}
await expect(subjectFromInputs(inputs)).rejects.toThrow(
/could not find subject at path/i
)
})
describe('glob patterns', () => {
beforeEach(async () => {
for (let i = 0; i < 3; i++) {
await fs.writeFile(path.join(tempDir, `file-${i}.txt`), fileContent)
}
})
it('should expand glob pattern to multiple subjects', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectPath: path.join(tempDir, 'file-*.txt')
}
const subjects = await subjectFromInputs(inputs)
expect(subjects).toHaveLength(3)
expect(subjects.map(s => s.name).sort()).toEqual([
'file-0.txt',
'file-1.txt',
'file-2.txt'
])
})
it('should handle comma-separated paths', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectPath: `${path.join(tempDir, 'file-0.txt')},${path.join(tempDir, 'file-1.txt')}`
}
const subjects = await subjectFromInputs(inputs)
expect(subjects).toHaveLength(2)
})
it('should handle newline-separated paths', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectPath: `${path.join(tempDir, 'file-0.txt')}\n${path.join(tempDir, 'file-2.txt')}`
}
const subjects = await subjectFromInputs(inputs)
expect(subjects).toHaveLength(2)
})
it('should support exclusion patterns', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectPath: `${path.join(tempDir, 'file-*.txt')},!${path.join(tempDir, 'file-1.txt')}`
}
const subjects = await subjectFromInputs(inputs)
expect(subjects).toHaveLength(2)
expect(subjects.map(s => s.name)).not.toContain('file-1.txt')
})
it('should deduplicate subjects with same name and digest', async () => {
// Create another directory with same file
const otherDir = await fs.mkdtemp(path.join(os.tmpdir(), 'subject-dup-'))
await fs.writeFile(path.join(otherDir, 'file-0.txt'), fileContent)
const inputs: SubjectInputs = {
...blankInputs,
subjectPath: `${path.join(tempDir, 'file-0.txt')},${path.join(otherDir, 'file-0.txt')}`
}
const subjects = await subjectFromInputs(inputs)
expect(subjects).toHaveLength(1)
await fs.rm(otherDir, { recursive: true, force: true })
})
})
it('should exclude directories from glob results', async () => {
await fs.mkdir(path.join(tempDir, 'subdir'))
await fs.writeFile(path.join(tempDir, 'file.txt'), fileContent)
const inputs: SubjectInputs = {
...blankInputs,
subjectPath: path.join(tempDir, '*')
}
const subjects = await subjectFromInputs(inputs)
expect(subjects).toHaveLength(1)
expect(subjects[0].name).toBe('file.txt')
})
it('should throw when too many subjects are specified', async () => {
// Create 1025 files (exceeds MAX_SUBJECT_COUNT of 1024)
for (let i = 0; i < 1025; i++) {
await fs.writeFile(path.join(tempDir, `file-${i}.txt`), `content-${i}`)
}
const inputs: SubjectInputs = {
...blankInputs,
subjectPath: path.join(tempDir, 'file-*.txt')
}
await expect(subjectFromInputs(inputs)).rejects.toThrow(/too many subjects/i)
})
})
describe('with subject-checksums', () => {
describe('from string', () => {
it('should parse sha256 checksums', async () => {
const checksums = `187dcd1506a170337415589ff00c8743f19d41cc31fca246c2739dfd450d0b9d artifact-linux
9ecbf449e286a8a8748c161c52aa28b6b2fc64ab86f94161c5d1b3abc18156c5 artifact-darwin`
const inputs: SubjectInputs = {
...blankInputs,
subjectChecksums: checksums
}
const subjects = await subjectFromInputs(inputs)
expect(subjects).toHaveLength(2)
expect(subjects).toContainEqual({
name: 'artifact-linux',
digest: { sha256: '187dcd1506a170337415589ff00c8743f19d41cc31fca246c2739dfd450d0b9d' }
})
expect(subjects).toContainEqual({
name: 'artifact-darwin',
digest: { sha256: '9ecbf449e286a8a8748c161c52aa28b6b2fc64ab86f94161c5d1b3abc18156c5' }
})
})
it('should parse sha512 checksums', async () => {
const sha512 = '5d8b4751ef31f9440d843fcfa4e53ca2e25b1cb1f13fd355fdc7c24b41fe645293291ea9297ba3989078abb77ebbaac66be073618a9e4974dbd0361881d4c718'
const checksums = `${sha512} artifact-amd64`
const inputs: SubjectInputs = {
...blankInputs,
subjectChecksums: checksums
}
const subjects = await subjectFromInputs(inputs)
expect(subjects).toHaveLength(1)
expect(subjects[0].digest).toEqual({ sha512 })
})
it('should handle binary mode flag (*)', async () => {
const checksums = `187dcd1506a170337415589ff00c8743f19d41cc31fca246c2739dfd450d0b9d *artifact.bin`
const inputs: SubjectInputs = {
...blankInputs,
subjectChecksums: checksums
}
const subjects = await subjectFromInputs(inputs)
expect(subjects[0].name).toBe('artifact.bin')
})
it('should handle text mode flag (space)', async () => {
const checksums = `187dcd1506a170337415589ff00c8743f19d41cc31fca246c2739dfd450d0b9d artifact.txt`
const inputs: SubjectInputs = {
...blankInputs,
subjectChecksums: checksums
}
const subjects = await subjectFromInputs(inputs)
expect(subjects[0].name).toBe('artifact.txt')
})
it('should handle checksums without mode flag', async () => {
// Single space between digest and name (no flag character)
const checksums = `187dcd1506a170337415589ff00c8743f19d41cc31fca246c2739dfd450d0b9d artifact-no-flag`
const inputs: SubjectInputs = {
...blankInputs,
subjectChecksums: checksums
}
const subjects = await subjectFromInputs(inputs)
expect(subjects[0].name).toBe('artifact-no-flag')
})
it('should skip malformed lines', async () => {
const checksums = `187dcd1506a170337415589ff00c8743f19d41cc31fca246c2739dfd450d0b9d valid-artifact
badline
9ecbf449e286a8a8748c161c52aa28b6b2fc64ab86f94161c5d1b3abc18156c5 another-artifact`
const inputs: SubjectInputs = {
...blankInputs,
subjectChecksums: checksums
}
const subjects = await subjectFromInputs(inputs)
expect(subjects).toHaveLength(2)
})
it('should deduplicate identical entries', async () => {
const checksums = `187dcd1506a170337415589ff00c8743f19d41cc31fca246c2739dfd450d0b9d artifact
187dcd1506a170337415589ff00c8743f19d41cc31fca246c2739dfd450d0b9d artifact`
const inputs: SubjectInputs = {
...blankInputs,
subjectChecksums: checksums
}
const subjects = await subjectFromInputs(inputs)
expect(subjects).toHaveLength(1)
})
it('should throw for invalid digest characters', async () => {
const checksums = `!!!!e68a080799ca83104630b56abb90d8dbcc5f8b5a8639cb691e269838f29e artifact`
const inputs: SubjectInputs = {
...blankInputs,
subjectChecksums: checksums
}
await expect(subjectFromInputs(inputs)).rejects.toThrow(/invalid digest/i)
})
it('should throw for unknown digest algorithm', async () => {
const checksums = `f861e artifact`
const inputs: SubjectInputs = {
...blankInputs,
subjectChecksums: checksums
}
await expect(subjectFromInputs(inputs)).rejects.toThrow(/unknown digest algorithm/i)
})
})
describe('from file', () => {
it('should read checksums from file', async () => {
const checksumFile = path.join(tempDir, 'SHA256SUMS')
const checksums = `187dcd1506a170337415589ff00c8743f19d41cc31fca246c2739dfd450d0b9d artifact-linux
9ecbf449e286a8a8748c161c52aa28b6b2fc64ab86f94161c5d1b3abc18156c5 artifact-darwin`
await fs.writeFile(checksumFile, checksums)
const inputs: SubjectInputs = {
...blankInputs,
subjectChecksums: checksumFile
}
const subjects = await subjectFromInputs(inputs)
expect(subjects).toHaveLength(2)
})
it('should throw when checksums path is a directory', async () => {
const inputs: SubjectInputs = {
...blankInputs,
subjectChecksums: tempDir
}
await expect(subjectFromInputs(inputs)).rejects.toThrow(
/subject checksums file not found/i
)
})
})
})
})
describe('formatSubjectDigest', () => {
it('should format digest as algorithm:hash', () => {
const subject = {
name: 'artifact',
digest: { sha256: 'abc123def456' }
}
expect(formatSubjectDigest(subject)).toBe('sha256:abc123def456')
})
it('should use first algorithm alphabetically when multiple exist', () => {
const subject = {
name: 'artifact',
digest: {
sha512: 'longer-hash',
sha256: 'shorter-hash'
}
}
expect(formatSubjectDigest(subject)).toBe('sha256:shorter-hash')
})
})

View File

@@ -30,21 +30,34 @@ inputs:
attestation. Must specify exactly one of "subject-path", "subject-digest",
or "subject-checksums".
required: false
subject-version:
description: >
Version of the subject for the attestation. Only used when
"push-to-registry" and "create-storage-record" are both set to true.
required: false
sbom-path:
description: >
Path to the JSON-formatted SBOM file (SPDX or CycloneDX) to attest.
File size cannot exceed 16MB. When provided, creates an SBOM attestation.
Cannot be used together with "predicate-type", "predicate", or
"predicate-path".
required: false
predicate-type:
description: >
URI identifying the type of the predicate.
required: true
URI identifying the type of the predicate. Required when using "predicate"
or "predicate-path" for custom attestations.
required: false
predicate:
description: >
String containing the value for the attestation predicate. String length
cannot exceed 16MB. Must supply exactly one of "predicate-path" or
"predicate".
"predicate" when creating custom attestations.
required: false
predicate-path:
description: >
Path to the file which contains the content for the attestation predicate.
File size cannot exceed 16MB. Must supply exactly one of "predicate-path"
or "predicate".
or "predicate" when creating custom attestations.
required: false
push-to-registry:
description: >
@@ -53,6 +66,12 @@ inputs:
the "subject-digest" parameter be specified. Defaults to false.
default: false
required: false
create-storage-record:
description: >
Whether to create a storage record for the artifact.
Requires that push-to-registry is set to true. Defaults to true.
default: true
required: false
show-summary:
description: >
Whether to attach a list of generated attestations to the workflow run
@@ -71,7 +90,9 @@ outputs:
description: 'The ID of the attestation.'
attestation-url:
description: 'The URL for the attestation summary.'
storage-record-ids:
description: 'The IDs of the storage records created for the artifact.'
runs:
using: node20
using: node24
main: ./dist/index.js

40
dist/606.index.js generated vendored
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');
/***/ })
};
;

125850
dist/index.js generated vendored

File diff suppressed because one or more lines are too long

3
dist/package.json generated vendored Normal file
View File

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

View File

@@ -1,13 +1,14 @@
import eslint from '@eslint/js'
import importplugin from 'eslint-plugin-import'
import jestplugin from 'eslint-plugin-jest'
import path from 'node:path'
import tseslint from 'typescript-eslint'
export default tseslint.config(
// Ignore non-project files
{
name: 'ignore',
ignores: ['.github', 'dist', 'coverage', '**/*.json', 'jest.setup.js']
ignores: ['.github', 'dist', 'coverage', '**/*.json', 'jest.setup.js', 'eslint.config.mjs']
},
// Use recommended rules from ESLint, TypeScript, and other plugins
eslint.configs.recommended,
@@ -21,7 +22,7 @@ export default tseslint.config(
languageOptions: {
ecmaVersion: 2023,
parserOptions: {
project: ['./.github/linters/tsconfig.json', './tsconfig.json']
project: [ './tsconfig.lint.json' ]
}
},
rules: {

View File

@@ -1 +1,3 @@
import { jest } from '@jest/globals'
process.stdout.write = jest.fn()

12297
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,10 @@
{
"name": "actions/attest",
"description": "Generate signed attestations for workflow artifacts",
"version": "2.4.0",
"version": "4.1.0",
"author": "",
"private": true,
"type": "module",
"homepage": "https://github.com/actions/attest",
"repository": {
"type": "git",
@@ -20,24 +21,27 @@
".": "./dist/index.js"
},
"engines": {
"node": ">=20"
"node": ">=24"
},
"scripts": {
"bundle": "npm run format:write && npm run package",
"ci-test": "jest",
"ci-test": "NODE_OPTIONS='--experimental-vm-modules' jest",
"format:write": "prettier --write **/*.ts",
"format:check": "prettier --check **/*.ts",
"lint:eslint": "npx eslint . -c ./.github/linters/eslint.config.mjs",
"lint:markdown": "npx markdownlint --config .github/linters/.markdown-lint.yml \"*.md\"",
"lint:eslint": "npx eslint",
"lint:markdown": "npx markdownlint --config .markdown-lint.yml \"*.md\"",
"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_OPTIONS='--experimental-vm-modules' jest",
"all": "npm run format:write && npm run lint && npm run test && npm run package"
},
"license": "MIT",
"jest": {
"preset": "ts-jest",
"preset": "ts-jest/presets/default-esm",
"extensionsToTreatAsEsm": [
".ts"
],
"setupFilesAfterEnv": [
"./jest.setup.js"
],
@@ -56,7 +60,12 @@
"/dist/"
],
"transform": {
"^.+\\.ts$": "ts-jest"
"^.+\\.ts$": [
"ts-jest",
{
"useESM": true
}
]
},
"coverageReporters": [
"json-summary",
@@ -69,31 +78,32 @@
]
},
"dependencies": {
"@actions/attest": "^1.6.0",
"@actions/core": "^1.11.1",
"@actions/github": "^6.0.1",
"@actions/glob": "^0.5.0",
"@sigstore/oci": "^0.5.0",
"@actions/attest": "^3.2.0",
"@actions/core": "^3.0.0",
"@actions/github": "^9.0.0",
"@actions/glob": "^0.6.1",
"@sigstore/oci": "^0.6.0",
"csv-parse": "^5.6.0"
},
"devDependencies": {
"@eslint/js": "^9.28.0",
"@sigstore/mock": "^0.10.0",
"@types/jest": "^29.5.14",
"@eslint/js": "^9.39.2",
"@jest/globals": "^30.3.0",
"@sigstore/mock": "^0.12.0",
"@types/jest": "^30.0.0",
"@types/make-fetch-happen": "^10.0.4",
"@types/node": "^22.15.30",
"@vercel/ncc": "^0.38.3",
"eslint": "^9.28.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jest": "^28.13.0",
"jest": "^29.7.0",
"js-yaml": "^4.1.0",
"markdownlint-cli": "^0.45.0",
"@types/node": "^25.6.0",
"@vercel/ncc": "^0.38.4",
"eslint": "^9.39.2",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jest": "^29.15.2",
"jest": "^30.3.0",
"js-yaml": "^4.1.1",
"markdownlint-cli": "^0.48.0",
"nock": "^13.5.6",
"prettier": "^3.5.3",
"ts-jest": "^29.3.4",
"typescript": "^5.8.3",
"typescript-eslint": "^8.34.0",
"undici": "^5.29.0"
"prettier": "^3.8.3",
"ts-jest": "^29.4.9",
"typescript": "^5.9.3",
"typescript-eslint": "^8.58.2",
"undici": "^7.24.4"
}
}

View File

@@ -1,4 +1,12 @@
import { Attestation, Predicate, Subject, attest } from '@actions/attest'
import {
Attestation,
Predicate,
Subject,
attest,
createStorageRecord
} from '@actions/attest'
import * as core from '@actions/core'
import * as github from '@actions/github'
import { attachArtifactToImage, getRegistryCredentials } from '@sigstore/oci'
import { formatSubjectDigest } from './subject'
@@ -8,6 +16,7 @@ const OCI_RETRY = 3
export type SigstoreInstance = 'public-good' | 'github'
export type AttestResult = Attestation & {
attestationDigest?: string
storageRecordIds?: number[]
}
export const createAttestation = async (
@@ -16,6 +25,8 @@ export const createAttestation = async (
opts: {
sigstoreInstance: SigstoreInstance
pushToRegistry: boolean
createStorageRecord: boolean
subjectVersion?: string
githubToken: string
}
): Promise<AttestResult> => {
@@ -33,10 +44,11 @@ export const createAttestation = async (
if (subjects.length === 1 && opts.pushToRegistry) {
const subject = subjects[0]
const credentials = getRegistryCredentials(subject.name)
const subjectDigest = formatSubjectDigest(subject)
const artifact = await attachArtifactToImage({
credentials,
imageName: subject.name,
imageDigest: formatSubjectDigest(subject),
imageDigest: subjectDigest,
artifact: Buffer.from(JSON.stringify(attestation.bundle)),
mediaType: attestation.bundle.mediaType,
annotations: {
@@ -48,7 +60,80 @@ export const createAttestation = async (
// Add the attestation's digest to the result
result.attestationDigest = artifact.digest
// Because creating a storage record requires the 'artifact-metadata:write'
// permission, we wrap this in a try/catch to avoid failing the entire
// attestation process if the token does not have the correct permissions.
if (opts.createStorageRecord) {
try {
const token = opts.githubToken
const isOrg = await repoOwnerIsOrg(token)
if (!isOrg) {
// The Artifact Metadata Storage Record API is only available to
// organizations. So if the repo owner is not an organization,
// storage record creation should not be attempted.
return result
}
const registryUrl = getRegistryURL(subject.name)
const artifactOpts = {
name: subject.name,
digest: subjectDigest,
version: opts.subjectVersion || undefined
}
const packageRegistryOpts = {
registryUrl
}
const records = await createStorageRecord(
artifactOpts,
packageRegistryOpts,
token
)
if (!records || records.length === 0) {
core.warning('No storage records were created.')
}
result.storageRecordIds = records
} catch (error) {
core.warning(`Failed to create storage record: ${error}`)
core.warning(
'Please check that the "artifact-metadata:write" permission has been included'
)
}
}
}
return result
}
// Call the GET /repos/{owner}/{repo} endpoint to determine if the repo
// owner is an organization. This is used to determine if storage
// record creation should be attempted.
export const repoOwnerIsOrg = async (githubToken: string): Promise<boolean> => {
const octokit = github.getOctokit(githubToken)
const { data: repo } = await octokit.rest.repos.get({
owner: github.context.repo.owner,
repo: github.context.repo.repo
})
return repo.owner?.type === 'Organization'
}
function getRegistryURL(subjectName: string): string {
let url: URL
try {
url = new URL(subjectName)
} catch {
url = new URL(`https://${subjectName}`)
}
/* istanbul ignore if */
if (url.protocol !== 'https:') {
throw new Error(
`Unsupported protocol ${url.protocol} in subject name ${subjectName}`
)
}
return url.origin
}

45
src/detect.ts Normal file
View File

@@ -0,0 +1,45 @@
export type AttestationType = 'provenance' | 'sbom' | 'custom'
export type DetectionInputs = {
sbomPath: string
predicateType: string
predicate: string
predicatePath: string
}
export const detectAttestationType = (
inputs: DetectionInputs
): AttestationType => {
const { sbomPath, predicateType, predicate, predicatePath } = inputs
// SBOM mode takes priority
if (sbomPath) {
return 'sbom'
}
// Custom mode when any predicate inputs are provided
if (predicateType || predicate || predicatePath) {
return 'custom'
}
// Default to provenance mode
return 'provenance'
}
export const validateAttestationInputs = (inputs: DetectionInputs): void => {
const { sbomPath, predicateType, predicate, predicatePath } = inputs
// Cannot combine sbom-path with predicate inputs
if (sbomPath && (predicateType || predicate || predicatePath)) {
throw new Error(
'Cannot specify sbom-path together with predicate-type, predicate, or predicate-path'
)
}
// Custom mode requires predicate-type
if ((predicate || predicatePath) && !predicateType) {
throw new Error(
'predicate-type is required when using predicate or predicate-path'
)
}
}

View File

@@ -9,10 +9,13 @@ const inputs: RunInputs = {
subjectName: core.getInput('subject-name'),
subjectDigest: core.getInput('subject-digest'),
subjectChecksums: core.getInput('subject-checksums'),
sbomPath: core.getInput('sbom-path'),
predicateType: core.getInput('predicate-type'),
predicate: core.getInput('predicate'),
predicatePath: core.getInput('predicate-path'),
pushToRegistry: core.getBooleanInput('push-to-registry'),
createStorageRecord: core.getBooleanInput('create-storage-record'),
subjectVersion: core.getInput('subject-version'),
showSummary: core.getBooleanInput('show-summary'),
githubToken: core.getInput('github-token'),
// undocumented -- not part of public interface

View File

@@ -1,11 +1,19 @@
import * as core from '@actions/core'
import * as github from '@actions/github'
import fs from 'fs'
import fs from 'fs/promises'
import os from 'os'
import path from 'path'
import { AttestResult, SigstoreInstance, createAttestation } from './attest'
import {
AttestationType,
DetectionInputs,
detectAttestationType,
validateAttestationInputs
} from './detect'
import { SEARCH_PUBLIC_GOOD_URL } from './endpoints'
import { PredicateInputs, predicateFromInputs } from './predicate'
import { generateProvenancePredicate } from './provenance'
import { generateSBOMPredicate, parseSBOMFromPath } from './sbom'
import * as style from './style'
import {
SubjectInputs,
@@ -13,14 +21,21 @@ import {
subjectFromInputs
} from './subject'
import type { Subject } from '@actions/attest'
import type { Predicate, Subject } from '@actions/attest'
const ATTESTATION_FILE_NAME = 'attestation.json'
const ATTESTATION_PATHS_FILE_NAME = 'created_attestation_paths.txt'
export type SBOMInputs = {
sbomPath: string
}
export type RunInputs = SubjectInputs &
PredicateInputs & {
PredicateInputs &
SBOMInputs & {
pushToRegistry: boolean
createStorageRecord: boolean
subjectVersion: string
githubToken: string
showSummary: boolean
privateSigning: boolean
@@ -57,34 +72,50 @@ export async function run(inputs: RunInputs): Promise<void> {
)
}
// Detect attestation type and validate inputs
const detectionInputs: DetectionInputs = {
sbomPath: inputs.sbomPath,
predicateType: inputs.predicateType,
predicate: inputs.predicate,
predicatePath: inputs.predicatePath
}
validateAttestationInputs(detectionInputs)
const attestationType = detectAttestationType(detectionInputs)
logAttestationType(attestationType)
const subjects = await subjectFromInputs({
...inputs,
downcaseName: inputs.pushToRegistry
})
const predicate = predicateFromInputs(inputs)
const outputPath = path.join(tempDir(), ATTESTATION_FILE_NAME)
// Generate predicate based on attestation type
const predicate = await getPredicateForType(attestationType, inputs)
const outputPath = path.join(await tempDir(), ATTESTATION_FILE_NAME)
core.setOutput('bundle-path', outputPath)
const att = await createAttestation(subjects, predicate, {
sigstoreInstance,
pushToRegistry: inputs.pushToRegistry,
createStorageRecord: inputs.createStorageRecord,
subjectVersion: inputs.subjectVersion,
githubToken: inputs.githubToken
})
logAttestation(subjects, att, sigstoreInstance)
// Write attestation bundle to output file
fs.writeFileSync(outputPath, JSON.stringify(att.bundle) + os.EOL, {
await fs.writeFile(outputPath, JSON.stringify(att.bundle) + os.EOL, {
encoding: 'utf-8',
flag: 'a'
})
const baseDir = process.env.RUNNER_TEMP
/* istanbul ignore else */
if (baseDir) {
const outputSummaryPath = path.join(baseDir, ATTESTATION_PATHS_FILE_NAME)
// Append the output path to the attestations paths file
fs.appendFileSync(outputSummaryPath, outputPath + os.EOL, {
await fs.appendFile(outputSummaryPath, outputPath + os.EOL, {
encoding: 'utf-8',
flag: 'a'
})
@@ -94,11 +125,18 @@ export async function run(inputs: RunInputs): Promise<void> {
)
}
/* istanbul ignore else */
if (att.attestationID) {
core.setOutput('attestation-id', att.attestationID)
core.setOutput('attestation-url', attestationURL(att.attestationID))
}
/* istanbul ignore if */
if (att.storageRecordIds) {
core.setOutput('storage-record-ids', att.storageRecordIds.join(','))
}
/* istanbul ignore else */
if (inputs.showSummary) {
await logSummary(att)
}
@@ -147,6 +185,7 @@ const logAttestation = (
core.info(attestation.certificate)
core.endGroup()
/* istanbul ignore if */
if (attestation.tlogID) {
core.info(
style.highlight(
@@ -156,6 +195,7 @@ const logAttestation = (
core.info(`${SEARCH_PUBLIC_GOOD_URL}?logIndex=${attestation.tlogID}`)
}
/* istanbul ignore else */
if (attestation.attestationID) {
core.info(style.highlight('Attestation uploaded to repository'))
core.info(attestationURL(attestation.attestationID))
@@ -165,12 +205,19 @@ const logAttestation = (
core.info(style.highlight('Attestation uploaded to registry'))
core.info(`${subjects[0].name}@${attestation.attestationDigest}`)
}
/* istanbul ignore next */
if (attestation.storageRecordIds && attestation.storageRecordIds.length > 0) {
core.info(style.highlight('Storage record created'))
core.info(`Storage record IDs: ${attestation.storageRecordIds.join(',')}`)
}
}
// Attach summary information to the GitHub Actions run
const logSummary = async (attestation: AttestResult): Promise<void> => {
const { attestationID } = attestation
/* istanbul ignore else */
if (attestationID) {
const url = attestationURL(attestationID)
core.summary.addHeading('Attestation Created', 3)
@@ -179,7 +226,7 @@ const logSummary = async (attestation: AttestResult): Promise<void> => {
}
}
const tempDir = (): string => {
const tempDir = async (): Promise<string> => {
const basePath = process.env['RUNNER_TEMP']
/* istanbul ignore if */
@@ -187,8 +234,35 @@ const tempDir = (): string => {
throw new Error('Missing RUNNER_TEMP environment variable')
}
return fs.mkdtempSync(path.join(basePath, path.sep))
return fs.mkdtemp(path.join(basePath, path.sep))
}
const attestationURL = (id: string): string =>
`${github.context.serverUrl}/${github.context.repo.owner}/${github.context.repo.repo}/attestations/${id}`
// Log the detected attestation type
const logAttestationType = (type: AttestationType): void => {
const typeLabels: Record<AttestationType, string> = {
provenance: 'Build Provenance',
sbom: 'SBOM',
custom: 'Custom'
}
core.info(`Attestation type: ${typeLabels[type]}`)
}
// Generate predicate based on attestation type
const getPredicateForType = async (
type: AttestationType,
inputs: RunInputs
): Promise<Predicate> => {
switch (type) {
case 'provenance':
return generateProvenancePredicate()
case 'sbom': {
const sbom = await parseSBOMFromPath(inputs.sbomPath)
return generateSBOMPredicate(sbom)
}
case 'custom':
return predicateFromInputs(inputs)
}
}

View File

@@ -1,4 +1,4 @@
import fs from 'fs'
import fs from 'fs/promises'
import type { Predicate } from '@actions/attest'
@@ -12,7 +12,9 @@ const MAX_PREDICATE_SIZE_BYTES = 16 * 1024 * 1024
// Returns the predicate specified by the action's inputs. The predicate value
// may be specified as a path to a file or as a string.
export const predicateFromInputs = (inputs: PredicateInputs): Predicate => {
export const predicateFromInputs = async (
inputs: PredicateInputs
): Promise<Predicate> => {
const { predicateType, predicate, predicatePath } = inputs
if (!predicateType) {
@@ -30,18 +32,22 @@ export const predicateFromInputs = (inputs: PredicateInputs): Predicate => {
let params: string = predicate
if (predicatePath) {
if (!fs.existsSync(predicatePath)) {
try {
await fs.access(predicatePath)
} catch {
throw new Error(`predicate file not found: ${predicatePath}`)
}
const stat = await fs.stat(predicatePath)
/* istanbul ignore next */
if (fs.statSync(predicatePath).size > MAX_PREDICATE_SIZE_BYTES) {
if (stat.size > MAX_PREDICATE_SIZE_BYTES) {
throw new Error(
`predicate file exceeds maximum allowed size: ${MAX_PREDICATE_SIZE_BYTES} bytes`
)
}
params = fs.readFileSync(predicatePath, 'utf-8')
params = await fs.readFile(predicatePath, 'utf-8')
} else {
if (predicate.length > MAX_PREDICATE_SIZE_BYTES) {
throw new Error(

7
src/provenance.ts Normal file
View File

@@ -0,0 +1,7 @@
import { buildSLSAProvenancePredicate } from '@actions/attest'
import type { Predicate } from '@actions/attest'
export const generateProvenancePredicate = async (): Promise<Predicate> => {
return buildSLSAProvenancePredicate()
}

96
src/sbom.ts Normal file
View File

@@ -0,0 +1,96 @@
import fs from 'fs/promises'
import type { Predicate } from '@actions/attest'
export type SBOM = {
type: 'spdx' | 'cyclonedx'
object: object
}
// SBOMs cannot exceed 16MB.
const MAX_SBOM_SIZE_BYTES = 16 * 1024 * 1024
export const parseSBOMFromPath = async (filePath: string): Promise<SBOM> => {
let stats
try {
stats = await fs.stat(filePath)
} catch (error) {
const err = error as NodeJS.ErrnoException
if (err.code === 'ENOENT') {
throw new Error('SBOM file not found')
}
throw error
}
if (stats.size > MAX_SBOM_SIZE_BYTES) {
throw new Error(
`SBOM file exceeds maximum allowed size: ${MAX_SBOM_SIZE_BYTES} bytes`
)
}
const fileContent = await fs.readFile(filePath, 'utf8')
const sbom = JSON.parse(fileContent) as object
if (checkIsSPDX(sbom)) {
return { type: 'spdx', object: sbom }
} else if (checkIsCycloneDX(sbom)) {
return { type: 'cyclonedx', object: sbom }
}
throw new Error(
'Unsupported SBOM format. Must be valid SPDX or CycloneDX JSON.'
)
}
const checkIsSPDX = (sbomObject: {
spdxVersion?: string
SPDXID?: string
}): boolean => {
return !!(sbomObject?.spdxVersion && sbomObject?.SPDXID)
}
const checkIsCycloneDX = (sbomObject: {
bomFormat?: string
serialNumber?: string
specVersion?: string
}): boolean => {
return !!(
sbomObject?.bomFormat &&
sbomObject?.serialNumber &&
sbomObject?.specVersion
)
}
export const generateSBOMPredicate = (sbom: SBOM): Predicate => {
switch (sbom.type) {
case 'spdx':
return generateSPDXPredicate(sbom.object)
case 'cyclonedx':
return generateCycloneDXPredicate(sbom.object)
default:
throw new Error('Unsupported SBOM format')
}
}
// ref: https://github.com/in-toto/attestation/blob/main/spec/predicates/spdx.md
const generateSPDXPredicate = (sbom: object): Predicate => {
const spdxVersion = (sbom as { spdxVersion?: string })?.['spdxVersion']
if (!spdxVersion) {
throw new Error('Cannot find spdxVersion in the SBOM')
}
const version = spdxVersion.split('-')[1]
return {
type: `https://spdx.dev/Document/v${version}`,
params: sbom
}
}
// ref: https://github.com/in-toto/attestation/blob/main/spec/predicates/cyclonedx.md
const generateCycloneDXPredicate = (sbom: object): Predicate => {
return {
type: 'https://cyclonedx.org/bom',
params: sbom
}
}

View File

@@ -2,7 +2,8 @@ import * as glob from '@actions/glob'
import assert from 'assert'
import crypto from 'crypto'
import { parse } from 'csv-parse/sync'
import fs from 'fs'
import { createReadStream } from 'fs'
import fs from 'fs/promises'
import os from 'os'
import path from 'path'
@@ -64,7 +65,7 @@ export const subjectFromInputs = async (
case !!subjectDigest:
return [getSubjectFromDigest(subjectDigest, name)]
case !!subjectChecksums:
return getSubjectFromChecksums(subjectChecksums)
return await getSubjectFromChecksums(subjectChecksums)
/* istanbul ignore next */
default:
// This should be unreachable, but TS requires a default case
@@ -93,13 +94,18 @@ const getSubjectFromPath = async (
// Expand the globbed paths to a list of actual paths
const paths = await glob.create(subjectPaths).then(async g => g.glob())
// Filter path list to just the files (not directories)
const files = paths.filter(p => fs.statSync(p).isFile())
if (files.length > MAX_SUBJECT_COUNT) {
throw new Error(
`Too many subjects specified. The maximum number of subjects is ${MAX_SUBJECT_COUNT}.`
)
// Filter path list to just the files (not directories), enforcing the maximum
const files: string[] = []
for (const p of paths) {
const stat = await fs.stat(p)
if (stat.isFile()) {
if (files.length >= MAX_SUBJECT_COUNT) {
throw new Error(
`Too many subjects specified (>${MAX_SUBJECT_COUNT}). The maximum number of subjects is ${MAX_SUBJECT_COUNT}.`
)
}
files.push(p)
}
}
for (const file of files) {
@@ -142,16 +148,21 @@ const getSubjectFromDigest = (
}
}
const getSubjectFromChecksums = (subjectChecksums: string): Subject[] => {
if (fs.existsSync(subjectChecksums)) {
const getSubjectFromChecksums = async (
subjectChecksums: string
): Promise<Subject[]> => {
try {
await fs.access(subjectChecksums)
return getSubjectFromChecksumsFile(subjectChecksums)
} else {
} catch {
return getSubjectFromChecksumsString(subjectChecksums)
}
}
const getSubjectFromChecksumsFile = (checksumsPath: string): Subject[] => {
const stats = fs.statSync(checksumsPath)
const getSubjectFromChecksumsFile = async (
checksumsPath: string
): Promise<Subject[]> => {
const stats = await fs.stat(checksumsPath)
if (!stats.isFile()) {
throw new Error(`subject checksums file not found: ${checksumsPath}`)
}
@@ -163,7 +174,7 @@ const getSubjectFromChecksumsFile = (checksumsPath: string): Subject[] => {
)
}
const checksums = fs.readFileSync(checksumsPath, 'utf-8')
const checksums = await fs.readFile(checksumsPath, 'utf-8')
return getSubjectFromChecksumsString(checksums)
}
@@ -181,18 +192,29 @@ const getSubjectFromChecksumsString = (checksums: string): Subject[] => {
continue
}
// Swallow the type identifier character at the beginning of the name
const name = record.slice(delimIndex + 2)
// It's common for checksum records to have a leading flag character before
// the artifact name. It will be either a '*' or a space.
const flag_and_name = record.slice(delimIndex + 1)
const name =
flag_and_name.startsWith('*') || flag_and_name.startsWith(' ')
? flag_and_name.slice(1)
: flag_and_name
const digest = record.slice(0, delimIndex)
if (!HEX_STRING_RE.test(digest)) {
throw new Error(`Invalid digest: ${digest}`)
}
subjects.push({
name,
digest: { [digestAlgorithm(digest)]: digest }
})
const alg = digestAlgorithm(digest)
// Only add the subject if it is not already in the list (deduplicate by name & digest)
if (!subjects.some(s => s.name === name && s.digest[alg] === digest)) {
subjects.push({
name,
digest: { [alg]: digest }
})
}
}
return subjects
@@ -207,7 +229,7 @@ const digestFile = async (
): Promise<string> => {
return new Promise((resolve, reject) => {
const hash = crypto.createHash(algorithm).setEncoding('hex')
fs.createReadStream(filePath)
createReadStream(filePath)
.once('error', reject)
.pipe(hash)
.once('finish', () => resolve(hash.read()))

View File

@@ -2,9 +2,10 @@
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"module": "ESNext",
"rootDir": "./src",
"moduleResolution": "NodeNext",
"moduleResolution": "Bundler",
"isolatedModules": true,
"baseUrl": "./",
"sourceMap": true,
"outDir": "./dist",

9
tsconfig.lint.json Normal file
View File

@@ -0,0 +1,9 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true
},
"include": ["./__tests__/**/*", "./src/**/*"],
"exclude": ["./dist", "./node_modules", "./coverage", "*.json"]
}