Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32795ed917 | ||
|
|
4fa34e85c5 | ||
|
|
9e752e3d76 | ||
|
|
a0652efe33 | ||
|
|
5b17eb7cb0 | ||
|
|
faa6467995 | ||
|
|
3ff4eb4c69 | ||
|
|
074a7714de | ||
|
|
72776582f8 | ||
|
|
e4e9a599b8 | ||
|
|
80d9f23382 | ||
|
|
12c083815e | ||
|
|
38ff958ab6 | ||
|
|
60d0be1445 |
30
README.md
30
README.md
@@ -65,7 +65,7 @@ See [action.yml](action.yml)
|
||||
with:
|
||||
# Path to the artifact serving as the subject of the attestation. Must
|
||||
# specify exactly one of "subject-path" or "subject-digest". May contain
|
||||
# a glob pattern or list of paths (total subject count cannot exceed 64).
|
||||
# a glob pattern or list of paths (total subject count cannot exceed 2500).
|
||||
subject-path:
|
||||
|
||||
# SHA256 digest of the subject for the attestation. Must be in the form
|
||||
@@ -81,12 +81,14 @@ See [action.yml](action.yml)
|
||||
# URI identifying the type of the predicate.
|
||||
predicate-type:
|
||||
|
||||
# JSON string containing the value for the attestation predicate. Must
|
||||
# supply exactly one of "predicate-path" or "predicate".
|
||||
# String containing the value for the attestation predicate. String length
|
||||
# cannot exceed 16MB. Must supply exactly one of "predicate-path" or
|
||||
# "predicate".
|
||||
predicate:
|
||||
|
||||
# Path to the file which contains the JSON content for the attestation
|
||||
# predicate. Must supply exactly one of "predicate-path" or "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".
|
||||
predicate-path:
|
||||
|
||||
# Whether to push the attestation to the image registry. Requires that the
|
||||
@@ -115,6 +117,20 @@ If multiple subjects are being attested at the same time, each attestation will
|
||||
be written to the output file on a separate line (using the [JSON Lines][7]
|
||||
format).
|
||||
|
||||
## Attestation Limits
|
||||
|
||||
### Subject Limits
|
||||
|
||||
No more than 2500 subjects can be attested at the same time. Subjects will be
|
||||
processed in batches 50. After the initial group of 50, each subsequent batch
|
||||
will incur an exponentially increasing amount of delay (capped at 1 minute of
|
||||
delay per batch) to avoid overwhelming the attestation API.
|
||||
|
||||
### Predicate Limits
|
||||
|
||||
Whether supplied via the `predicate` or `predicatePath` input, the predicate
|
||||
string cannot exceed 16MB.
|
||||
|
||||
## Examples
|
||||
|
||||
### Identify Subject by Path
|
||||
@@ -175,8 +191,8 @@ 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.
|
||||
|
||||
> **NOTE**: When pushing to Docker Hub, please use "index.docker.io" as the
|
||||
> registry portion of the image name.
|
||||
> **NOTE**: When pushing to Docker Hub, please use "docker.io" as the registry
|
||||
> portion of the image name.
|
||||
|
||||
```yaml
|
||||
name: build-attested-image
|
||||
|
||||
@@ -2,12 +2,17 @@
|
||||
* Unit tests for the action's entrypoint, src/index.ts
|
||||
*/
|
||||
|
||||
import * as core from '@actions/core'
|
||||
import * as main from '../src/main'
|
||||
|
||||
// Mock the action's entrypoint
|
||||
const runMock = jest.spyOn(main, 'run').mockImplementation()
|
||||
const getBooleanInputMock = jest.spyOn(core, 'getBooleanInput')
|
||||
|
||||
describe('index', () => {
|
||||
beforeEach(() => {
|
||||
getBooleanInputMock.mockImplementation(() => false)
|
||||
})
|
||||
it('calls run when imported', async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
require('../src/index')
|
||||
|
||||
@@ -20,8 +20,6 @@ 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 getInputMock = jest.spyOn(core, 'getInput')
|
||||
const getBooleanInputMock = jest.spyOn(core, 'getBooleanInput')
|
||||
const setOutputMock = jest.spyOn(core, 'setOutput')
|
||||
const setFailedMock = jest.spyOn(core, 'setFailed')
|
||||
|
||||
@@ -38,12 +36,28 @@ const runMock = jest.spyOn(main, 'run')
|
||||
const mockAgent = new MockAgent()
|
||||
setGlobalDispatcher(mockAgent)
|
||||
|
||||
const defaultInputs: main.RunInputs = {
|
||||
predicate: '',
|
||||
predicateType: '',
|
||||
predicatePath: '',
|
||||
subjectName: '',
|
||||
subjectDigest: '',
|
||||
subjectPath: '',
|
||||
pushToRegistry: false,
|
||||
githubToken: '',
|
||||
privateSigning: false,
|
||||
batchSize: 50
|
||||
}
|
||||
|
||||
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: '' }
|
||||
@@ -62,9 +76,6 @@ describe('action', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
|
||||
// Mock OIDC token endpoint
|
||||
const tokenURL = 'https://token.url'
|
||||
|
||||
nock(tokenURL)
|
||||
.get('/')
|
||||
.query({ audience: 'sigstore' })
|
||||
@@ -95,24 +106,22 @@ describe('action', () => {
|
||||
})
|
||||
|
||||
describe('when ACTIONS_ID_TOKEN_REQUEST_URL is not set', () => {
|
||||
const inputs = {
|
||||
'subject-digest': subjectDigest,
|
||||
'subject-name': subjectName,
|
||||
'predicate-type': predicateType,
|
||||
const inputs: main.RunInputs = {
|
||||
...defaultInputs,
|
||||
subjectDigest,
|
||||
subjectName,
|
||||
predicateType,
|
||||
predicate,
|
||||
'github-token': 'gh-token'
|
||||
githubToken: 'gh-token'
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
// Nullify the OIDC token URL
|
||||
process.env.ACTIONS_ID_TOKEN_REQUEST_URL = ''
|
||||
|
||||
getInputMock.mockImplementation(mockInput(inputs))
|
||||
getBooleanInputMock.mockImplementation(() => false)
|
||||
})
|
||||
|
||||
it('sets a failed status', async () => {
|
||||
await main.run()
|
||||
await main.run(inputs)
|
||||
|
||||
expect(runMock).toHaveReturned()
|
||||
expect(setFailedMock).toHaveBeenCalledWith(
|
||||
@@ -124,12 +133,8 @@ describe('action', () => {
|
||||
})
|
||||
|
||||
describe('when no inputs are provided', () => {
|
||||
beforeEach(() => {
|
||||
getInputMock.mockImplementation(() => '')
|
||||
})
|
||||
|
||||
it('sets a failed status', async () => {
|
||||
await main.run()
|
||||
await main.run(defaultInputs)
|
||||
|
||||
expect(runMock).toHaveReturned()
|
||||
expect(setFailedMock).toHaveBeenCalledWith(
|
||||
@@ -139,12 +144,13 @@ describe('action', () => {
|
||||
})
|
||||
|
||||
describe('when the repository is private', () => {
|
||||
const inputs = {
|
||||
'subject-digest': subjectDigest,
|
||||
'subject-name': subjectName,
|
||||
'predicate-type': predicateType,
|
||||
const inputs: main.RunInputs = {
|
||||
...defaultInputs,
|
||||
subjectDigest,
|
||||
subjectName,
|
||||
predicateType,
|
||||
predicate,
|
||||
'github-token': 'gh-token'
|
||||
githubToken: 'gh-token'
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -154,9 +160,6 @@ describe('action', () => {
|
||||
repo: { owner: 'foo', repo: 'bar' }
|
||||
})
|
||||
|
||||
getInputMock.mockImplementation(mockInput(inputs))
|
||||
getBooleanInputMock.mockImplementation(() => false)
|
||||
|
||||
await mockFulcio({
|
||||
baseURL: 'https://fulcio.githubapp.com',
|
||||
strict: false
|
||||
@@ -165,7 +168,7 @@ describe('action', () => {
|
||||
})
|
||||
|
||||
it('invokes the action w/o error', async () => {
|
||||
await main.run()
|
||||
await main.run(inputs)
|
||||
|
||||
expect(runMock).toHaveReturned()
|
||||
expect(setFailedMock).not.toHaveBeenCalledWith()
|
||||
@@ -204,12 +207,14 @@ describe('action', () => {
|
||||
const getRegCredsSpy = jest.spyOn(oci, 'getRegistryCredentials')
|
||||
const attachArtifactSpy = jest.spyOn(oci, 'attachArtifactToImage')
|
||||
|
||||
const inputs = {
|
||||
'subject-digest': subjectDigest,
|
||||
'subject-name': subjectName,
|
||||
'predicate-type': predicateType,
|
||||
const inputs: main.RunInputs = {
|
||||
...defaultInputs,
|
||||
subjectDigest,
|
||||
subjectName,
|
||||
predicateType,
|
||||
predicate,
|
||||
'github-token': 'gh-token'
|
||||
githubToken: 'gh-token',
|
||||
pushToRegistry: true
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -219,11 +224,6 @@ describe('action', () => {
|
||||
repo: { owner: 'foo', repo: 'bar' }
|
||||
})
|
||||
|
||||
// Mock the action's inputs
|
||||
getInputMock.mockImplementation(mockInput(inputs))
|
||||
// This is where we mock the push-to-registry input
|
||||
getBooleanInputMock.mockImplementation(() => true)
|
||||
|
||||
await mockFulcio({
|
||||
baseURL: 'https://fulcio.sigstore.dev',
|
||||
strict: false
|
||||
@@ -244,7 +244,7 @@ describe('action', () => {
|
||||
})
|
||||
|
||||
it('invokes the action w/o error', async () => {
|
||||
await main.run()
|
||||
await main.run(inputs)
|
||||
|
||||
expect(runMock).toHaveReturned()
|
||||
expect(setFailedMock).not.toHaveBeenCalled()
|
||||
@@ -289,11 +289,16 @@ describe('action', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('when too many subjects are specified', () => {
|
||||
describe('when the subject count exceeds the batch size', () => {
|
||||
let dir = ''
|
||||
const filename = 'subject'
|
||||
let scope: nock.Scope
|
||||
|
||||
beforeEach(async () => {
|
||||
const filename = 'subject'
|
||||
// Start from scratch
|
||||
nock.cleanAll()
|
||||
|
||||
const subjectCount = 5
|
||||
const content = 'file content'
|
||||
|
||||
// Set-up temp directory
|
||||
@@ -301,7 +306,90 @@ describe('action', () => {
|
||||
dir = await fs.mkdtemp(tmpDir + path.sep)
|
||||
|
||||
// Add files for glob testing
|
||||
for (let i = 0; i < 65; i++) {
|
||||
for (let i = 0; i < subjectCount; i++) {
|
||||
await fs.writeFile(path.join(dir, `${filename}-${i}`), content)
|
||||
|
||||
// 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' })
|
||||
|
||||
// Set-up a GH API mock for each subject
|
||||
mockAgent
|
||||
.get('https://api.github.com')
|
||||
.intercept({
|
||||
path: /^\/repos\/.*\/.*\/attestations$/,
|
||||
method: 'post'
|
||||
})
|
||||
.reply(201, { id: attestationID })
|
||||
}
|
||||
|
||||
// Set-up a OIDC token mock for each subject
|
||||
scope = nock(tokenURL)
|
||||
.get('/')
|
||||
.query({ audience: 'sigstore' })
|
||||
.times(subjectCount)
|
||||
.reply(200, { value: oidcToken })
|
||||
|
||||
// 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('invokes the action w/o error', async () => {
|
||||
const inputs: main.RunInputs = {
|
||||
...defaultInputs,
|
||||
subjectPath: path.join(dir, `${filename}-*`),
|
||||
predicateType,
|
||||
predicate,
|
||||
githubToken: 'gh-token',
|
||||
batchSize: 2
|
||||
}
|
||||
await main.run(inputs)
|
||||
|
||||
expect(runMock).toHaveReturned()
|
||||
expect(setFailedMock).not.toHaveBeenCalled()
|
||||
expect(infoMock).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.stringMatching('Processing subject batch 1/3')
|
||||
)
|
||||
expect(infoMock).toHaveBeenNthCalledWith(
|
||||
10,
|
||||
expect.stringMatching('Processing subject batch 2/3')
|
||||
)
|
||||
expect(infoMock).toHaveBeenNthCalledWith(
|
||||
19,
|
||||
expect.stringMatching('Processing subject batch 3/3')
|
||||
)
|
||||
expect(scope.isDone()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the subject count exceeds the max', () => {
|
||||
let dir = ''
|
||||
const filename = 'subject'
|
||||
|
||||
beforeEach(async () => {
|
||||
const subjectCount = 2501
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -310,14 +398,6 @@ describe('action', () => {
|
||||
payload: { repository: { visibility: 'private' } },
|
||||
repo: { owner: 'foo', repo: 'bar' }
|
||||
})
|
||||
|
||||
// Mock the action's inputs
|
||||
getInputMock.mockImplementation(
|
||||
mockInput({
|
||||
predicate: '{}',
|
||||
'subject-path': path.join(dir, `${filename}-*`)
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
@@ -326,27 +406,25 @@ describe('action', () => {
|
||||
})
|
||||
|
||||
it('sets a failed status', async () => {
|
||||
await main.run()
|
||||
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 64.'
|
||||
'Too many subjects specified. The maximum number of subjects is 2500.'
|
||||
)
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function mockInput(inputs: Record<string, string>): typeof core.getInput {
|
||||
return (name: string): string => {
|
||||
if (name in inputs) {
|
||||
return inputs[name]
|
||||
}
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
||||
@@ -1,92 +1,129 @@
|
||||
import fs from 'fs/promises'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import { predicateFromInputs } from '../src/predicate'
|
||||
import { predicateFromInputs, PredicateInputs } from '../src/predicate'
|
||||
|
||||
describe('subjectFromInputs', () => {
|
||||
afterEach(() => {
|
||||
process.env['INPUT_PREDICATE'] = ''
|
||||
process.env['INPUT_PREDICATE-PATH'] = ''
|
||||
process.env['INPUT_PREDICATE-TYPE'] = ''
|
||||
})
|
||||
const blankInputs: PredicateInputs = {
|
||||
predicateType: '',
|
||||
predicate: '',
|
||||
predicatePath: ''
|
||||
}
|
||||
|
||||
describe('when no inputs are provided', () => {
|
||||
it('throws an error', () => {
|
||||
expect(() => predicateFromInputs()).toThrow(/predicate-type/i)
|
||||
expect(() => predicateFromInputs(blankInputs)).toThrow(/predicate-type/i)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when neither predicate path nor predicate are provided', () => {
|
||||
beforeEach(() => {
|
||||
process.env['INPUT_PREDICATE-TYPE'] = 'https://example.com/predicate'
|
||||
})
|
||||
|
||||
it('throws an error', () => {
|
||||
expect(() => predicateFromInputs()).toThrow(
|
||||
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', () => {
|
||||
beforeEach(() => {
|
||||
process.env['INPUT_PREDICATE-PATH'] = 'path/to/predicate'
|
||||
process.env['INPUT_PREDICATE'] = '{}'
|
||||
process.env['INPUT_PREDICATE-TYPE'] = 'https://example.com/predicate'
|
||||
})
|
||||
|
||||
it('throws an error', () => {
|
||||
expect(() => predicateFromInputs()).toThrow(
|
||||
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', () => {
|
||||
let dir = ''
|
||||
const filename = 'subject'
|
||||
const predicateType = 'https://example.com/predicate'
|
||||
const content = '{}'
|
||||
let predicatePath = ''
|
||||
|
||||
beforeEach(async () => {
|
||||
// Set-up temp directory
|
||||
const tmpDir = await fs.realpath(os.tmpdir())
|
||||
dir = await fs.mkdtemp(tmpDir + path.sep)
|
||||
const dir = await fs.mkdtemp(tmpDir + path.sep)
|
||||
|
||||
const filename = 'subject'
|
||||
predicatePath = path.join(dir, filename)
|
||||
|
||||
// Write file to temp directory
|
||||
await fs.writeFile(path.join(dir, filename), content)
|
||||
await fs.writeFile(predicatePath, content)
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
// Clean-up temp directory
|
||||
await fs.rm(dir, { recursive: true })
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
process.env['INPUT_PREDICATE-PATH'] = path.join(dir, filename)
|
||||
process.env['INPUT_PREDICATE-TYPE'] = 'https://example.com/predicate'
|
||||
await fs.rm(path.parse(predicatePath).dir, { recursive: true })
|
||||
})
|
||||
|
||||
it('returns the predicate', () => {
|
||||
expect(predicateFromInputs()).toEqual({
|
||||
type: 'https://example.com/predicate',
|
||||
params: {}
|
||||
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 = '{}'
|
||||
|
||||
beforeEach(() => {
|
||||
process.env['INPUT_PREDICATE'] = content
|
||||
process.env['INPUT_PREDICATE-TYPE'] = 'https://example.com/predicate'
|
||||
})
|
||||
|
||||
it('returns the predicate', () => {
|
||||
expect(predicateFromInputs()).toEqual({
|
||||
type: 'https://example.com/predicate',
|
||||
params: {}
|
||||
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/
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
15
__tests__/style.test.ts
Normal file
15
__tests__/style.test.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
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')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -2,85 +2,90 @@ import crypto from 'crypto'
|
||||
import fs from 'fs/promises'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import { subjectFromInputs } from '../src/subject'
|
||||
import { subjectFromInputs, SubjectInputs } from '../src/subject'
|
||||
|
||||
describe('subjectFromInputs', () => {
|
||||
afterEach(() => {
|
||||
process.env['INPUT_SUBJECT-PATH'] = ''
|
||||
process.env['INPUT_SUBJECT-DIGEST'] = ''
|
||||
process.env['INPUT_SUBJECT-NAME'] = ''
|
||||
})
|
||||
const blankInputs: SubjectInputs = {
|
||||
subjectPath: '',
|
||||
subjectName: '',
|
||||
subjectDigest: ''
|
||||
}
|
||||
|
||||
describe('when no inputs are provided', () => {
|
||||
it('throws an error', async () => {
|
||||
await expect(subjectFromInputs()).rejects.toThrow(
|
||||
await expect(subjectFromInputs(blankInputs)).rejects.toThrow(
|
||||
/one of subject-path or subject-digest must be provided/i
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when both subject path and subject digest are provided', () => {
|
||||
beforeEach(() => {
|
||||
process.env['INPUT_SUBJECT-PATH'] = 'path/to/subject'
|
||||
process.env['INPUT_SUBJECT-DIGEST'] = 'digest'
|
||||
})
|
||||
|
||||
it('throws an error', async () => {
|
||||
await expect(subjectFromInputs()).rejects.toThrow(
|
||||
const inputs: SubjectInputs = {
|
||||
subjectName: 'foo',
|
||||
subjectPath: 'path/to/subject',
|
||||
subjectDigest: 'digest'
|
||||
}
|
||||
|
||||
await expect(subjectFromInputs(inputs)).rejects.toThrow(
|
||||
/only one of subject-path or subject-digest may be provided/i
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when subject digest is provided but not the name', () => {
|
||||
beforeEach(() => {
|
||||
process.env['INPUT_SUBJECT-DIGEST'] = 'digest'
|
||||
})
|
||||
|
||||
it('throws an error', async () => {
|
||||
await expect(subjectFromInputs()).rejects.toThrow(
|
||||
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'
|
||||
const name = 'Subject'
|
||||
|
||||
describe('when the digest is malformed', () => {
|
||||
beforeEach(() => {
|
||||
process.env['INPUT_SUBJECT-DIGEST'] = 'digest'
|
||||
process.env['INPUT_SUBJECT-NAME'] = 'subject'
|
||||
})
|
||||
|
||||
it('throws an error', async () => {
|
||||
await expect(subjectFromInputs()).rejects.toThrow(
|
||||
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 alogrithm is not supported', () => {
|
||||
beforeEach(() => {
|
||||
process.env['INPUT_SUBJECT-DIGEST'] = 'md5:deadbeef'
|
||||
process.env['INPUT_SUBJECT-NAME'] = 'subject'
|
||||
})
|
||||
|
||||
it('throws an error', async () => {
|
||||
await expect(subjectFromInputs()).rejects.toThrow(
|
||||
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', () => {
|
||||
beforeEach(() => {
|
||||
process.env['INPUT_SUBJECT-DIGEST'] = 'sha256:deadbeef'
|
||||
process.env['INPUT_SUBJECT-NAME'] = 'subject'
|
||||
})
|
||||
|
||||
it('throws an error', async () => {
|
||||
await expect(subjectFromInputs()).rejects.toThrow(
|
||||
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
|
||||
)
|
||||
})
|
||||
@@ -91,13 +96,14 @@ describe('subjectFromInputs', () => {
|
||||
const digest =
|
||||
'7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32'
|
||||
|
||||
beforeEach(() => {
|
||||
process.env['INPUT_SUBJECT-DIGEST'] = `${alg}:${digest}`
|
||||
process.env['INPUT_SUBJECT-NAME'] = name
|
||||
})
|
||||
|
||||
it('returns the subject', async () => {
|
||||
const subject = await subjectFromInputs()
|
||||
const inputs: SubjectInputs = {
|
||||
...blankInputs,
|
||||
subjectDigest: `${alg}:${digest}`,
|
||||
subjectName: name
|
||||
}
|
||||
|
||||
const subject = await subjectFromInputs(inputs)
|
||||
|
||||
expect(subject).toBeDefined()
|
||||
expect(subject).toHaveLength(1)
|
||||
@@ -105,23 +111,47 @@ describe('subjectFromInputs', () => {
|
||||
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', () => {
|
||||
beforeEach(() => {
|
||||
process.env['INPUT_SUBJECT-PATH'] = '/f/a/k/e'
|
||||
})
|
||||
|
||||
it('throws an error', async () => {
|
||||
await expect(subjectFromInputs()).rejects.toThrow(
|
||||
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 eixts', () => {
|
||||
describe('when the file exists', () => {
|
||||
let dir = ''
|
||||
const filename = 'subject'
|
||||
const content = 'file content'
|
||||
@@ -152,12 +182,13 @@ describe('subjectFromInputs', () => {
|
||||
})
|
||||
|
||||
describe('when no name is provided', () => {
|
||||
beforeEach(() => {
|
||||
process.env['INPUT_SUBJECT-PATH'] = path.join(dir, filename)
|
||||
})
|
||||
|
||||
it('returns the subject', async () => {
|
||||
const subject = await subjectFromInputs()
|
||||
const inputs: SubjectInputs = {
|
||||
...blankInputs,
|
||||
subjectPath: path.join(dir, filename)
|
||||
}
|
||||
|
||||
const subject = await subjectFromInputs(inputs)
|
||||
|
||||
expect(subject).toBeDefined()
|
||||
expect(subject).toHaveLength(1)
|
||||
@@ -169,13 +200,14 @@ describe('subjectFromInputs', () => {
|
||||
describe('when a name is provided', () => {
|
||||
const name = 'mysubject'
|
||||
|
||||
beforeEach(() => {
|
||||
process.env['INPUT_SUBJECT-PATH'] = path.join(dir, filename)
|
||||
process.env['INPUT_SUBJECT-NAME'] = name
|
||||
})
|
||||
|
||||
it('returns the subject', async () => {
|
||||
const subject = await subjectFromInputs()
|
||||
const inputs: SubjectInputs = {
|
||||
...blankInputs,
|
||||
subjectPath: path.join(dir, filename),
|
||||
subjectName: name
|
||||
}
|
||||
|
||||
const subject = await subjectFromInputs(inputs)
|
||||
|
||||
expect(subject).toBeDefined()
|
||||
expect(subject).toHaveLength(1)
|
||||
@@ -185,12 +217,13 @@ describe('subjectFromInputs', () => {
|
||||
})
|
||||
|
||||
describe('when a file glob is supplied', () => {
|
||||
beforeEach(async () => {
|
||||
process.env['INPUT_SUBJECT-PATH'] = path.join(dir, 'subject-*')
|
||||
})
|
||||
|
||||
it('returns the multiple subjects', async () => {
|
||||
const subjects = await subjectFromInputs()
|
||||
const inputs: SubjectInputs = {
|
||||
...blankInputs,
|
||||
subjectPath: path.join(dir, 'subject-*')
|
||||
}
|
||||
|
||||
const subjects = await subjectFromInputs(inputs)
|
||||
|
||||
expect(subjects).toBeDefined()
|
||||
expect(subjects).toHaveLength(3)
|
||||
@@ -204,12 +237,13 @@ describe('subjectFromInputs', () => {
|
||||
})
|
||||
|
||||
describe('when a file glob is supplied which also matches non-files', () => {
|
||||
beforeEach(async () => {
|
||||
process.env['INPUT_SUBJECT-PATH'] = `${dir}*`
|
||||
})
|
||||
|
||||
it('returns the subjects (excluding non-files)', async () => {
|
||||
const subjects = await subjectFromInputs()
|
||||
const inputs: SubjectInputs = {
|
||||
...blankInputs,
|
||||
subjectPath: `${dir}*`
|
||||
}
|
||||
|
||||
const subjects = await subjectFromInputs(inputs)
|
||||
|
||||
expect(subjects).toBeDefined()
|
||||
expect(subjects).toHaveLength(7)
|
||||
@@ -217,13 +251,13 @@ describe('subjectFromInputs', () => {
|
||||
})
|
||||
|
||||
describe('when a comma-separated list is supplied', () => {
|
||||
beforeEach(async () => {
|
||||
process.env['INPUT_SUBJECT-PATH'] =
|
||||
`${path.join(dir, 'subject-1')},${path.join(dir, 'subject-2')}`
|
||||
})
|
||||
|
||||
it('returns the multiple subjects', async () => {
|
||||
const subjects = await subjectFromInputs()
|
||||
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)
|
||||
@@ -240,13 +274,13 @@ describe('subjectFromInputs', () => {
|
||||
})
|
||||
|
||||
describe('when a multi-line list is supplied', () => {
|
||||
beforeEach(async () => {
|
||||
process.env['INPUT_SUBJECT-PATH'] =
|
||||
`${path.join(dir, 'subject-0')}\n${path.join(dir, 'subject-2')}`
|
||||
})
|
||||
|
||||
it('returns the multiple subjects', async () => {
|
||||
const subjects = await subjectFromInputs()
|
||||
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)
|
||||
@@ -263,13 +297,13 @@ describe('subjectFromInputs', () => {
|
||||
})
|
||||
|
||||
describe('when a multi-line glob list is supplied', () => {
|
||||
beforeEach(async () => {
|
||||
process.env['INPUT_SUBJECT-PATH'] =
|
||||
`${path.join(dir, 'subject-*')}\n ${path.join(dir, 'other-*')} `
|
||||
})
|
||||
|
||||
it('returns the multiple subjects', async () => {
|
||||
const subjects = await subjectFromInputs()
|
||||
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)
|
||||
|
||||
10
action.yml
10
action.yml
@@ -10,7 +10,7 @@ inputs:
|
||||
description: >
|
||||
Path to the artifact serving as the subject of the attestation. Must
|
||||
specify exactly one of "subject-path" or "subject-digest". May contain a
|
||||
glob pattern or list of paths (total subject count cannot exceed 64).
|
||||
glob pattern or list of paths (total subject count cannot exceed 2500).
|
||||
required: false
|
||||
subject-digest:
|
||||
description: >
|
||||
@@ -30,13 +30,15 @@ inputs:
|
||||
required: true
|
||||
predicate:
|
||||
description: >
|
||||
String containing the value for the attestation predicate. Must supply
|
||||
exactly one of "predicate-path" or "predicate".
|
||||
String containing the value for the attestation predicate. String length
|
||||
cannot exceed 16MB. Must supply exactly one of "predicate-path" or
|
||||
"predicate".
|
||||
required: false
|
||||
predicate-path:
|
||||
description: >
|
||||
Path to the file which contains the content for the attestation predicate.
|
||||
Must supply exactly one of "predicate-path" or "predicate".
|
||||
File size cannot exceed 16MB. Must supply exactly one of "predicate-path"
|
||||
or "predicate".
|
||||
required: false
|
||||
push-to-registry:
|
||||
description: >
|
||||
|
||||
617
dist/index.js
generated
vendored
617
dist/index.js
generated
vendored
File diff suppressed because it is too large
Load Diff
472
package-lock.json
generated
472
package-lock.json
generated
@@ -1,40 +1,40 @@
|
||||
{
|
||||
"name": "actions/attest",
|
||||
"version": "1.1.1",
|
||||
"version": "1.2.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "actions/attest",
|
||||
"version": "1.1.1",
|
||||
"version": "1.2.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/attest": "^1.2.1",
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/glob": "^0.4.0",
|
||||
"@sigstore/oci": "^0.3.2",
|
||||
"csv-parse": "^5.5.5"
|
||||
"@sigstore/oci": "^0.3.4",
|
||||
"csv-parse": "^5.5.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sigstore/mock": "^0.7.2",
|
||||
"@sigstore/mock": "^0.7.4",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/make-fetch-happen": "^10.0.4",
|
||||
"@types/node": "^20.12.10",
|
||||
"@typescript-eslint/eslint-plugin": "^7.8.0",
|
||||
"@typescript-eslint/parser": "^7.8.0",
|
||||
"@types/node": "^20.12.12",
|
||||
"@typescript-eslint/eslint-plugin": "^7.11.0",
|
||||
"@typescript-eslint/parser": "^7.11.0",
|
||||
"@vercel/ncc": "^0.38.1",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-github": "^4.10.2",
|
||||
"eslint-plugin-jest": "^28.5.0",
|
||||
"eslint-plugin-jsonc": "^2.15.1",
|
||||
"eslint-plugin-jsonc": "^2.16.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"jest": "^29.7.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"markdownlint-cli": "^0.40.0",
|
||||
"markdownlint-cli": "^0.41.0",
|
||||
"nock": "^13.5.4",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-eslint": "^16.3.0",
|
||||
"ts-jest": "^29.1.2",
|
||||
"ts-jest": "^29.1.3",
|
||||
"typescript": "^5.4.5",
|
||||
"undici": "^5.28.4"
|
||||
},
|
||||
@@ -1708,18 +1708,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sigstore/mock": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@sigstore/mock/-/mock-0.7.2.tgz",
|
||||
"integrity": "sha512-hnYwvtTfqgZPgoOx+KN5T1JMzrIgLkpZsra7zTJxHyfL1kDmzVQkPGWT8N8UmLvr3sHrsWan2946fPDi1YMmWg==",
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@sigstore/mock/-/mock-0.7.4.tgz",
|
||||
"integrity": "sha512-ij9X2Fij9fcH7upxf3KuAZ38ecGSMm+Asvbik5xiHTBUcwe1+bZ5eG6k5p1eHaNY+XJ581bC6O33871Bm5m5mQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@peculiar/webcrypto": "^1.4.6",
|
||||
"@peculiar/x509": "^1.9.7",
|
||||
"@sigstore/protobuf-specs": "^0.3.0",
|
||||
"@sigstore/protobuf-specs": "^0.3.2",
|
||||
"asn1js": "^3.0.5",
|
||||
"bytestreamjs": "^2.0.1",
|
||||
"canonicalize": "^2.0.0",
|
||||
"jose": "^5.2.3",
|
||||
"jose": "^5.2.4",
|
||||
"nock": "^13.5.4",
|
||||
"pkijs": "^3.0.16",
|
||||
"pvutils": "^1.1.3"
|
||||
@@ -1729,9 +1729,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sigstore/oci": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@sigstore/oci/-/oci-0.3.2.tgz",
|
||||
"integrity": "sha512-3UJC2SV+A4HuILse/jvodDI+0QIN13fErxu3roX5HU9wOeP31UHH/WMQBlN3l5DVewXTufNs3Q85DzOI1tQNLQ==",
|
||||
"version": "0.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@sigstore/oci/-/oci-0.3.4.tgz",
|
||||
"integrity": "sha512-ydRTsvHOmLWnlR2BTtG1pHYvLkHG/oaqVyd2WDkfLU7B3dIWfqavE80VCzidNWuZpXN7m8+uBNatus2Qva1ktA==",
|
||||
"dependencies": {
|
||||
"make-fetch-happen": "^13.0.1",
|
||||
"proc-log": "^4.2.0"
|
||||
@@ -1741,9 +1741,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sigstore/protobuf-specs": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.1.tgz",
|
||||
"integrity": "sha512-aIL8Z9NsMr3C64jyQzE0XlkEyBLpgEJJFDHLVVStkFV5Q3Il/r/YtY6NJWKQ4cy4AE7spP1IX5Jq7VCAxHHMfQ==",
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.2.tgz",
|
||||
"integrity": "sha512-c6B0ehIWxMI8wiS/bj6rHMPqeFvngFV7cDU/MY+B16P9Z3Mp9k8L93eYZ7BYzSickzuqAQqAq0V956b3Ju6mLw==",
|
||||
"engines": {
|
||||
"node": "^16.14.0 || >=18.0.0"
|
||||
}
|
||||
@@ -1926,7 +1926,9 @@
|
||||
"version": "7.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/json5": {
|
||||
"version": "0.0.29",
|
||||
@@ -1958,9 +1960,9 @@
|
||||
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.12.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.10.tgz",
|
||||
"integrity": "sha512-Eem5pH9pmWBHoGAT8Dr5fdc5rYA+4NAovdM4EktRPVAAiJhmWWfQrA0cFhAbOsQdSfIHjAud6YdkbL69+zSKjw==",
|
||||
"version": "20.12.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz",
|
||||
"integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==",
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
@@ -1989,12 +1991,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/semver": {
|
||||
"version": "7.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
|
||||
"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/send": {
|
||||
"version": "0.17.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
|
||||
@@ -2041,21 +2037,19 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.8.0.tgz",
|
||||
"integrity": "sha512-gFTT+ezJmkwutUPmB0skOj3GZJtlEGnlssems4AjkVweUPGj7jRwwqg0Hhg7++kPGJqKtTYx+R05Ftww372aIg==",
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.11.0.tgz",
|
||||
"integrity": "sha512-P+qEahbgeHW4JQ/87FuItjBj8O3MYv5gELDzr8QaQ7fsll1gSMTYb6j87MYyxwf3DtD7uGFB9ShwgmCJB5KmaQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.10.0",
|
||||
"@typescript-eslint/scope-manager": "7.8.0",
|
||||
"@typescript-eslint/type-utils": "7.8.0",
|
||||
"@typescript-eslint/utils": "7.8.0",
|
||||
"@typescript-eslint/visitor-keys": "7.8.0",
|
||||
"debug": "^4.3.4",
|
||||
"@typescript-eslint/scope-manager": "7.11.0",
|
||||
"@typescript-eslint/type-utils": "7.11.0",
|
||||
"@typescript-eslint/utils": "7.11.0",
|
||||
"@typescript-eslint/visitor-keys": "7.11.0",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^5.3.1",
|
||||
"natural-compare": "^1.4.0",
|
||||
"semver": "^7.6.0",
|
||||
"ts-api-utils": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2076,15 +2070,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.8.0.tgz",
|
||||
"integrity": "sha512-KgKQly1pv0l4ltcftP59uQZCi4HUYswCLbTqVZEJu7uLX8CTLyswqMLqLN+2QFz4jCptqWVV4SB7vdxcH2+0kQ==",
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.11.0.tgz",
|
||||
"integrity": "sha512-yimw99teuaXVWsBcPO1Ais02kwJ1jmNA1KxE7ng0aT7ndr1pT1wqj0OJnsYVGKKlc4QJai86l/025L6z8CljOg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "7.8.0",
|
||||
"@typescript-eslint/types": "7.8.0",
|
||||
"@typescript-eslint/typescript-estree": "7.8.0",
|
||||
"@typescript-eslint/visitor-keys": "7.8.0",
|
||||
"@typescript-eslint/scope-manager": "7.11.0",
|
||||
"@typescript-eslint/types": "7.11.0",
|
||||
"@typescript-eslint/typescript-estree": "7.11.0",
|
||||
"@typescript-eslint/visitor-keys": "7.11.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2104,13 +2098,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.8.0.tgz",
|
||||
"integrity": "sha512-viEmZ1LmwsGcnr85gIq+FCYI7nO90DVbE37/ll51hjv9aG+YZMb4WDE2fyWpUR4O/UrhGRpYXK/XajcGTk2B8g==",
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.11.0.tgz",
|
||||
"integrity": "sha512-27tGdVEiutD4POirLZX4YzT180vevUURJl4wJGmm6TrQoiYwuxTIY98PBp6L2oN+JQxzE0URvYlzJaBHIekXAw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "7.8.0",
|
||||
"@typescript-eslint/visitor-keys": "7.8.0"
|
||||
"@typescript-eslint/types": "7.11.0",
|
||||
"@typescript-eslint/visitor-keys": "7.11.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || >=20.0.0"
|
||||
@@ -2121,13 +2115,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.8.0.tgz",
|
||||
"integrity": "sha512-H70R3AefQDQpz9mGv13Uhi121FNMh+WEaRqcXTX09YEDky21km4dV1ZXJIp8QjXc4ZaVkXVdohvWDzbnbHDS+A==",
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.11.0.tgz",
|
||||
"integrity": "sha512-WmppUEgYy+y1NTseNMJ6mCFxt03/7jTOy08bcg7bxJJdsM4nuhnchyBbE8vryveaJUf62noH7LodPSo5Z0WUCg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/typescript-estree": "7.8.0",
|
||||
"@typescript-eslint/utils": "7.8.0",
|
||||
"@typescript-eslint/typescript-estree": "7.11.0",
|
||||
"@typescript-eslint/utils": "7.11.0",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^1.3.0"
|
||||
},
|
||||
@@ -2148,9 +2142,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.8.0.tgz",
|
||||
"integrity": "sha512-wf0peJ+ZGlcH+2ZS23aJbOv+ztjeeP8uQ9GgwMJGVLx/Nj9CJt17GWgWWoSmoRVKAX2X+7fzEnAjxdvK2gqCLw==",
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.11.0.tgz",
|
||||
"integrity": "sha512-MPEsDRZTyCiXkD4vd3zywDCifi7tatc4K37KqTprCvaXptP7Xlpdw0NR2hRJTetG5TxbWDB79Ys4kLmHliEo/w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^18.18.0 || >=20.0.0"
|
||||
@@ -2161,13 +2155,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.8.0.tgz",
|
||||
"integrity": "sha512-5pfUCOwK5yjPaJQNy44prjCwtr981dO8Qo9J9PwYXZ0MosgAbfEMB008dJ5sNo3+/BN6ytBPuSvXUg9SAqB0dg==",
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.11.0.tgz",
|
||||
"integrity": "sha512-cxkhZ2C/iyi3/6U9EPc5y+a6csqHItndvN/CzbNXTNrsC3/ASoYQZEt9uMaEp+xFNjasqQyszp5TumAVKKvJeQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "7.8.0",
|
||||
"@typescript-eslint/visitor-keys": "7.8.0",
|
||||
"@typescript-eslint/types": "7.11.0",
|
||||
"@typescript-eslint/visitor-keys": "7.11.0",
|
||||
"debug": "^4.3.4",
|
||||
"globby": "^11.1.0",
|
||||
"is-glob": "^4.0.3",
|
||||
@@ -2213,18 +2207,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.8.0.tgz",
|
||||
"integrity": "sha512-L0yFqOCflVqXxiZyXrDr80lnahQfSOfc9ELAAZ75sqicqp2i36kEZZGuUymHNFoYOqxRT05up760b4iGsl02nQ==",
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.11.0.tgz",
|
||||
"integrity": "sha512-xlAWwPleNRHwF37AhrZurOxA1wyXowW4PqVXZVUNCLjB48CqdPJoJWkrpH2nij9Q3Lb7rtWindtoXwxjxlKKCA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.4.0",
|
||||
"@types/json-schema": "^7.0.15",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@typescript-eslint/scope-manager": "7.8.0",
|
||||
"@typescript-eslint/types": "7.8.0",
|
||||
"@typescript-eslint/typescript-estree": "7.8.0",
|
||||
"semver": "^7.6.0"
|
||||
"@typescript-eslint/scope-manager": "7.11.0",
|
||||
"@typescript-eslint/types": "7.11.0",
|
||||
"@typescript-eslint/typescript-estree": "7.11.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || >=20.0.0"
|
||||
@@ -2238,12 +2229,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.8.0.tgz",
|
||||
"integrity": "sha512-q4/gibTNBQNA0lGyYQCmWRS5D15n8rXh4QjK3KV+MBPlTYHpfBUT3D3PaPR/HeNiI9W6R7FvlkcGhNyAoP+caA==",
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.11.0.tgz",
|
||||
"integrity": "sha512-7syYk4MzjxTEk0g/w3iqtgxnFQspDJfn6QKD36xMuuhTzjcxY7F8EmBLnALjVyaOF1/bVocu3bS/2/F7rXrveQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "7.8.0",
|
||||
"@typescript-eslint/types": "7.11.0",
|
||||
"eslint-visitor-keys": "^3.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3046,9 +3037,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "12.0.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz",
|
||||
"integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==",
|
||||
"version": "12.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
|
||||
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@@ -3104,9 +3095,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/csv-parse": {
|
||||
"version": "5.5.5",
|
||||
"resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-5.5.5.tgz",
|
||||
"integrity": "sha512-erCk7tyU3yLWAhk6wvKxnyPtftuy/6Ak622gOO7BCJ05+TYffnPCJF905wmOQm+BpkX54OdAl8pveJwUdpnCXQ=="
|
||||
"version": "5.5.6",
|
||||
"resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-5.5.6.tgz",
|
||||
"integrity": "sha512-uNpm30m/AGSkLxxy7d9yRXpJQFrZzVWLFBkS+6ngPcZkw/5k3L/jjFuj7tVnEpRn+QgmiXr21nDlhCiUK4ij2A=="
|
||||
},
|
||||
"node_modules/damerau-levenshtein": {
|
||||
"version": "1.0.8",
|
||||
@@ -3802,9 +3793,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-jsonc": {
|
||||
"version": "2.15.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-jsonc/-/eslint-plugin-jsonc-2.15.1.tgz",
|
||||
"integrity": "sha512-PVFrqIJa8BbM/e828RSn0SwB/Z5ye+2LDuy2XqG6AymNgPsfApRRcznsbxP7VrjdLEU4Nb+g9n/d6opyp0jp9A==",
|
||||
"version": "2.16.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-jsonc/-/eslint-plugin-jsonc-2.16.0.tgz",
|
||||
"integrity": "sha512-Af/ZL5mgfb8FFNleH6KlO4/VdmDuTqmM+SPnWcdoWywTetv7kq+vQe99UyQb9XO3b0OWLVuTH7H0d/PXYCMdSg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
@@ -5714,9 +5705,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jose": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/jose/-/jose-5.2.3.tgz",
|
||||
"integrity": "sha512-KUXdbctm1uHVL8BYhnyHkgp3zDX5KW8ZhAKVFEfUbU2P8Alpzjb+48hHvjOdQIyPshoblhzsuqOwEEAbtHVirA==",
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/jose/-/jose-5.3.0.tgz",
|
||||
"integrity": "sha512-IChe9AtAE79ru084ow8jzkN2lNrG3Ntfiv65Cvj9uOCE2m5LNsdHG+9EbxWxAoWRF9TgDOqLN5jm08++owDVRg==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/panva"
|
||||
@@ -6260,14 +6251,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/markdownlint-cli": {
|
||||
"version": "0.40.0",
|
||||
"resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.40.0.tgz",
|
||||
"integrity": "sha512-JXhI3dRQcaqwiFYpPz6VJ7aKYheD53GmTz9y4D/d0F1MbZDGOp9pqKlbOfUX/pHP/iAoeiE4wYRmk8/kjLakxA==",
|
||||
"version": "0.41.0",
|
||||
"resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.41.0.tgz",
|
||||
"integrity": "sha512-kp29tKrMKdn+xonfefjp3a/MsNzAd9c5ke0ydMEI9PR98bOjzglYN4nfMSaIs69msUf1DNkgevAIAPtK2SeX0Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"commander": "~12.0.0",
|
||||
"commander": "~12.1.0",
|
||||
"get-stdin": "~9.0.0",
|
||||
"glob": "~10.3.12",
|
||||
"glob": "~10.4.1",
|
||||
"ignore": "~5.3.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonc-parser": "~3.2.1",
|
||||
@@ -6275,7 +6266,7 @@
|
||||
"markdownlint": "~0.34.0",
|
||||
"minimatch": "~9.0.4",
|
||||
"run-con": "~1.3.2",
|
||||
"toml": "~3.0.0"
|
||||
"smol-toml": "~1.2.0"
|
||||
},
|
||||
"bin": {
|
||||
"markdownlint": "markdownlint.js"
|
||||
@@ -6294,27 +6285,45 @@
|
||||
}
|
||||
},
|
||||
"node_modules/markdownlint-cli/node_modules/glob": {
|
||||
"version": "10.3.12",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz",
|
||||
"integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==",
|
||||
"version": "10.4.1",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz",
|
||||
"integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"foreground-child": "^3.1.0",
|
||||
"jackspeak": "^2.3.6",
|
||||
"minimatch": "^9.0.1",
|
||||
"minipass": "^7.0.4",
|
||||
"path-scurry": "^1.10.2"
|
||||
"jackspeak": "^3.1.2",
|
||||
"minimatch": "^9.0.4",
|
||||
"minipass": "^7.1.2",
|
||||
"path-scurry": "^1.11.1"
|
||||
},
|
||||
"bin": {
|
||||
"glob": "dist/esm/bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
"node": ">=16 || 14 >=14.18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/markdownlint-cli/node_modules/jackspeak": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.1.2.tgz",
|
||||
"integrity": "sha512-kWmLKn2tRtfYMF/BakihVVRzBKOxz4gJMiL2Rj91WnAB5TPZumSH99R/Yf1qE1u4uRimvCSJfm6hnxohXeEXjQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@isaacs/cliui": "^8.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@pkgjs/parseargs": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/markdownlint-cli/node_modules/minimatch": {
|
||||
"version": "9.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
|
||||
@@ -6419,8 +6428,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/minipass": {
|
||||
"version": "7.0.4",
|
||||
"license": "ISC",
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
||||
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
@@ -6856,15 +6866,15 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/path-scurry": {
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz",
|
||||
"integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==",
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
|
||||
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
|
||||
"dependencies": {
|
||||
"lru-cache": "^10.2.0",
|
||||
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
"node": ">=16 || 14 >=14.18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
@@ -7692,6 +7702,16 @@
|
||||
"npm": ">= 3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/smol-toml": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.2.0.tgz",
|
||||
"integrity": "sha512-KObxdQANC/xje3OoatMbSwQf2XAvJ0RbK+4nmQRszFNZptbNRnMWqbLF/zb4sMi9xJ6HNyhWXeuZ9zC/I/XY7w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 18",
|
||||
"pnpm": ">= 9"
|
||||
}
|
||||
},
|
||||
"node_modules/socks": {
|
||||
"version": "2.7.3",
|
||||
"license": "MIT",
|
||||
@@ -8041,12 +8061,6 @@
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/toml": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz",
|
||||
"integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ts-api-utils": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
|
||||
@@ -8060,9 +8074,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ts-jest": {
|
||||
"version": "29.1.2",
|
||||
"version": "29.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.3.tgz",
|
||||
"integrity": "sha512-6L9qz3ginTd1NKhOxmkP0qU3FyKjj5CPoY+anszfVn6Pmv/RIKzhiMCsH7Yb7UvJR9I2A64rm4zQl531s2F1iw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bs-logger": "0.x",
|
||||
"fast-json-stable-stringify": "2.x",
|
||||
@@ -8077,10 +8092,11 @@
|
||||
"ts-jest": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^16.10.0 || ^18.0.0 || >=20.0.0"
|
||||
"node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": ">=7.0.0-beta.0 <8",
|
||||
"@jest/transform": "^29.0.0",
|
||||
"@jest/types": "^29.0.0",
|
||||
"babel-jest": "^29.0.0",
|
||||
"jest": "^29.0.0",
|
||||
@@ -8090,6 +8106,9 @@
|
||||
"@babel/core": {
|
||||
"optional": true
|
||||
},
|
||||
"@jest/transform": {
|
||||
"optional": true
|
||||
},
|
||||
"@jest/types": {
|
||||
"optional": true
|
||||
},
|
||||
@@ -9796,36 +9815,36 @@
|
||||
"integrity": "sha512-JzBqdVIyqm2FRQCulY6nbQzMpJJpSiJ8XXWMhtOX9eKgaXXpfNOF53lzQEjIydlStnd/eFtuC1dW4VYdD93oRg=="
|
||||
},
|
||||
"@sigstore/mock": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@sigstore/mock/-/mock-0.7.2.tgz",
|
||||
"integrity": "sha512-hnYwvtTfqgZPgoOx+KN5T1JMzrIgLkpZsra7zTJxHyfL1kDmzVQkPGWT8N8UmLvr3sHrsWan2946fPDi1YMmWg==",
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@sigstore/mock/-/mock-0.7.4.tgz",
|
||||
"integrity": "sha512-ij9X2Fij9fcH7upxf3KuAZ38ecGSMm+Asvbik5xiHTBUcwe1+bZ5eG6k5p1eHaNY+XJ581bC6O33871Bm5m5mQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@peculiar/webcrypto": "^1.4.6",
|
||||
"@peculiar/x509": "^1.9.7",
|
||||
"@sigstore/protobuf-specs": "^0.3.0",
|
||||
"@sigstore/protobuf-specs": "^0.3.2",
|
||||
"asn1js": "^3.0.5",
|
||||
"bytestreamjs": "^2.0.1",
|
||||
"canonicalize": "^2.0.0",
|
||||
"jose": "^5.2.3",
|
||||
"jose": "^5.2.4",
|
||||
"nock": "^13.5.4",
|
||||
"pkijs": "^3.0.16",
|
||||
"pvutils": "^1.1.3"
|
||||
}
|
||||
},
|
||||
"@sigstore/oci": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@sigstore/oci/-/oci-0.3.2.tgz",
|
||||
"integrity": "sha512-3UJC2SV+A4HuILse/jvodDI+0QIN13fErxu3roX5HU9wOeP31UHH/WMQBlN3l5DVewXTufNs3Q85DzOI1tQNLQ==",
|
||||
"version": "0.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@sigstore/oci/-/oci-0.3.4.tgz",
|
||||
"integrity": "sha512-ydRTsvHOmLWnlR2BTtG1pHYvLkHG/oaqVyd2WDkfLU7B3dIWfqavE80VCzidNWuZpXN7m8+uBNatus2Qva1ktA==",
|
||||
"requires": {
|
||||
"make-fetch-happen": "^13.0.1",
|
||||
"proc-log": "^4.2.0"
|
||||
}
|
||||
},
|
||||
"@sigstore/protobuf-specs": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.1.tgz",
|
||||
"integrity": "sha512-aIL8Z9NsMr3C64jyQzE0XlkEyBLpgEJJFDHLVVStkFV5Q3Il/r/YtY6NJWKQ4cy4AE7spP1IX5Jq7VCAxHHMfQ=="
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.2.tgz",
|
||||
"integrity": "sha512-c6B0ehIWxMI8wiS/bj6rHMPqeFvngFV7cDU/MY+B16P9Z3Mp9k8L93eYZ7BYzSickzuqAQqAq0V956b3Ju6mLw=="
|
||||
},
|
||||
"@sigstore/sign": {
|
||||
"version": "2.3.1",
|
||||
@@ -9988,7 +10007,9 @@
|
||||
"version": "7.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"@types/json5": {
|
||||
"version": "0.0.29",
|
||||
@@ -10019,9 +10040,9 @@
|
||||
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "20.12.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.10.tgz",
|
||||
"integrity": "sha512-Eem5pH9pmWBHoGAT8Dr5fdc5rYA+4NAovdM4EktRPVAAiJhmWWfQrA0cFhAbOsQdSfIHjAud6YdkbL69+zSKjw==",
|
||||
"version": "20.12.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz",
|
||||
"integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==",
|
||||
"requires": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
@@ -10048,12 +10069,6 @@
|
||||
"version": "0.12.5",
|
||||
"dev": true
|
||||
},
|
||||
"@types/semver": {
|
||||
"version": "7.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
|
||||
"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/send": {
|
||||
"version": "0.17.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
|
||||
@@ -10096,73 +10111,71 @@
|
||||
"dev": true
|
||||
},
|
||||
"@typescript-eslint/eslint-plugin": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.8.0.tgz",
|
||||
"integrity": "sha512-gFTT+ezJmkwutUPmB0skOj3GZJtlEGnlssems4AjkVweUPGj7jRwwqg0Hhg7++kPGJqKtTYx+R05Ftww372aIg==",
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.11.0.tgz",
|
||||
"integrity": "sha512-P+qEahbgeHW4JQ/87FuItjBj8O3MYv5gELDzr8QaQ7fsll1gSMTYb6j87MYyxwf3DtD7uGFB9ShwgmCJB5KmaQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@eslint-community/regexpp": "^4.10.0",
|
||||
"@typescript-eslint/scope-manager": "7.8.0",
|
||||
"@typescript-eslint/type-utils": "7.8.0",
|
||||
"@typescript-eslint/utils": "7.8.0",
|
||||
"@typescript-eslint/visitor-keys": "7.8.0",
|
||||
"debug": "^4.3.4",
|
||||
"@typescript-eslint/scope-manager": "7.11.0",
|
||||
"@typescript-eslint/type-utils": "7.11.0",
|
||||
"@typescript-eslint/utils": "7.11.0",
|
||||
"@typescript-eslint/visitor-keys": "7.11.0",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^5.3.1",
|
||||
"natural-compare": "^1.4.0",
|
||||
"semver": "^7.6.0",
|
||||
"ts-api-utils": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/parser": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.8.0.tgz",
|
||||
"integrity": "sha512-KgKQly1pv0l4ltcftP59uQZCi4HUYswCLbTqVZEJu7uLX8CTLyswqMLqLN+2QFz4jCptqWVV4SB7vdxcH2+0kQ==",
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.11.0.tgz",
|
||||
"integrity": "sha512-yimw99teuaXVWsBcPO1Ais02kwJ1jmNA1KxE7ng0aT7ndr1pT1wqj0OJnsYVGKKlc4QJai86l/025L6z8CljOg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/scope-manager": "7.8.0",
|
||||
"@typescript-eslint/types": "7.8.0",
|
||||
"@typescript-eslint/typescript-estree": "7.8.0",
|
||||
"@typescript-eslint/visitor-keys": "7.8.0",
|
||||
"@typescript-eslint/scope-manager": "7.11.0",
|
||||
"@typescript-eslint/types": "7.11.0",
|
||||
"@typescript-eslint/typescript-estree": "7.11.0",
|
||||
"@typescript-eslint/visitor-keys": "7.11.0",
|
||||
"debug": "^4.3.4"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/scope-manager": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.8.0.tgz",
|
||||
"integrity": "sha512-viEmZ1LmwsGcnr85gIq+FCYI7nO90DVbE37/ll51hjv9aG+YZMb4WDE2fyWpUR4O/UrhGRpYXK/XajcGTk2B8g==",
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.11.0.tgz",
|
||||
"integrity": "sha512-27tGdVEiutD4POirLZX4YzT180vevUURJl4wJGmm6TrQoiYwuxTIY98PBp6L2oN+JQxzE0URvYlzJaBHIekXAw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "7.8.0",
|
||||
"@typescript-eslint/visitor-keys": "7.8.0"
|
||||
"@typescript-eslint/types": "7.11.0",
|
||||
"@typescript-eslint/visitor-keys": "7.11.0"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/type-utils": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.8.0.tgz",
|
||||
"integrity": "sha512-H70R3AefQDQpz9mGv13Uhi121FNMh+WEaRqcXTX09YEDky21km4dV1ZXJIp8QjXc4ZaVkXVdohvWDzbnbHDS+A==",
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.11.0.tgz",
|
||||
"integrity": "sha512-WmppUEgYy+y1NTseNMJ6mCFxt03/7jTOy08bcg7bxJJdsM4nuhnchyBbE8vryveaJUf62noH7LodPSo5Z0WUCg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/typescript-estree": "7.8.0",
|
||||
"@typescript-eslint/utils": "7.8.0",
|
||||
"@typescript-eslint/typescript-estree": "7.11.0",
|
||||
"@typescript-eslint/utils": "7.11.0",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/types": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.8.0.tgz",
|
||||
"integrity": "sha512-wf0peJ+ZGlcH+2ZS23aJbOv+ztjeeP8uQ9GgwMJGVLx/Nj9CJt17GWgWWoSmoRVKAX2X+7fzEnAjxdvK2gqCLw==",
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.11.0.tgz",
|
||||
"integrity": "sha512-MPEsDRZTyCiXkD4vd3zywDCifi7tatc4K37KqTprCvaXptP7Xlpdw0NR2hRJTetG5TxbWDB79Ys4kLmHliEo/w==",
|
||||
"dev": true
|
||||
},
|
||||
"@typescript-eslint/typescript-estree": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.8.0.tgz",
|
||||
"integrity": "sha512-5pfUCOwK5yjPaJQNy44prjCwtr981dO8Qo9J9PwYXZ0MosgAbfEMB008dJ5sNo3+/BN6ytBPuSvXUg9SAqB0dg==",
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.11.0.tgz",
|
||||
"integrity": "sha512-cxkhZ2C/iyi3/6U9EPc5y+a6csqHItndvN/CzbNXTNrsC3/ASoYQZEt9uMaEp+xFNjasqQyszp5TumAVKKvJeQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "7.8.0",
|
||||
"@typescript-eslint/visitor-keys": "7.8.0",
|
||||
"@typescript-eslint/types": "7.11.0",
|
||||
"@typescript-eslint/visitor-keys": "7.11.0",
|
||||
"debug": "^4.3.4",
|
||||
"globby": "^11.1.0",
|
||||
"is-glob": "^4.0.3",
|
||||
@@ -10192,27 +10205,24 @@
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/utils": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.8.0.tgz",
|
||||
"integrity": "sha512-L0yFqOCflVqXxiZyXrDr80lnahQfSOfc9ELAAZ75sqicqp2i36kEZZGuUymHNFoYOqxRT05up760b4iGsl02nQ==",
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.11.0.tgz",
|
||||
"integrity": "sha512-xlAWwPleNRHwF37AhrZurOxA1wyXowW4PqVXZVUNCLjB48CqdPJoJWkrpH2nij9Q3Lb7rtWindtoXwxjxlKKCA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@eslint-community/eslint-utils": "^4.4.0",
|
||||
"@types/json-schema": "^7.0.15",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@typescript-eslint/scope-manager": "7.8.0",
|
||||
"@typescript-eslint/types": "7.8.0",
|
||||
"@typescript-eslint/typescript-estree": "7.8.0",
|
||||
"semver": "^7.6.0"
|
||||
"@typescript-eslint/scope-manager": "7.11.0",
|
||||
"@typescript-eslint/types": "7.11.0",
|
||||
"@typescript-eslint/typescript-estree": "7.11.0"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/visitor-keys": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.8.0.tgz",
|
||||
"integrity": "sha512-q4/gibTNBQNA0lGyYQCmWRS5D15n8rXh4QjK3KV+MBPlTYHpfBUT3D3PaPR/HeNiI9W6R7FvlkcGhNyAoP+caA==",
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.11.0.tgz",
|
||||
"integrity": "sha512-7syYk4MzjxTEk0g/w3iqtgxnFQspDJfn6QKD36xMuuhTzjcxY7F8EmBLnALjVyaOF1/bVocu3bS/2/F7rXrveQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "7.8.0",
|
||||
"@typescript-eslint/types": "7.11.0",
|
||||
"eslint-visitor-keys": "^3.4.3"
|
||||
}
|
||||
},
|
||||
@@ -10722,9 +10732,9 @@
|
||||
}
|
||||
},
|
||||
"commander": {
|
||||
"version": "12.0.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz",
|
||||
"integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==",
|
||||
"version": "12.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
|
||||
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
|
||||
"dev": true
|
||||
},
|
||||
"common-tags": {
|
||||
@@ -10760,9 +10770,9 @@
|
||||
}
|
||||
},
|
||||
"csv-parse": {
|
||||
"version": "5.5.5",
|
||||
"resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-5.5.5.tgz",
|
||||
"integrity": "sha512-erCk7tyU3yLWAhk6wvKxnyPtftuy/6Ak622gOO7BCJ05+TYffnPCJF905wmOQm+BpkX54OdAl8pveJwUdpnCXQ=="
|
||||
"version": "5.5.6",
|
||||
"resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-5.5.6.tgz",
|
||||
"integrity": "sha512-uNpm30m/AGSkLxxy7d9yRXpJQFrZzVWLFBkS+6ngPcZkw/5k3L/jjFuj7tVnEpRn+QgmiXr21nDlhCiUK4ij2A=="
|
||||
},
|
||||
"damerau-levenshtein": {
|
||||
"version": "1.0.8",
|
||||
@@ -11267,9 +11277,9 @@
|
||||
}
|
||||
},
|
||||
"eslint-plugin-jsonc": {
|
||||
"version": "2.15.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-jsonc/-/eslint-plugin-jsonc-2.15.1.tgz",
|
||||
"integrity": "sha512-PVFrqIJa8BbM/e828RSn0SwB/Z5ye+2LDuy2XqG6AymNgPsfApRRcznsbxP7VrjdLEU4Nb+g9n/d6opyp0jp9A==",
|
||||
"version": "2.16.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-jsonc/-/eslint-plugin-jsonc-2.16.0.tgz",
|
||||
"integrity": "sha512-Af/ZL5mgfb8FFNleH6KlO4/VdmDuTqmM+SPnWcdoWywTetv7kq+vQe99UyQb9XO3b0OWLVuTH7H0d/PXYCMdSg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
@@ -12500,9 +12510,9 @@
|
||||
}
|
||||
},
|
||||
"jose": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/jose/-/jose-5.2.3.tgz",
|
||||
"integrity": "sha512-KUXdbctm1uHVL8BYhnyHkgp3zDX5KW8ZhAKVFEfUbU2P8Alpzjb+48hHvjOdQIyPshoblhzsuqOwEEAbtHVirA==",
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/jose/-/jose-5.3.0.tgz",
|
||||
"integrity": "sha512-IChe9AtAE79ru084ow8jzkN2lNrG3Ntfiv65Cvj9uOCE2m5LNsdHG+9EbxWxAoWRF9TgDOqLN5jm08++owDVRg==",
|
||||
"dev": true
|
||||
},
|
||||
"js-tokens": {
|
||||
@@ -12920,14 +12930,14 @@
|
||||
}
|
||||
},
|
||||
"markdownlint-cli": {
|
||||
"version": "0.40.0",
|
||||
"resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.40.0.tgz",
|
||||
"integrity": "sha512-JXhI3dRQcaqwiFYpPz6VJ7aKYheD53GmTz9y4D/d0F1MbZDGOp9pqKlbOfUX/pHP/iAoeiE4wYRmk8/kjLakxA==",
|
||||
"version": "0.41.0",
|
||||
"resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.41.0.tgz",
|
||||
"integrity": "sha512-kp29tKrMKdn+xonfefjp3a/MsNzAd9c5ke0ydMEI9PR98bOjzglYN4nfMSaIs69msUf1DNkgevAIAPtK2SeX0Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"commander": "~12.0.0",
|
||||
"commander": "~12.1.0",
|
||||
"get-stdin": "~9.0.0",
|
||||
"glob": "~10.3.12",
|
||||
"glob": "~10.4.1",
|
||||
"ignore": "~5.3.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonc-parser": "~3.2.1",
|
||||
@@ -12935,7 +12945,7 @@
|
||||
"markdownlint": "~0.34.0",
|
||||
"minimatch": "~9.0.4",
|
||||
"run-con": "~1.3.2",
|
||||
"toml": "~3.0.0"
|
||||
"smol-toml": "~1.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"brace-expansion": {
|
||||
@@ -12948,16 +12958,26 @@
|
||||
}
|
||||
},
|
||||
"glob": {
|
||||
"version": "10.3.12",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz",
|
||||
"integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==",
|
||||
"version": "10.4.1",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz",
|
||||
"integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"foreground-child": "^3.1.0",
|
||||
"jackspeak": "^2.3.6",
|
||||
"minimatch": "^9.0.1",
|
||||
"minipass": "^7.0.4",
|
||||
"path-scurry": "^1.10.2"
|
||||
"jackspeak": "^3.1.2",
|
||||
"minimatch": "^9.0.4",
|
||||
"minipass": "^7.1.2",
|
||||
"path-scurry": "^1.11.1"
|
||||
}
|
||||
},
|
||||
"jackspeak": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.1.2.tgz",
|
||||
"integrity": "sha512-kWmLKn2tRtfYMF/BakihVVRzBKOxz4gJMiL2Rj91WnAB5TPZumSH99R/Yf1qE1u4uRimvCSJfm6hnxohXeEXjQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@isaacs/cliui": "^8.0.2",
|
||||
"@pkgjs/parseargs": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
@@ -13025,7 +13045,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"minipass": {
|
||||
"version": "7.0.4"
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
||||
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="
|
||||
},
|
||||
"minipass-collect": {
|
||||
"version": "2.0.1",
|
||||
@@ -13306,9 +13328,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"path-scurry": {
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz",
|
||||
"integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==",
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
|
||||
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
|
||||
"requires": {
|
||||
"lru-cache": "^10.2.0",
|
||||
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
|
||||
@@ -13806,6 +13828,12 @@
|
||||
"smart-buffer": {
|
||||
"version": "4.2.0"
|
||||
},
|
||||
"smol-toml": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.2.0.tgz",
|
||||
"integrity": "sha512-KObxdQANC/xje3OoatMbSwQf2XAvJ0RbK+4nmQRszFNZptbNRnMWqbLF/zb4sMi9xJ6HNyhWXeuZ9zC/I/XY7w==",
|
||||
"dev": true
|
||||
},
|
||||
"socks": {
|
||||
"version": "2.7.3",
|
||||
"requires": {
|
||||
@@ -14035,12 +14063,6 @@
|
||||
"is-number": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"toml": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz",
|
||||
"integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==",
|
||||
"dev": true
|
||||
},
|
||||
"ts-api-utils": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
|
||||
@@ -14049,7 +14071,9 @@
|
||||
"requires": {}
|
||||
},
|
||||
"ts-jest": {
|
||||
"version": "29.1.2",
|
||||
"version": "29.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.3.tgz",
|
||||
"integrity": "sha512-6L9qz3ginTd1NKhOxmkP0qU3FyKjj5CPoY+anszfVn6Pmv/RIKzhiMCsH7Yb7UvJR9I2A64rm4zQl531s2F1iw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bs-logger": "0.x",
|
||||
|
||||
20
package.json
20
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "actions/attest",
|
||||
"description": "Generate signed attestations for workflow artifacts",
|
||||
"version": "1.1.1",
|
||||
"version": "1.2.0",
|
||||
"author": "",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/actions/attest",
|
||||
@@ -72,29 +72,29 @@
|
||||
"@actions/attest": "^1.2.1",
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/glob": "^0.4.0",
|
||||
"@sigstore/oci": "^0.3.2",
|
||||
"csv-parse": "^5.5.5"
|
||||
"@sigstore/oci": "^0.3.4",
|
||||
"csv-parse": "^5.5.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sigstore/mock": "^0.7.2",
|
||||
"@sigstore/mock": "^0.7.4",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/make-fetch-happen": "^10.0.4",
|
||||
"@types/node": "^20.12.10",
|
||||
"@typescript-eslint/eslint-plugin": "^7.8.0",
|
||||
"@typescript-eslint/parser": "^7.8.0",
|
||||
"@types/node": "^20.12.12",
|
||||
"@typescript-eslint/eslint-plugin": "^7.11.0",
|
||||
"@typescript-eslint/parser": "^7.11.0",
|
||||
"@vercel/ncc": "^0.38.1",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-github": "^4.10.2",
|
||||
"eslint-plugin-jest": "^28.5.0",
|
||||
"eslint-plugin-jsonc": "^2.15.1",
|
||||
"eslint-plugin-jsonc": "^2.16.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"jest": "^29.7.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"markdownlint-cli": "^0.40.0",
|
||||
"markdownlint-cli": "^0.41.0",
|
||||
"nock": "^13.5.4",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-eslint": "^16.3.0",
|
||||
"ts-jest": "^29.1.2",
|
||||
"ts-jest": "^29.1.3",
|
||||
"typescript": "^5.4.5",
|
||||
"undici": "^5.28.4"
|
||||
}
|
||||
|
||||
67
src/attest.ts
Normal file
67
src/attest.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { Attestation, Predicate, Subject, attest } from '@actions/attest'
|
||||
import { attachArtifactToImage, getRegistryCredentials } from '@sigstore/oci'
|
||||
|
||||
const OCI_TIMEOUT = 2000
|
||||
const OCI_RETRY = 3
|
||||
|
||||
export type SigstoreInstance = 'public-good' | 'github'
|
||||
export type AttestResult = Attestation & {
|
||||
subjectName: string
|
||||
subjectDigest: string
|
||||
attestationDigest?: string
|
||||
}
|
||||
|
||||
export const createAttestation = async (
|
||||
subject: Subject,
|
||||
predicate: Predicate,
|
||||
opts: {
|
||||
sigstoreInstance: SigstoreInstance
|
||||
pushToRegistry: boolean
|
||||
githubToken: string
|
||||
}
|
||||
): Promise<AttestResult> => {
|
||||
// Sign provenance w/ Sigstore
|
||||
const attestation = await attest({
|
||||
subjectName: subject.name,
|
||||
subjectDigest: subject.digest,
|
||||
predicateType: predicate.type,
|
||||
predicate: predicate.params,
|
||||
sigstore: opts.sigstoreInstance,
|
||||
token: opts.githubToken
|
||||
})
|
||||
|
||||
const subDigest = subjectDigest(subject)
|
||||
const result: AttestResult = {
|
||||
...attestation,
|
||||
subjectName: subject.name,
|
||||
subjectDigest: subDigest
|
||||
}
|
||||
|
||||
if (opts.pushToRegistry) {
|
||||
const credentials = getRegistryCredentials(subject.name)
|
||||
const artifact = await attachArtifactToImage({
|
||||
credentials,
|
||||
imageName: subject.name,
|
||||
imageDigest: subDigest,
|
||||
artifact: Buffer.from(JSON.stringify(attestation.bundle)),
|
||||
mediaType: attestation.bundle.mediaType,
|
||||
annotations: {
|
||||
'dev.sigstore.bundle.content': 'dsse-envelope',
|
||||
'dev.sigstore.bundle.predicateType': predicate.type
|
||||
},
|
||||
fetchOpts: { timeout: OCI_TIMEOUT, retry: OCI_RETRY }
|
||||
})
|
||||
|
||||
// Add the attestation's digest to the result
|
||||
result.attestationDigest = artifact.digest
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Returns the subject's digest as a formatted string of the form
|
||||
// "<algorithm>:<digest>".
|
||||
const subjectDigest = (subject: Subject): string => {
|
||||
const alg = Object.keys(subject.digest).sort()[0]
|
||||
return `${alg}:${subject.digest[alg]}`
|
||||
}
|
||||
24
src/index.ts
24
src/index.ts
@@ -1,7 +1,27 @@
|
||||
/**
|
||||
* The entrypoint for the action.
|
||||
*/
|
||||
import { run } from './main'
|
||||
import * as core from '@actions/core'
|
||||
import { run, RunInputs } from './main'
|
||||
|
||||
const DEFAULT_BATCH_SIZE = 50
|
||||
|
||||
const inputs: RunInputs = {
|
||||
subjectPath: core.getInput('subject-path'),
|
||||
subjectName: core.getInput('subject-name'),
|
||||
subjectDigest: core.getInput('subject-digest'),
|
||||
predicateType: core.getInput('predicate-type'),
|
||||
predicate: core.getInput('predicate'),
|
||||
predicatePath: core.getInput('predicate-path'),
|
||||
pushToRegistry: core.getBooleanInput('push-to-registry'),
|
||||
githubToken: core.getInput('github-token'),
|
||||
// undocumented -- not part of public interface
|
||||
privateSigning: ['true', 'True', 'TRUE', '1'].includes(
|
||||
core.getInput('private-signing')
|
||||
),
|
||||
// internal only
|
||||
batchSize: DEFAULT_BATCH_SIZE
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
run()
|
||||
run(inputs)
|
||||
|
||||
209
src/main.ts
209
src/main.ts
@@ -1,26 +1,25 @@
|
||||
import { Attestation, Predicate, Subject, attest } from '@actions/attest'
|
||||
import * as core from '@actions/core'
|
||||
import * as github from '@actions/github'
|
||||
import { attachArtifactToImage, getRegistryCredentials } from '@sigstore/oci'
|
||||
import fs from 'fs'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import { AttestResult, SigstoreInstance, createAttestation } from './attest'
|
||||
import { SEARCH_PUBLIC_GOOD_URL } from './endpoints'
|
||||
import { predicateFromInputs } from './predicate'
|
||||
import { subjectFromInputs } from './subject'
|
||||
import { PredicateInputs, predicateFromInputs } from './predicate'
|
||||
import * as style from './style'
|
||||
import { SubjectInputs, subjectFromInputs } from './subject'
|
||||
|
||||
type SigstoreInstance = 'public-good' | 'github'
|
||||
type AttestedSubject = { subject: Subject; attestationID: string }
|
||||
|
||||
const COLOR_CYAN = '\x1B[36m'
|
||||
const COLOR_GRAY = '\x1B[38;5;244m'
|
||||
const COLOR_DEFAULT = '\x1B[39m'
|
||||
const ATTESTATION_FILE_NAME = 'attestation.jsonl'
|
||||
const DELAY_INTERVAL_MS = 75
|
||||
const DELAY_MAX_MS = 1200
|
||||
|
||||
const MAX_SUBJECT_COUNT = 64
|
||||
|
||||
const OCI_TIMEOUT = 2000
|
||||
const OCI_RETRY = 3
|
||||
export type RunInputs = SubjectInputs &
|
||||
PredicateInputs & {
|
||||
pushToRegistry: boolean
|
||||
githubToken: string
|
||||
privateSigning: boolean
|
||||
batchSize: number
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
const logHandler = (level: string, ...args: unknown[]): void => {
|
||||
@@ -34,7 +33,7 @@ const logHandler = (level: string, ...args: unknown[]): void => {
|
||||
* The main function for the action.
|
||||
* @returns {Promise<void>} Resolves when the action is complete.
|
||||
*/
|
||||
export async function run(): Promise<void> {
|
||||
export async function run(inputs: RunInputs): Promise<void> {
|
||||
process.on('log', logHandler)
|
||||
|
||||
// Provenance visibility will be public ONLY if we can confirm that the
|
||||
@@ -42,61 +41,62 @@ export async function run(): Promise<void> {
|
||||
// Otherwise, it will be private.
|
||||
const sigstoreInstance: SigstoreInstance =
|
||||
github.context.payload.repository?.visibility === 'public' &&
|
||||
core.getInput('private-signing') !== 'true'
|
||||
!inputs.privateSigning
|
||||
? 'public-good'
|
||||
: 'github'
|
||||
|
||||
try {
|
||||
const atts: AttestedSubject[] = []
|
||||
const atts: AttestResult[] = []
|
||||
if (!process.env.ACTIONS_ID_TOKEN_REQUEST_URL) {
|
||||
throw new Error(
|
||||
'missing "id-token" permission. Please add "permissions: id-token: write" to your workflow.'
|
||||
)
|
||||
}
|
||||
|
||||
// Gather list of subjets
|
||||
const subjects = await subjectFromInputs()
|
||||
if (subjects.length > MAX_SUBJECT_COUNT) {
|
||||
throw new Error(
|
||||
`Too many subjects specified. The maximum number of subjects is ${MAX_SUBJECT_COUNT}.`
|
||||
)
|
||||
}
|
||||
const subjects = await subjectFromInputs({
|
||||
...inputs,
|
||||
downcaseName: inputs.pushToRegistry
|
||||
})
|
||||
const predicate = predicateFromInputs(inputs)
|
||||
|
||||
const predicate = predicateFromInputs()
|
||||
const outputPath = path.join(tempDir(), ATTESTATION_FILE_NAME)
|
||||
|
||||
// Generate attestations for each subject serially
|
||||
for (const subject of subjects) {
|
||||
const att = await createAttestation(subject, predicate, sigstoreInstance)
|
||||
|
||||
// Write attestation bundle to output file
|
||||
fs.writeFileSync(outputPath, JSON.stringify(att.bundle) + os.EOL, {
|
||||
encoding: 'utf-8',
|
||||
flag: 'a'
|
||||
})
|
||||
|
||||
if (att.attestationID) {
|
||||
atts.push({ subject, attestationID: att.attestationID })
|
||||
}
|
||||
}
|
||||
|
||||
if (atts.length > 0) {
|
||||
core.summary.addHeading(
|
||||
/* istanbul ignore next */
|
||||
atts.length > 1 ? 'Attestations Created' : 'Attestation Created',
|
||||
3
|
||||
)
|
||||
|
||||
for (const { subject, attestationID } of atts) {
|
||||
core.summary.addLink(
|
||||
`${subject.name}@${subjectDigest(subject)}`,
|
||||
attestationURL(attestationID)
|
||||
)
|
||||
}
|
||||
core.summary.write()
|
||||
}
|
||||
|
||||
core.setOutput('bundle-path', outputPath)
|
||||
|
||||
const subjectChunks = chunkArray(subjects, inputs.batchSize)
|
||||
|
||||
// Generate attestations for each subject serially, working in batches
|
||||
for (let i = 0; i < subjectChunks.length; i++) {
|
||||
if (subjectChunks.length > 1) {
|
||||
core.info(`Processing subject batch ${i + 1}/${subjectChunks.length}`)
|
||||
}
|
||||
|
||||
// Calculate the delay time for this batch
|
||||
const delayTime = delay(i)
|
||||
|
||||
for (const subject of subjectChunks[i]) {
|
||||
// Delay between attestations (only when chunk size > 1)
|
||||
if (i > 0) {
|
||||
await new Promise(resolve => setTimeout(resolve, delayTime))
|
||||
}
|
||||
|
||||
const att = await createAttestation(subject, predicate, {
|
||||
sigstoreInstance,
|
||||
pushToRegistry: inputs.pushToRegistry,
|
||||
githubToken: inputs.githubToken
|
||||
})
|
||||
atts.push(att)
|
||||
|
||||
logAttestation(att, sigstoreInstance)
|
||||
|
||||
// Write attestation bundle to output file
|
||||
fs.writeFileSync(outputPath, JSON.stringify(att.bundle) + os.EOL, {
|
||||
encoding: 'utf-8',
|
||||
flag: 'a'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
logSummary(atts)
|
||||
} catch (err) {
|
||||
// Fail the workflow run if an error occurs
|
||||
core.setFailed(
|
||||
@@ -108,7 +108,9 @@ export async function run(): Promise<void> {
|
||||
if (err instanceof Error && 'cause' in err) {
|
||||
const innerErr = err.cause
|
||||
core.info(
|
||||
mute(innerErr instanceof Error ? innerErr.toString() : `${innerErr}`)
|
||||
style.mute(
|
||||
innerErr instanceof Error ? innerErr.toString() : `${innerErr}`
|
||||
)
|
||||
)
|
||||
}
|
||||
} finally {
|
||||
@@ -116,27 +118,19 @@ export async function run(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
const createAttestation = async (
|
||||
subject: Subject,
|
||||
predicate: Predicate,
|
||||
// Log details about the attestation to the GitHub Actions run
|
||||
const logAttestation = (
|
||||
attestation: AttestResult,
|
||||
sigstoreInstance: SigstoreInstance
|
||||
): Promise<Attestation> => {
|
||||
// Sign provenance w/ Sigstore
|
||||
const attestation = await attest({
|
||||
subjectName: subject.name,
|
||||
subjectDigest: subject.digest,
|
||||
predicateType: predicate.type,
|
||||
predicate: predicate.params,
|
||||
sigstore: sigstoreInstance,
|
||||
token: core.getInput('github-token')
|
||||
})
|
||||
|
||||
core.info(`Attestation created for ${subject.name}@${subjectDigest(subject)}`)
|
||||
): void => {
|
||||
core.info(
|
||||
`Attestation created for ${attestation.subjectName}@${attestation.subjectDigest}`
|
||||
)
|
||||
|
||||
const instanceName =
|
||||
sigstoreInstance === 'public-good' ? 'Public Good' : 'GitHub'
|
||||
core.startGroup(
|
||||
highlight(
|
||||
style.highlight(
|
||||
`Attestation signed using certificate from ${instanceName} Sigstore instance`
|
||||
)
|
||||
)
|
||||
@@ -145,43 +139,44 @@ const createAttestation = async (
|
||||
|
||||
if (attestation.tlogID) {
|
||||
core.info(
|
||||
highlight('Attestation signature uploaded to Rekor transparency log')
|
||||
style.highlight(
|
||||
'Attestation signature uploaded to Rekor transparency log'
|
||||
)
|
||||
)
|
||||
core.info(`${SEARCH_PUBLIC_GOOD_URL}?logIndex=${attestation.tlogID}`)
|
||||
}
|
||||
|
||||
if (attestation.attestationID) {
|
||||
core.info(highlight('Attestation uploaded to repository'))
|
||||
core.info(style.highlight('Attestation uploaded to repository'))
|
||||
core.info(attestationURL(attestation.attestationID))
|
||||
}
|
||||
|
||||
if (core.getBooleanInput('push-to-registry', { required: false })) {
|
||||
const credentials = getRegistryCredentials(subject.name)
|
||||
const artifact = await attachArtifactToImage({
|
||||
credentials,
|
||||
imageName: subject.name,
|
||||
imageDigest: subjectDigest(subject),
|
||||
artifact: Buffer.from(JSON.stringify(attestation.bundle)),
|
||||
mediaType: attestation.bundle.mediaType,
|
||||
annotations: {
|
||||
'dev.sigstore.bundle.content': 'dsse-envelope',
|
||||
'dev.sigstore.bundle.predicateType': core.getInput('predicate-type')
|
||||
},
|
||||
fetchOpts: { timeout: OCI_TIMEOUT, retry: OCI_RETRY }
|
||||
})
|
||||
core.info(highlight('Attestation uploaded to registry'))
|
||||
core.info(`${subject.name}@${artifact.digest}`)
|
||||
if (attestation.attestationDigest) {
|
||||
core.info(style.highlight('Attestation uploaded to registry'))
|
||||
core.info(`${attestation.subjectName}@${attestation.attestationDigest}`)
|
||||
}
|
||||
|
||||
return attestation
|
||||
}
|
||||
|
||||
// Emphasis string using ANSI color codes
|
||||
const highlight = (str: string): string => `${COLOR_CYAN}${str}${COLOR_DEFAULT}`
|
||||
// Attach summary information to the GitHub Actions run
|
||||
const logSummary = (attestations: AttestResult[]): void => {
|
||||
if (attestations.length > 0) {
|
||||
core.summary.addHeading(
|
||||
/* istanbul ignore next */
|
||||
attestations.length > 1 ? 'Attestations Created' : 'Attestation Created',
|
||||
3
|
||||
)
|
||||
|
||||
// De-emphasize string using ANSI color codes
|
||||
/* istanbul ignore next */
|
||||
const mute = (str: string): string => `${COLOR_GRAY}${str}${COLOR_DEFAULT}`
|
||||
for (const { subjectName, subjectDigest, attestationID } of attestations) {
|
||||
if (attestationID) {
|
||||
core.summary.addLink(
|
||||
`${subjectName}@${subjectDigest}`,
|
||||
attestationURL(attestationID)
|
||||
)
|
||||
}
|
||||
}
|
||||
core.summary.write()
|
||||
}
|
||||
}
|
||||
|
||||
const tempDir = (): string => {
|
||||
const basePath = process.env['RUNNER_TEMP']
|
||||
@@ -194,12 +189,18 @@ const tempDir = (): string => {
|
||||
return fs.mkdtempSync(path.join(basePath, path.sep))
|
||||
}
|
||||
|
||||
// Returns the subject's digest as a formatted string of the form
|
||||
// "<algorithm>:<digest>".
|
||||
const subjectDigest = (subject: Subject): string => {
|
||||
const alg = Object.keys(subject.digest).sort()[0]
|
||||
return `${alg}:${subject.digest[alg]}`
|
||||
// Transforms an array into an array of arrays, each containing at most
|
||||
// `chunkSize` elements.
|
||||
const chunkArray = <T>(array: T[], chunkSize: number): T[][] => {
|
||||
return Array.from(
|
||||
{ length: Math.ceil(array.length / chunkSize) },
|
||||
(_, index) => array.slice(index * chunkSize, (index + 1) * chunkSize)
|
||||
)
|
||||
}
|
||||
|
||||
// Calculate the delay time for a given iteration
|
||||
const delay = (iteration: number): number =>
|
||||
Math.min(DELAY_INTERVAL_MS * 2 ** iteration, DELAY_MAX_MS)
|
||||
|
||||
const attestationURL = (id: string): string =>
|
||||
`${github.context.serverUrl}/${github.context.repo.owner}/${github.context.repo.repo}/attestations/${id}`
|
||||
|
||||
@@ -1,26 +1,56 @@
|
||||
import * as core from '@actions/core'
|
||||
import fs from 'fs'
|
||||
|
||||
import type { Predicate } from '@actions/attest'
|
||||
|
||||
export type PredicateInputs = {
|
||||
predicateType: string
|
||||
predicate: string
|
||||
predicatePath: string
|
||||
}
|
||||
|
||||
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 = (): Predicate => {
|
||||
const predicateType = core.getInput('predicate-type', { required: true })
|
||||
const predicateStr = core.getInput('predicate', { required: false })
|
||||
const predicatePath = core.getInput('predicate-path', { required: false })
|
||||
export const predicateFromInputs = (inputs: PredicateInputs): Predicate => {
|
||||
const { predicateType, predicate, predicatePath } = inputs
|
||||
|
||||
if (!predicatePath && !predicateStr) {
|
||||
if (!predicateType) {
|
||||
throw new Error('predicate-type must be provided')
|
||||
}
|
||||
|
||||
if (!predicatePath && !predicate) {
|
||||
throw new Error('One of predicate-path or predicate must be provided')
|
||||
}
|
||||
|
||||
if (predicatePath && predicateStr) {
|
||||
if (predicatePath && predicate) {
|
||||
throw new Error('Only one of predicate-path or predicate may be provided')
|
||||
}
|
||||
|
||||
const params = predicatePath
|
||||
? fs.readFileSync(predicatePath, 'utf-8')
|
||||
: predicateStr
|
||||
let params: string = predicate
|
||||
|
||||
if (predicatePath) {
|
||||
if (!fs.existsSync(predicatePath)) {
|
||||
throw new Error(`predicate file not found: ${predicatePath}`)
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (fs.statSync(predicatePath).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')
|
||||
} else {
|
||||
if (predicate.length > MAX_PREDICATE_SIZE_BYTES) {
|
||||
throw new Error(
|
||||
`predicate string exceeds maximum allowed size: ${MAX_PREDICATE_SIZE_BYTES} bytes`
|
||||
)
|
||||
}
|
||||
|
||||
params = predicate
|
||||
}
|
||||
|
||||
return { type: predicateType, params: JSON.parse(params) }
|
||||
}
|
||||
|
||||
11
src/style.ts
Normal file
11
src/style.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
const COLOR_CYAN = '\x1B[36m'
|
||||
const COLOR_GRAY = '\x1B[38;5;244m'
|
||||
const COLOR_DEFAULT = '\x1B[39m'
|
||||
|
||||
// Emphasis string using ANSI color codes
|
||||
export const highlight = (str: string): string =>
|
||||
`${COLOR_CYAN}${str}${COLOR_DEFAULT}`
|
||||
|
||||
// De-emphasize string using ANSI color codes
|
||||
export const mute = (str: string): string =>
|
||||
`${COLOR_GRAY}${str}${COLOR_DEFAULT}`
|
||||
@@ -1,4 +1,3 @@
|
||||
import * as core from '@actions/core'
|
||||
import * as glob from '@actions/glob'
|
||||
import crypto from 'crypto'
|
||||
import { parse } from 'csv-parse/sync'
|
||||
@@ -7,16 +6,23 @@ import path from 'path'
|
||||
|
||||
import type { Subject } from '@actions/attest'
|
||||
|
||||
const MAX_SUBJECT_COUNT = 2500
|
||||
const DIGEST_ALGORITHM = 'sha256'
|
||||
|
||||
export type SubjectInputs = {
|
||||
subjectPath: string
|
||||
subjectName: string
|
||||
subjectDigest: string
|
||||
downcaseName?: boolean
|
||||
}
|
||||
// Returns the subject specified by the action's inputs. The subject may be
|
||||
// specified as a path to a file or as a digest. If a path is provided, the
|
||||
// file's digest is calculated and returned along with the subject's name. If a
|
||||
// digest is provided, the name must also be provided.
|
||||
export const subjectFromInputs = async (): Promise<Subject[]> => {
|
||||
const subjectPath = core.getInput('subject-path', { required: false })
|
||||
const subjectDigest = core.getInput('subject-digest', { required: false })
|
||||
const subjectName = core.getInput('subject-name', { required: false })
|
||||
export const subjectFromInputs = async (
|
||||
inputs: SubjectInputs
|
||||
): Promise<Subject[]> => {
|
||||
const { subjectPath, subjectDigest, subjectName, downcaseName } = inputs
|
||||
|
||||
if (!subjectPath && !subjectDigest) {
|
||||
throw new Error('One of subject-path or subject-digest must be provided')
|
||||
@@ -32,10 +38,14 @@ export const subjectFromInputs = async (): Promise<Subject[]> => {
|
||||
throw new Error('subject-name must be provided when using subject-digest')
|
||||
}
|
||||
|
||||
// If push-to-registry is enabled, ensure the subject name is lowercase
|
||||
// to conform to OCI image naming conventions
|
||||
const name = downcaseName ? subjectName.toLowerCase() : subjectName
|
||||
|
||||
if (subjectPath) {
|
||||
return await getSubjectFromPath(subjectPath, subjectName)
|
||||
return await getSubjectFromPath(subjectPath, name)
|
||||
} else {
|
||||
return [getSubjectFromDigest(subjectDigest, subjectName)]
|
||||
return [getSubjectFromDigest(subjectDigest, name)]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,34 +55,41 @@ const getSubjectFromPath = async (
|
||||
subjectPath: string,
|
||||
subjectName?: string
|
||||
): Promise<Subject[]> => {
|
||||
const subjects: Subject[] = []
|
||||
const digestedSubjects: Subject[] = []
|
||||
const files: string[] = []
|
||||
|
||||
// Parse the list of subject paths
|
||||
const subjectPaths = parseList(subjectPath)
|
||||
|
||||
// Expand the globbed paths to a list of files
|
||||
for (const subPath of subjectPaths) {
|
||||
// Expand the globbed path to a list of files
|
||||
/* eslint-disable-next-line github/no-then */
|
||||
const files = await glob.create(subPath).then(async g => g.glob())
|
||||
|
||||
for (const file of files) {
|
||||
// Skip anything that is NOT a file
|
||||
if (!fs.statSync(file).isFile()) {
|
||||
continue
|
||||
}
|
||||
|
||||
const name = subjectName || path.parse(file).base
|
||||
const digest = await digestFile(DIGEST_ALGORITHM, file)
|
||||
|
||||
subjects.push({ name, digest: { [DIGEST_ALGORITHM]: digest } })
|
||||
}
|
||||
files.push(...(await glob.create(subPath).then(async g => g.glob())))
|
||||
}
|
||||
|
||||
if (subjects.length === 0) {
|
||||
if (files.length > MAX_SUBJECT_COUNT) {
|
||||
throw new Error(
|
||||
`Too many subjects specified. The maximum number of subjects is ${MAX_SUBJECT_COUNT}.`
|
||||
)
|
||||
}
|
||||
|
||||
for (const file of files) {
|
||||
// Skip anything that is NOT a file
|
||||
if (!fs.statSync(file).isFile()) {
|
||||
continue
|
||||
}
|
||||
|
||||
const name = subjectName || path.parse(file).base
|
||||
const digest = await digestFile(DIGEST_ALGORITHM, file)
|
||||
|
||||
digestedSubjects.push({ name, digest: { [DIGEST_ALGORITHM]: digest } })
|
||||
}
|
||||
|
||||
if (digestedSubjects.length === 0) {
|
||||
throw new Error(`Could not find subject at path ${subjectPath}`)
|
||||
}
|
||||
|
||||
return Promise.all(subjects)
|
||||
return digestedSubjects
|
||||
}
|
||||
|
||||
// Returns the subject specified by the digest of a file. The digest is returned
|
||||
|
||||
Reference in New Issue
Block a user