Compare commits

..
Author SHA1 Message Date
Tatyana Kostromskaya 904b21412d . 2023-10-05 16:10:23 +02:00
Tatyana Kostromskaya 844682e182 . 2023-10-04 15:45:37 +00:00
Tatyana Kostromskaya f699b96ecf . 2023-10-04 15:33:15 +00:00
Tatyana Kostromskaya 774f139bef . 2023-10-04 14:56:21 +00:00
Tatyana Kostromskaya 5a395b732b . 2023-10-04 12:27:24 +00:00
Tatyana Kostromskaya 89e7efa45d . 2023-10-04 09:50:55 +00:00
48 changed files with 1089 additions and 2718 deletions
+14 -9
View File
@@ -24,10 +24,10 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Set Node.js 20.x - name: Set Node.js 20.x
uses: actions/setup-node@v4 uses: actions/setup-node@v3
with: with:
node-version: 20.x node-version: 20.x
@@ -54,8 +54,8 @@ jobs:
echo '${{ env.file1 }}' > artifact-path/first.txt echo '${{ env.file1 }}' > artifact-path/first.txt
echo '${{ env.file2 }}' > artifact-path/second.txt echo '${{ env.file2 }}' > artifact-path/second.txt
- name: Upload Artifacts using actions/github-script@v7 - name: Upload Artifacts using actions/github-script@v6
uses: actions/github-script@v7 uses: actions/github-script@v6
with: with:
script: | script: |
const artifact = require('./packages/artifact/lib/artifact') const artifact = require('./packages/artifact/lib/artifact')
@@ -68,20 +68,25 @@ jobs:
const uploadResult = await artifact.create().uploadArtifact(artifactName, fileContents, './') const uploadResult = await artifact.create().uploadArtifact(artifactName, fileContents, './')
console.log(uploadResult) console.log(uploadResult)
const success = uploadResult.success
const size = uploadResult.size const size = uploadResult.size
const id = uploadResult.id const id = uploadResult.id
console.log(`Successfully uploaded artifact ${id}`) if (!success) {
throw new Error('Failed to upload artifact')
} else {
console.log(`Successfully uploaded artifact ${id}`)
}
verify: verify:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [build] needs: [build]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Set Node.js 20.x - name: Set Node.js 20.x
uses: actions/setup-node@v4 uses: actions/setup-node@v3
with: with:
node-version: 20.x node-version: 20.x
@@ -96,8 +101,8 @@ jobs:
npm run tsc npm run tsc
working-directory: packages/artifact working-directory: packages/artifact
- name: List artifacts using actions/github-script@v7 - name: List artifacts using actions/github-script@v6
uses: actions/github-script@v7 uses: actions/github-script@v6
with: with:
script: | script: |
const artifact = require('./packages/artifact/lib/artifact') const artifact = require('./packages/artifact/lib/artifact')
+40 -40
View File
@@ -185,12 +185,12 @@
} }
}, },
"node_modules/@babel/generator": { "node_modules/@babel/generator": {
"version": "7.23.0", "version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.10.tgz",
"integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "integrity": "sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@babel/types": "^7.23.0", "@babel/types": "^7.22.10",
"@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/gen-mapping": "^0.3.2",
"@jridgewell/trace-mapping": "^0.3.17", "@jridgewell/trace-mapping": "^0.3.17",
"jsesc": "^2.5.1" "jsesc": "^2.5.1"
@@ -225,22 +225,22 @@
} }
}, },
"node_modules/@babel/helper-environment-visitor": { "node_modules/@babel/helper-environment-visitor": {
"version": "7.22.20", "version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz",
"integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/helper-function-name": { "node_modules/@babel/helper-function-name": {
"version": "7.23.0", "version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz",
"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@babel/template": "^7.22.15", "@babel/template": "^7.22.5",
"@babel/types": "^7.23.0" "@babel/types": "^7.22.5"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -332,9 +332,9 @@
} }
}, },
"node_modules/@babel/helper-validator-identifier": { "node_modules/@babel/helper-validator-identifier": {
"version": "7.22.20", "version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz",
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -449,9 +449,9 @@
} }
}, },
"node_modules/@babel/parser": { "node_modules/@babel/parser": {
"version": "7.23.0", "version": "7.22.13",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.13.tgz",
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "integrity": "sha512-3l6+4YOvc9wx7VlCSw4yQfcBo01ECA8TicQfbnCPuCEpRQrf+gTUyGdxNw+pyTUyywp6JRD1w0YQs9TpBXYlkw==",
"dev": true, "dev": true,
"bin": { "bin": {
"parser": "bin/babel-parser.js" "parser": "bin/babel-parser.js"
@@ -650,33 +650,33 @@
} }
}, },
"node_modules/@babel/template": { "node_modules/@babel/template": {
"version": "7.22.15", "version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz",
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.22.13", "@babel/code-frame": "^7.22.5",
"@babel/parser": "^7.22.15", "@babel/parser": "^7.22.5",
"@babel/types": "^7.22.15" "@babel/types": "^7.22.5"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/traverse": { "node_modules/@babel/traverse": {
"version": "7.23.2", "version": "7.22.11",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.11.tgz",
"integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", "integrity": "sha512-mzAenteTfomcB7mfPtyi+4oe5BZ6MXxWcn4CX+h4IRJ+OOGXBrWU6jDQavkQI9Vuc5P+donFabBfFCcmWka9lQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.22.13", "@babel/code-frame": "^7.22.10",
"@babel/generator": "^7.23.0", "@babel/generator": "^7.22.10",
"@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-environment-visitor": "^7.22.5",
"@babel/helper-function-name": "^7.23.0", "@babel/helper-function-name": "^7.22.5",
"@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6", "@babel/helper-split-export-declaration": "^7.22.6",
"@babel/parser": "^7.23.0", "@babel/parser": "^7.22.11",
"@babel/types": "^7.23.0", "@babel/types": "^7.22.11",
"debug": "^4.1.0", "debug": "^4.1.0",
"globals": "^11.1.0" "globals": "^11.1.0"
}, },
@@ -694,13 +694,13 @@
} }
}, },
"node_modules/@babel/types": { "node_modules/@babel/types": {
"version": "7.23.0", "version": "7.22.11",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.11.tgz",
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "integrity": "sha512-siazHiGuZRz9aB9NpHy9GOs9xiQPKnMzgdr493iI1M67vRXpnEq8ZOOKzezC5q7zwuQ6sDhdSp4SD9ixKSqKZg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@babel/helper-string-parser": "^7.22.5", "@babel/helper-string-parser": "^7.22.5",
"@babel/helper-validator-identifier": "^7.22.20", "@babel/helper-validator-identifier": "^7.22.5",
"to-fast-properties": "^2.0.0" "to-fast-properties": "^2.0.0"
}, },
"engines": { "engines": {
@@ -3262,9 +3262,9 @@
} }
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.6.2", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
"integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.0", "follow-redirects": "^1.15.0",
@@ -2,14 +2,18 @@ import * as http from 'http'
import * as net from 'net' import * as net from 'net'
import {HttpClient} from '@actions/http-client' import {HttpClient} from '@actions/http-client'
import * as config from '../src/internal/shared/config' import * as config from '../src/internal/shared/config'
import {internalArtifactTwirpClient} from '../src/internal/shared/artifact-twirp-client' import {createArtifactTwirpClient} from '../src/internal/shared/artifact-twirp-client'
import {noopLogs} from './common' import * as core from '@actions/core'
jest.mock('@actions/http-client') jest.mock('@actions/http-client')
describe('artifact-http-client', () => { describe('artifact-http-client', () => {
beforeAll(() => { beforeAll(() => {
noopLogs() // mock all output so that there is less noise when running tests
jest.spyOn(console, 'log').mockImplementation(() => {})
jest.spyOn(core, 'debug').mockImplementation(() => {})
jest.spyOn(core, 'info').mockImplementation(() => {})
jest.spyOn(core, 'warning').mockImplementation(() => {})
jest jest
.spyOn(config, 'getResultsServiceUrl') .spyOn(config, 'getResultsServiceUrl')
.mockReturnValue('http://localhost:8080') .mockReturnValue('http://localhost:8080')
@@ -21,7 +25,7 @@ describe('artifact-http-client', () => {
}) })
it('should successfully create a client', () => { it('should successfully create a client', () => {
const client = internalArtifactTwirpClient() const client = createArtifactTwirpClient('upload')
expect(client).toBeDefined() expect(client).toBeDefined()
}) })
@@ -46,7 +50,7 @@ describe('artifact-http-client', () => {
} }
}) })
const client = internalArtifactTwirpClient() const client = createArtifactTwirpClient('upload')
const artifact = await client.CreateArtifact({ const artifact = await client.CreateArtifact({
workflowRunBackendId: '1234', workflowRunBackendId: '1234',
workflowJobRunBackendId: '5678', workflowJobRunBackendId: '5678',
@@ -94,11 +98,12 @@ describe('artifact-http-client', () => {
} }
}) })
const client = internalArtifactTwirpClient({ const client = createArtifactTwirpClient(
maxAttempts: 5, 'upload',
retryIntervalMs: 1, 5, // retry 5 times
retryMultiplier: 1.5 1, // wait 1 ms
}) 1.5 // backoff factor
)
const artifact = await client.CreateArtifact({ const artifact = await client.CreateArtifact({
workflowRunBackendId: '1234', workflowRunBackendId: '1234',
workflowJobRunBackendId: '5678', workflowJobRunBackendId: '5678',
@@ -133,11 +138,12 @@ describe('artifact-http-client', () => {
post: mockPost post: mockPost
} }
}) })
const client = internalArtifactTwirpClient({ const client = createArtifactTwirpClient(
maxAttempts: 5, 'upload',
retryIntervalMs: 1, 5, // retry 5 times
retryMultiplier: 1.5 1, // wait 1 ms
}) 1.5 // backoff factor
)
await expect(async () => { await expect(async () => {
await client.CreateArtifact({ await client.CreateArtifact({
workflowRunBackendId: '1234', workflowRunBackendId: '1234',
@@ -172,11 +178,12 @@ describe('artifact-http-client', () => {
post: mockPost post: mockPost
} }
}) })
const client = internalArtifactTwirpClient({ const client = createArtifactTwirpClient(
maxAttempts: 5, 'upload',
retryIntervalMs: 1, 5, // retry 5 times
retryMultiplier: 1.5 1, // wait 1 ms
}) 1.5 // backoff factor
)
await expect(async () => { await expect(async () => {
await client.CreateArtifact({ await client.CreateArtifact({
workflowRunBackendId: '1234', workflowRunBackendId: '1234',
-9
View File
@@ -1,9 +0,0 @@
import * as core from '@actions/core'
// noopLogs mocks the console.log and core.* functions to prevent output in the console while testing
export const noopLogs = (): void => {
jest.spyOn(console, 'log').mockImplementation(() => {})
jest.spyOn(core, 'debug').mockImplementation(() => {})
jest.spyOn(core, 'info').mockImplementation(() => {})
jest.spyOn(core, 'warning').mockImplementation(() => {})
}
@@ -2,20 +2,14 @@ import fs from 'fs'
import * as http from 'http' import * as http from 'http'
import * as net from 'net' import * as net from 'net'
import * as path from 'path' import * as path from 'path'
import * as core from '@actions/core'
import * as github from '@actions/github' import * as github from '@actions/github'
import {HttpClient} from '@actions/http-client' import {HttpClient} from '@actions/http-client'
import type {RestEndpointMethods} from '@octokit/plugin-rest-endpoint-methods/dist-types/generated/method-types' import type {RestEndpointMethods} from '@octokit/plugin-rest-endpoint-methods/dist-types/generated/method-types'
import archiver from 'archiver' import archiver from 'archiver'
import { import {downloadArtifact} from '../src/internal/download/download-artifact'
downloadArtifactInternal,
downloadArtifactPublic
} from '../src/internal/download/download-artifact'
import {getUserAgentString} from '../src/internal/shared/user-agent' import {getUserAgentString} from '../src/internal/shared/user-agent'
import {noopLogs} from './common'
import * as config from '../src/internal/shared/config'
import {ArtifactServiceClientJSON} from '../src/generated'
import * as util from '../src/internal/shared/util'
type MockedDownloadArtifact = jest.MockedFunction< type MockedDownloadArtifact = jest.MockedFunction<
RestEndpointMethods['actions']['downloadArtifact'] RestEndpointMethods['actions']['downloadArtifact']
@@ -38,16 +32,10 @@ const fixtures = {
] ]
}, },
artifactID: 1234, artifactID: 1234,
artifactName: 'my-artifact',
artifactSize: 123456,
repositoryOwner: 'actions', repositoryOwner: 'actions',
repositoryName: 'toolkit', repositoryName: 'toolkit',
token: 'ghp_1234567890', token: 'ghp_1234567890',
blobStorageUrl: 'https://blob-storage.local?signed=true', blobStorageUrl: 'https://blob-storage.local?signed=true'
backendIds: {
workflowRunBackendId: 'c4d7c21f-ba3f-4ddc-a8c8-6f2f626f8422',
workflowJobRunBackendId: '760803a1-f890-4d25-9a6e-a3fc01a0c7cf'
}
} }
jest.mock('@actions/github', () => ({ jest.mock('@actions/github', () => ({
@@ -86,381 +74,206 @@ const expectExtractedArchive = async (dir: string): Promise<void> => {
} }
} }
const setup = async (): Promise<void> => {
noopLogs()
await fs.promises.mkdir(testDir, {recursive: true})
await createTestArchive()
process.env['GITHUB_WORKSPACE'] = fixtures.workspaceDir
}
const cleanup = async (): Promise<void> => {
jest.restoreAllMocks()
await fs.promises.rm(testDir, {recursive: true})
delete process.env['GITHUB_WORKSPACE']
}
const mockGetArtifactSuccess = jest.fn(() => {
const message = new http.IncomingMessage(new net.Socket())
message.statusCode = 200
message.push(fs.readFileSync(fixtures.exampleArtifact.path))
return {
message
}
})
const mockGetArtifactFailure = jest.fn(() => {
const message = new http.IncomingMessage(new net.Socket())
message.statusCode = 500
message.push('Internal Server Error')
return {
message
}
})
describe('download-artifact', () => { describe('download-artifact', () => {
describe('public', () => { beforeEach(async () => {
beforeEach(setup) jest.spyOn(core, 'debug').mockImplementation(() => {})
afterEach(cleanup) jest.spyOn(core, 'info').mockImplementation(() => {})
jest.spyOn(core, 'warning').mockImplementation(() => {})
it('should successfully download an artifact to $GITHUB_WORKSPACE', async () => { await fs.promises.mkdir(testDir, {recursive: true})
const downloadArtifactMock = github.getOctokit(fixtures.token).rest await createTestArchive()
.actions.downloadArtifact as MockedDownloadArtifact
downloadArtifactMock.mockResolvedValueOnce({
headers: {
location: fixtures.blobStorageUrl
},
status: 302,
url: '',
data: Buffer.from('')
})
const mockHttpClient = (HttpClient as jest.Mock).mockImplementation( process.env['GITHUB_WORKSPACE'] = fixtures.workspaceDir
() => { })
return {
get: mockGetArtifactSuccess
}
}
)
const response = await downloadArtifactPublic( afterEach(async () => {
jest.restoreAllMocks()
await fs.promises.rm(testDir, {recursive: true})
delete process.env['GITHUB_WORKSPACE']
})
it('should successfully download an artifact to $GITHUB_WORKSPACE', async () => {
const downloadArtifactMock = github.getOctokit(fixtures.token).rest.actions
.downloadArtifact as MockedDownloadArtifact
downloadArtifactMock.mockResolvedValueOnce({
headers: {
location: fixtures.blobStorageUrl
},
status: 302,
url: '',
data: Buffer.from('')
})
const getMock = jest.fn(() => {
const message = new http.IncomingMessage(new net.Socket())
message.statusCode = 200
message.push(fs.readFileSync(fixtures.exampleArtifact.path))
return {
message
}
})
const httpClientMock = (HttpClient as jest.Mock).mockImplementation(() => {
return {
get: getMock
}
})
const response = await downloadArtifact(
fixtures.artifactID,
fixtures.repositoryOwner,
fixtures.repositoryName,
fixtures.token
)
expect(downloadArtifactMock).toHaveBeenCalledWith({
owner: fixtures.repositoryOwner,
repo: fixtures.repositoryName,
artifact_id: fixtures.artifactID,
archive_format: 'zip',
request: {
redirect: 'manual'
}
})
expect(httpClientMock).toHaveBeenCalledWith(getUserAgentString())
expect(getMock).toHaveBeenCalledWith(fixtures.blobStorageUrl)
expectExtractedArchive(fixtures.workspaceDir)
expect(response.success).toBe(true)
expect(response.downloadPath).toBe(fixtures.workspaceDir)
})
it('should successfully download an artifact to user defined path', async () => {
const customPath = path.join(testDir, 'custom')
const downloadArtifactMock = github.getOctokit(fixtures.token).rest.actions
.downloadArtifact as MockedDownloadArtifact
downloadArtifactMock.mockResolvedValueOnce({
headers: {
location: fixtures.blobStorageUrl
},
status: 302,
url: '',
data: Buffer.from('')
})
const getMock = jest.fn(() => {
const message = new http.IncomingMessage(new net.Socket())
message.statusCode = 200
message.push(fs.readFileSync(fixtures.exampleArtifact.path))
return {
message
}
})
const httpClientMock = (HttpClient as jest.Mock).mockImplementation(() => {
return {
get: getMock
}
})
const response = await downloadArtifact(
fixtures.artifactID,
fixtures.repositoryOwner,
fixtures.repositoryName,
fixtures.token,
{
path: customPath
}
)
expect(downloadArtifactMock).toHaveBeenCalledWith({
owner: fixtures.repositoryOwner,
repo: fixtures.repositoryName,
artifact_id: fixtures.artifactID,
archive_format: 'zip',
request: {
redirect: 'manual'
}
})
expect(httpClientMock).toHaveBeenCalledWith(getUserAgentString())
expect(getMock).toHaveBeenCalledWith(fixtures.blobStorageUrl)
expectExtractedArchive(customPath)
expect(response.success).toBe(true)
expect(response.downloadPath).toBe(customPath)
})
it('should fail if download artifact API does not respond with location', async () => {
const downloadArtifactMock = github.getOctokit(fixtures.token).rest.actions
.downloadArtifact as MockedDownloadArtifact
downloadArtifactMock.mockResolvedValueOnce({
headers: {},
status: 302,
url: '',
data: Buffer.from('')
})
await expect(
downloadArtifact(
fixtures.artifactID, fixtures.artifactID,
fixtures.repositoryOwner, fixtures.repositoryOwner,
fixtures.repositoryName, fixtures.repositoryName,
fixtures.token fixtures.token
) )
).rejects.toBeInstanceOf(Error)
expect(downloadArtifactMock).toHaveBeenCalledWith({ expect(downloadArtifactMock).toHaveBeenCalledWith({
owner: fixtures.repositoryOwner, owner: fixtures.repositoryOwner,
repo: fixtures.repositoryName, repo: fixtures.repositoryName,
artifact_id: fixtures.artifactID, artifact_id: fixtures.artifactID,
archive_format: 'zip', archive_format: 'zip',
request: { request: {
redirect: 'manual' redirect: 'manual'
} }
}) })
expect(mockHttpClient).toHaveBeenCalledWith(getUserAgentString()) })
expect(mockGetArtifactSuccess).toHaveBeenCalledWith(
fixtures.blobStorageUrl it('should fail if blob storage response is non-200', async () => {
) const downloadArtifactMock = github.getOctokit(fixtures.token).rest.actions
expectExtractedArchive(fixtures.workspaceDir) .downloadArtifact as MockedDownloadArtifact
expect(response.downloadPath).toBe(fixtures.workspaceDir) downloadArtifactMock.mockResolvedValueOnce({
headers: {
location: fixtures.blobStorageUrl
},
status: 302,
url: '',
data: Buffer.from('')
}) })
it('should successfully download an artifact to user defined path', async () => { const getMock = jest.fn(() => {
const customPath = path.join(testDir, 'custom') const message = new http.IncomingMessage(new net.Socket())
message.statusCode = 500
message.push('Internal Server Error')
return {
message
}
})
const httpClientMock = (HttpClient as jest.Mock).mockImplementation(() => {
return {
get: getMock
}
})
const downloadArtifactMock = github.getOctokit(fixtures.token).rest await expect(
.actions.downloadArtifact as MockedDownloadArtifact downloadArtifact(
downloadArtifactMock.mockResolvedValueOnce({
headers: {
location: fixtures.blobStorageUrl
},
status: 302,
url: '',
data: Buffer.from('')
})
const mockHttpClient = (HttpClient as jest.Mock).mockImplementation(
() => {
return {
get: mockGetArtifactSuccess
}
}
)
const response = await downloadArtifactPublic(
fixtures.artifactID, fixtures.artifactID,
fixtures.repositoryOwner, fixtures.repositoryOwner,
fixtures.repositoryName, fixtures.repositoryName,
fixtures.token, fixtures.token
{
path: customPath
}
) )
).rejects.toBeInstanceOf(Error)
expect(downloadArtifactMock).toHaveBeenCalledWith({ expect(downloadArtifactMock).toHaveBeenCalledWith({
owner: fixtures.repositoryOwner, owner: fixtures.repositoryOwner,
repo: fixtures.repositoryName, repo: fixtures.repositoryName,
artifact_id: fixtures.artifactID, artifact_id: fixtures.artifactID,
archive_format: 'zip', archive_format: 'zip',
request: { request: {
redirect: 'manual' redirect: 'manual'
} }
})
expect(mockHttpClient).toHaveBeenCalledWith(getUserAgentString())
expect(mockGetArtifactSuccess).toHaveBeenCalledWith(
fixtures.blobStorageUrl
)
expectExtractedArchive(customPath)
expect(response.downloadPath).toBe(customPath)
})
it('should fail if download artifact API does not respond with location', async () => {
const downloadArtifactMock = github.getOctokit(fixtures.token).rest
.actions.downloadArtifact as MockedDownloadArtifact
downloadArtifactMock.mockResolvedValueOnce({
headers: {},
status: 302,
url: '',
data: Buffer.from('')
})
await expect(
downloadArtifactPublic(
fixtures.artifactID,
fixtures.repositoryOwner,
fixtures.repositoryName,
fixtures.token
)
).rejects.toBeInstanceOf(Error)
expect(downloadArtifactMock).toHaveBeenCalledWith({
owner: fixtures.repositoryOwner,
repo: fixtures.repositoryName,
artifact_id: fixtures.artifactID,
archive_format: 'zip',
request: {
redirect: 'manual'
}
})
})
it('should fail if blob storage response is non-200', async () => {
const downloadArtifactMock = github.getOctokit(fixtures.token).rest
.actions.downloadArtifact as MockedDownloadArtifact
downloadArtifactMock.mockResolvedValueOnce({
headers: {
location: fixtures.blobStorageUrl
},
status: 302,
url: '',
data: Buffer.from('')
})
const mockHttpClient = (HttpClient as jest.Mock).mockImplementation(
() => {
return {
get: mockGetArtifactFailure
}
}
)
await expect(
downloadArtifactPublic(
fixtures.artifactID,
fixtures.repositoryOwner,
fixtures.repositoryName,
fixtures.token
)
).rejects.toBeInstanceOf(Error)
expect(downloadArtifactMock).toHaveBeenCalledWith({
owner: fixtures.repositoryOwner,
repo: fixtures.repositoryName,
artifact_id: fixtures.artifactID,
archive_format: 'zip',
request: {
redirect: 'manual'
}
})
expect(mockHttpClient).toHaveBeenCalledWith(getUserAgentString())
expect(mockGetArtifactFailure).toHaveBeenCalledWith(
fixtures.blobStorageUrl
)
})
})
describe('internal', () => {
beforeEach(async () => {
await setup()
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
jest
.spyOn(util, 'getBackendIdsFromToken')
.mockReturnValue(fixtures.backendIds)
jest
.spyOn(config, 'getResultsServiceUrl')
.mockReturnValue('https://results.local')
})
afterEach(async () => {
await cleanup()
})
it('should successfully download an artifact to $GITHUB_WORKSPACE', async () => {
const mockListArtifacts = jest
.spyOn(ArtifactServiceClientJSON.prototype, 'ListArtifacts')
.mockResolvedValue({
artifacts: [
{
...fixtures.backendIds,
databaseId: fixtures.artifactID.toString(),
name: fixtures.artifactName,
size: fixtures.artifactSize.toString()
}
]
})
const mockGetSignedArtifactURL = jest
.spyOn(ArtifactServiceClientJSON.prototype, 'GetSignedArtifactURL')
.mockReturnValue(
Promise.resolve({
signedUrl: fixtures.blobStorageUrl
})
)
const mockHttpClient = (HttpClient as jest.Mock).mockImplementation(
() => {
return {
get: mockGetArtifactSuccess
}
}
)
const response = await downloadArtifactInternal(fixtures.artifactID)
expectExtractedArchive(fixtures.workspaceDir)
expect(response.downloadPath).toBe(fixtures.workspaceDir)
expect(mockHttpClient).toHaveBeenCalledWith(getUserAgentString())
expect(mockListArtifacts).toHaveBeenCalledWith({
idFilter: {
value: fixtures.artifactID.toString()
},
...fixtures.backendIds
})
expect(mockGetSignedArtifactURL).toHaveBeenCalledWith({
...fixtures.backendIds,
name: fixtures.artifactName
})
})
it('should successfully download an artifact to user defined path', async () => {
const customPath = path.join(testDir, 'custom')
const mockListArtifacts = jest
.spyOn(ArtifactServiceClientJSON.prototype, 'ListArtifacts')
.mockResolvedValue({
artifacts: [
{
...fixtures.backendIds,
databaseId: fixtures.artifactID.toString(),
name: fixtures.artifactName,
size: fixtures.artifactSize.toString()
}
]
})
const mockGetSignedArtifactURL = jest
.spyOn(ArtifactServiceClientJSON.prototype, 'GetSignedArtifactURL')
.mockReturnValue(
Promise.resolve({
signedUrl: fixtures.blobStorageUrl
})
)
const mockHttpClient = (HttpClient as jest.Mock).mockImplementation(
() => {
return {
get: mockGetArtifactSuccess
}
}
)
const response = await downloadArtifactInternal(fixtures.artifactID, {
path: customPath
})
expectExtractedArchive(customPath)
expect(response.downloadPath).toBe(customPath)
expect(mockHttpClient).toHaveBeenCalledWith(getUserAgentString())
expect(mockListArtifacts).toHaveBeenCalledWith({
idFilter: {
value: fixtures.artifactID.toString()
},
...fixtures.backendIds
})
expect(mockGetSignedArtifactURL).toHaveBeenCalledWith({
...fixtures.backendIds,
name: fixtures.artifactName
})
})
it('should fail if download artifact API does not respond with location', async () => {
jest
.spyOn(ArtifactServiceClientJSON.prototype, 'ListArtifacts')
.mockRejectedValue(new Error('boom'))
await expect(
downloadArtifactInternal(fixtures.artifactID)
).rejects.toBeInstanceOf(Error)
})
it('should fail if blob storage response is non-200', async () => {
const mockListArtifacts = jest
.spyOn(ArtifactServiceClientJSON.prototype, 'ListArtifacts')
.mockResolvedValue({
artifacts: [
{
...fixtures.backendIds,
databaseId: fixtures.artifactID.toString(),
name: fixtures.artifactName,
size: fixtures.artifactSize.toString()
}
]
})
const mockGetSignedArtifactURL = jest
.spyOn(ArtifactServiceClientJSON.prototype, 'GetSignedArtifactURL')
.mockReturnValue(
Promise.resolve({
signedUrl: fixtures.blobStorageUrl
})
)
const mockHttpClient = (HttpClient as jest.Mock).mockImplementation(
() => {
return {
get: mockGetArtifactFailure
}
}
)
await expect(
downloadArtifactInternal(fixtures.artifactID)
).rejects.toBeInstanceOf(Error)
expect(mockHttpClient).toHaveBeenCalledWith(getUserAgentString())
expect(mockListArtifacts).toHaveBeenCalledWith({
idFilter: {
value: fixtures.artifactID.toString()
},
...fixtures.backendIds
})
expect(mockGetSignedArtifactURL).toHaveBeenCalledWith({
...fixtures.backendIds,
name: fixtures.artifactName
})
}) })
expect(httpClientMock).toHaveBeenCalledWith(getUserAgentString())
expect(getMock).toHaveBeenCalledWith(fixtures.blobStorageUrl)
}) })
}) })
@@ -1,239 +0,0 @@
import * as github from '@actions/github'
import type {RequestInterface} from '@octokit/types'
import {
getArtifactInternal,
getArtifactPublic
} from '../src/internal/find/get-artifact'
import * as config from '../src/internal/shared/config'
import {ArtifactServiceClientJSON, Timestamp} from '../src/generated'
import * as util from '../src/internal/shared/util'
import {noopLogs} from './common'
import {
ArtifactNotFoundError,
InvalidResponseError
} from '../src/internal/shared/errors'
type MockedRequest = jest.MockedFunction<RequestInterface<object>>
jest.mock('@actions/github', () => ({
getOctokit: jest.fn().mockReturnValue({
request: jest.fn()
})
}))
const fixtures = {
repo: 'toolkit',
owner: 'actions',
token: 'ghp_1234567890',
runId: 123,
backendIds: {
workflowRunBackendId: 'c4d7c21f-ba3f-4ddc-a8c8-6f2f626f8422',
workflowJobRunBackendId: '760803a1-f890-4d25-9a6e-a3fc01a0c7cf'
},
artifacts: [
{
id: 1,
name: 'my-artifact',
size: 456,
createdAt: new Date('2023-12-01')
},
{
id: 2,
name: 'my-artifact',
size: 456,
createdAt: new Date('2023-12-02')
}
]
}
describe('get-artifact', () => {
beforeAll(() => {
noopLogs()
})
describe('public', () => {
it('should return the artifact if it is found', async () => {
const mockRequest = github.getOctokit(fixtures.token)
.request as MockedRequest
mockRequest.mockResolvedValueOnce({
status: 200,
headers: {},
url: '',
data: {
artifacts: [
{
name: fixtures.artifacts[0].name,
id: fixtures.artifacts[0].id,
size_in_bytes: fixtures.artifacts[0].size,
created_at: fixtures.artifacts[0].createdAt.toISOString()
}
]
}
})
const response = await getArtifactPublic(
fixtures.artifacts[0].name,
fixtures.runId,
fixtures.owner,
fixtures.repo,
fixtures.token
)
expect(response).toEqual({
artifact: fixtures.artifacts[0]
})
})
it('should return the latest artifact if multiple are found', async () => {
const mockRequest = github.getOctokit(fixtures.token)
.request as MockedRequest
mockRequest.mockResolvedValueOnce({
status: 200,
headers: {},
url: '',
data: {
artifacts: fixtures.artifacts.map(artifact => ({
name: artifact.name,
id: artifact.id,
size_in_bytes: artifact.size,
created_at: artifact.createdAt.toISOString()
}))
}
})
const response = await getArtifactPublic(
fixtures.artifacts[0].name,
fixtures.runId,
fixtures.owner,
fixtures.repo,
fixtures.token
)
expect(response).toEqual({
artifact: fixtures.artifacts[1]
})
})
it('should fail if no artifacts are found', async () => {
const mockRequest = github.getOctokit(fixtures.token)
.request as MockedRequest
mockRequest.mockResolvedValueOnce({
status: 200,
headers: {},
url: '',
data: {
artifacts: []
}
})
const response = getArtifactPublic(
fixtures.artifacts[0].name,
fixtures.runId,
fixtures.owner,
fixtures.repo,
fixtures.token
)
expect(response).rejects.toThrowError(ArtifactNotFoundError)
})
it('should fail if non-200 response', async () => {
const mockRequest = github.getOctokit(fixtures.token)
.request as MockedRequest
mockRequest.mockResolvedValueOnce({
status: 404,
headers: {},
url: '',
data: {}
})
const response = getArtifactPublic(
fixtures.artifacts[0].name,
fixtures.runId,
fixtures.owner,
fixtures.repo,
fixtures.token
)
expect(response).rejects.toThrowError(InvalidResponseError)
})
})
describe('internal', () => {
beforeEach(() => {
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
jest
.spyOn(util, 'getBackendIdsFromToken')
.mockReturnValue(fixtures.backendIds)
jest
.spyOn(config, 'getResultsServiceUrl')
.mockReturnValue('https://results.local')
})
it('should return the artifact if it is found', async () => {
jest
.spyOn(ArtifactServiceClientJSON.prototype, 'ListArtifacts')
.mockResolvedValue({
artifacts: [
{
...fixtures.backendIds,
databaseId: fixtures.artifacts[0].id.toString(),
name: fixtures.artifacts[0].name,
size: fixtures.artifacts[0].size.toString(),
createdAt: Timestamp.fromDate(fixtures.artifacts[0].createdAt)
}
]
})
const response = await getArtifactInternal(fixtures.artifacts[0].name)
expect(response).toEqual({
artifact: fixtures.artifacts[0]
})
})
it('should return the latest artifact if multiple are found', async () => {
jest
.spyOn(ArtifactServiceClientJSON.prototype, 'ListArtifacts')
.mockResolvedValue({
artifacts: fixtures.artifacts.map(artifact => ({
...fixtures.backendIds,
databaseId: artifact.id.toString(),
name: artifact.name,
size: artifact.size.toString(),
createdAt: Timestamp.fromDate(artifact.createdAt)
}))
})
const response = await getArtifactInternal(fixtures.artifacts[0].name)
expect(response).toEqual({
artifact: fixtures.artifacts[1]
})
})
it('should fail if no artifacts are found', async () => {
jest
.spyOn(ArtifactServiceClientJSON.prototype, 'ListArtifacts')
.mockResolvedValue({
artifacts: []
})
const response = getArtifactInternal(fixtures.artifacts[0].name)
expect(response).rejects.toThrowError(ArtifactNotFoundError)
})
it('should fail if non-200 response', async () => {
jest
.spyOn(ArtifactServiceClientJSON.prototype, 'ListArtifacts')
.mockRejectedValue(new Error('boom'))
const response = getArtifactInternal(fixtures.artifacts[0].name)
expect(response).rejects.toThrow()
})
})
})
@@ -1,242 +0,0 @@
import * as github from '@actions/github'
import type {RestEndpointMethods} from '@octokit/plugin-rest-endpoint-methods/dist-types/generated/method-types'
import type {RestEndpointMethodTypes} from '@octokit/plugin-rest-endpoint-methods/dist-types/generated/parameters-and-response-types'
import {
listArtifactsInternal,
listArtifactsPublic
} from '../src/internal/find/list-artifacts'
import * as config from '../src/internal/shared/config'
import {ArtifactServiceClientJSON, Timestamp} from '../src/generated'
import * as util from '../src/internal/shared/util'
import {noopLogs} from './common'
import {Artifact} from '../src/internal/shared/interfaces'
type MockedListWorkflowRunArtifacts = jest.MockedFunction<
RestEndpointMethods['actions']['listWorkflowRunArtifacts']
>
jest.mock('@actions/github', () => ({
getOctokit: jest.fn().mockReturnValue({
rest: {
actions: {
listWorkflowRunArtifacts: jest.fn()
}
}
})
}))
const artifactsToListResponse = (
artifacts: Artifact[]
): RestEndpointMethodTypes['actions']['listWorkflowRunArtifacts']['response']['data'] => {
return {
total_count: artifacts.length,
artifacts: artifacts.map(artifact => ({
name: artifact.name,
id: artifact.id,
size_in_bytes: artifact.size,
created_at: artifact.createdAt?.toISOString() || '',
run_id: fixtures.runId,
// unused fields for tests
url: '',
archive_download_url: '',
expired: false,
expires_at: '',
node_id: '',
run_url: '',
type: '',
updated_at: ''
}))
}
}
const fixtures = {
repo: 'toolkit',
owner: 'actions',
token: 'ghp_1234567890',
runId: 123,
backendIds: {
workflowRunBackendId: 'c4d7c21f-ba3f-4ddc-a8c8-6f2f626f8422',
workflowJobRunBackendId: '760803a1-f890-4d25-9a6e-a3fc01a0c7cf'
},
artifacts: [
{
id: 1,
name: 'my-artifact',
size: 456,
createdAt: new Date('2023-12-01')
},
{
id: 2,
name: 'my-artifact',
size: 456,
createdAt: new Date('2023-12-02')
}
]
}
describe('list-artifact', () => {
beforeAll(() => {
noopLogs()
})
describe('public', () => {
it('should return a list of artifacts', async () => {
const mockListArtifacts = github.getOctokit(fixtures.token).rest.actions
.listWorkflowRunArtifacts as MockedListWorkflowRunArtifacts
mockListArtifacts.mockResolvedValueOnce({
status: 200,
headers: {},
url: '',
data: artifactsToListResponse(fixtures.artifacts)
})
const response = await listArtifactsPublic(
fixtures.runId,
fixtures.owner,
fixtures.repo,
fixtures.token,
false
)
expect(response).toEqual({
artifacts: fixtures.artifacts
})
})
it('should return the latest artifact when latest is specified', async () => {
const mockListArtifacts = github.getOctokit(fixtures.token).rest.actions
.listWorkflowRunArtifacts as MockedListWorkflowRunArtifacts
mockListArtifacts.mockResolvedValueOnce({
status: 200,
headers: {},
url: '',
data: artifactsToListResponse(fixtures.artifacts)
})
const response = await listArtifactsPublic(
fixtures.runId,
fixtures.owner,
fixtures.repo,
fixtures.token,
true
)
expect(response).toEqual({
artifacts: [fixtures.artifacts[1]]
})
})
it('can return empty artifacts', async () => {
const mockListArtifacts = github.getOctokit(fixtures.token).rest.actions
.listWorkflowRunArtifacts as MockedListWorkflowRunArtifacts
mockListArtifacts.mockResolvedValueOnce({
status: 200,
headers: {},
url: '',
data: {
total_count: 0,
artifacts: []
}
})
const response = await listArtifactsPublic(
fixtures.runId,
fixtures.owner,
fixtures.repo,
fixtures.token,
true
)
expect(response).toEqual({
artifacts: []
})
})
it('should fail if non-200 response', async () => {
const mockListArtifacts = github.getOctokit(fixtures.token).rest.actions
.listWorkflowRunArtifacts as MockedListWorkflowRunArtifacts
mockListArtifacts.mockRejectedValue(new Error('boom'))
await expect(
listArtifactsPublic(
fixtures.runId,
fixtures.owner,
fixtures.repo,
fixtures.token,
false
)
).rejects.toThrow('boom')
})
})
describe('internal', () => {
beforeEach(() => {
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
jest
.spyOn(util, 'getBackendIdsFromToken')
.mockReturnValue(fixtures.backendIds)
jest
.spyOn(config, 'getResultsServiceUrl')
.mockReturnValue('https://results.local')
})
it('should return a list of artifacts', async () => {
jest
.spyOn(ArtifactServiceClientJSON.prototype, 'ListArtifacts')
.mockResolvedValue({
artifacts: fixtures.artifacts.map(artifact => ({
...fixtures.backendIds,
databaseId: artifact.id.toString(),
name: artifact.name,
size: artifact.size.toString(),
createdAt: Timestamp.fromDate(artifact.createdAt)
}))
})
const response = await listArtifactsInternal(false)
expect(response).toEqual({
artifacts: fixtures.artifacts
})
})
it('should return the latest artifact when latest is specified', async () => {
jest
.spyOn(ArtifactServiceClientJSON.prototype, 'ListArtifacts')
.mockResolvedValue({
artifacts: fixtures.artifacts.map(artifact => ({
...fixtures.backendIds,
databaseId: artifact.id.toString(),
name: artifact.name,
size: artifact.size.toString(),
createdAt: Timestamp.fromDate(artifact.createdAt)
}))
})
const response = await listArtifactsInternal(true)
expect(response).toEqual({
artifacts: [fixtures.artifacts[1]]
})
})
it('can return empty artifacts', async () => {
jest
.spyOn(ArtifactServiceClientJSON.prototype, 'ListArtifacts')
.mockResolvedValue({
artifacts: []
})
const response = await listArtifactsInternal(false)
expect(response).toEqual({
artifacts: []
})
})
it('should fail if non-200 response', async () => {
jest
.spyOn(ArtifactServiceClientJSON.prototype, 'ListArtifacts')
.mockRejectedValue(new Error('boom'))
await expect(listArtifactsInternal(false)).rejects.toThrow('boom')
})
})
})
@@ -3,11 +3,15 @@ import {
validateFilePath validateFilePath
} from '../src/internal/upload/path-and-artifact-name-validation' } from '../src/internal/upload/path-and-artifact-name-validation'
import {noopLogs} from './common' import * as core from '@actions/core'
describe('Path and artifact name validation', () => { describe('Path and artifact name validation', () => {
beforeAll(() => { beforeAll(() => {
noopLogs() // mock all output so that there is less noise when running tests
jest.spyOn(console, 'log').mockImplementation(() => {})
jest.spyOn(core, 'debug').mockImplementation(() => {})
jest.spyOn(core, 'info').mockImplementation(() => {})
jest.spyOn(core, 'warning').mockImplementation(() => {})
}) })
it('Check Artifact Name for any invalid characters', () => { it('Check Artifact Name for any invalid characters', () => {
@@ -1,3 +1,4 @@
import * as core from '@actions/core'
import * as uploadZipSpecification from '../src/internal/upload/upload-zip-specification' import * as uploadZipSpecification from '../src/internal/upload/upload-zip-specification'
import * as zip from '../src/internal/upload/zip' import * as zip from '../src/internal/upload/zip'
import * as util from '../src/internal/shared/util' import * as util from '../src/internal/shared/util'
@@ -6,12 +7,14 @@ import * as config from '../src/internal/shared/config'
import {Timestamp, ArtifactServiceClientJSON} from '../src/generated' import {Timestamp, ArtifactServiceClientJSON} from '../src/generated'
import * as blobUpload from '../src/internal/upload/blob-upload' import * as blobUpload from '../src/internal/upload/blob-upload'
import {uploadArtifact} from '../src/internal/upload/upload-artifact' import {uploadArtifact} from '../src/internal/upload/upload-artifact'
import {noopLogs} from './common'
import {FilesNotFoundError} from '../src/internal/shared/errors'
describe('upload-artifact', () => { describe('upload-artifact', () => {
beforeEach(() => { beforeEach(() => {
noopLogs() // mock all output so that there is less noise when running tests
jest.spyOn(console, 'log').mockImplementation(() => {})
jest.spyOn(core, 'debug').mockImplementation(() => {})
jest.spyOn(core, 'info').mockImplementation(() => {})
jest.spyOn(core, 'warning').mockImplementation(() => {})
}) })
afterEach(() => { afterEach(() => {
@@ -60,8 +63,9 @@ describe('upload-artifact', () => {
) )
jest.spyOn(blobUpload, 'uploadZipToBlobStorage').mockReturnValue( jest.spyOn(blobUpload, 'uploadZipToBlobStorage').mockReturnValue(
Promise.resolve({ Promise.resolve({
isSuccess: true,
uploadSize: 1234, uploadSize: 1234,
sha256Hash: 'test-sha256-hash' md5Hash: 'test-md5-hash'
}) })
) )
jest jest
@@ -84,7 +88,7 @@ describe('upload-artifact', () => {
'/home/user/files/plz-upload' '/home/user/files/plz-upload'
) )
expect(uploadResp).resolves.toEqual({size: 1234, id: 1}) expect(uploadResp).resolves.toEqual({success: true, size: 1234, id: 1})
}) })
it('should throw an error if the root directory is invalid', () => { it('should throw an error if the root directory is invalid', () => {
@@ -107,7 +111,7 @@ describe('upload-artifact', () => {
expect(uploadResp).rejects.toThrow('Invalid root directory') expect(uploadResp).rejects.toThrow('Invalid root directory')
}) })
it('should reject if there are no files to upload', () => { it('should return false if there are no files to upload', () => {
jest jest
.spyOn(uploadZipSpecification, 'validateRootDirectory') .spyOn(uploadZipSpecification, 'validateRootDirectory')
.mockReturnValue() .mockReturnValue()
@@ -124,10 +128,10 @@ describe('upload-artifact', () => {
], ],
'/home/user/files/plz-upload' '/home/user/files/plz-upload'
) )
expect(uploadResp).rejects.toThrowError(FilesNotFoundError) expect(uploadResp).resolves.toEqual({success: false})
}) })
it('should reject if no backend IDs are found', () => { it('should return false if no backend IDs are found', () => {
jest jest
.spyOn(uploadZipSpecification, 'validateRootDirectory') .spyOn(uploadZipSpecification, 'validateRootDirectory')
.mockReturnValue() .mockReturnValue()
@@ -151,6 +155,9 @@ describe('upload-artifact', () => {
jest jest
.spyOn(zip, 'createZipUploadStream') .spyOn(zip, 'createZipUploadStream')
.mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1))) .mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1)))
jest
.spyOn(util, 'getBackendIdsFromToken')
.mockReturnValue({workflowRunBackendId: '', workflowJobRunBackendId: ''})
const uploadResp = uploadArtifact( const uploadResp = uploadArtifact(
'test-artifact', 'test-artifact',
@@ -162,7 +169,7 @@ describe('upload-artifact', () => {
'/home/user/files/plz-upload' '/home/user/files/plz-upload'
) )
expect(uploadResp).rejects.toThrow() expect(uploadResp).resolves.toEqual({success: false})
}) })
it('should return false if the creation request fails', () => { it('should return false if the creation request fails', () => {
@@ -217,7 +224,7 @@ describe('upload-artifact', () => {
'/home/user/files/plz-upload' '/home/user/files/plz-upload'
) )
expect(uploadResp).rejects.toThrow() expect(uploadResp).resolves.toEqual({success: false})
}) })
it('should return false if blob storage upload is unsuccessful', () => { it('should return false if blob storage upload is unsuccessful', () => {
@@ -262,7 +269,7 @@ describe('upload-artifact', () => {
) )
jest jest
.spyOn(blobUpload, 'uploadZipToBlobStorage') .spyOn(blobUpload, 'uploadZipToBlobStorage')
.mockReturnValue(Promise.reject(new Error('boom'))) .mockReturnValue(Promise.resolve({isSuccess: false}))
// ArtifactHttpClient mocks // ArtifactHttpClient mocks
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token') jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
@@ -280,10 +287,10 @@ describe('upload-artifact', () => {
'/home/user/files/plz-upload' '/home/user/files/plz-upload'
) )
expect(uploadResp).rejects.toThrow() expect(uploadResp).resolves.toEqual({success: false})
}) })
it('should reject if finalize artifact fails', () => { it('should return false if finalize artifact fails', () => {
const mockDate = new Date('2020-01-01') const mockDate = new Date('2020-01-01')
jest jest
.spyOn(uploadZipSpecification, 'validateRootDirectory') .spyOn(uploadZipSpecification, 'validateRootDirectory')
@@ -325,8 +332,9 @@ describe('upload-artifact', () => {
) )
jest.spyOn(blobUpload, 'uploadZipToBlobStorage').mockReturnValue( jest.spyOn(blobUpload, 'uploadZipToBlobStorage').mockReturnValue(
Promise.resolve({ Promise.resolve({
isSuccess: true,
uploadSize: 1234, uploadSize: 1234,
sha256Hash: 'test-sha256-hash' md5Hash: 'test-md5-hash'
}) })
) )
jest jest
@@ -349,6 +357,6 @@ describe('upload-artifact', () => {
'/home/user/files/plz-upload' '/home/user/files/plz-upload'
) )
expect(uploadResp).rejects.toThrow() expect(uploadResp).resolves.toEqual({success: false})
}) })
}) })
@@ -1,11 +1,11 @@
import * as io from '../../io/src/io' import * as io from '../../io/src/io'
import * as path from 'path' import * as path from 'path'
import {promises as fs} from 'fs' import {promises as fs} from 'fs'
import * as core from '@actions/core'
import { import {
getUploadZipSpecification, getUploadZipSpecification,
validateRootDirectory validateRootDirectory
} from '../src/internal/upload/upload-zip-specification' } from '../src/internal/upload/upload-zip-specification'
import {noopLogs} from './common'
const root = path.join(__dirname, '_temp', 'upload-specification') const root = path.join(__dirname, '_temp', 'upload-specification')
const goodItem1Path = path.join( const goodItem1Path = path.join(
@@ -51,7 +51,11 @@ const artifactFilesToUpload = [
describe('Search', () => { describe('Search', () => {
beforeAll(async () => { beforeAll(async () => {
noopLogs() // mock all output so that there is less noise when running tests
jest.spyOn(console, 'log').mockImplementation(() => {})
jest.spyOn(core, 'debug').mockImplementation(() => {})
jest.spyOn(core, 'info').mockImplementation(() => {})
jest.spyOn(core, 'warning').mockImplementation(() => {})
// clear temp directory // clear temp directory
await io.rmRF(root) await io.rmRF(root)
+5 -4
View File
@@ -1,12 +1,13 @@
import * as config from '../src/internal/shared/config' import * as config from '../src/internal/shared/config'
import * as util from '../src/internal/shared/util' import * as util from '../src/internal/shared/util'
export const testRuntimeToken =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwic2NwIjoiQWN0aW9ucy5FeGFtcGxlIEFjdGlvbnMuQW5vdGhlckV4YW1wbGU6dGVzdCBBY3Rpb25zLlJlc3VsdHM6Y2U3ZjU0YzctNjFjNy00YWFlLTg4N2YtMzBkYTQ3NWY1ZjFhOmNhMzk1MDg1LTA0MGEtNTI2Yi0yY2U4LWJkYzg1ZjY5Mjc3NCIsImlhdCI6MTUxNjIzOTAyMn0.XYnI_wHPBlUi1mqYveJnnkJhp4dlFjqxzRmISPsqfw8'
describe('get-backend-ids-from-token', () => { describe('get-backend-ids-from-token', () => {
it('should return backend ids when the token is valid', () => { it('should return backend ids when the token is valid', () => {
jest.spyOn(config, 'getRuntimeToken').mockReturnValue(testRuntimeToken) jest
.spyOn(config, 'getRuntimeToken')
.mockReturnValue(
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwic2NwIjoiQWN0aW9ucy5FeGFtcGxlIEFjdGlvbnMuQW5vdGhlckV4YW1wbGU6dGVzdCBBY3Rpb25zLlJlc3VsdHM6Y2U3ZjU0YzctNjFjNy00YWFlLTg4N2YtMzBkYTQ3NWY1ZjFhOmNhMzk1MDg1LTA0MGEtNTI2Yi0yY2U4LWJkYzg1ZjY5Mjc3NCIsImlhdCI6MTUxNjIzOTAyMn0.XYnI_wHPBlUi1mqYveJnnkJhp4dlFjqxzRmISPsqfw8'
)
const backendIds = util.getBackendIdsFromToken() const backendIds = util.getBackendIdsFromToken()
expect(backendIds.workflowRunBackendId).toBe( expect(backendIds.workflowRunBackendId).toBe(
+1 -1
View File
@@ -30,7 +30,7 @@
}, },
"scripts": { "scripts": {
"audit-moderate": "npm install && npm audit --json --audit-level=moderate > audit.json", "audit-moderate": "npm install && npm audit --json --audit-level=moderate > audit.json",
"test": "cd ../../ && npm run test ./packages/artifact", "test": "echo \"Error: run tests from root\" && exit 1",
"bootstrap": "cd ../../ && npm run bootstrap", "bootstrap": "cd ../../ && npm run bootstrap",
"tsc-run": "tsc", "tsc-run": "tsc",
"tsc": "npm run bootstrap && npm run tsc-run" "tsc": "npm run bootstrap && npm run tsc-run"
-1
View File
@@ -4,7 +4,6 @@ import {ArtifactClient, Client} from './internal/client'
* Exported functionality that we want to expose for any users of @actions/artifact * Exported functionality that we want to expose for any users of @actions/artifact
*/ */
export * from './internal/shared/interfaces' export * from './internal/shared/interfaces'
export * from './internal/shared/errors'
export {ArtifactClient} export {ArtifactClient}
export function create(): ArtifactClient { export function create(): ArtifactClient {
@@ -12,7 +12,6 @@ import type { PartialMessage } from "@protobuf-ts/runtime";
import { reflectionMergePartial } from "@protobuf-ts/runtime"; import { reflectionMergePartial } from "@protobuf-ts/runtime";
import { MESSAGE_TYPE } from "@protobuf-ts/runtime"; import { MESSAGE_TYPE } from "@protobuf-ts/runtime";
import { MessageType } from "@protobuf-ts/runtime"; import { MessageType } from "@protobuf-ts/runtime";
import { Int64Value } from "../../../google/protobuf/wrappers";
import { StringValue } from "../../../google/protobuf/wrappers"; import { StringValue } from "../../../google/protobuf/wrappers";
import { Timestamp } from "../../../google/protobuf/timestamp"; import { Timestamp } from "../../../google/protobuf/timestamp";
/** /**
@@ -91,111 +90,6 @@ export interface FinalizeArtifactResponse {
*/ */
artifactId: string; artifactId: string;
} }
/**
* @generated from protobuf message github.actions.results.api.v1.ListArtifactsRequest
*/
export interface ListArtifactsRequest {
/**
* The backend plan ID
*
* @generated from protobuf field: string workflow_run_backend_id = 1;
*/
workflowRunBackendId: string;
/**
* The backend job ID
*
* @generated from protobuf field: string workflow_job_run_backend_id = 2;
*/
workflowJobRunBackendId: string;
/**
* Name of the artifact to filter on
*
* @generated from protobuf field: google.protobuf.StringValue name_filter = 3;
*/
nameFilter?: StringValue; // optional
/**
* Monolith Database ID of the artifact to filter on
*
* @generated from protobuf field: google.protobuf.Int64Value id_filter = 4;
*/
idFilter?: Int64Value; // optional
}
/**
* @generated from protobuf message github.actions.results.api.v1.ListArtifactsResponse
*/
export interface ListArtifactsResponse {
/**
* @generated from protobuf field: repeated github.actions.results.api.v1.ListArtifactsResponse.MonolithArtifact artifacts = 1;
*/
artifacts: ListArtifactsResponse_MonolithArtifact[];
}
/**
* @generated from protobuf message github.actions.results.api.v1.ListArtifactsResponse.MonolithArtifact
*/
export interface ListArtifactsResponse_MonolithArtifact {
/**
* The backend plan ID
*
* @generated from protobuf field: string workflow_run_backend_id = 1;
*/
workflowRunBackendId: string;
/**
* The backend job ID
*
* @generated from protobuf field: string workflow_job_run_backend_id = 2;
*/
workflowJobRunBackendId: string;
/**
* Monolith database ID of the artifact
*
* @generated from protobuf field: int64 database_id = 3;
*/
databaseId: string;
/**
* Name of the artifact
*
* @generated from protobuf field: string name = 4;
*/
name: string;
/**
* Size of the artifact in bytes
*
* @generated from protobuf field: int64 size = 5;
*/
size: string;
/**
* When the artifact was created in the monolith
*
* @generated from protobuf field: google.protobuf.Timestamp created_at = 6;
*/
createdAt?: Timestamp;
}
/**
* @generated from protobuf message github.actions.results.api.v1.GetSignedArtifactURLRequest
*/
export interface GetSignedArtifactURLRequest {
/**
* @generated from protobuf field: string workflow_run_backend_id = 1;
*/
workflowRunBackendId: string;
/**
* @generated from protobuf field: string workflow_job_run_backend_id = 2;
*/
workflowJobRunBackendId: string;
/**
* @generated from protobuf field: string name = 3;
*/
name: string;
}
/**
* @generated from protobuf message github.actions.results.api.v1.GetSignedArtifactURLResponse
*/
export interface GetSignedArtifactURLResponse {
/**
* @generated from protobuf field: string signed_url = 1;
*/
signedUrl: string;
}
// @generated message type with reflection information, may provide speed optimized methods // @generated message type with reflection information, may provide speed optimized methods
class CreateArtifactRequest$Type extends MessageType<CreateArtifactRequest> { class CreateArtifactRequest$Type extends MessageType<CreateArtifactRequest> {
constructor() { constructor() {
@@ -454,317 +348,10 @@ class FinalizeArtifactResponse$Type extends MessageType<FinalizeArtifactResponse
* @generated MessageType for protobuf message github.actions.results.api.v1.FinalizeArtifactResponse * @generated MessageType for protobuf message github.actions.results.api.v1.FinalizeArtifactResponse
*/ */
export const FinalizeArtifactResponse = new FinalizeArtifactResponse$Type(); export const FinalizeArtifactResponse = new FinalizeArtifactResponse$Type();
// @generated message type with reflection information, may provide speed optimized methods
class ListArtifactsRequest$Type extends MessageType<ListArtifactsRequest> {
constructor() {
super("github.actions.results.api.v1.ListArtifactsRequest", [
{ no: 1, name: "workflow_run_backend_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
{ no: 2, name: "workflow_job_run_backend_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
{ no: 3, name: "name_filter", kind: "message", T: () => StringValue },
{ no: 4, name: "id_filter", kind: "message", T: () => Int64Value }
]);
}
create(value?: PartialMessage<ListArtifactsRequest>): ListArtifactsRequest {
const message = { workflowRunBackendId: "", workflowJobRunBackendId: "" };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
if (value !== undefined)
reflectionMergePartial<ListArtifactsRequest>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ListArtifactsRequest): ListArtifactsRequest {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* string workflow_run_backend_id */ 1:
message.workflowRunBackendId = reader.string();
break;
case /* string workflow_job_run_backend_id */ 2:
message.workflowJobRunBackendId = reader.string();
break;
case /* google.protobuf.StringValue name_filter */ 3:
message.nameFilter = StringValue.internalBinaryRead(reader, reader.uint32(), options, message.nameFilter);
break;
case /* google.protobuf.Int64Value id_filter */ 4:
message.idFilter = Int64Value.internalBinaryRead(reader, reader.uint32(), options, message.idFilter);
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: ListArtifactsRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* string workflow_run_backend_id = 1; */
if (message.workflowRunBackendId !== "")
writer.tag(1, WireType.LengthDelimited).string(message.workflowRunBackendId);
/* string workflow_job_run_backend_id = 2; */
if (message.workflowJobRunBackendId !== "")
writer.tag(2, WireType.LengthDelimited).string(message.workflowJobRunBackendId);
/* google.protobuf.StringValue name_filter = 3; */
if (message.nameFilter)
StringValue.internalBinaryWrite(message.nameFilter, writer.tag(3, WireType.LengthDelimited).fork(), options).join();
/* google.protobuf.Int64Value id_filter = 4; */
if (message.idFilter)
Int64Value.internalBinaryWrite(message.idFilter, writer.tag(4, WireType.LengthDelimited).fork(), options).join();
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message github.actions.results.api.v1.ListArtifactsRequest
*/
export const ListArtifactsRequest = new ListArtifactsRequest$Type();
// @generated message type with reflection information, may provide speed optimized methods
class ListArtifactsResponse$Type extends MessageType<ListArtifactsResponse> {
constructor() {
super("github.actions.results.api.v1.ListArtifactsResponse", [
{ no: 1, name: "artifacts", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => ListArtifactsResponse_MonolithArtifact }
]);
}
create(value?: PartialMessage<ListArtifactsResponse>): ListArtifactsResponse {
const message = { artifacts: [] };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
if (value !== undefined)
reflectionMergePartial<ListArtifactsResponse>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ListArtifactsResponse): ListArtifactsResponse {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* repeated github.actions.results.api.v1.ListArtifactsResponse.MonolithArtifact artifacts */ 1:
message.artifacts.push(ListArtifactsResponse_MonolithArtifact.internalBinaryRead(reader, reader.uint32(), options));
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: ListArtifactsResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* repeated github.actions.results.api.v1.ListArtifactsResponse.MonolithArtifact artifacts = 1; */
for (let i = 0; i < message.artifacts.length; i++)
ListArtifactsResponse_MonolithArtifact.internalBinaryWrite(message.artifacts[i], writer.tag(1, WireType.LengthDelimited).fork(), options).join();
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message github.actions.results.api.v1.ListArtifactsResponse
*/
export const ListArtifactsResponse = new ListArtifactsResponse$Type();
// @generated message type with reflection information, may provide speed optimized methods
class ListArtifactsResponse_MonolithArtifact$Type extends MessageType<ListArtifactsResponse_MonolithArtifact> {
constructor() {
super("github.actions.results.api.v1.ListArtifactsResponse.MonolithArtifact", [
{ no: 1, name: "workflow_run_backend_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
{ no: 2, name: "workflow_job_run_backend_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
{ no: 3, name: "database_id", kind: "scalar", T: 3 /*ScalarType.INT64*/ },
{ no: 4, name: "name", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
{ no: 5, name: "size", kind: "scalar", T: 3 /*ScalarType.INT64*/ },
{ no: 6, name: "created_at", kind: "message", T: () => Timestamp }
]);
}
create(value?: PartialMessage<ListArtifactsResponse_MonolithArtifact>): ListArtifactsResponse_MonolithArtifact {
const message = { workflowRunBackendId: "", workflowJobRunBackendId: "", databaseId: "0", name: "", size: "0" };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
if (value !== undefined)
reflectionMergePartial<ListArtifactsResponse_MonolithArtifact>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ListArtifactsResponse_MonolithArtifact): ListArtifactsResponse_MonolithArtifact {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* string workflow_run_backend_id */ 1:
message.workflowRunBackendId = reader.string();
break;
case /* string workflow_job_run_backend_id */ 2:
message.workflowJobRunBackendId = reader.string();
break;
case /* int64 database_id */ 3:
message.databaseId = reader.int64().toString();
break;
case /* string name */ 4:
message.name = reader.string();
break;
case /* int64 size */ 5:
message.size = reader.int64().toString();
break;
case /* google.protobuf.Timestamp created_at */ 6:
message.createdAt = Timestamp.internalBinaryRead(reader, reader.uint32(), options, message.createdAt);
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: ListArtifactsResponse_MonolithArtifact, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* string workflow_run_backend_id = 1; */
if (message.workflowRunBackendId !== "")
writer.tag(1, WireType.LengthDelimited).string(message.workflowRunBackendId);
/* string workflow_job_run_backend_id = 2; */
if (message.workflowJobRunBackendId !== "")
writer.tag(2, WireType.LengthDelimited).string(message.workflowJobRunBackendId);
/* int64 database_id = 3; */
if (message.databaseId !== "0")
writer.tag(3, WireType.Varint).int64(message.databaseId);
/* string name = 4; */
if (message.name !== "")
writer.tag(4, WireType.LengthDelimited).string(message.name);
/* int64 size = 5; */
if (message.size !== "0")
writer.tag(5, WireType.Varint).int64(message.size);
/* google.protobuf.Timestamp created_at = 6; */
if (message.createdAt)
Timestamp.internalBinaryWrite(message.createdAt, writer.tag(6, WireType.LengthDelimited).fork(), options).join();
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message github.actions.results.api.v1.ListArtifactsResponse.MonolithArtifact
*/
export const ListArtifactsResponse_MonolithArtifact = new ListArtifactsResponse_MonolithArtifact$Type();
// @generated message type with reflection information, may provide speed optimized methods
class GetSignedArtifactURLRequest$Type extends MessageType<GetSignedArtifactURLRequest> {
constructor() {
super("github.actions.results.api.v1.GetSignedArtifactURLRequest", [
{ no: 1, name: "workflow_run_backend_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
{ no: 2, name: "workflow_job_run_backend_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
{ no: 3, name: "name", kind: "scalar", T: 9 /*ScalarType.STRING*/ }
]);
}
create(value?: PartialMessage<GetSignedArtifactURLRequest>): GetSignedArtifactURLRequest {
const message = { workflowRunBackendId: "", workflowJobRunBackendId: "", name: "" };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
if (value !== undefined)
reflectionMergePartial<GetSignedArtifactURLRequest>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GetSignedArtifactURLRequest): GetSignedArtifactURLRequest {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* string workflow_run_backend_id */ 1:
message.workflowRunBackendId = reader.string();
break;
case /* string workflow_job_run_backend_id */ 2:
message.workflowJobRunBackendId = reader.string();
break;
case /* string name */ 3:
message.name = reader.string();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: GetSignedArtifactURLRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* string workflow_run_backend_id = 1; */
if (message.workflowRunBackendId !== "")
writer.tag(1, WireType.LengthDelimited).string(message.workflowRunBackendId);
/* string workflow_job_run_backend_id = 2; */
if (message.workflowJobRunBackendId !== "")
writer.tag(2, WireType.LengthDelimited).string(message.workflowJobRunBackendId);
/* string name = 3; */
if (message.name !== "")
writer.tag(3, WireType.LengthDelimited).string(message.name);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message github.actions.results.api.v1.GetSignedArtifactURLRequest
*/
export const GetSignedArtifactURLRequest = new GetSignedArtifactURLRequest$Type();
// @generated message type with reflection information, may provide speed optimized methods
class GetSignedArtifactURLResponse$Type extends MessageType<GetSignedArtifactURLResponse> {
constructor() {
super("github.actions.results.api.v1.GetSignedArtifactURLResponse", [
{ no: 1, name: "signed_url", kind: "scalar", T: 9 /*ScalarType.STRING*/ }
]);
}
create(value?: PartialMessage<GetSignedArtifactURLResponse>): GetSignedArtifactURLResponse {
const message = { signedUrl: "" };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
if (value !== undefined)
reflectionMergePartial<GetSignedArtifactURLResponse>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GetSignedArtifactURLResponse): GetSignedArtifactURLResponse {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* string signed_url */ 1:
message.signedUrl = reader.string();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: GetSignedArtifactURLResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* string signed_url = 1; */
if (message.signedUrl !== "")
writer.tag(1, WireType.LengthDelimited).string(message.signedUrl);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message github.actions.results.api.v1.GetSignedArtifactURLResponse
*/
export const GetSignedArtifactURLResponse = new GetSignedArtifactURLResponse$Type();
/** /**
* @generated ServiceType for protobuf service github.actions.results.api.v1.ArtifactService * @generated ServiceType for protobuf service github.actions.results.api.v1.ArtifactService
*/ */
export const ArtifactService = new ServiceType("github.actions.results.api.v1.ArtifactService", [ export const ArtifactService = new ServiceType("github.actions.results.api.v1.ArtifactService", [
{ name: "CreateArtifact", options: {}, I: CreateArtifactRequest, O: CreateArtifactResponse }, { name: "CreateArtifact", options: {}, I: CreateArtifactRequest, O: CreateArtifactResponse },
{ name: "FinalizeArtifact", options: {}, I: FinalizeArtifactRequest, O: FinalizeArtifactResponse }, { name: "FinalizeArtifact", options: {}, I: FinalizeArtifactRequest, O: FinalizeArtifactResponse }
{ name: "ListArtifacts", options: {}, I: ListArtifactsRequest, O: ListArtifactsResponse },
{ name: "GetSignedArtifactURL", options: {}, I: GetSignedArtifactURLRequest, O: GetSignedArtifactURLResponse }
]); ]);
@@ -6,18 +6,14 @@ import {
TwirpErrorCode, TwirpErrorCode,
Interceptor, Interceptor,
TwirpContentType, TwirpContentType,
chainInterceptors, chainInterceptors
} from "twirp-ts"; } from 'twirp-ts'
import { import {
CreateArtifactRequest, CreateArtifactRequest,
CreateArtifactResponse, CreateArtifactResponse,
FinalizeArtifactRequest, FinalizeArtifactRequest,
FinalizeArtifactResponse, FinalizeArtifactResponse
ListArtifactsRequest, } from './artifact'
ListArtifactsResponse,
GetSignedArtifactURLRequest,
GetSignedArtifactURLResponse,
} from "./artifact";
//==================================// //==================================//
// Client Code // // Client Code //
@@ -27,51 +23,43 @@ interface Rpc {
request( request(
service: string, service: string,
method: string, method: string,
contentType: "application/json" | "application/protobuf", contentType: 'application/json' | 'application/protobuf',
data: object | Uint8Array data: object | Uint8Array
): Promise<object | Uint8Array>; ): Promise<object | Uint8Array>
} }
export interface ArtifactServiceClient { export interface ArtifactServiceClient {
CreateArtifact( CreateArtifact(
request: CreateArtifactRequest request: CreateArtifactRequest
): Promise<CreateArtifactResponse>; ): Promise<CreateArtifactResponse>
FinalizeArtifact( FinalizeArtifact(
request: FinalizeArtifactRequest request: FinalizeArtifactRequest
): Promise<FinalizeArtifactResponse>; ): Promise<FinalizeArtifactResponse>
ListArtifacts(request: ListArtifactsRequest): Promise<ListArtifactsResponse>;
GetSignedArtifactURL(
request: GetSignedArtifactURLRequest
): Promise<GetSignedArtifactURLResponse>;
} }
export class ArtifactServiceClientJSON implements ArtifactServiceClient { export class ArtifactServiceClientJSON implements ArtifactServiceClient {
private readonly rpc: Rpc; private readonly rpc: Rpc
constructor(rpc: Rpc) { constructor(rpc: Rpc) {
this.rpc = rpc; this.rpc = rpc
this.CreateArtifact.bind(this); this.CreateArtifact.bind(this)
this.FinalizeArtifact.bind(this); this.FinalizeArtifact.bind(this)
this.ListArtifacts.bind(this);
this.GetSignedArtifactURL.bind(this);
} }
CreateArtifact( CreateArtifact(
request: CreateArtifactRequest request: CreateArtifactRequest
): Promise<CreateArtifactResponse> { ): Promise<CreateArtifactResponse> {
const data = CreateArtifactRequest.toJson(request, { const data = CreateArtifactRequest.toJson(request, {
useProtoFieldName: true, useProtoFieldName: true,
emitDefaultValues: false, emitDefaultValues: false
}); })
const promise = this.rpc.request( const promise = this.rpc.request(
"github.actions.results.api.v1.ArtifactService", 'github.actions.results.api.v1.ArtifactService',
"CreateArtifact", 'CreateArtifact',
"application/json", 'application/json',
data as object data as object
); )
return promise.then((data) => return promise.then(data =>
CreateArtifactResponse.fromJson(data as any, { CreateArtifactResponse.fromJson(data as any, {ignoreUnknownFields: true})
ignoreUnknownFields: true, )
})
);
} }
FinalizeArtifact( FinalizeArtifact(
@@ -79,123 +67,57 @@ export class ArtifactServiceClientJSON implements ArtifactServiceClient {
): Promise<FinalizeArtifactResponse> { ): Promise<FinalizeArtifactResponse> {
const data = FinalizeArtifactRequest.toJson(request, { const data = FinalizeArtifactRequest.toJson(request, {
useProtoFieldName: true, useProtoFieldName: true,
emitDefaultValues: false, emitDefaultValues: false
}); })
const promise = this.rpc.request( const promise = this.rpc.request(
"github.actions.results.api.v1.ArtifactService", 'github.actions.results.api.v1.ArtifactService',
"FinalizeArtifact", 'FinalizeArtifact',
"application/json", 'application/json',
data as object data as object
); )
return promise.then((data) => return promise.then(data =>
FinalizeArtifactResponse.fromJson(data as any, { FinalizeArtifactResponse.fromJson(data as any, {
ignoreUnknownFields: true, ignoreUnknownFields: true
}) })
); )
}
ListArtifacts(request: ListArtifactsRequest): Promise<ListArtifactsResponse> {
const data = ListArtifactsRequest.toJson(request, {
useProtoFieldName: true,
emitDefaultValues: false,
});
const promise = this.rpc.request(
"github.actions.results.api.v1.ArtifactService",
"ListArtifacts",
"application/json",
data as object
);
return promise.then((data) =>
ListArtifactsResponse.fromJson(data as any, { ignoreUnknownFields: true })
);
}
GetSignedArtifactURL(
request: GetSignedArtifactURLRequest
): Promise<GetSignedArtifactURLResponse> {
const data = GetSignedArtifactURLRequest.toJson(request, {
useProtoFieldName: true,
emitDefaultValues: false,
});
const promise = this.rpc.request(
"github.actions.results.api.v1.ArtifactService",
"GetSignedArtifactURL",
"application/json",
data as object
);
return promise.then((data) =>
GetSignedArtifactURLResponse.fromJson(data as any, {
ignoreUnknownFields: true,
})
);
} }
} }
export class ArtifactServiceClientProtobuf implements ArtifactServiceClient { export class ArtifactServiceClientProtobuf implements ArtifactServiceClient {
private readonly rpc: Rpc; private readonly rpc: Rpc
constructor(rpc: Rpc) { constructor(rpc: Rpc) {
this.rpc = rpc; this.rpc = rpc
this.CreateArtifact.bind(this); this.CreateArtifact.bind(this)
this.FinalizeArtifact.bind(this); this.FinalizeArtifact.bind(this)
this.ListArtifacts.bind(this);
this.GetSignedArtifactURL.bind(this);
} }
CreateArtifact( CreateArtifact(
request: CreateArtifactRequest request: CreateArtifactRequest
): Promise<CreateArtifactResponse> { ): Promise<CreateArtifactResponse> {
const data = CreateArtifactRequest.toBinary(request); const data = CreateArtifactRequest.toBinary(request)
const promise = this.rpc.request( const promise = this.rpc.request(
"github.actions.results.api.v1.ArtifactService", 'github.actions.results.api.v1.ArtifactService',
"CreateArtifact", 'CreateArtifact',
"application/protobuf", 'application/protobuf',
data data
); )
return promise.then((data) => return promise.then(data =>
CreateArtifactResponse.fromBinary(data as Uint8Array) CreateArtifactResponse.fromBinary(data as Uint8Array)
); )
} }
FinalizeArtifact( FinalizeArtifact(
request: FinalizeArtifactRequest request: FinalizeArtifactRequest
): Promise<FinalizeArtifactResponse> { ): Promise<FinalizeArtifactResponse> {
const data = FinalizeArtifactRequest.toBinary(request); const data = FinalizeArtifactRequest.toBinary(request)
const promise = this.rpc.request( const promise = this.rpc.request(
"github.actions.results.api.v1.ArtifactService", 'github.actions.results.api.v1.ArtifactService',
"FinalizeArtifact", 'FinalizeArtifact',
"application/protobuf", 'application/protobuf',
data data
); )
return promise.then((data) => return promise.then(data =>
FinalizeArtifactResponse.fromBinary(data as Uint8Array) FinalizeArtifactResponse.fromBinary(data as Uint8Array)
); )
}
ListArtifacts(request: ListArtifactsRequest): Promise<ListArtifactsResponse> {
const data = ListArtifactsRequest.toBinary(request);
const promise = this.rpc.request(
"github.actions.results.api.v1.ArtifactService",
"ListArtifacts",
"application/protobuf",
data
);
return promise.then((data) =>
ListArtifactsResponse.fromBinary(data as Uint8Array)
);
}
GetSignedArtifactURL(
request: GetSignedArtifactURLRequest
): Promise<GetSignedArtifactURLResponse> {
const data = GetSignedArtifactURLRequest.toBinary(request);
const promise = this.rpc.request(
"github.actions.results.api.v1.ArtifactService",
"GetSignedArtifactURL",
"application/protobuf",
data
);
return promise.then((data) =>
GetSignedArtifactURLResponse.fromBinary(data as Uint8Array)
);
} }
} }
@@ -207,45 +129,33 @@ export interface ArtifactServiceTwirp<T extends TwirpContext = TwirpContext> {
CreateArtifact( CreateArtifact(
ctx: T, ctx: T,
request: CreateArtifactRequest request: CreateArtifactRequest
): Promise<CreateArtifactResponse>; ): Promise<CreateArtifactResponse>
FinalizeArtifact( FinalizeArtifact(
ctx: T, ctx: T,
request: FinalizeArtifactRequest request: FinalizeArtifactRequest
): Promise<FinalizeArtifactResponse>; ): Promise<FinalizeArtifactResponse>
ListArtifacts(
ctx: T,
request: ListArtifactsRequest
): Promise<ListArtifactsResponse>;
GetSignedArtifactURL(
ctx: T,
request: GetSignedArtifactURLRequest
): Promise<GetSignedArtifactURLResponse>;
} }
export enum ArtifactServiceMethod { export enum ArtifactServiceMethod {
CreateArtifact = "CreateArtifact", CreateArtifact = 'CreateArtifact',
FinalizeArtifact = "FinalizeArtifact", FinalizeArtifact = 'FinalizeArtifact'
ListArtifacts = "ListArtifacts",
GetSignedArtifactURL = "GetSignedArtifactURL",
} }
export const ArtifactServiceMethodList = [ export const ArtifactServiceMethodList = [
ArtifactServiceMethod.CreateArtifact, ArtifactServiceMethod.CreateArtifact,
ArtifactServiceMethod.FinalizeArtifact, ArtifactServiceMethod.FinalizeArtifact
ArtifactServiceMethod.ListArtifacts, ]
ArtifactServiceMethod.GetSignedArtifactURL,
];
export function createArtifactServiceServer< export function createArtifactServiceServer<
T extends TwirpContext = TwirpContext T extends TwirpContext = TwirpContext
>(service: ArtifactServiceTwirp<T>) { >(service: ArtifactServiceTwirp<T>) {
return new TwirpServer<ArtifactServiceTwirp, T>({ return new TwirpServer<ArtifactServiceTwirp, T>({
service, service,
packageName: "github.actions.results.api.v1", packageName: 'github.actions.results.api.v1',
serviceName: "ArtifactService", serviceName: 'ArtifactService',
methodList: ArtifactServiceMethodList, methodList: ArtifactServiceMethodList,
matchRoute: matchArtifactServiceRoute, matchRoute: matchArtifactServiceRoute
}); })
} }
function matchArtifactServiceRoute<T extends TwirpContext = TwirpContext>( function matchArtifactServiceRoute<T extends TwirpContext = TwirpContext>(
@@ -253,7 +163,7 @@ function matchArtifactServiceRoute<T extends TwirpContext = TwirpContext>(
events: RouterEvents<T> events: RouterEvents<T>
) { ) {
switch (method) { switch (method) {
case "CreateArtifact": case 'CreateArtifact':
return async ( return async (
ctx: T, ctx: T,
service: ArtifactServiceTwirp, service: ArtifactServiceTwirp,
@@ -264,16 +174,16 @@ function matchArtifactServiceRoute<T extends TwirpContext = TwirpContext>(
CreateArtifactResponse CreateArtifactResponse
>[] >[]
) => { ) => {
ctx = { ...ctx, methodName: "CreateArtifact" }; ctx = {...ctx, methodName: 'CreateArtifact'}
await events.onMatch(ctx); await events.onMatch(ctx)
return handleArtifactServiceCreateArtifactRequest( return handleArtifactServiceCreateArtifactRequest(
ctx, ctx,
service, service,
data, data,
interceptors interceptors
); )
}; }
case "FinalizeArtifact": case 'FinalizeArtifact':
return async ( return async (
ctx: T, ctx: T,
service: ArtifactServiceTwirp, service: ArtifactServiceTwirp,
@@ -284,59 +194,19 @@ function matchArtifactServiceRoute<T extends TwirpContext = TwirpContext>(
FinalizeArtifactResponse FinalizeArtifactResponse
>[] >[]
) => { ) => {
ctx = { ...ctx, methodName: "FinalizeArtifact" }; ctx = {...ctx, methodName: 'FinalizeArtifact'}
await events.onMatch(ctx); await events.onMatch(ctx)
return handleArtifactServiceFinalizeArtifactRequest( return handleArtifactServiceFinalizeArtifactRequest(
ctx, ctx,
service, service,
data, data,
interceptors interceptors
); )
}; }
case "ListArtifacts":
return async (
ctx: T,
service: ArtifactServiceTwirp,
data: Buffer,
interceptors?: Interceptor<
T,
ListArtifactsRequest,
ListArtifactsResponse
>[]
) => {
ctx = { ...ctx, methodName: "ListArtifacts" };
await events.onMatch(ctx);
return handleArtifactServiceListArtifactsRequest(
ctx,
service,
data,
interceptors
);
};
case "GetSignedArtifactURL":
return async (
ctx: T,
service: ArtifactServiceTwirp,
data: Buffer,
interceptors?: Interceptor<
T,
GetSignedArtifactURLRequest,
GetSignedArtifactURLResponse
>[]
) => {
ctx = { ...ctx, methodName: "GetSignedArtifactURL" };
await events.onMatch(ctx);
return handleArtifactServiceGetSignedArtifactURLRequest(
ctx,
service,
data,
interceptors
);
};
default: default:
events.onNotFound(); events.onNotFound()
const msg = `no handler found`; const msg = `no handler found`
throw new TwirpError(TwirpErrorCode.BadRoute, msg); throw new TwirpError(TwirpErrorCode.BadRoute, msg)
} }
} }
@@ -355,17 +225,17 @@ function handleArtifactServiceCreateArtifactRequest<
service, service,
data, data,
interceptors interceptors
); )
case TwirpContentType.Protobuf: case TwirpContentType.Protobuf:
return handleArtifactServiceCreateArtifactProtobuf<T>( return handleArtifactServiceCreateArtifactProtobuf<T>(
ctx, ctx,
service, service,
data, data,
interceptors interceptors
); )
default: default:
const msg = "unexpected Content-Type"; const msg = 'unexpected Content-Type'
throw new TwirpError(TwirpErrorCode.BadRoute, msg); throw new TwirpError(TwirpErrorCode.BadRoute, msg)
} }
} }
@@ -388,79 +258,17 @@ function handleArtifactServiceFinalizeArtifactRequest<
service, service,
data, data,
interceptors interceptors
); )
case TwirpContentType.Protobuf: case TwirpContentType.Protobuf:
return handleArtifactServiceFinalizeArtifactProtobuf<T>( return handleArtifactServiceFinalizeArtifactProtobuf<T>(
ctx, ctx,
service, service,
data, data,
interceptors interceptors
); )
default: default:
const msg = "unexpected Content-Type"; const msg = 'unexpected Content-Type'
throw new TwirpError(TwirpErrorCode.BadRoute, msg); throw new TwirpError(TwirpErrorCode.BadRoute, msg)
}
}
function handleArtifactServiceListArtifactsRequest<
T extends TwirpContext = TwirpContext
>(
ctx: T,
service: ArtifactServiceTwirp,
data: Buffer,
interceptors?: Interceptor<T, ListArtifactsRequest, ListArtifactsResponse>[]
): Promise<string | Uint8Array> {
switch (ctx.contentType) {
case TwirpContentType.JSON:
return handleArtifactServiceListArtifactsJSON<T>(
ctx,
service,
data,
interceptors
);
case TwirpContentType.Protobuf:
return handleArtifactServiceListArtifactsProtobuf<T>(
ctx,
service,
data,
interceptors
);
default:
const msg = "unexpected Content-Type";
throw new TwirpError(TwirpErrorCode.BadRoute, msg);
}
}
function handleArtifactServiceGetSignedArtifactURLRequest<
T extends TwirpContext = TwirpContext
>(
ctx: T,
service: ArtifactServiceTwirp,
data: Buffer,
interceptors?: Interceptor<
T,
GetSignedArtifactURLRequest,
GetSignedArtifactURLResponse
>[]
): Promise<string | Uint8Array> {
switch (ctx.contentType) {
case TwirpContentType.JSON:
return handleArtifactServiceGetSignedArtifactURLJSON<T>(
ctx,
service,
data,
interceptors
);
case TwirpContentType.Protobuf:
return handleArtifactServiceGetSignedArtifactURLProtobuf<T>(
ctx,
service,
data,
interceptors
);
default:
const msg = "unexpected Content-Type";
throw new TwirpError(TwirpErrorCode.BadRoute, msg);
} }
} }
async function handleArtifactServiceCreateArtifactJSON< async function handleArtifactServiceCreateArtifactJSON<
@@ -471,18 +279,16 @@ async function handleArtifactServiceCreateArtifactJSON<
data: Buffer, data: Buffer,
interceptors?: Interceptor<T, CreateArtifactRequest, CreateArtifactResponse>[] interceptors?: Interceptor<T, CreateArtifactRequest, CreateArtifactResponse>[]
) { ) {
let request: CreateArtifactRequest; let request: CreateArtifactRequest
let response: CreateArtifactResponse; let response: CreateArtifactResponse
try { try {
const body = JSON.parse(data.toString() || "{}"); const body = JSON.parse(data.toString() || '{}')
request = CreateArtifactRequest.fromJson(body, { request = CreateArtifactRequest.fromJson(body, {ignoreUnknownFields: true})
ignoreUnknownFields: true,
});
} catch (e) { } catch (e) {
if (e instanceof Error) { if (e instanceof Error) {
const msg = "the json request could not be decoded"; const msg = 'the json request could not be decoded'
throw new TwirpError(TwirpErrorCode.Malformed, msg).withCause(e, true); throw new TwirpError(TwirpErrorCode.Malformed, msg).withCause(e, true)
} }
} }
@@ -491,20 +297,20 @@ async function handleArtifactServiceCreateArtifactJSON<
T, T,
CreateArtifactRequest, CreateArtifactRequest,
CreateArtifactResponse CreateArtifactResponse
>; >
response = await interceptor(ctx, request!, (ctx, inputReq) => { response = await interceptor(ctx, request!, (ctx, inputReq) => {
return service.CreateArtifact(ctx, inputReq); return service.CreateArtifact(ctx, inputReq)
}); })
} else { } else {
response = await service.CreateArtifact(ctx, request!); response = await service.CreateArtifact(ctx, request!)
} }
return JSON.stringify( return JSON.stringify(
CreateArtifactResponse.toJson(response, { CreateArtifactResponse.toJson(response, {
useProtoFieldName: true, useProtoFieldName: true,
emitDefaultValues: false, emitDefaultValues: false
}) as string }) as string
); )
} }
async function handleArtifactServiceFinalizeArtifactJSON< async function handleArtifactServiceFinalizeArtifactJSON<
@@ -519,18 +325,18 @@ async function handleArtifactServiceFinalizeArtifactJSON<
FinalizeArtifactResponse FinalizeArtifactResponse
>[] >[]
) { ) {
let request: FinalizeArtifactRequest; let request: FinalizeArtifactRequest
let response: FinalizeArtifactResponse; let response: FinalizeArtifactResponse
try { try {
const body = JSON.parse(data.toString() || "{}"); const body = JSON.parse(data.toString() || '{}')
request = FinalizeArtifactRequest.fromJson(body, { request = FinalizeArtifactRequest.fromJson(body, {
ignoreUnknownFields: true, ignoreUnknownFields: true
}); })
} catch (e) { } catch (e) {
if (e instanceof Error) { if (e instanceof Error) {
const msg = "the json request could not be decoded"; const msg = 'the json request could not be decoded'
throw new TwirpError(TwirpErrorCode.Malformed, msg).withCause(e, true); throw new TwirpError(TwirpErrorCode.Malformed, msg).withCause(e, true)
} }
} }
@@ -539,112 +345,20 @@ async function handleArtifactServiceFinalizeArtifactJSON<
T, T,
FinalizeArtifactRequest, FinalizeArtifactRequest,
FinalizeArtifactResponse FinalizeArtifactResponse
>; >
response = await interceptor(ctx, request!, (ctx, inputReq) => { response = await interceptor(ctx, request!, (ctx, inputReq) => {
return service.FinalizeArtifact(ctx, inputReq); return service.FinalizeArtifact(ctx, inputReq)
}); })
} else { } else {
response = await service.FinalizeArtifact(ctx, request!); response = await service.FinalizeArtifact(ctx, request!)
} }
return JSON.stringify( return JSON.stringify(
FinalizeArtifactResponse.toJson(response, { FinalizeArtifactResponse.toJson(response, {
useProtoFieldName: true, useProtoFieldName: true,
emitDefaultValues: false, emitDefaultValues: false
}) as string }) as string
); )
}
async function handleArtifactServiceListArtifactsJSON<
T extends TwirpContext = TwirpContext
>(
ctx: T,
service: ArtifactServiceTwirp,
data: Buffer,
interceptors?: Interceptor<T, ListArtifactsRequest, ListArtifactsResponse>[]
) {
let request: ListArtifactsRequest;
let response: ListArtifactsResponse;
try {
const body = JSON.parse(data.toString() || "{}");
request = ListArtifactsRequest.fromJson(body, {
ignoreUnknownFields: true,
});
} catch (e) {
if (e instanceof Error) {
const msg = "the json request could not be decoded";
throw new TwirpError(TwirpErrorCode.Malformed, msg).withCause(e, true);
}
}
if (interceptors && interceptors.length > 0) {
const interceptor = chainInterceptors(...interceptors) as Interceptor<
T,
ListArtifactsRequest,
ListArtifactsResponse
>;
response = await interceptor(ctx, request!, (ctx, inputReq) => {
return service.ListArtifacts(ctx, inputReq);
});
} else {
response = await service.ListArtifacts(ctx, request!);
}
return JSON.stringify(
ListArtifactsResponse.toJson(response, {
useProtoFieldName: true,
emitDefaultValues: false,
}) as string
);
}
async function handleArtifactServiceGetSignedArtifactURLJSON<
T extends TwirpContext = TwirpContext
>(
ctx: T,
service: ArtifactServiceTwirp,
data: Buffer,
interceptors?: Interceptor<
T,
GetSignedArtifactURLRequest,
GetSignedArtifactURLResponse
>[]
) {
let request: GetSignedArtifactURLRequest;
let response: GetSignedArtifactURLResponse;
try {
const body = JSON.parse(data.toString() || "{}");
request = GetSignedArtifactURLRequest.fromJson(body, {
ignoreUnknownFields: true,
});
} catch (e) {
if (e instanceof Error) {
const msg = "the json request could not be decoded";
throw new TwirpError(TwirpErrorCode.Malformed, msg).withCause(e, true);
}
}
if (interceptors && interceptors.length > 0) {
const interceptor = chainInterceptors(...interceptors) as Interceptor<
T,
GetSignedArtifactURLRequest,
GetSignedArtifactURLResponse
>;
response = await interceptor(ctx, request!, (ctx, inputReq) => {
return service.GetSignedArtifactURL(ctx, inputReq);
});
} else {
response = await service.GetSignedArtifactURL(ctx, request!);
}
return JSON.stringify(
GetSignedArtifactURLResponse.toJson(response, {
useProtoFieldName: true,
emitDefaultValues: false,
}) as string
);
} }
async function handleArtifactServiceCreateArtifactProtobuf< async function handleArtifactServiceCreateArtifactProtobuf<
T extends TwirpContext = TwirpContext T extends TwirpContext = TwirpContext
@@ -654,15 +368,15 @@ async function handleArtifactServiceCreateArtifactProtobuf<
data: Buffer, data: Buffer,
interceptors?: Interceptor<T, CreateArtifactRequest, CreateArtifactResponse>[] interceptors?: Interceptor<T, CreateArtifactRequest, CreateArtifactResponse>[]
) { ) {
let request: CreateArtifactRequest; let request: CreateArtifactRequest
let response: CreateArtifactResponse; let response: CreateArtifactResponse
try { try {
request = CreateArtifactRequest.fromBinary(data); request = CreateArtifactRequest.fromBinary(data)
} catch (e) { } catch (e) {
if (e instanceof Error) { if (e instanceof Error) {
const msg = "the protobuf request could not be decoded"; const msg = 'the protobuf request could not be decoded'
throw new TwirpError(TwirpErrorCode.Malformed, msg).withCause(e, true); throw new TwirpError(TwirpErrorCode.Malformed, msg).withCause(e, true)
} }
} }
@@ -671,15 +385,15 @@ async function handleArtifactServiceCreateArtifactProtobuf<
T, T,
CreateArtifactRequest, CreateArtifactRequest,
CreateArtifactResponse CreateArtifactResponse
>; >
response = await interceptor(ctx, request!, (ctx, inputReq) => { response = await interceptor(ctx, request!, (ctx, inputReq) => {
return service.CreateArtifact(ctx, inputReq); return service.CreateArtifact(ctx, inputReq)
}); })
} else { } else {
response = await service.CreateArtifact(ctx, request!); response = await service.CreateArtifact(ctx, request!)
} }
return Buffer.from(CreateArtifactResponse.toBinary(response)); return Buffer.from(CreateArtifactResponse.toBinary(response))
} }
async function handleArtifactServiceFinalizeArtifactProtobuf< async function handleArtifactServiceFinalizeArtifactProtobuf<
@@ -694,15 +408,15 @@ async function handleArtifactServiceFinalizeArtifactProtobuf<
FinalizeArtifactResponse FinalizeArtifactResponse
>[] >[]
) { ) {
let request: FinalizeArtifactRequest; let request: FinalizeArtifactRequest
let response: FinalizeArtifactResponse; let response: FinalizeArtifactResponse
try { try {
request = FinalizeArtifactRequest.fromBinary(data); request = FinalizeArtifactRequest.fromBinary(data)
} catch (e) { } catch (e) {
if (e instanceof Error) { if (e instanceof Error) {
const msg = "the protobuf request could not be decoded"; const msg = 'the protobuf request could not be decoded'
throw new TwirpError(TwirpErrorCode.Malformed, msg).withCause(e, true); throw new TwirpError(TwirpErrorCode.Malformed, msg).withCause(e, true)
} }
} }
@@ -711,89 +425,13 @@ async function handleArtifactServiceFinalizeArtifactProtobuf<
T, T,
FinalizeArtifactRequest, FinalizeArtifactRequest,
FinalizeArtifactResponse FinalizeArtifactResponse
>; >
response = await interceptor(ctx, request!, (ctx, inputReq) => { response = await interceptor(ctx, request!, (ctx, inputReq) => {
return service.FinalizeArtifact(ctx, inputReq); return service.FinalizeArtifact(ctx, inputReq)
}); })
} else { } else {
response = await service.FinalizeArtifact(ctx, request!); response = await service.FinalizeArtifact(ctx, request!)
} }
return Buffer.from(FinalizeArtifactResponse.toBinary(response)); return Buffer.from(FinalizeArtifactResponse.toBinary(response))
}
async function handleArtifactServiceListArtifactsProtobuf<
T extends TwirpContext = TwirpContext
>(
ctx: T,
service: ArtifactServiceTwirp,
data: Buffer,
interceptors?: Interceptor<T, ListArtifactsRequest, ListArtifactsResponse>[]
) {
let request: ListArtifactsRequest;
let response: ListArtifactsResponse;
try {
request = ListArtifactsRequest.fromBinary(data);
} catch (e) {
if (e instanceof Error) {
const msg = "the protobuf request could not be decoded";
throw new TwirpError(TwirpErrorCode.Malformed, msg).withCause(e, true);
}
}
if (interceptors && interceptors.length > 0) {
const interceptor = chainInterceptors(...interceptors) as Interceptor<
T,
ListArtifactsRequest,
ListArtifactsResponse
>;
response = await interceptor(ctx, request!, (ctx, inputReq) => {
return service.ListArtifacts(ctx, inputReq);
});
} else {
response = await service.ListArtifacts(ctx, request!);
}
return Buffer.from(ListArtifactsResponse.toBinary(response));
}
async function handleArtifactServiceGetSignedArtifactURLProtobuf<
T extends TwirpContext = TwirpContext
>(
ctx: T,
service: ArtifactServiceTwirp,
data: Buffer,
interceptors?: Interceptor<
T,
GetSignedArtifactURLRequest,
GetSignedArtifactURLResponse
>[]
) {
let request: GetSignedArtifactURLRequest;
let response: GetSignedArtifactURLResponse;
try {
request = GetSignedArtifactURLRequest.fromBinary(data);
} catch (e) {
if (e instanceof Error) {
const msg = "the protobuf request could not be decoded";
throw new TwirpError(TwirpErrorCode.Malformed, msg).withCause(e, true);
}
}
if (interceptors && interceptors.length > 0) {
const interceptor = chainInterceptors(...interceptors) as Interceptor<
T,
GetSignedArtifactURLRequest,
GetSignedArtifactURLResponse
>;
response = await interceptor(ctx, request!, (ctx, inputReq) => {
return service.GetSignedArtifactURL(ctx, inputReq);
});
} else {
response = await service.GetSignedArtifactURL(ctx, request!);
}
return Buffer.from(GetSignedArtifactURLResponse.toBinary(response));
} }
+122 -106
View File
@@ -1,23 +1,17 @@
import {warning} from '@actions/core' import {warning} from '@actions/core'
import {isGhes} from './shared/config' import {isGhes} from './shared/config'
import { import {
UploadArtifactOptions, UploadOptions,
UploadArtifactResponse, UploadResponse,
DownloadArtifactOptions, DownloadArtifactOptions,
GetArtifactResponse, GetArtifactResponse,
ListArtifactsOptions,
ListArtifactsResponse, ListArtifactsResponse,
DownloadArtifactResponse, DownloadArtifactResponse
FindOptions
} from './shared/interfaces' } from './shared/interfaces'
import {uploadArtifact} from './upload/upload-artifact' import {uploadArtifact} from './upload/upload-artifact'
import { import {downloadArtifact} from './download/download-artifact'
downloadArtifactPublic, import {getArtifact} from './find/get-artifact'
downloadArtifactInternal import {listArtifacts} from './find/list-artifacts'
} from './download/download-artifact'
import {getArtifactPublic, getArtifactInternal} from './find/get-artifact'
import {listArtifactsPublic, listArtifactsInternal} from './find/list-artifacts'
import {GHESNotSupportedError} from './shared/errors'
export interface ArtifactClient { export interface ArtifactClient {
/** /**
@@ -27,60 +21,72 @@ export interface ArtifactClient {
* @param files A list of absolute or relative paths that denote what files should be uploaded * @param files A list of absolute or relative paths that denote what files should be uploaded
* @param rootDirectory An absolute or relative file path that denotes the root parent directory of the files being uploaded * @param rootDirectory An absolute or relative file path that denotes the root parent directory of the files being uploaded
* @param options Extra options for customizing the upload behavior * @param options Extra options for customizing the upload behavior
* @returns single UploadArtifactResponse object * @returns single UploadResponse object
*/ */
uploadArtifact( uploadArtifact(
name: string, name: string,
files: string[], files: string[],
rootDirectory: string, rootDirectory: string,
options?: UploadArtifactOptions options?: UploadOptions
): Promise<UploadArtifactResponse> ): Promise<UploadResponse>
/** /**
* Lists all artifacts that are part of the current workflow run. * Lists all artifacts that are part of a workflow run.
* This function will return at most 1000 artifacts per workflow run.
* *
* If options.findBy is specified, this will call the public List-Artifacts API which can list from other runs. * This calls the public List-Artifacts API https://docs.github.com/en/rest/actions/artifacts?apiVersion=2022-11-28#list-workflow-run-artifacts
* https://docs.github.com/en/rest/actions/artifacts?apiVersion=2022-11-28#list-workflow-run-artifacts * Due to paginated responses from the public API. This function will return at most 1000 artifacts per workflow run (100 per page * maximum 10 calls)
* *
* @param options Extra options that allow for the customization of the list behavior * @param workflowRunId The workflow run id that the artifact belongs to
* @param repositoryOwner The owner of the repository that the artifact belongs to
* @param repositoryName The name of the repository that the artifact belongs to
* @param token A token with the appropriate permission to the repository to list artifacts
* @returns ListArtifactResponse object * @returns ListArtifactResponse object
*/ */
listArtifacts( listArtifacts(
options?: ListArtifactsOptions & FindOptions workflowRunId: number,
repositoryOwner: string,
repositoryName: string,
token: string
): Promise<ListArtifactsResponse> ): Promise<ListArtifactsResponse>
/** /**
* Finds an artifact by name. * Finds an artifact by name given a repository and workflow run id.
* If there are multiple artifacts with the same name in the same workflow run, this will return the latest.
* If the artifact is not found, it will throw.
* *
* If options.findBy is specified, this will use the public List Artifacts API with a name filter which can get artifacts from other runs. * This calls the public List-Artifacts API with a name filter https://docs.github.com/en/rest/actions/artifacts?apiVersion=2022-11-28#list-workflow-run-artifacts
* https://docs.github.com/en/rest/actions/artifacts?apiVersion=2022-11-28#list-workflow-run-artifacts
* @actions/artifact > 2.0.0 does not allow for creating multiple artifacts with the same name in the same workflow run. * @actions/artifact > 2.0.0 does not allow for creating multiple artifacts with the same name in the same workflow run.
* It is possible to have multiple artifacts with the same name in the same workflow run by using old versions of upload-artifact (v1,v2 and v3), @actions/artifact < v2.0.0 or it is a rerun. * It is possible to have multiple artifacts with the same name in the same workflow run by using old versions of upload-artifact (v1,v2 and v3) or @actions/artifact < v2.0.0
* If there are multiple artifacts with the same name in the same workflow run this function will return the first artifact that matches the name. * If there are multiple artifacts with the same name in the same workflow run this function will return the first artifact that matches the name.
* *
* @param artifactName The name of the artifact to find * @param artifactName The name of the artifact to find
* @param options Extra options that allow for the customization of the get behavior * @param workflowRunId The workflow run id that the artifact belongs to
* @param repositoryOwner The owner of the repository that the artifact belongs to
* @param repositoryName The name of the repository that the artifact belongs to
* @param token A token with the appropriate permission to the repository to find the artifact
*/ */
getArtifact( getArtifact(
artifactName: string, artifactName: string,
options?: FindOptions workflowRunId: number,
repositoryOwner: string,
repositoryName: string,
token: string
): Promise<GetArtifactResponse> ): Promise<GetArtifactResponse>
/** /**
* Downloads an artifact and unzips the content. * Downloads an artifact and unzips the content
*
* If options.findBy is specified, this will use the public Download Artifact API https://docs.github.com/en/rest/actions/artifacts?apiVersion=2022-11-28#download-an-artifact
* *
* @param artifactId The name of the artifact to download * @param artifactId The name of the artifact to download
* @param repositoryOwner The owner of the repository that the artifact belongs to
* @param repositoryName The name of the repository that the artifact belongs to
* @param token A token with the appropriate permission to the repository to download the artifact
* @param options Extra options that allow for the customization of the download behavior * @param options Extra options that allow for the customization of the download behavior
* @returns single DownloadArtifactResponse object * @returns single DownloadArtifactResponse object
*/ */
downloadArtifact( downloadArtifact(
artifactId: number, artifactId: number,
options?: DownloadArtifactOptions & FindOptions repositoryOwner: string,
repositoryName: string,
token: string,
options?: DownloadArtifactOptions
): Promise<DownloadArtifactResponse> ): Promise<DownloadArtifactResponse>
} }
@@ -99,13 +105,18 @@ export class Client implements ArtifactClient {
name: string, name: string,
files: string[], files: string[],
rootDirectory: string, rootDirectory: string,
options?: UploadArtifactOptions options?: UploadOptions | undefined
): Promise<UploadArtifactResponse> { ): Promise<UploadResponse> {
try { if (isGhes()) {
if (isGhes()) { warning(
throw new GHESNotSupportedError() `@actions/artifact v2.0.0+ and upload-artifact@v4+ are not currently supported on GHES.`
)
return {
success: false
} }
}
try {
return uploadArtifact(name, files, rootDirectory, options) return uploadArtifact(name, files, rootDirectory, options)
} catch (error) { } catch (error) {
warning( warning(
@@ -115,8 +126,9 @@ Errors can be temporary, so please try again and optionally run the action with
If the error persists, please check whether Actions is operating normally at [https://githubstatus.com](https://www.githubstatus.com).` If the error persists, please check whether Actions is operating normally at [https://githubstatus.com](https://www.githubstatus.com).`
) )
return {
throw error success: false
}
} }
} }
@@ -125,39 +137,40 @@ If the error persists, please check whether Actions is operating normally at [ht
*/ */
async downloadArtifact( async downloadArtifact(
artifactId: number, artifactId: number,
options?: DownloadArtifactOptions & FindOptions repositoryOwner: string,
repositoryName: string,
token: string,
options?: DownloadArtifactOptions
): Promise<DownloadArtifactResponse> { ): Promise<DownloadArtifactResponse> {
if (isGhes()) {
warning(
`@actions/artifact v2.0.0+ and download-artifact@v4+ are not currently supported on GHES.`
)
return {
success: false
}
}
try { try {
if (isGhes()) { return downloadArtifact(
throw new GHESNotSupportedError() artifactId,
} repositoryOwner,
repositoryName,
if (options?.findBy) { token,
const { options
findBy: {repositoryOwner, repositoryName, token}, )
...downloadOptions
} = options
return downloadArtifactPublic(
artifactId,
repositoryOwner,
repositoryName,
token,
downloadOptions
)
}
return downloadArtifactInternal(artifactId, options)
} catch (error) { } catch (error) {
warning( warning(
`Download Artifact failed with error: ${error}. `Artifact download failed with error: ${error}.
Errors can be temporary, so please try again and optionally run the action with debug mode enabled for more information. Errors can be temporary, so please try again and optionally run the action with debug mode enabled for more information.
If the error persists, please check whether Actions and API requests are operating normally at [https://githubstatus.com](https://www.githubstatus.com).` If the error persists, please check whether Actions and API requests are operating normally at [https://githubstatus.com](https://www.githubstatus.com).`
) )
throw error return {
success: false
}
} }
} }
@@ -165,28 +178,27 @@ If the error persists, please check whether Actions and API requests are operati
* List Artifacts * List Artifacts
*/ */
async listArtifacts( async listArtifacts(
options?: ListArtifactsOptions & FindOptions workflowRunId: number,
repositoryOwner: string,
repositoryName: string,
token: string
): Promise<ListArtifactsResponse> { ): Promise<ListArtifactsResponse> {
if (isGhes()) {
warning(
`@actions/artifact v2.0.0+ and download-artifact@v4+ are not currently supported on GHES.`
)
return {
artifacts: []
}
}
try { try {
if (isGhes()) { return listArtifacts(
throw new GHESNotSupportedError() workflowRunId,
} repositoryOwner,
repositoryName,
if (options?.findBy) { token
const { )
findBy: {workflowRunId, repositoryOwner, repositoryName, token}
} = options
return listArtifactsPublic(
workflowRunId,
repositoryOwner,
repositoryName,
token,
options?.latest
)
}
return listArtifactsInternal(options?.latest)
} catch (error: unknown) { } catch (error: unknown) {
warning( warning(
`Listing Artifacts failed with error: ${error}. `Listing Artifacts failed with error: ${error}.
@@ -196,7 +208,9 @@ Errors can be temporary, so please try again and optionally run the action with
If the error persists, please check whether Actions and API requests are operating normally at [https://githubstatus.com](https://www.githubstatus.com).` If the error persists, please check whether Actions and API requests are operating normally at [https://githubstatus.com](https://www.githubstatus.com).`
) )
throw error return {
artifacts: []
}
} }
} }
@@ -205,37 +219,39 @@ If the error persists, please check whether Actions and API requests are operati
*/ */
async getArtifact( async getArtifact(
artifactName: string, artifactName: string,
options?: FindOptions workflowRunId: number,
repositoryOwner: string,
repositoryName: string,
token: string
): Promise<GetArtifactResponse> { ): Promise<GetArtifactResponse> {
if (isGhes()) {
warning(
`@actions/artifact v2.0.0+ and download-artifact@v4+ are not currently supported on GHES.`
)
return {
success: false
}
}
try { try {
if (isGhes()) { return getArtifact(
throw new GHESNotSupportedError() artifactName,
} workflowRunId,
repositoryOwner,
if (options?.findBy) { repositoryName,
const { token
findBy: {workflowRunId, repositoryOwner, repositoryName, token} )
} = options
return getArtifactPublic(
artifactName,
workflowRunId,
repositoryOwner,
repositoryName,
token
)
}
return getArtifactInternal(artifactName)
} catch (error: unknown) { } catch (error: unknown) {
warning( warning(
`Get Artifact failed with error: ${error}. `Fetching Artifact failed with error: ${error}.
Errors can be temporary, so please try again and optionally run the action with debug mode enabled for more information. Errors can be temporary, so please try again and optionally run the action with debug mode enabled for more information.
If the error persists, please check whether Actions and API requests are operating normally at [https://githubstatus.com](https://www.githubstatus.com).` If the error persists, please check whether Actions and API requests are operating normally at [https://githubstatus.com](https://www.githubstatus.com).`
) )
throw error return {
success: false
}
} }
} }
} }
@@ -9,14 +9,6 @@ import {
} from '../shared/interfaces' } from '../shared/interfaces'
import {getUserAgentString} from '../shared/user-agent' import {getUserAgentString} from '../shared/user-agent'
import {getGitHubWorkspaceDir} from '../shared/config' import {getGitHubWorkspaceDir} from '../shared/config'
import {internalArtifactTwirpClient} from '../shared/artifact-twirp-client'
import {
GetSignedArtifactURLRequest,
Int64Value,
ListArtifactsRequest
} from '../../generated'
import {getBackendIdsFromToken} from '../shared/util'
import {ArtifactNotFoundError} from '../shared/errors'
const scrubQueryParameters = (url: string): string => { const scrubQueryParameters = (url: string): string => {
const parsed = new URL(url) const parsed = new URL(url)
@@ -50,14 +42,23 @@ async function streamExtract(url: string, directory: string): Promise<void> {
return response.message.pipe(unzipper.Extract({path: directory})).promise() return response.message.pipe(unzipper.Extract({path: directory})).promise()
} }
export async function downloadArtifactPublic( export async function downloadArtifact(
artifactId: number, artifactId: number,
repositoryOwner: string, repositoryOwner: string,
repositoryName: string, repositoryName: string,
token: string, token: string,
options?: DownloadArtifactOptions options?: DownloadArtifactOptions
): Promise<DownloadArtifactResponse> { ): Promise<DownloadArtifactResponse> {
const downloadPath = await resolveOrCreateDirectory(options?.path) const downloadPath = options?.path || getGitHubWorkspaceDir()
if (!(await exists(downloadPath))) {
core.debug(
`Artifact destination folder does not exist, creating: ${downloadPath}`
)
await fs.mkdir(downloadPath, {recursive: true})
} else {
core.debug(`Artifact destination folder already exists: ${downloadPath}`)
}
const api = github.getOctokit(token) const api = github.getOctokit(token)
@@ -96,72 +97,5 @@ export async function downloadArtifactPublic(
throw new Error(`Unable to download and extract artifact: ${error.message}`) throw new Error(`Unable to download and extract artifact: ${error.message}`)
} }
return {downloadPath} return {success: true, downloadPath}
}
export async function downloadArtifactInternal(
artifactId: number,
options?: DownloadArtifactOptions
): Promise<DownloadArtifactResponse> {
const downloadPath = await resolveOrCreateDirectory(options?.path)
const artifactClient = internalArtifactTwirpClient()
const {workflowRunBackendId, workflowJobRunBackendId} =
getBackendIdsFromToken()
const listReq: ListArtifactsRequest = {
workflowRunBackendId,
workflowJobRunBackendId,
idFilter: Int64Value.create({value: artifactId.toString()})
}
const {artifacts} = await artifactClient.ListArtifacts(listReq)
if (artifacts.length === 0) {
throw new ArtifactNotFoundError(
`No artifacts found for ID: ${artifactId}\nAre you trying to download from a different run? Try specifying a github-token with \`actions:read\` scope.`
)
}
if (artifacts.length > 1) {
core.warning('Multiple artifacts found, defaulting to first.')
}
const signedReq: GetSignedArtifactURLRequest = {
workflowRunBackendId: artifacts[0].workflowRunBackendId,
workflowJobRunBackendId: artifacts[0].workflowJobRunBackendId,
name: artifacts[0].name
}
const {signedUrl} = await artifactClient.GetSignedArtifactURL(signedReq)
core.info(
`Redirecting to blob download url: ${scrubQueryParameters(signedUrl)}`
)
try {
core.info(`Starting download of artifact to: ${downloadPath}`)
await streamExtract(signedUrl, downloadPath)
core.info(`Artifact download completed successfully.`)
} catch (error) {
throw new Error(`Unable to download and extract artifact: ${error.message}`)
}
return {downloadPath}
}
async function resolveOrCreateDirectory(
downloadPath = getGitHubWorkspaceDir()
): Promise<string> {
if (!(await exists(downloadPath))) {
core.debug(
`Artifact destination folder does not exist, creating: ${downloadPath}`
)
await fs.mkdir(downloadPath, {recursive: true})
} else {
core.debug(`Artifact destination folder already exists: ${downloadPath}`)
}
return downloadPath
} }
@@ -1,18 +1,14 @@
import {GetArtifactResponse} from '../shared/interfaces'
import {getOctokit} from '@actions/github' import {getOctokit} from '@actions/github'
import {retry} from '@octokit/plugin-retry' import {getUserAgentString} from '../shared/user-agent'
import * as core from '@actions/core'
import {OctokitOptions} from '@octokit/core/dist-types/types'
import {defaults as defaultGitHubOptions} from '@actions/github/lib/utils' import {defaults as defaultGitHubOptions} from '@actions/github/lib/utils'
import {getRetryOptions} from './retry-options' import {getRetryOptions} from './retry-options'
import {requestLog} from '@octokit/plugin-request-log' import {requestLog} from '@octokit/plugin-request-log'
import {GetArtifactResponse} from '../shared/interfaces' import {retry} from '@octokit/plugin-retry'
import {getBackendIdsFromToken} from '../shared/util' import * as core from '@actions/core'
import {getUserAgentString} from '../shared/user-agent' import {OctokitOptions} from '@octokit/core/dist-types/types'
import {internalArtifactTwirpClient} from '../shared/artifact-twirp-client'
import {ListArtifactsRequest, StringValue, Timestamp} from '../../generated'
import {ArtifactNotFoundError, InvalidResponseError} from '../shared/errors'
export async function getArtifactPublic( export async function getArtifact(
artifactName: string, artifactName: string,
workflowRunId: number, workflowRunId: number,
repositoryOwner: string, repositoryOwner: string,
@@ -42,76 +38,32 @@ export async function getArtifactPublic(
) )
if (getArtifactResp.status !== 200) { if (getArtifactResp.status !== 200) {
throw new InvalidResponseError( core.warning(`non-200 response from GitHub API: ${getArtifactResp.status}`)
`Invalid response from GitHub API: ${getArtifactResp.status} (${getArtifactResp?.headers?.['x-github-request-id']})` return {
) success: false
}
} }
if (getArtifactResp.data.artifacts.length === 0) { if (getArtifactResp.data.artifacts.length === 0) {
throw new ArtifactNotFoundError( core.warning('no artifacts found')
`Artifact not found for name: ${artifactName}` return {
) success: false
}
} }
let artifact = getArtifactResp.data.artifacts[0]
if (getArtifactResp.data.artifacts.length > 1) { if (getArtifactResp.data.artifacts.length > 1) {
artifact = getArtifactResp.data.artifacts.sort((a, b) => b.id - a.id)[0] core.warning(
core.debug( 'more than one artifact found for a single name, returning first'
`More than one artifact found for a single name, returning newest (id: ${artifact.id})`
) )
} }
return { return {
success: true,
artifact: { artifact: {
name: artifact.name, name: getArtifactResp.data.artifacts[0].name,
id: artifact.id, id: getArtifactResp.data.artifacts[0].id,
size: artifact.size_in_bytes, url: getArtifactResp.data.artifacts[0].url,
createdAt: artifact.created_at ? new Date(artifact.created_at) : undefined size: getArtifactResp.data.artifacts[0].size_in_bytes
}
}
}
export async function getArtifactInternal(
artifactName: string
): Promise<GetArtifactResponse> {
const artifactClient = internalArtifactTwirpClient()
const {workflowRunBackendId, workflowJobRunBackendId} =
getBackendIdsFromToken()
const req: ListArtifactsRequest = {
workflowRunBackendId,
workflowJobRunBackendId,
nameFilter: StringValue.create({value: artifactName})
}
const res = await artifactClient.ListArtifacts(req)
if (res.artifacts.length === 0) {
throw new ArtifactNotFoundError(
`Artifact not found for name: ${artifactName}`
)
}
let artifact = res.artifacts[0]
if (res.artifacts.length > 1) {
artifact = res.artifacts.sort(
(a, b) => Number(b.databaseId) - Number(a.databaseId)
)[0]
core.debug(
`More than one artifact found for a single name, returning newest (id: ${artifact.databaseId})`
)
}
return {
artifact: {
name: artifact.name,
id: Number(artifact.databaseId),
size: Number(artifact.size),
createdAt: artifact.createdAt
? Timestamp.toDate(artifact.createdAt)
: undefined
} }
} }
} }
@@ -7,27 +7,23 @@ import {defaults as defaultGitHubOptions} from '@actions/github/lib/utils'
import {requestLog} from '@octokit/plugin-request-log' import {requestLog} from '@octokit/plugin-request-log'
import {retry} from '@octokit/plugin-retry' import {retry} from '@octokit/plugin-retry'
import {OctokitOptions} from '@octokit/core/dist-types/types' import {OctokitOptions} from '@octokit/core/dist-types/types'
import {internalArtifactTwirpClient} from '../shared/artifact-twirp-client'
import {getBackendIdsFromToken} from '../shared/util'
import {ListArtifactsRequest, Timestamp} from '../../generated'
// Limiting to 1000 for perf reasons // Limiting to 1000 for perf reasons
const maximumArtifactCount = 1000 const maximumArtifactCount = 1000
const paginationCount = 100 const paginationCount = 100
const maxNumberOfPages = maximumArtifactCount / paginationCount const maxNumberOfPages = maximumArtifactCount / paginationCount
export async function listArtifactsPublic( export async function listArtifacts(
workflowRunId: number, workflowRunId: number,
repositoryOwner: string, repositoryOwner: string,
repositoryName: string, repositoryName: string,
token: string, token: string
latest = false
): Promise<ListArtifactsResponse> { ): Promise<ListArtifactsResponse> {
info( info(
`Fetching artifact list for workflow run ${workflowRunId} in repository ${repositoryOwner}/${repositoryName}` `Fetching artifact list for workflow run ${workflowRunId} in repository ${repositoryOwner}/${repositoryName}`
) )
let artifacts: Artifact[] = [] const artifacts: Artifact[] = []
const [retryOpts, requestOpts] = getRetryOptions(defaultGitHubOptions) const [retryOpts, requestOpts] = getRetryOptions(defaultGitHubOptions)
const opts: OctokitOptions = { const opts: OctokitOptions = {
@@ -66,8 +62,8 @@ export async function listArtifactsPublic(
artifacts.push({ artifacts.push({
name: artifact.name, name: artifact.name,
id: artifact.id, id: artifact.id,
size: artifact.size_in_bytes, url: artifact.url,
createdAt: artifact.created_at ? new Date(artifact.created_at) : undefined size: artifact.size_in_bytes
}) })
} }
@@ -93,73 +89,15 @@ export async function listArtifactsPublic(
artifacts.push({ artifacts.push({
name: artifact.name, name: artifact.name,
id: artifact.id, id: artifact.id,
size: artifact.size_in_bytes, url: artifact.url,
createdAt: artifact.created_at size: artifact.size_in_bytes
? new Date(artifact.created_at)
: undefined
}) })
} }
} }
if (latest) { info(`Finished fetching artifact list`)
artifacts = filterLatest(artifacts)
}
info(`Found ${artifacts.length} artifact(s)`)
return { return {
artifacts artifacts
} }
} }
export async function listArtifactsInternal(
latest = false
): Promise<ListArtifactsResponse> {
const artifactClient = internalArtifactTwirpClient()
const {workflowRunBackendId, workflowJobRunBackendId} =
getBackendIdsFromToken()
const req: ListArtifactsRequest = {
workflowRunBackendId,
workflowJobRunBackendId
}
const res = await artifactClient.ListArtifacts(req)
let artifacts: Artifact[] = res.artifacts.map(artifact => ({
name: artifact.name,
id: Number(artifact.databaseId),
size: Number(artifact.size),
createdAt: artifact.createdAt
? Timestamp.toDate(artifact.createdAt)
: undefined
}))
if (latest) {
artifacts = filterLatest(artifacts)
}
info(`Found ${artifacts.length} artifact(s)`)
return {
artifacts
}
}
/**
* Filters a list of artifacts to only include the latest artifact for each name
* @param artifacts The artifacts to filter
* @returns The filtered list of artifacts
*/
function filterLatest(artifacts: Artifact[]): Artifact[] {
artifacts.sort((a, b) => b.id - a.id)
const latestArtifacts: Artifact[] = []
const seenArtifactNames = new Set<string>()
for (const artifact of artifacts) {
if (!seenArtifactNames.has(artifact.name)) {
latestArtifacts.push(artifact)
seenArtifactNames.add(artifact.name)
}
}
return latestArtifacts
}
@@ -3,7 +3,6 @@ import {BearerCredentialHandler} from '@actions/http-client/lib/auth'
import {info, debug} from '@actions/core' import {info, debug} from '@actions/core'
import {ArtifactServiceClientJSON} from '../../generated' import {ArtifactServiceClientJSON} from '../../generated'
import {getResultsServiceUrl, getRuntimeToken} from './config' import {getResultsServiceUrl, getRuntimeToken} from './config'
import {getUserAgentString} from './user-agent'
// The twirp http client must implement this interface // The twirp http client must implement this interface
interface Rpc { interface Rpc {
@@ -53,8 +52,8 @@ class ArtifactHttpClient implements Rpc {
contentType: 'application/json' | 'application/protobuf', contentType: 'application/json' | 'application/protobuf',
data: object | Uint8Array data: object | Uint8Array
): Promise<object | Uint8Array> { ): Promise<object | Uint8Array> {
const url = new URL(`/twirp/${service}/${method}`, this.baseUrl).href const url = `${this.baseUrl}/twirp/${service}/${method}`
debug(`[Request] ${method} ${url}`) debug(`Requesting ${url}`)
const headers = { const headers = {
'Content-Type': contentType 'Content-Type': contentType
} }
@@ -80,8 +79,6 @@ class ArtifactHttpClient implements Rpc {
try { try {
const response = await operation() const response = await operation()
const statusCode = response.message.statusCode const statusCode = response.message.statusCode
debug(`[Response] ${response.message.statusCode}`)
debug(JSON.stringify(response.message.headers, null, 2))
if (this.isSuccessStatusCode(statusCode)) { if (this.isSuccessStatusCode(statusCode)) {
return response return response
@@ -160,16 +157,17 @@ class ArtifactHttpClient implements Rpc {
} }
} }
export function internalArtifactTwirpClient(options?: { export function createArtifactTwirpClient(
maxAttempts?: number type: 'upload' | 'download',
retryIntervalMs?: number maxAttempts?: number,
baseRetryIntervalMilliseconds?: number,
retryMultiplier?: number retryMultiplier?: number
}): ArtifactServiceClientJSON { ): ArtifactServiceClientJSON {
const client = new ArtifactHttpClient( const client = new ArtifactHttpClient(
getUserAgentString(), `@actions/artifact-${type}`,
options?.maxAttempts, maxAttempts,
options?.retryIntervalMs, baseRetryIntervalMilliseconds,
options?.retryMultiplier retryMultiplier
) )
return new ArtifactServiceClientJSON(client) return new ArtifactServiceClientJSON(client)
} }
@@ -1,5 +1,3 @@
import os from 'os'
// Used for controlling the highWaterMark value of the zip that is being streamed // Used for controlling the highWaterMark value of the zip that is being streamed
// The same value is used as the chunk size that is use during upload to blob storage // The same value is used as the chunk size that is use during upload to blob storage
export function getUploadChunkSize(): number { export function getUploadChunkSize(): number {
@@ -19,8 +17,7 @@ export function getResultsServiceUrl(): string {
if (!resultsUrl) { if (!resultsUrl) {
throw new Error('Unable to get the ACTIONS_RESULTS_URL env variable') throw new Error('Unable to get the ACTIONS_RESULTS_URL env variable')
} }
return resultsUrl
return new URL(resultsUrl).origin
} }
export function isGhes(): boolean { export function isGhes(): boolean {
@@ -37,17 +34,3 @@ export function getGitHubWorkspaceDir(): string {
} }
return ghWorkspaceDir return ghWorkspaceDir
} }
// Mimics behavior of azcopy: https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azcopy-optimize
// If your machine has fewer than 5 CPUs, then the value of this variable is set to 32.
// Otherwise, the default value is equal to 16 multiplied by the number of CPUs. The maximum value of this variable is 300.
export function getConcurrency(): number {
const numCPUs = os.cpus().length
if (numCPUs <= 4) {
return 32
}
const concurrency = 16 * numCPUs
return concurrency > 300 ? 300 : concurrency
}
@@ -1,37 +0,0 @@
export class FilesNotFoundError extends Error {
files: string[]
constructor(files: string[] = []) {
let message = 'No files were found to upload'
if (files.length > 0) {
message += `: ${files.join(', ')}`
}
super(message)
this.files = files
this.name = 'FilesNotFoundError'
}
}
export class InvalidResponseError extends Error {
constructor(message: string) {
super(message)
this.name = 'InvalidResponseError'
}
}
export class ArtifactNotFoundError extends Error {
constructor(message = 'Artifact not found') {
super(message)
this.name = 'ArtifactNotFoundError'
}
}
export class GHESNotSupportedError extends Error {
constructor(
message = '@actions/artifact v2.0.0+ and download-artifact@v4+ are not currently supported on GHES.'
) {
super(message)
this.name = 'GHESNotSupportedError'
}
}
@@ -3,7 +3,12 @@
* UploadArtifact * * UploadArtifact *
* * * *
*****************************************************************************/ *****************************************************************************/
export interface UploadArtifactResponse { export interface UploadResponse {
/**
* Denotes if an artifact was successfully uploaded
*/
success: boolean
/** /**
* Total size of the artifact in bytes. Not provided if no artifact was uploaded * Total size of the artifact in bytes. Not provided if no artifact was uploaded
*/ */
@@ -16,7 +21,7 @@ export interface UploadArtifactResponse {
id?: number id?: number
} }
export interface UploadArtifactOptions { export interface UploadOptions {
/** /**
* Duration after which artifact will expire in days. * Duration after which artifact will expire in days.
* *
@@ -33,17 +38,6 @@ export interface UploadArtifactOptions {
* input of 0 assumes default retention setting. * input of 0 assumes default retention setting.
*/ */
retentionDays?: number retentionDays?: number
/**
* The level of compression for Zlib to be applied to the artifact archive.
* The value can range from 0 to 9:
* - 0: No compression
* - 1: Best speed
* - 6: Default compression (same as GNU Gzip)
* - 9: Best compression
* Higher levels will result in better compression, but will take longer to complete.
* For large files that are not easily compressed, a value of 0 is recommended for significantly faster uploads.
*/
compressionLevel?: number
} }
/***************************************************************************** /*****************************************************************************
@@ -53,10 +47,15 @@ export interface UploadArtifactOptions {
*****************************************************************************/ *****************************************************************************/
export interface GetArtifactResponse { export interface GetArtifactResponse {
/**
* If an artifact was found
*/
success: boolean
/** /**
* Metadata about the artifact that was found * Metadata about the artifact that was found
*/ */
artifact: Artifact artifact?: Artifact
} }
/***************************************************************************** /*****************************************************************************
@@ -64,15 +63,6 @@ export interface GetArtifactResponse {
* ListArtifact * * ListArtifact *
* * * *
*****************************************************************************/ *****************************************************************************/
export interface ListArtifactsOptions {
/**
* Filter the workflow run's artifacts to the latest by name
* In the case of reruns, this can be useful to avoid duplicates
*/
latest?: boolean
}
export interface ListArtifactsResponse { export interface ListArtifactsResponse {
/** /**
* A list of artifacts that were found * A list of artifacts that were found
@@ -86,6 +76,10 @@ export interface ListArtifactsResponse {
* * * *
*****************************************************************************/ *****************************************************************************/
export interface DownloadArtifactResponse { export interface DownloadArtifactResponse {
/**
* If the artifact download was successful
*/
success: boolean
/** /**
* The path where the artifact was downloaded to * The path where the artifact was downloaded to
*/ */
@@ -115,28 +109,13 @@ export interface Artifact {
*/ */
id: number id: number
/**
* The URL of the artifact
*/
url: string
/** /**
* The size of the artifact in bytes * The size of the artifact in bytes
*/ */
size: number size: number
/**
* The time when the artifact was created
*/
createdAt?: Date
}
// FindOptions are for fetching Artifact(s) out of the scope of the current run.
// Must specify a PAT with actions:read scope for cross run/repo lookup otherwise these will be ignored.
export interface FindOptions {
findBy?: {
// Token with actions:read permissions
token: string
// WorkflowRun of the artifact(s) to lookup
workflowRunId: number
// Repository owner
repositoryOwner: string
// Repository name
repositoryName: string
}
} }
+9 -15
View File
@@ -1,4 +1,3 @@
import * as core from '@actions/core'
import {getRuntimeToken} from './config' import {getRuntimeToken} from './config'
import jwt_decode from 'jwt-decode' import jwt_decode from 'jwt-decode'
@@ -12,7 +11,7 @@ interface ActionsToken {
} }
const InvalidJwtError = new Error( const InvalidJwtError = new Error(
'Failed to get backend IDs: The provided JWT token is invalid and/or missing claims' 'Failed to get backend IDs: The provided JWT token is invalid'
) )
// uses the JWT token claims to get the // uses the JWT token claims to get the
@@ -42,29 +41,24 @@ export function getBackendIdsFromToken(): BackendIds {
for (const scopes of scpParts) { for (const scopes of scpParts) {
const scopeParts = scopes.split(':') const scopeParts = scopes.split(':')
if (scopeParts?.[0] !== 'Actions.Results') {
// not the Actions.Results scope
continue
}
/* /*
* example scopeParts: * example scopeParts:
* ["Actions.Results", "ce7f54c7-61c7-4aae-887f-30da475f5f1a", "ca395085-040a-526b-2ce8-bdc85f692774"] * ["Actions.Results", "ce7f54c7-61c7-4aae-887f-30da475f5f1a", "ca395085-040a-526b-2ce8-bdc85f692774"]
*/ */
if (scopeParts.length !== 3) { if (scopeParts.length !== 3) {
// missing expected number of claims // not the Actions.Results scope
throw InvalidJwtError continue
} }
const ids = { if (scopeParts[0] !== 'Actions.Results') {
// not the Actions.Results scope
continue
}
return {
workflowRunBackendId: scopeParts[1], workflowRunBackendId: scopeParts[1],
workflowJobRunBackendId: scopeParts[2] workflowJobRunBackendId: scopeParts[2]
} }
core.debug(`Workflow Run Backend ID: ${ids.workflowRunBackendId}`)
core.debug(`Workflow Job Run Backend ID: ${ids.workflowJobRunBackendId}`)
return ids
} }
throw InvalidJwtError throw InvalidJwtError
@@ -1,21 +1,26 @@
import {BlobClient, BlockBlobUploadStreamOptions} from '@azure/storage-blob' import {BlobClient, BlockBlobUploadStreamOptions} from '@azure/storage-blob'
import {TransferProgressEvent} from '@azure/core-http' import {TransferProgressEvent} from '@azure/core-http'
import {ZipUploadStream} from './zip' import {ZipUploadStream} from './zip'
import {getUploadChunkSize, getConcurrency} from '../shared/config' import {getUploadChunkSize} from '../shared/config'
import * as core from '@actions/core' import * as core from '@actions/core'
import * as crypto from 'crypto' import * as crypto from 'crypto'
import * as stream from 'stream' import * as stream from 'stream'
export interface BlobUploadResponse { export interface BlobUploadResponse {
/**
* If the upload was successful or not
*/
isSuccess: boolean
/** /**
* The total reported upload size in bytes. Empty if the upload failed * The total reported upload size in bytes. Empty if the upload failed
*/ */
uploadSize?: number uploadSize?: number
/** /**
* The SHA256 hash of the uploaded file. Empty if the upload failed * The MD5 hash of the uploaded file. Empty if the upload failed
*/ */
sha256Hash?: string md5Hash?: string
} }
export async function uploadZipToBlobStorage( export async function uploadZipToBlobStorage(
@@ -24,13 +29,13 @@ export async function uploadZipToBlobStorage(
): Promise<BlobUploadResponse> { ): Promise<BlobUploadResponse> {
let uploadByteCount = 0 let uploadByteCount = 0
const maxConcurrency = getConcurrency() const maxBuffers = 5
const bufferSize = getUploadChunkSize() const bufferSize = getUploadChunkSize()
const blobClient = new BlobClient(authenticatedUploadURL) const blobClient = new BlobClient(authenticatedUploadURL)
const blockBlobClient = blobClient.getBlockBlobClient() const blockBlobClient = blobClient.getBlockBlobClient()
core.debug( core.debug(
`Uploading artifact zip to blob storage with maxConcurrency: ${maxConcurrency}, bufferSize: ${bufferSize}` `Uploading artifact zip to blob storage with maxBuffers: ${maxBuffers}, bufferSize: ${bufferSize}`
) )
const uploadCallback = (progress: TransferProgressEvent): void => { const uploadCallback = (progress: TransferProgressEvent): void => {
@@ -43,36 +48,49 @@ export async function uploadZipToBlobStorage(
onProgress: uploadCallback onProgress: uploadCallback
} }
let sha256Hash: string | undefined = undefined let md5Hash: string | undefined = undefined
const uploadStream = new stream.PassThrough() const uploadStream = new stream.PassThrough()
const hashStream = crypto.createHash('sha256') const hashStream = crypto.createHash('md5')
zipUploadStream.pipe(uploadStream) // This stream is used for the upload zipUploadStream.pipe(uploadStream) // This stream is used for the upload
zipUploadStream.pipe(hashStream).setEncoding('hex') // This stream is used to compute a hash of the zip content that gets used. Integrity check zipUploadStream.pipe(hashStream).setEncoding('hex') // This stream is used to compute a hash of the zip content that gets used. Integrity check
core.info('Beginning upload of artifact content to blob storage') try {
core.info('Beginning upload of artifact content to blob storage')
await blockBlobClient.uploadStream( await blockBlobClient.uploadStream(
uploadStream, uploadStream,
bufferSize, bufferSize,
maxConcurrency, maxBuffers,
options options
) )
core.info('Finished uploading artifact content to blob storage!') core.info('Finished uploading artifact content to blob storage!')
hashStream.end() hashStream.end()
sha256Hash = hashStream.read() as string md5Hash = hashStream.read() as string
core.info(`SHA256 hash of uploaded artifact zip is ${sha256Hash}`) core.info(`MD5 hash of uploaded artifact zip is ${md5Hash}`)
} catch (error) {
core.warning(
`Failed to upload artifact zip to blob storage, error: ${error}`
)
return {
isSuccess: false
}
}
if (uploadByteCount === 0) { if (uploadByteCount === 0) {
core.warning( core.warning(
`No data was uploaded to blob storage. Reported upload byte count is 0.` `No data was uploaded to blob storage. Reported upload byte count is 0`
) )
return {
isSuccess: false
}
} }
return { return {
isSuccess: true,
uploadSize: uploadByteCount, uploadSize: uploadByteCount,
sha256Hash md5Hash
} }
} }
@@ -1,11 +1,8 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import { import {UploadOptions, UploadResponse} from '../shared/interfaces'
UploadArtifactOptions,
UploadArtifactResponse
} from '../shared/interfaces'
import {getExpiration} from './retention' import {getExpiration} from './retention'
import {validateArtifactName} from './path-and-artifact-name-validation' import {validateArtifactName} from './path-and-artifact-name-validation'
import {internalArtifactTwirpClient} from '../shared/artifact-twirp-client' import {createArtifactTwirpClient} from '../shared/artifact-twirp-client'
import { import {
UploadZipSpecification, UploadZipSpecification,
getUploadZipSpecification, getUploadZipSpecification,
@@ -19,14 +16,13 @@ import {
FinalizeArtifactRequest, FinalizeArtifactRequest,
StringValue StringValue
} from '../../generated' } from '../../generated'
import {FilesNotFoundError, InvalidResponseError} from '../shared/errors'
export async function uploadArtifact( export async function uploadArtifact(
name: string, name: string,
files: string[], files: string[],
rootDirectory: string, rootDirectory: string,
options?: UploadArtifactOptions | undefined options?: UploadOptions | undefined
): Promise<UploadArtifactResponse> { ): Promise<UploadResponse> {
validateArtifactName(name) validateArtifactName(name)
validateRootDirectory(rootDirectory) validateRootDirectory(rootDirectory)
@@ -35,21 +31,31 @@ export async function uploadArtifact(
rootDirectory rootDirectory
) )
if (zipSpecification.length === 0) { if (zipSpecification.length === 0) {
throw new FilesNotFoundError( core.warning(`No files were found to upload`)
zipSpecification.flatMap(s => (s.sourcePath ? [s.sourcePath] : [])) return {
) success: false
}
} }
const zipUploadStream = await createZipUploadStream( const zipUploadStream = await createZipUploadStream(zipSpecification)
zipSpecification,
options?.compressionLevel
)
// get the IDs needed for the artifact creation // get the IDs needed for the artifact creation
const backendIds = getBackendIdsFromToken() const backendIds = getBackendIdsFromToken()
if (!backendIds.workflowRunBackendId || !backendIds.workflowJobRunBackendId) {
core.warning(
`Failed to get the necessary backend ids which are required to create the artifact`
)
return {
success: false
}
}
core.debug(`Workflow Run Backend ID: ${backendIds.workflowRunBackendId}`)
core.debug(
`Workflow Job Run Backend ID: ${backendIds.workflowJobRunBackendId}`
)
// create the artifact client // create the artifact client
const artifactClient = internalArtifactTwirpClient() const artifactClient = createArtifactTwirpClient('upload')
// create the artifact // create the artifact
const createArtifactReq: CreateArtifactRequest = { const createArtifactReq: CreateArtifactRequest = {
@@ -68,9 +74,10 @@ export async function uploadArtifact(
const createArtifactResp = const createArtifactResp =
await artifactClient.CreateArtifact(createArtifactReq) await artifactClient.CreateArtifact(createArtifactReq)
if (!createArtifactResp.ok) { if (!createArtifactResp.ok) {
throw new InvalidResponseError( core.warning(`Failed to create artifact`)
'CreateArtifact: response from backend was not ok' return {
) success: false
}
} }
// Upload zip to blob storage // Upload zip to blob storage
@@ -78,6 +85,11 @@ export async function uploadArtifact(
createArtifactResp.signedUploadUrl, createArtifactResp.signedUploadUrl,
zipUploadStream zipUploadStream
) )
if (uploadResult.isSuccess === false) {
return {
success: false
}
}
// finalize the artifact // finalize the artifact
const finalizeArtifactReq: FinalizeArtifactRequest = { const finalizeArtifactReq: FinalizeArtifactRequest = {
@@ -87,9 +99,9 @@ export async function uploadArtifact(
size: uploadResult.uploadSize ? uploadResult.uploadSize.toString() : '0' size: uploadResult.uploadSize ? uploadResult.uploadSize.toString() : '0'
} }
if (uploadResult.sha256Hash) { if (uploadResult.md5Hash) {
finalizeArtifactReq.hash = StringValue.create({ finalizeArtifactReq.hash = StringValue.create({
value: `sha256:${uploadResult.sha256Hash}` value: `md5:${uploadResult.md5Hash}`
}) })
} }
@@ -98,9 +110,10 @@ export async function uploadArtifact(
const finalizeArtifactResp = const finalizeArtifactResp =
await artifactClient.FinalizeArtifact(finalizeArtifactReq) await artifactClient.FinalizeArtifact(finalizeArtifactReq)
if (!finalizeArtifactResp.ok) { if (!finalizeArtifactResp.ok) {
throw new InvalidResponseError( core.warning(`Failed to finalize artifact`)
'FinalizeArtifact: response from backend was not ok' return {
) success: false
}
} }
const artifactId = BigInt(finalizeArtifactResp.artifactId) const artifactId = BigInt(finalizeArtifactResp.artifactId)
@@ -109,6 +122,7 @@ export async function uploadArtifact(
) )
return { return {
success: true,
size: uploadResult.uploadSize, size: uploadResult.uploadSize,
id: Number(artifactId) id: Number(artifactId)
} }
+6 -10
View File
@@ -5,8 +5,6 @@ import {createReadStream} from 'fs'
import {UploadZipSpecification} from './upload-zip-specification' import {UploadZipSpecification} from './upload-zip-specification'
import {getUploadChunkSize} from '../shared/config' import {getUploadChunkSize} from '../shared/config'
export const DEFAULT_COMPRESSION_LEVEL = 6
// Custom stream transformer so we can set the highWaterMark property // Custom stream transformer so we can set the highWaterMark property
// See https://github.com/nodejs/node/issues/8855 // See https://github.com/nodejs/node/issues/8855
export class ZipUploadStream extends stream.Transform { export class ZipUploadStream extends stream.Transform {
@@ -23,16 +21,14 @@ export class ZipUploadStream extends stream.Transform {
} }
export async function createZipUploadStream( export async function createZipUploadStream(
uploadSpecification: UploadZipSpecification[], uploadSpecification: UploadZipSpecification[]
compressionLevel: number = DEFAULT_COMPRESSION_LEVEL
): Promise<ZipUploadStream> { ): Promise<ZipUploadStream> {
core.debug(
`Creating Artifact archive with compressionLevel: ${compressionLevel}`
)
const zip = archiver.create('zip', { const zip = archiver.create('zip', {
highWaterMark: getUploadChunkSize(), zlib: {level: 9} // Sets the compression level.
zlib: {level: compressionLevel} // Available options are 0-9
// 0 => no compression
// 1 => fastest with low compression
// 9 => highest compression ratio but the slowest
}) })
// register callbacks for various events during the zip lifecycle // register callbacks for various events during the zip lifecycle
-25
View File
@@ -333,28 +333,3 @@ toPlatformPath('/foo/bar') // => \foo\bar
// On a Linux runner. // On a Linux runner.
toPlatformPath('\\foo\\bar') // => /foo/bar toPlatformPath('\\foo\\bar') // => /foo/bar
``` ```
#### Platform helper
Provides shorthands for getting information about platform action is running on.
```js
import { platform } from '@actions/core'
/* equals to a call of os.platform() */
platform.platform // 'win32' | 'darwin' | 'linux' | 'freebsd' | 'openbsd' | 'android' | 'cygwin' | 'sunos'
/* equals to a call of os.arch() */
platform.arch // 'x64' | 'arm' | 'arm64' | 'ia32' | 'mips' | 'mipsel' | 'ppc' | 'ppc64' | 'riscv64' | 's390' | 's390x'
/* common shorthands for platform-specific logic */
platform.isWindows // true
platform.isMacOS // false
platform.isLinux // false
/* run platform-specific script to get more details about the exact platform, works on Windows, MacOS and Linux */
const {
name, // Microsoft Windows 11 Enterprise
version, // 10.0.22621
} = await platform.getDetails()
```
-29
View File
@@ -1,29 +0,0 @@
import os from 'os'
import {platform} from '../src/core'
describe('getInfo', () => {
it('returns the platform info', async () => {
const info = await platform.getDetails()
expect(info).toEqual({
name: expect.any(String),
platform: expect.any(String),
arch: expect.any(String),
version: expect.any(String),
isWindows: expect.any(Boolean),
isMacOS: expect.any(Boolean),
isLinux: expect.any(Boolean)
})
})
it('returns the platform info with the correct name', async () => {
const isWindows = os.platform() === 'win32'
const isMacOS = os.platform() === 'darwin'
const isLinux = os.platform() === 'linux'
const info = await platform.getDetails()
expect(info.platform).toEqual(os.platform())
expect(info.isWindows).toEqual(isWindows)
expect(info.isMacOS).toEqual(isMacOS)
expect(info.isLinux).toEqual(isLinux)
})
})
-27
View File
@@ -9,7 +9,6 @@
"version": "1.10.1", "version": "1.10.1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/exec": "^1.1.1",
"@actions/http-client": "^2.0.1", "@actions/http-client": "^2.0.1",
"uuid": "^8.3.2" "uuid": "^8.3.2"
}, },
@@ -18,14 +17,6 @@
"@types/uuid": "^8.3.4" "@types/uuid": "^8.3.4"
} }
}, },
"node_modules/@actions/exec": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
"dependencies": {
"@actions/io": "^1.0.1"
}
},
"node_modules/@actions/http-client": { "node_modules/@actions/http-client": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.1.0.tgz", "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.1.0.tgz",
@@ -34,11 +25,6 @@
"tunnel": "^0.0.6" "tunnel": "^0.0.6"
} }
}, },
"node_modules/@actions/io": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz",
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="
},
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "12.0.2", "version": "12.0.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.2.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.2.tgz",
@@ -69,14 +55,6 @@
} }
}, },
"dependencies": { "dependencies": {
"@actions/exec": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
"requires": {
"@actions/io": "^1.0.1"
}
},
"@actions/http-client": { "@actions/http-client": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.1.0.tgz", "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.1.0.tgz",
@@ -85,11 +63,6 @@
"tunnel": "^0.0.6" "tunnel": "^0.0.6"
} }
}, },
"@actions/io": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz",
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="
},
"@types/node": { "@types/node": {
"version": "12.0.2", "version": "12.0.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.2.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.2.tgz",
-1
View File
@@ -36,7 +36,6 @@
"url": "https://github.com/actions/toolkit/issues" "url": "https://github.com/actions/toolkit/issues"
}, },
"dependencies": { "dependencies": {
"@actions/exec": "^1.1.1",
"@actions/http-client": "^2.0.1", "@actions/http-client": "^2.0.1",
"uuid": "^8.3.2" "uuid": "^8.3.2"
}, },
-5
View File
@@ -386,8 +386,3 @@ export {markdownSummary} from './summary'
* Path exports * Path exports
*/ */
export {toPosixPath, toWin32Path, toPlatformPath} from './path-utils' export {toPosixPath, toWin32Path, toPlatformPath} from './path-utils'
/**
* Platform utilities exports
*/
export * as platform from './platform'
-87
View File
@@ -1,87 +0,0 @@
import os from 'os'
import * as exec from '@actions/exec'
const getWindowsInfo = async (): Promise<{name: string; version: string}> => {
const {stdout: version} = await exec.getExecOutput(
'powershell -command "(Get-CimInstance -ClassName Win32_OperatingSystem).Version"',
undefined,
{
silent: true
}
)
const {stdout: name} = await exec.getExecOutput(
'powershell -command "(Get-CimInstance -ClassName Win32_OperatingSystem).Caption"',
undefined,
{
silent: true
}
)
return {
name: name.trim(),
version: version.trim()
}
}
const getMacOsInfo = async (): Promise<{
name: string
version: string
}> => {
const {stdout} = await exec.getExecOutput('sw_vers', undefined, {
silent: true
})
const version = stdout.match(/ProductVersion:\s*(.+)/)?.[1] ?? ''
const name = stdout.match(/ProductName:\s*(.+)/)?.[1] ?? ''
return {
name,
version
}
}
const getLinuxInfo = async (): Promise<{
name: string
version: string
}> => {
const {stdout} = await exec.getExecOutput('lsb_release', ['-i', '-r', '-s'], {
silent: true
})
const [name, version] = stdout.trim().split('\n')
return {
name,
version
}
}
export const platform = os.platform()
export const arch = os.arch()
export const isWindows = platform === 'win32'
export const isMacOS = platform === 'darwin'
export const isLinux = platform === 'linux'
export async function getDetails(): Promise<{
name: string
platform: string
arch: string
version: string
isWindows: boolean
isMacOS: boolean
isLinux: boolean
}> {
return {
...(await (isWindows
? getWindowsInfo()
: isMacOS
? getMacOsInfo()
: getLinuxInfo())),
platform,
arch,
isWindows,
isMacOS,
isLinux
}
}
-4
View File
@@ -1,9 +1,5 @@
# @actions/github Releases # @actions/github Releases
### 6.0.0
- Support the latest Octokit in @actions/github [#1553](https://github.com/actions/toolkit/pull/1553)
- Drop support of NodeJS v14, v16
### 5.1.1 ### 5.1.1
- Export default octokit options [#1188](https://github.com/actions/toolkit/pull/1188) - Export default octokit options [#1188](https://github.com/actions/toolkit/pull/1188)
@@ -1,6 +1,7 @@
import * as http from 'http' import * as http from 'http'
import * as https from 'https' import * as https from 'https'
import {createProxy} from 'proxy' import { ProxyServer, createProxy } from "proxy";
import { ProxyAgent, fetch as undiciFetch } from "undici";
// Default values are set when the module is imported, so we need to set proxy first. // Default values are set when the module is imported, so we need to set proxy first.
const proxyUrl = 'http://127.0.0.1:8081' const proxyUrl = 'http://127.0.0.1:8081'
+1 -1
View File
@@ -1,5 +1,5 @@
import * as http from 'http' import * as http from 'http'
import {createProxy} from 'proxy' import { createProxy } from 'proxy'
import {getOctokit} from '../src/github' import {getOctokit} from '../src/github'
import {GitHub, getOctokitOptions} from '../src/utils' import {GitHub, getOctokitOptions} from '../src/utils'
+5
View File
@@ -0,0 +1,5 @@
declare module 'proxy' {
import * as http from 'http'
function internal(): http.Server
export = internal
}
+243 -116
View File
@@ -1,30 +1,32 @@
{ {
"name": "@actions/github", "name": "@actions/github",
"version": "6.0.0", "version": "6.0.2",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@actions/github", "name": "@actions/github",
"version": "6.0.0", "version": "6.0.2",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/http-client": "^2.2.0", "@actions/http-client": "file:actions-http-client-3.0.3.tgz",
"@octokit/core": "^5.0.1", "@octokit/core": "^4.2.4",
"@octokit/plugin-paginate-rest": "^9.0.0", "@octokit/plugin-paginate-rest": "^6.1.2",
"@octokit/plugin-rest-endpoint-methods": "^10.0.0" "@octokit/plugin-rest-endpoint-methods": "^7.2.3",
"undici": "^5.25.2"
}, },
"devDependencies": { "devDependencies": {
"@types/proxy": "^1.0.1",
"proxy": "^2.1.1" "proxy": "^2.1.1"
} }
}, },
"node_modules/@actions/http-client": { "node_modules/@actions/http-client": {
"version": "2.2.0", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.0.tgz", "resolved": "file:actions-http-client-3.0.3.tgz",
"integrity": "sha512-q+epW0trjVUUHboliPb4UF9g2msf+w61b32tAkFEwL/IwP0DQWgbCMM0Hbe3e3WXSKz5VcUXbzJQgy8Hkra/Lg==", "integrity": "sha512-oD3QP51ndN4IMUntw5mWcP7yZ/7c9cpLRejqbUwe9cT5vC72bWHLr5odlPzE+RAsoEVZMEkKazJbCjXdTzq8og==",
"license": "MIT",
"dependencies": { "dependencies": {
"tunnel": "^0.0.6", "tunnel": "^0.0.6"
"undici": "^5.25.4"
} }
}, },
"node_modules/@fastify/busboy": { "node_modules/@fastify/busboy": {
@@ -36,123 +38,153 @@
} }
}, },
"node_modules/@octokit/auth-token": { "node_modules/@octokit/auth-token": {
"version": "4.0.0", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-3.0.4.tgz",
"integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", "integrity": "sha512-TWFX7cZF2LXoCvdmJWY7XVPi74aSY0+FfBZNSXEXFkMpjcqsQwDSYVv5FhRFaI0V1ECnwbz4j59T/G+rXNWaIQ==",
"engines": { "engines": {
"node": ">= 18" "node": ">= 14"
} }
}, },
"node_modules/@octokit/core": { "node_modules/@octokit/core": {
"version": "5.0.1", "version": "4.2.4",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.0.1.tgz", "resolved": "https://registry.npmjs.org/@octokit/core/-/core-4.2.4.tgz",
"integrity": "sha512-lyeeeZyESFo+ffI801SaBKmCfsvarO+dgV8/0gD8u1d87clbEdWsP5yC+dSj3zLhb2eIf5SJrn6vDz9AheETHw==", "integrity": "sha512-rYKilwgzQ7/imScn3M9/pFfUf4I1AZEH3KhyJmtPdE2zfaXAn2mFfUy4FbKewzc2We5y/LlKLj36fWJLKC2SIQ==",
"dependencies": { "dependencies": {
"@octokit/auth-token": "^4.0.0", "@octokit/auth-token": "^3.0.0",
"@octokit/graphql": "^7.0.0", "@octokit/graphql": "^5.0.0",
"@octokit/request": "^8.0.2", "@octokit/request": "^6.0.0",
"@octokit/request-error": "^5.0.0", "@octokit/request-error": "^3.0.0",
"@octokit/types": "^12.0.0", "@octokit/types": "^9.0.0",
"before-after-hook": "^2.2.0", "before-after-hook": "^2.2.0",
"universal-user-agent": "^6.0.0" "universal-user-agent": "^6.0.0"
}, },
"engines": { "engines": {
"node": ">= 18" "node": ">= 14"
} }
}, },
"node_modules/@octokit/endpoint": { "node_modules/@octokit/endpoint": {
"version": "9.0.1", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.1.tgz", "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-7.0.6.tgz",
"integrity": "sha512-hRlOKAovtINHQPYHZlfyFwaM8OyetxeoC81lAkBy34uLb8exrZB50SQdeW3EROqiY9G9yxQTpp5OHTV54QD+vA==", "integrity": "sha512-5L4fseVRUsDFGR00tMWD/Trdeeihn999rTMGRMC1G/Ldi1uWlWJzI98H4Iak5DB/RVvQuyMYKqSK/R6mbSOQyg==",
"dependencies": { "dependencies": {
"@octokit/types": "^12.0.0", "@octokit/types": "^9.0.0",
"is-plain-object": "^5.0.0", "is-plain-object": "^5.0.0",
"universal-user-agent": "^6.0.0" "universal-user-agent": "^6.0.0"
}, },
"engines": { "engines": {
"node": ">= 18" "node": ">= 14"
} }
}, },
"node_modules/@octokit/graphql": { "node_modules/@octokit/graphql": {
"version": "7.0.2", "version": "5.0.6",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.0.2.tgz", "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-5.0.6.tgz",
"integrity": "sha512-OJ2iGMtj5Tg3s6RaXH22cJcxXRi7Y3EBqbHTBRq+PQAqfaS8f/236fUrWhfSn8P4jovyzqucxme7/vWSSZBX2Q==", "integrity": "sha512-Fxyxdy/JH0MnIB5h+UQ3yCoh1FG4kWXfFKkpWqjZHw/p+Kc8Y44Hu/kCgNBT6nU1shNumEchmW/sUO1JuQnPcw==",
"dependencies": { "dependencies": {
"@octokit/request": "^8.0.1", "@octokit/request": "^6.0.0",
"@octokit/types": "^12.0.0", "@octokit/types": "^9.0.0",
"universal-user-agent": "^6.0.0" "universal-user-agent": "^6.0.0"
}, },
"engines": { "engines": {
"node": ">= 18" "node": ">= 14"
} }
}, },
"node_modules/@octokit/openapi-types": { "node_modules/@octokit/openapi-types": {
"version": "19.0.0", "version": "18.1.1",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-19.0.0.tgz", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-18.1.1.tgz",
"integrity": "sha512-PclQ6JGMTE9iUStpzMkwLCISFn/wDeRjkZFIKALpvJQNBGwDoYYi2fFvuHwssoQ1rXI5mfh6jgTgWuddeUzfWw==" "integrity": "sha512-VRaeH8nCDtF5aXWnjPuEMIYf1itK/s3JYyJcWFJT8X9pSNnBtriDf7wlEWsGuhPLl4QIH4xM8fqTXDwJ3Mu6sw=="
}, },
"node_modules/@octokit/plugin-paginate-rest": { "node_modules/@octokit/plugin-paginate-rest": {
"version": "9.0.0", "version": "6.1.2",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.0.0.tgz", "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-6.1.2.tgz",
"integrity": "sha512-oIJzCpttmBTlEhBmRvb+b9rlnGpmFgDtZ0bB6nq39qIod6A5DP+7RkVLMOixIgRCYSHDTeayWqmiJ2SZ6xgfdw==", "integrity": "sha512-qhrmtQeHU/IivxucOV1bbI/xZyC/iOBhclokv7Sut5vnejAIAEXVcGQeRpQlU39E0WwK9lNvJHphHri/DB6lbQ==",
"dependencies": { "dependencies": {
"@octokit/types": "^12.0.0" "@octokit/tsconfig": "^1.0.2",
"@octokit/types": "^9.2.3"
}, },
"engines": { "engines": {
"node": ">= 18" "node": ">= 14"
}, },
"peerDependencies": { "peerDependencies": {
"@octokit/core": ">=5" "@octokit/core": ">=4"
} }
}, },
"node_modules/@octokit/plugin-rest-endpoint-methods": { "node_modules/@octokit/plugin-rest-endpoint-methods": {
"version": "10.0.0", "version": "7.2.3",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.0.0.tgz", "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-7.2.3.tgz",
"integrity": "sha512-16VkwE2v6rXU+/gBsYC62M8lKWOphY5Lg4wpjYnVE9Zbu0J6IwiT5kILoj1YOB53XLmcJR+Nqp8DmifOPY4H3g==", "integrity": "sha512-I5Gml6kTAkzVlN7KCtjOM+Ruwe/rQppp0QU372K1GP7kNOYEKe8Xn5BW4sE62JAHdwpq95OQK/qGNyKQMUzVgA==",
"dependencies": { "dependencies": {
"@octokit/types": "^12.0.0" "@octokit/types": "^10.0.0"
}, },
"engines": { "engines": {
"node": ">= 18" "node": ">= 14"
}, },
"peerDependencies": { "peerDependencies": {
"@octokit/core": ">=5" "@octokit/core": ">=3"
}
},
"node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-10.0.0.tgz",
"integrity": "sha512-Vm8IddVmhCgU1fxC1eyinpwqzXPEYu0NrYzD3YZjlGjyftdLBTeqNblRC0jmJmgxbJIsQlyogVeGnrNaaMVzIg==",
"dependencies": {
"@octokit/openapi-types": "^18.0.0"
} }
}, },
"node_modules/@octokit/request": { "node_modules/@octokit/request": {
"version": "8.1.4", "version": "6.2.8",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.4.tgz", "resolved": "https://registry.npmjs.org/@octokit/request/-/request-6.2.8.tgz",
"integrity": "sha512-M0aaFfpGPEKrg7XoA/gwgRvc9MSXHRO2Ioki1qrPDbl1e9YhjIwVoHE7HIKmv/m3idzldj//xBujcFNqGX6ENA==", "integrity": "sha512-ow4+pkVQ+6XVVsekSYBzJC0VTVvh/FCTUUgTsboGq+DTeWdyIFV8WSCdo0RIxk6wSkBTHqIK1mYuY7nOBXOchw==",
"dependencies": { "dependencies": {
"@octokit/endpoint": "^9.0.0", "@octokit/endpoint": "^7.0.0",
"@octokit/request-error": "^5.0.0", "@octokit/request-error": "^3.0.0",
"@octokit/types": "^12.0.0", "@octokit/types": "^9.0.0",
"is-plain-object": "^5.0.0", "is-plain-object": "^5.0.0",
"node-fetch": "^2.6.7",
"universal-user-agent": "^6.0.0" "universal-user-agent": "^6.0.0"
}, },
"engines": { "engines": {
"node": ">= 18" "node": ">= 14"
} }
}, },
"node_modules/@octokit/request-error": { "node_modules/@octokit/request-error": {
"version": "5.0.1", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.0.1.tgz", "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.3.tgz",
"integrity": "sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ==", "integrity": "sha512-crqw3V5Iy2uOU5Np+8M/YexTlT8zxCfI+qu+LxUB7SZpje4Qmx3mub5DfEKSO8Ylyk0aogi6TYdf6kxzh2BguQ==",
"dependencies": { "dependencies": {
"@octokit/types": "^12.0.0", "@octokit/types": "^9.0.0",
"deprecation": "^2.0.0", "deprecation": "^2.0.0",
"once": "^1.4.0" "once": "^1.4.0"
}, },
"engines": { "engines": {
"node": ">= 18" "node": ">= 14"
} }
}, },
"node_modules/@octokit/tsconfig": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@octokit/tsconfig/-/tsconfig-1.0.2.tgz",
"integrity": "sha512-I0vDR0rdtP8p2lGMzvsJzbhdOWy405HcGovrspJ8RRibHnyRgggUSNO5AIox5LmqiwmatHKYsvj6VGFHkqS7lA=="
},
"node_modules/@octokit/types": { "node_modules/@octokit/types": {
"version": "12.0.0", "version": "9.3.2",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.0.0.tgz", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-9.3.2.tgz",
"integrity": "sha512-EzD434aHTFifGudYAygnFlS1Tl6KhbTynEWELQXIbTY8Msvb5nEqTZIm7sbPEt4mQYLZwu3zPKVdeIrw0g7ovg==", "integrity": "sha512-D4iHGTdAnEEVsB8fl95m1hiz7D5YiRdQ9b/OEb3BYRVwbLsGHcRVPz+u+BgRLNk0Q0/4iZCBqDN96j2XNxfXrA==",
"dependencies": { "dependencies": {
"@octokit/openapi-types": "^19.0.0" "@octokit/openapi-types": "^18.0.0"
}
},
"node_modules/@types/node": {
"version": "20.8.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.2.tgz",
"integrity": "sha512-Vvycsc9FQdwhxE3y3DzeIxuEJbWGDsnrxvMADzTDF/lcdR9/K+AQIeAghTQsHtotg/q0j3WEOYS/jQgSdWue3w==",
"dev": true
},
"node_modules/@types/proxy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@types/proxy/-/proxy-1.0.2.tgz",
"integrity": "sha512-NDNsg7YuClVzEenn9SUButu43blypWvljGsIkDV7HI4N9apjrS0aeeMTUG0PYa71lD1AvIgvjkBagqHDiomDjA==",
"dev": true,
"dependencies": {
"@types/node": "*"
} }
}, },
"node_modules/ansi-styles": { "node_modules/ansi-styles": {
@@ -303,6 +335,25 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true "dev": true
}, },
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/once": { "node_modules/once": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -337,6 +388,11 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/tunnel": { "node_modules/tunnel": {
"version": "0.0.6", "version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
@@ -361,6 +417,20 @@
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
}, },
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/wrappy": { "node_modules/wrappy": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@@ -369,12 +439,10 @@
}, },
"dependencies": { "dependencies": {
"@actions/http-client": { "@actions/http-client": {
"version": "2.2.0", "version": "file:actions-http-client-3.0.3.tgz",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.0.tgz", "integrity": "sha512-oD3QP51ndN4IMUntw5mWcP7yZ/7c9cpLRejqbUwe9cT5vC72bWHLr5odlPzE+RAsoEVZMEkKazJbCjXdTzq8og==",
"integrity": "sha512-q+epW0trjVUUHboliPb4UF9g2msf+w61b32tAkFEwL/IwP0DQWgbCMM0Hbe3e3WXSKz5VcUXbzJQgy8Hkra/Lg==",
"requires": { "requires": {
"tunnel": "^0.0.6", "tunnel": "^0.0.6"
"undici": "^5.25.4"
} }
}, },
"@fastify/busboy": { "@fastify/busboy": {
@@ -383,93 +451,125 @@
"integrity": "sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ==" "integrity": "sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ=="
}, },
"@octokit/auth-token": { "@octokit/auth-token": {
"version": "4.0.0", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-3.0.4.tgz",
"integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==" "integrity": "sha512-TWFX7cZF2LXoCvdmJWY7XVPi74aSY0+FfBZNSXEXFkMpjcqsQwDSYVv5FhRFaI0V1ECnwbz4j59T/G+rXNWaIQ=="
}, },
"@octokit/core": { "@octokit/core": {
"version": "5.0.1", "version": "4.2.4",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.0.1.tgz", "resolved": "https://registry.npmjs.org/@octokit/core/-/core-4.2.4.tgz",
"integrity": "sha512-lyeeeZyESFo+ffI801SaBKmCfsvarO+dgV8/0gD8u1d87clbEdWsP5yC+dSj3zLhb2eIf5SJrn6vDz9AheETHw==", "integrity": "sha512-rYKilwgzQ7/imScn3M9/pFfUf4I1AZEH3KhyJmtPdE2zfaXAn2mFfUy4FbKewzc2We5y/LlKLj36fWJLKC2SIQ==",
"requires": { "requires": {
"@octokit/auth-token": "^4.0.0", "@octokit/auth-token": "^3.0.0",
"@octokit/graphql": "^7.0.0", "@octokit/graphql": "^5.0.0",
"@octokit/request": "^8.0.2", "@octokit/request": "^6.0.0",
"@octokit/request-error": "^5.0.0", "@octokit/request-error": "^3.0.0",
"@octokit/types": "^12.0.0", "@octokit/types": "^9.0.0",
"before-after-hook": "^2.2.0", "before-after-hook": "^2.2.0",
"universal-user-agent": "^6.0.0" "universal-user-agent": "^6.0.0"
} }
}, },
"@octokit/endpoint": { "@octokit/endpoint": {
"version": "9.0.1", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.1.tgz", "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-7.0.6.tgz",
"integrity": "sha512-hRlOKAovtINHQPYHZlfyFwaM8OyetxeoC81lAkBy34uLb8exrZB50SQdeW3EROqiY9G9yxQTpp5OHTV54QD+vA==", "integrity": "sha512-5L4fseVRUsDFGR00tMWD/Trdeeihn999rTMGRMC1G/Ldi1uWlWJzI98H4Iak5DB/RVvQuyMYKqSK/R6mbSOQyg==",
"requires": { "requires": {
"@octokit/types": "^12.0.0", "@octokit/types": "^9.0.0",
"is-plain-object": "^5.0.0", "is-plain-object": "^5.0.0",
"universal-user-agent": "^6.0.0" "universal-user-agent": "^6.0.0"
} }
}, },
"@octokit/graphql": { "@octokit/graphql": {
"version": "7.0.2", "version": "5.0.6",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.0.2.tgz", "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-5.0.6.tgz",
"integrity": "sha512-OJ2iGMtj5Tg3s6RaXH22cJcxXRi7Y3EBqbHTBRq+PQAqfaS8f/236fUrWhfSn8P4jovyzqucxme7/vWSSZBX2Q==", "integrity": "sha512-Fxyxdy/JH0MnIB5h+UQ3yCoh1FG4kWXfFKkpWqjZHw/p+Kc8Y44Hu/kCgNBT6nU1shNumEchmW/sUO1JuQnPcw==",
"requires": { "requires": {
"@octokit/request": "^8.0.1", "@octokit/request": "^6.0.0",
"@octokit/types": "^12.0.0", "@octokit/types": "^9.0.0",
"universal-user-agent": "^6.0.0" "universal-user-agent": "^6.0.0"
} }
}, },
"@octokit/openapi-types": { "@octokit/openapi-types": {
"version": "19.0.0", "version": "18.1.1",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-19.0.0.tgz", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-18.1.1.tgz",
"integrity": "sha512-PclQ6JGMTE9iUStpzMkwLCISFn/wDeRjkZFIKALpvJQNBGwDoYYi2fFvuHwssoQ1rXI5mfh6jgTgWuddeUzfWw==" "integrity": "sha512-VRaeH8nCDtF5aXWnjPuEMIYf1itK/s3JYyJcWFJT8X9pSNnBtriDf7wlEWsGuhPLl4QIH4xM8fqTXDwJ3Mu6sw=="
}, },
"@octokit/plugin-paginate-rest": { "@octokit/plugin-paginate-rest": {
"version": "9.0.0", "version": "6.1.2",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.0.0.tgz", "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-6.1.2.tgz",
"integrity": "sha512-oIJzCpttmBTlEhBmRvb+b9rlnGpmFgDtZ0bB6nq39qIod6A5DP+7RkVLMOixIgRCYSHDTeayWqmiJ2SZ6xgfdw==", "integrity": "sha512-qhrmtQeHU/IivxucOV1bbI/xZyC/iOBhclokv7Sut5vnejAIAEXVcGQeRpQlU39E0WwK9lNvJHphHri/DB6lbQ==",
"requires": { "requires": {
"@octokit/types": "^12.0.0" "@octokit/tsconfig": "^1.0.2",
"@octokit/types": "^9.2.3"
} }
}, },
"@octokit/plugin-rest-endpoint-methods": { "@octokit/plugin-rest-endpoint-methods": {
"version": "10.0.0", "version": "7.2.3",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.0.0.tgz", "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-7.2.3.tgz",
"integrity": "sha512-16VkwE2v6rXU+/gBsYC62M8lKWOphY5Lg4wpjYnVE9Zbu0J6IwiT5kILoj1YOB53XLmcJR+Nqp8DmifOPY4H3g==", "integrity": "sha512-I5Gml6kTAkzVlN7KCtjOM+Ruwe/rQppp0QU372K1GP7kNOYEKe8Xn5BW4sE62JAHdwpq95OQK/qGNyKQMUzVgA==",
"requires": { "requires": {
"@octokit/types": "^12.0.0" "@octokit/types": "^10.0.0"
},
"dependencies": {
"@octokit/types": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-10.0.0.tgz",
"integrity": "sha512-Vm8IddVmhCgU1fxC1eyinpwqzXPEYu0NrYzD3YZjlGjyftdLBTeqNblRC0jmJmgxbJIsQlyogVeGnrNaaMVzIg==",
"requires": {
"@octokit/openapi-types": "^18.0.0"
}
}
} }
}, },
"@octokit/request": { "@octokit/request": {
"version": "8.1.4", "version": "6.2.8",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.4.tgz", "resolved": "https://registry.npmjs.org/@octokit/request/-/request-6.2.8.tgz",
"integrity": "sha512-M0aaFfpGPEKrg7XoA/gwgRvc9MSXHRO2Ioki1qrPDbl1e9YhjIwVoHE7HIKmv/m3idzldj//xBujcFNqGX6ENA==", "integrity": "sha512-ow4+pkVQ+6XVVsekSYBzJC0VTVvh/FCTUUgTsboGq+DTeWdyIFV8WSCdo0RIxk6wSkBTHqIK1mYuY7nOBXOchw==",
"requires": { "requires": {
"@octokit/endpoint": "^9.0.0", "@octokit/endpoint": "^7.0.0",
"@octokit/request-error": "^5.0.0", "@octokit/request-error": "^3.0.0",
"@octokit/types": "^12.0.0", "@octokit/types": "^9.0.0",
"is-plain-object": "^5.0.0", "is-plain-object": "^5.0.0",
"node-fetch": "^2.6.7",
"universal-user-agent": "^6.0.0" "universal-user-agent": "^6.0.0"
} }
}, },
"@octokit/request-error": { "@octokit/request-error": {
"version": "5.0.1", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.0.1.tgz", "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.3.tgz",
"integrity": "sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ==", "integrity": "sha512-crqw3V5Iy2uOU5Np+8M/YexTlT8zxCfI+qu+LxUB7SZpje4Qmx3mub5DfEKSO8Ylyk0aogi6TYdf6kxzh2BguQ==",
"requires": { "requires": {
"@octokit/types": "^12.0.0", "@octokit/types": "^9.0.0",
"deprecation": "^2.0.0", "deprecation": "^2.0.0",
"once": "^1.4.0" "once": "^1.4.0"
} }
}, },
"@octokit/tsconfig": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@octokit/tsconfig/-/tsconfig-1.0.2.tgz",
"integrity": "sha512-I0vDR0rdtP8p2lGMzvsJzbhdOWy405HcGovrspJ8RRibHnyRgggUSNO5AIox5LmqiwmatHKYsvj6VGFHkqS7lA=="
},
"@octokit/types": { "@octokit/types": {
"version": "12.0.0", "version": "9.3.2",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.0.0.tgz", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-9.3.2.tgz",
"integrity": "sha512-EzD434aHTFifGudYAygnFlS1Tl6KhbTynEWELQXIbTY8Msvb5nEqTZIm7sbPEt4mQYLZwu3zPKVdeIrw0g7ovg==", "integrity": "sha512-D4iHGTdAnEEVsB8fl95m1hiz7D5YiRdQ9b/OEb3BYRVwbLsGHcRVPz+u+BgRLNk0Q0/4iZCBqDN96j2XNxfXrA==",
"requires": { "requires": {
"@octokit/openapi-types": "^19.0.0" "@octokit/openapi-types": "^18.0.0"
}
},
"@types/node": {
"version": "20.8.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.2.tgz",
"integrity": "sha512-Vvycsc9FQdwhxE3y3DzeIxuEJbWGDsnrxvMADzTDF/lcdR9/K+AQIeAghTQsHtotg/q0j3WEOYS/jQgSdWue3w==",
"dev": true
},
"@types/proxy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@types/proxy/-/proxy-1.0.2.tgz",
"integrity": "sha512-NDNsg7YuClVzEenn9SUButu43blypWvljGsIkDV7HI4N9apjrS0aeeMTUG0PYa71lD1AvIgvjkBagqHDiomDjA==",
"dev": true,
"requires": {
"@types/node": "*"
} }
}, },
"ansi-styles": { "ansi-styles": {
@@ -585,6 +685,14 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true "dev": true
}, },
"node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"requires": {
"whatwg-url": "^5.0.0"
}
},
"once": { "once": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -613,6 +721,11 @@
"has-flag": "^3.0.0" "has-flag": "^3.0.0"
} }
}, },
"tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"tunnel": { "tunnel": {
"version": "0.0.6", "version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
@@ -631,6 +744,20 @@
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
}, },
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"requires": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+8 -6
View File
@@ -1,6 +1,6 @@
{ {
"name": "@actions/github", "name": "@actions/github",
"version": "6.0.0", "version": "6.0.2",
"description": "Actions github lib", "description": "Actions github lib",
"keywords": [ "keywords": [
"github", "github",
@@ -38,12 +38,14 @@
"url": "https://github.com/actions/toolkit/issues" "url": "https://github.com/actions/toolkit/issues"
}, },
"dependencies": { "dependencies": {
"@actions/http-client": "^2.2.0", "@actions/http-client": "file:actions-http-client-3.0.3.tgz",
"@octokit/core": "^5.0.1", "@octokit/core": "^4.2.4",
"@octokit/plugin-paginate-rest": "^9.0.0", "@octokit/plugin-paginate-rest": "^6.1.2",
"@octokit/plugin-rest-endpoint-methods": "^10.0.0" "@octokit/plugin-rest-endpoint-methods": "^7.2.3",
"undici": "^5.25.2"
}, },
"devDependencies": { "devDependencies": {
"@types/proxy": "^1.0.1",
"proxy": "^2.1.1" "proxy": "^2.1.1"
} }
} }
-2
View File
@@ -16,7 +16,6 @@ export class Context {
action: string action: string
actor: string actor: string
job: string job: string
runAttempt: number
runNumber: number runNumber: number
runId: number runId: number
apiUrl: string apiUrl: string
@@ -45,7 +44,6 @@ export class Context {
this.action = process.env.GITHUB_ACTION as string this.action = process.env.GITHUB_ACTION as string
this.actor = process.env.GITHUB_ACTOR as string this.actor = process.env.GITHUB_ACTOR as string
this.job = process.env.GITHUB_JOB as string this.job = process.env.GITHUB_JOB as string
this.runAttempt = parseInt(process.env.GITHUB_RUN_ATTEMPT as string, 10)
this.runNumber = parseInt(process.env.GITHUB_RUN_NUMBER as string, 10) this.runNumber = parseInt(process.env.GITHUB_RUN_NUMBER as string, 10)
this.runId = parseInt(process.env.GITHUB_RUN_ID as string, 10) this.runId = parseInt(process.env.GITHUB_RUN_ID as string, 10)
this.apiUrl = process.env.GITHUB_API_URL ?? `https://api.github.com` this.apiUrl = process.env.GITHUB_API_URL ?? `https://api.github.com`
+10 -12
View File
@@ -1,7 +1,7 @@
import * as http from 'http' import * as http from 'http'
import * as httpClient from '@actions/http-client' import * as httpClient from '@actions/http-client'
import {OctokitOptions} from '@octokit/core/dist-types/types' import {OctokitOptions} from '@octokit/core/dist-types/types'
import {ProxyAgent, fetch} from 'undici' import { ProxyAgent, Agent, fetch as undiciFetch } from "undici";
export function getAuthString( export function getAuthString(
token: string, token: string,
@@ -21,22 +21,20 @@ export function getProxyAgent(destinationUrl: string): http.Agent {
return hc.getAgent(destinationUrl) return hc.getAgent(destinationUrl)
} }
export function getProxyAgentDispatcher( export function getProxyAgentDispatcher(destinationUrl: string): ProxyAgent | undefined {
destinationUrl: string
): ProxyAgent | undefined {
const hc = new httpClient.HttpClient() const hc = new httpClient.HttpClient()
return hc.getAgentDispatcher(destinationUrl) return hc.getAgentDispatcher(destinationUrl)
} }
export function getProxyFetch(destinationUrl): typeof fetch { export function getProxyFetch(destinationUrl): any {
const httpDispatcher = getProxyAgentDispatcher(destinationUrl) const httpAgent = getProxyAgentDispatcher(destinationUrl)
const proxyFetch: typeof fetch = async (url, opts) => { const proxyFetch: typeof undiciFetch = (url, opts) => {
return fetch(url, { return undiciFetch(url, {
...opts, ...opts,
dispatcher: httpDispatcher dispatcher: httpAgent,
}) });
} };
return proxyFetch return proxyFetch;
} }
export function getApiBaseUrl(): string { export function getApiBaseUrl(): string {
-3
View File
@@ -1,8 +1,5 @@
## Releases ## Releases
## 2.2.0
- Add function to return proxy agent dispatcher for compatibility with latest octokit packages [#1547](https://github.com/actions/toolkit/pull/1547)
## 2.1.1 ## 2.1.1
- Add `HttpClientResponse.readBodyBuffer` method to read from a response stream and return a buffer [#1475](https://github.com/actions/toolkit/pull/1475) - Add `HttpClientResponse.readBodyBuffer` method to read from a response stream and return a buffer [#1475](https://github.com/actions/toolkit/pull/1475)
+10 -2
View File
@@ -3,7 +3,7 @@
import * as http from 'http' import * as http from 'http'
import * as httpm from '../lib/' import * as httpm from '../lib/'
import * as pm from '../lib/proxy' import * as pm from '../lib/proxy'
import {ProxyAgent} from 'undici' import { ProxyAgent, Agent, fetch as undiciFetch } from "undici";
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
const proxy = require('proxy') const proxy = require('proxy')
@@ -296,7 +296,7 @@ describe('proxy', () => {
expect(agent.proxyOptions.proxyAuth).toBe('user:password') expect(agent.proxyOptions.proxyAuth).toBe('user:password')
}) })
it('ProxyAgent is returned when proxy setting are provided', async () => { it('proxy settings return ProxyAgent', async () => {
process.env['https_proxy'] = 'http://127.0.0.1:8080' process.env['https_proxy'] = 'http://127.0.0.1:8080'
const httpClient = new httpm.HttpClient() const httpClient = new httpm.HttpClient()
const agent = httpClient.getAgentDispatcher('https://some-url') const agent = httpClient.getAgentDispatcher('https://some-url')
@@ -304,6 +304,14 @@ describe('proxy', () => {
console.log(agent) console.log(agent)
expect(agent instanceof ProxyAgent).toBe(true) expect(agent instanceof ProxyAgent).toBe(true)
}) })
// it('proxyAuth is set in tunnel agent when authentication is provided', async () => {
// const httpClient = new httpm.HttpClient()
// const agent: Agent | ProxyAgent = httpClient.getAgentDispatcher('https://some-url')
// // eslint-disable-next-line no-console
// console.log(agent)
// expect(agent instanceof Agent).toBe(true)
// })
}) })
function _clearVars(): void { function _clearVars(): void {
Binary file not shown.
+10 -6
View File
@@ -1,28 +1,29 @@
{ {
"name": "@actions/http-client", "name": "@actions/http-client",
"version": "2.2.0", "version": "3.0.3",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@actions/http-client", "name": "@actions/http-client",
"version": "2.2.0", "version": "3.0.3",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"tunnel": "^0.0.6", "tunnel": "^0.0.6"
"undici": "^5.25.4"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "20.7.1", "@types/node": "20.7.1",
"@types/proxy": "^1.0.1", "@types/proxy": "^1.0.1",
"@types/tunnel": "0.0.3", "@types/tunnel": "0.0.3",
"proxy": "^2.1.1" "proxy": "^2.1.1",
"undici": "^5.25.4"
} }
}, },
"node_modules/@fastify/busboy": { "node_modules/@fastify/busboy": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.0.0.tgz", "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.0.0.tgz",
"integrity": "sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ==", "integrity": "sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ==",
"dev": true,
"engines": { "engines": {
"node": ">=14" "node": ">=14"
} }
@@ -219,6 +220,7 @@
"version": "5.25.4", "version": "5.25.4",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.25.4.tgz", "resolved": "https://registry.npmjs.org/undici/-/undici-5.25.4.tgz",
"integrity": "sha512-450yJxT29qKMf3aoudzFpIciqpx6Pji3hEWaXqXmanbXF58LTAGCKxcJjxMXWu3iG+Mudgo3ZUfDB6YDFd/dAw==", "integrity": "sha512-450yJxT29qKMf3aoudzFpIciqpx6Pji3hEWaXqXmanbXF58LTAGCKxcJjxMXWu3iG+Mudgo3ZUfDB6YDFd/dAw==",
"dev": true,
"dependencies": { "dependencies": {
"@fastify/busboy": "^2.0.0" "@fastify/busboy": "^2.0.0"
}, },
@@ -231,7 +233,8 @@
"@fastify/busboy": { "@fastify/busboy": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.0.0.tgz", "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.0.0.tgz",
"integrity": "sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ==" "integrity": "sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ==",
"dev": true
}, },
"@types/node": { "@types/node": {
"version": "20.7.1", "version": "20.7.1",
@@ -384,6 +387,7 @@
"version": "5.25.4", "version": "5.25.4",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.25.4.tgz", "resolved": "https://registry.npmjs.org/undici/-/undici-5.25.4.tgz",
"integrity": "sha512-450yJxT29qKMf3aoudzFpIciqpx6Pji3hEWaXqXmanbXF58LTAGCKxcJjxMXWu3iG+Mudgo3ZUfDB6YDFd/dAw==", "integrity": "sha512-450yJxT29qKMf3aoudzFpIciqpx6Pji3hEWaXqXmanbXF58LTAGCKxcJjxMXWu3iG+Mudgo3ZUfDB6YDFd/dAw==",
"dev": true,
"requires": { "requires": {
"@fastify/busboy": "^2.0.0" "@fastify/busboy": "^2.0.0"
} }
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@actions/http-client", "name": "@actions/http-client",
"version": "2.2.0", "version": "3.0.3",
"description": "Actions Http Client", "description": "Actions Http Client",
"keywords": [ "keywords": [
"github", "github",
@@ -42,10 +42,10 @@
"@types/node": "20.7.1", "@types/node": "20.7.1",
"@types/tunnel": "0.0.3", "@types/tunnel": "0.0.3",
"proxy": "^2.1.1", "proxy": "^2.1.1",
"undici": "^5.25.4",
"@types/proxy": "^1.0.1" "@types/proxy": "^1.0.1"
}, },
"dependencies": { "dependencies": {
"tunnel": "^0.0.6", "tunnel": "^0.0.6"
"undici": "^5.25.4"
} }
} }
+68 -13
View File
@@ -6,7 +6,7 @@ import * as ifm from './interfaces'
import * as net from 'net' import * as net from 'net'
import * as pm from './proxy' import * as pm from './proxy'
import * as tunnel from 'tunnel' import * as tunnel from 'tunnel'
import {ProxyAgent} from 'undici' import { ProxyAgent, Agent, fetch as undiciFetch } from "undici";
export enum HttpCodes { export enum HttpCodes {
OK = 200, OK = 200,
@@ -138,6 +138,7 @@ export class HttpClient {
private _maxRetries = 1 private _maxRetries = 1
private _agent: any private _agent: any
private _proxyAgent: any private _proxyAgent: any
private _agentDispatcher: any
private _proxyAgentDispatcher: any private _proxyAgentDispatcher: any
private _keepAlive = false private _keepAlive = false
private _disposed = false private _disposed = false
@@ -570,11 +571,12 @@ export class HttpClient {
const parsedUrl = new URL(serverUrl) const parsedUrl = new URL(serverUrl)
const proxyUrl = pm.getProxyUrl(parsedUrl) const proxyUrl = pm.getProxyUrl(parsedUrl)
const useProxy = proxyUrl && proxyUrl.hostname const useProxy = proxyUrl && proxyUrl.hostname
if (!useProxy) { if (useProxy) {
return return this._getProxyAgentDispatcher(parsedUrl, proxyUrl)
}
else {
return;
} }
return this._getProxyAgentDispatcher(parsedUrl, proxyUrl)
} }
private _prepareRequest( private _prepareRequest(
@@ -716,25 +718,38 @@ export class HttpClient {
private _getProxyAgentDispatcher(parsedUrl: URL, proxyUrl: URL): ProxyAgent { private _getProxyAgentDispatcher(parsedUrl: URL, proxyUrl: URL): ProxyAgent {
let proxyAgent let proxyAgent
const useProxy = proxyUrl && proxyUrl.hostname
if (this._keepAlive) { if (this._keepAlive && useProxy) {
proxyAgent = this._proxyAgentDispatcher proxyAgent = this._proxyAgentDispatcher
} }
if (this._keepAlive && !useProxy) {
proxyAgent = this._agentDispatcher
}
// if agent is already assigned use that agent. // if agent is already assigned use that agent.
if (proxyAgent) { if (proxyAgent) {
return proxyAgent return proxyAgent
} }
const usingSsl = parsedUrl.protocol === 'https:' const usingSsl = parsedUrl.protocol === 'https:'
proxyAgent = new ProxyAgent({ let maxSockets = 100
uri: proxyUrl.href, if (this.requestOptions) {
pipelining: !this._keepAlive ? 0 : 1, maxSockets = this.requestOptions.maxSockets || http.globalAgent.maxSockets
...((proxyUrl.username || proxyUrl.password) && { }
token: `${proxyUrl.username}:${proxyUrl.password}`
// This is `useProxy` again, but we need to check `proxyURl` directly for TypeScripts's flow analysis.
if (proxyUrl && proxyUrl.hostname) {
proxyAgent = new ProxyAgent({
uri: proxyUrl.href,
pipelining: (!this._keepAlive ? 0 : 1),
...((proxyUrl.username || proxyUrl.password) && {
token: `${proxyUrl.username}:${proxyUrl.password}`
}),
}) })
}) this._proxyAgentDispatcher = proxyAgent
this._proxyAgentDispatcher = proxyAgent }
if (usingSsl && this._ignoreSslError) { if (usingSsl && this._ignoreSslError) {
// we don't want to set NODE_TLS_REJECT_UNAUTHORIZED=0 since that will affect request for entire process // we don't want to set NODE_TLS_REJECT_UNAUTHORIZED=0 since that will affect request for entire process
@@ -748,6 +763,46 @@ export class HttpClient {
return proxyAgent return proxyAgent
} }
private _getAgentDispatcher(parsedUrl: URL): Agent {
let agent;
if (this._keepAlive) {
agent = this._agentDispatcher
}
// if agent is already assigned use that agent.
if (agent) {
return agent
}
const usingSsl = parsedUrl.protocol === 'https:'
let maxSockets = 100
if (this.requestOptions) {
maxSockets = this.requestOptions.maxSockets || http.globalAgent.maxSockets
}
// if reusing agent across request and tunneling agent isn't assigned create a new agent
if (!agent) {
agent = new Agent(
{
pipelining: (!this._keepAlive ? 0 : 1),
}
)
this._agentDispatcher = agent
}
if (usingSsl && this._ignoreSslError) {
// we don't want to set NODE_TLS_REJECT_UNAUTHORIZED=0 since that will affect request for entire process
// http.RequestOptions doesn't expose a way to modify RequestOptions.agent.options
// we have to cast it to any and change it directly
agent.options = Object.assign(agent.options.connect || {}, {
rejectUnauthorized: false
})
}
return agent
}
private async _performExponentialBackoff(retryNumber: number): Promise<void> { private async _performExponentialBackoff(retryNumber: number): Promise<void> {
retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber) retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber)
const ms: number = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber) const ms: number = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber)
+9 -16
View File
@@ -93,20 +93,13 @@
} }
}, },
"node_modules/debug": { "node_modules/debug": {
"version": "4.3.4", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"ms": "2.1.2" "ms": "^2.1.1"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
} }
}, },
"node_modules/json-stringify-safe": { "node_modules/json-stringify-safe": {
@@ -246,12 +239,12 @@
} }
}, },
"debug": { "debug": {
"version": "4.3.4", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"dev": true, "dev": true,
"requires": { "requires": {
"ms": "2.1.2" "ms": "^2.1.1"
} }
}, },
"json-stringify-safe": { "json-stringify-safe": {