Merge remote-tracking branch 'origin/main' into prepare-cache-release-v5.0.0

This commit is contained in:
Salman Muin Kayser Chishti
2025-12-11 14:27:41 +00:00
10 changed files with 332 additions and 75 deletions
+5 -5
View File
@@ -69,16 +69,16 @@ jobs:
id-token: write
steps:
- name: Set Node.js 24.x
uses: actions/setup-node@v5
with:
node-version: 24.x
- name: download artifact
uses: actions/download-artifact@v4
with:
name: ${{ github.event.inputs.package }}
- name: setup authentication
run: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" >> .npmrc
env:
NPM_TOKEN: ${{ secrets.TOKEN }}
- name: publish
run: npm publish --provenance *.tgz
+76
View File
@@ -15,6 +15,14 @@ initiated.
See [Using artifact attestations to establish provenance for builds](https://docs.github.com/en/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds)
for more information on artifact attestations.
## Table of Contents
- [Usage](#usage)
- [attest](#attest)
- [attestProvenance](#attestprovenance)
- [Attestation](#attestation)
- [Sigstore Instance](#sigstore-instance)
- [Storage](#storage)
## Usage
### `attest`
@@ -165,6 +173,74 @@ export type Attestation = {
For details about the Sigstore bundle format, see the [Bundle protobuf
specification](https://github.com/sigstore/protobuf-specs/blob/main/protos/sigstore_bundle.proto).
### createStorageRecord
The `createStorageRecord` function creates an
[artifact metadata storage record](https://docs.github.com/en/rest/orgs/artifact-metadata?apiVersion=2022-11-28#create-artifact-metadata-storage-record)
on behalf of an attested artifact. It accepts parameters defining artifact
and package registry details. The storage record contains metadata about where the artifact is stored on a given package registry.
```js
const { createStorageRecord } = require('@actions/attest');
const core = require('@actions/core');
async function run() {
// In order to persist attestations to the repo, this should be a token with
// repository write permissions.
const ghToken = core.getInput('gh-token');
const record = await createStorageRecord(
artifactOptions: {
name: 'my-artifact-name',
digest: { 'sha256': '36ab4667...'},
version: "v1.0.0"
},
packageRegistryOptions: {
registryUrl: "https://my-fave-pkg-registry.com"
},
token: ghToken
);
console.log(record);
}
run();
```
The `createStorageRecord` function supports the following options:
```typescript
// Artifact details to associate the record with
export type ArtifactOptions = {
// The name of the artifact
name: string
// The digest of the artifact
digest: string
// The version of the artifact
version?: string
// The status of the artifact
status?: string
}
// Includes details about the package registry the artifact was published to
export type PackageRegistryOptions = {
// The URL of the package registry
registryUrl: string
// The URL of the artifact in the package registry
artifactUrl?: string
// The package registry repository the artifact was published to.
repo?: string
// The path of the artifact in the package registry repository.
path?: string
}
// GitHub token for writing attestations.
token: string
// Optional parameters for the write operation.
// The number of times to retry the request.
retryAttempts?: number
// HTTP headers to include in request to Artifact Metadata API.
headers?: RequestHeaders
```
## Sigstore Instance
When generating the signed attestation there are two different Sigstore
@@ -0,0 +1,137 @@
import {MockAgent, setGlobalDispatcher} from 'undici'
import {createStorageRecord} from '../src/artifactMetadata'
describe('createStorageRecord', () => {
const originalEnv = process.env
const token = 'token'
const headers = {'X-GitHub-Foo': 'true'}
const artifactOptions = {
name: 'my-lib',
version: '1.0.0',
digest: `sha256:${'a'.repeat(64)}`
}
const packageRegistryOptions = {
registryUrl: 'https://my-registry.org'
}
const mockAgent = new MockAgent()
setGlobalDispatcher(mockAgent)
beforeEach(() => {
process.env = {
...originalEnv,
GITHUB_REPOSITORY: 'foo/bar'
}
})
afterEach(() => {
process.env = originalEnv
})
describe('when the api call is successful', () => {
beforeEach(() => {
mockAgent
.get('https://api.github.com')
.intercept({
path: '/orgs/foo/artifacts/metadata/storage-record',
method: 'POST',
headers: {authorization: `token ${token}`, ...headers},
body: JSON.stringify({
name: 'my-lib',
version: '1.0.0',
digest: `sha256:${'a'.repeat(64)}`,
registry_url: 'https://my-registry.org'
})
})
.reply(200, {storage_records: [{id: 123}, {id: 456}]})
})
it('persists the storage record', async () => {
await expect(
createStorageRecord(
artifactOptions,
packageRegistryOptions,
token,
undefined,
headers
)
).resolves.toEqual([123, 456])
})
})
describe('when the api call fails', () => {
beforeEach(() => {
mockAgent
.get('https://api.github.com')
.intercept({
path: '/orgs/foo/artifacts/metadata/storage-record',
method: 'POST',
headers: {authorization: `token ${token}`},
body: JSON.stringify({
name: 'my-lib',
version: '1.0.0',
digest: `sha256:${'a'.repeat(64)}`,
registry_url: 'https://my-registry.org'
})
})
.reply(500, 'oops')
})
it('throws an error', async () => {
await expect(
createStorageRecord(
artifactOptions,
packageRegistryOptions,
token,
0,
headers
)
).rejects.toThrow(/oops/)
})
})
describe('when the api call fails but succeeds on retry', () => {
beforeEach(() => {
const pool = mockAgent.get('https://api.github.com')
pool
.intercept({
path: '/orgs/foo/artifacts/metadata/storage-record',
method: 'POST',
headers: {authorization: `token ${token}`},
body: JSON.stringify({
...artifactOptions,
registry_url: packageRegistryOptions.registryUrl
})
})
.reply(500, 'oops')
.times(1)
pool
.intercept({
path: '/orgs/foo/artifacts/metadata/storage-record',
method: 'POST',
headers: {authorization: `token ${token}`},
body: JSON.stringify({
...artifactOptions,
registry_url: packageRegistryOptions.registryUrl
})
})
.reply(200, {storage_records: [{id: 123}, {id: 456}]})
.times(1)
})
it('persists the storage record', async () => {
await expect(
createStorageRecord(
artifactOptions,
packageRegistryOptions,
token,
undefined,
headers
)
).resolves.toEqual([123, 456])
})
})
})
+2 -3
View File
@@ -1,12 +1,12 @@
{
"name": "@actions/attest",
"version": "2.0.0",
"version": "2.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@actions/attest",
"version": "2.0.0",
"version": "2.1.0",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.11.1",
@@ -192,7 +192,6 @@
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.2.tgz",
"integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@octokit/auth-token": "^4.0.0",
"@octokit/graphql": "^7.1.0",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/attest",
"version": "2.0.0",
"version": "2.1.0",
"description": "Actions attestation lib",
"keywords": [
"github",
@@ -55,4 +55,4 @@
"@octokit/core": "^5.2.0"
}
}
}
}
+85
View File
@@ -0,0 +1,85 @@
import * as github from '@actions/github'
import {retry} from '@octokit/plugin-retry'
import {RequestHeaders} from '@octokit/types'
const CREATE_STORAGE_RECORD_REQUEST =
'POST /orgs/{owner}/artifacts/metadata/storage-record'
const DEFAULT_RETRY_COUNT = 5
/**
* Options for creating a storage record for an attested artifact.
*/
export type ArtifactOptions = {
// Includes details about the attested artifact
// The name of the artifact
name: string
// The digest of the artifact
digest: string
// The version of the artifact
version?: string
// The status of the artifact
status?: string
}
// Includes details about the package registry the artifact was published to
export type PackageRegistryOptions = {
// The URL of the package registry
registryUrl: string
// The URL of the artifact in the package registry
artifactUrl?: string
// The package registry repository the artifact was published to.
repo?: string
// The path of the artifact in the package registry repository.
path?: string
}
/**
* Writes a storage record on behalf of an artifact that has been attested
* @param artifactOptions - parameters for the storage record API request.
* @param packageRegistryOptions - parameters for the package registry API request.
* @param token - GitHub token used to authenticate the request.
* @param retryAttempts - The number of retries to attempt if the request fails.
* @param headers - Additional headers to include in the request.
*
* @returns The ID of the storage record.
* @throws Error if the storage record fails to persist.
*/
export async function createStorageRecord(
artifactOptions: ArtifactOptions,
packageRegistryOptions: PackageRegistryOptions,
token: string,
retryAttempts?: number,
headers?: RequestHeaders
): Promise<number[]> {
const retries = retryAttempts ?? DEFAULT_RETRY_COUNT
const octokit = github.getOctokit(token, {retry: {retries}}, retry)
try {
const response = await octokit.request(CREATE_STORAGE_RECORD_REQUEST, {
owner: github.context.repo.owner,
headers,
...buildRequestParams(artifactOptions, packageRegistryOptions)
})
const data =
typeof response.data == 'string'
? JSON.parse(response.data)
: response.data
return data?.storage_records.map((r: {id: number}) => r.id)
} catch (err) {
const message = err instanceof Error ? err.message : err
throw new Error(`Failed to persist storage record: ${message}`)
}
}
function buildRequestParams(
artifactOptions: ArtifactOptions,
packageRegistryOptions: PackageRegistryOptions
): Record<string, unknown> {
const {registryUrl, artifactUrl, ...rest} = packageRegistryOptions
return {
...artifactOptions,
registry_url: registryUrl,
artifact_url: artifactUrl,
...rest
}
}
+1
View File
@@ -1,3 +1,4 @@
export {createStorageRecord} from './artifactMetadata'
export {AttestOptions, attest} from './attest'
export {
AttestProvenanceOptions,
+3
View File
@@ -1,5 +1,8 @@
# @actions/core Releases
## 2.0.1
- Bump @actions/exec from 1.1.1 to 2.0.0 [#2199](https://github.com/actions/toolkit/pull/2199)
## 2.0.0
- Add support for Node 24 [#2110](https://github.com/actions/toolkit/pull/2110)
- Bump @actions/http-client from 2.0.1 to 3.0.0
+19 -63
View File
@@ -1,15 +1,15 @@
{
"name": "@actions/core",
"version": "2.0.0",
"lockfileVersion": 2,
"version": "2.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@actions/core",
"version": "2.0.0",
"version": "2.0.1",
"license": "MIT",
"dependencies": {
"@actions/exec": "^1.1.1",
"@actions/exec": "^2.0.0",
"@actions/http-client": "^3.0.0"
},
"devDependencies": {
@@ -17,11 +17,12 @@
}
},
"node_modules/@actions/exec": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-2.0.0.tgz",
"integrity": "sha512-k8ngrX2voJ/RIN6r9xB82NVqKpnMRtxDoiO+g3olkIUpQNqjArXrCQceduQZCQj3P3xm32pChRLqRrtXTlqhIw==",
"license": "MIT",
"dependencies": {
"@actions/io": "^1.0.1"
"@actions/io": "^2.0.0"
}
},
"node_modules/@actions/http-client": {
@@ -35,9 +36,10 @@
}
},
"node_modules/@actions/io": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz",
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-2.0.0.tgz",
"integrity": "sha512-Jv33IN09XLO+0HS79aaODsvIRyduiF7NY/F6LYeK5oeUmrsz7aFdRphQjFoESF4jS7lMauDOttKALcpapVDIAg==",
"license": "MIT"
},
"node_modules/@fastify/busboy": {
"version": "2.1.1",
@@ -49,15 +51,17 @@
}
},
"node_modules/@types/node": {
"version": "16.18.112",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.112.tgz",
"integrity": "sha512-EKrbKUGJROm17+dY/gMi31aJlGLJ75e1IkTojt9n6u+hnaTBDs+M1bIdOawpk2m6YUAXq/R2W0SxCng1tndHCg==",
"dev": true
"version": "16.18.126",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.126.tgz",
"integrity": "sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw==",
"dev": true,
"license": "MIT"
},
"node_modules/tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
"license": "MIT",
"engines": {
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
}
@@ -74,53 +78,5 @@
"node": ">=14.0"
}
}
},
"dependencies": {
"@actions/exec": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
"requires": {
"@actions/io": "^1.0.1"
}
},
"@actions/http-client": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-3.0.0.tgz",
"integrity": "sha512-1s3tXAfVMSz9a4ZEBkXXRQD4QhY3+GAsWSbaYpeknPOKEeyRiU3lH+bHiLMZdo2x/fIeQ/hscL1wCkDLVM2DZQ==",
"requires": {
"tunnel": "^0.0.6",
"undici": "^5.28.5"
}
},
"@actions/io": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz",
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="
},
"@fastify/busboy": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="
},
"@types/node": {
"version": "16.18.112",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.112.tgz",
"integrity": "sha512-EKrbKUGJROm17+dY/gMi31aJlGLJ75e1IkTojt9n6u+hnaTBDs+M1bIdOawpk2m6YUAXq/R2W0SxCng1tndHCg==",
"dev": true
},
"tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
},
"undici": {
"version": "5.29.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz",
"integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==",
"requires": {
"@fastify/busboy": "^2.0.0"
}
}
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/core",
"version": "2.0.0",
"version": "2.0.1",
"description": "Actions core lib",
"keywords": [
"github",
@@ -36,7 +36,7 @@
"url": "https://github.com/actions/toolkit/issues"
},
"dependencies": {
"@actions/exec": "^1.1.1",
"@actions/exec": "^2.0.0",
"@actions/http-client": "^3.0.0"
},
"devDependencies": {