Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4902d3a118 | ||
|
|
04d1a7ec3c | ||
|
|
e1b7e78d60 | ||
|
|
7640cf17c1 | ||
|
|
8d7ed4fb57 | ||
|
|
5afc042a74 | ||
|
|
5e5faf73fc | ||
|
|
9e63a77e7a | ||
|
|
146143a9b4 | ||
|
|
6635d12ce0 | ||
|
|
dccc3f7f1c | ||
|
|
66d5434f23 | ||
|
|
c8466d1fac | ||
|
|
264230c2c5 | ||
|
|
32dbccb77b |
@@ -32,7 +32,7 @@ jobs:
|
|||||||
run: npm run bootstrap
|
run: npm run bootstrap
|
||||||
|
|
||||||
- name: audit tools (without allow-list)
|
- name: audit tools (without allow-list)
|
||||||
run: npm audit --audit-level=moderate --omit dev
|
run: npm audit --audit-level=moderate
|
||||||
|
|
||||||
- name: audit packages
|
- name: audit packages
|
||||||
run: npm run audit-all
|
run: npm run audit-all
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
name: Publish NPM
|
name: Publish NPM
|
||||||
|
|
||||||
run-name: Publish NPM - ${{ github.event.inputs.package }}
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
|
|||||||
Generated
+2542
-165
File diff suppressed because it is too large
Load Diff
+15
-1
@@ -32,5 +32,19 @@
|
|||||||
"prettier": "^3.0.0",
|
"prettier": "^3.0.0",
|
||||||
"ts-jest": "^29.1.1",
|
"ts-jest": "^29.1.1",
|
||||||
"typescript": "^5.2.2"
|
"typescript": "^5.2.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@actions/artifact": "^2.1.7",
|
||||||
|
"@actions/attest": "^1.2.1",
|
||||||
|
"@actions/cache": "^3.2.4",
|
||||||
|
"@actions/core": "^1.10.1",
|
||||||
|
"@actions/exec": "^1.1.1",
|
||||||
|
"@actions/github": "^6.0.0",
|
||||||
|
"@actions/glob": "^0.4.0",
|
||||||
|
"@actions/http-client": "^2.2.1",
|
||||||
|
"@actions/io": "^1.1.3",
|
||||||
|
"@actions/tool-cache": "^2.0.1",
|
||||||
|
"tunnel": "^0.0.6",
|
||||||
|
"undici": "^6.18.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,5 @@
|
|||||||
# @actions/artifact Releases
|
# @actions/artifact Releases
|
||||||
|
|
||||||
### 2.1.11
|
|
||||||
|
|
||||||
- Fixed a bug with relative symlinks resolution [#1844](https://github.com/actions/toolkit/pull/1844)
|
|
||||||
- Use native `crypto` [#1815](https://github.com/actions/toolkit/pull/1815)
|
|
||||||
|
|
||||||
### 2.1.10
|
|
||||||
|
|
||||||
- Fixed a regression with symlinks not being automatically resolved [#1830](https://github.com/actions/toolkit/pull/1830)
|
|
||||||
- Fixed a regression with chunk timeout [#1786](https://github.com/actions/toolkit/pull/1786)
|
|
||||||
|
|
||||||
### 2.1.9
|
|
||||||
|
|
||||||
- Fixed artifact upload chunk timeout logic [#1774](https://github.com/actions/toolkit/pull/1774)
|
|
||||||
- Use lazy stream to prevent issues with open file limits [#1771](https://github.com/actions/toolkit/pull/1771)
|
|
||||||
|
|
||||||
### 2.1.8
|
|
||||||
|
|
||||||
- Allows `*.localhost` domains for hostname checks for local development.
|
|
||||||
|
|
||||||
### 2.1.7
|
### 2.1.7
|
||||||
|
|
||||||
- Update unzip-stream dependency and reverted to using `unzip.Extract()`
|
- Update unzip-stream dependency and reverted to using `unzip.Extract()`
|
||||||
|
|||||||
@@ -20,11 +20,6 @@ describe('isGhes', () => {
|
|||||||
expect(config.isGhes()).toBe(false)
|
expect(config.isGhes()).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return false when the request domain ends with .localhost', () => {
|
|
||||||
process.env.GITHUB_SERVER_URL = 'https://github.localhost'
|
|
||||||
expect(config.isGhes()).toBe(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return false when the request domain is specific to an enterprise', () => {
|
it('should return false when the request domain is specific to an enterprise', () => {
|
||||||
process.env.GITHUB_SERVER_URL = 'https://my-enterprise.github.com'
|
process.env.GITHUB_SERVER_URL = 'https://my-enterprise.github.com'
|
||||||
expect(config.isGhes()).toBe(true)
|
expect(config.isGhes()).toBe(true)
|
||||||
|
|||||||
@@ -1,173 +1,260 @@
|
|||||||
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'
|
||||||
|
import * as retention from '../src/internal/upload/retention'
|
||||||
import * as config from '../src/internal/shared/config'
|
import * as config from '../src/internal/shared/config'
|
||||||
import {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 {noopLogs} from './common'
|
||||||
import {FilesNotFoundError} from '../src/internal/shared/errors'
|
import {FilesNotFoundError} from '../src/internal/shared/errors'
|
||||||
import {BlockBlobUploadStreamOptions} from '@azure/storage-blob'
|
import {BlockBlobClient} from '@azure/storage-blob'
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import unzip from 'unzip-stream'
|
|
||||||
|
|
||||||
const uploadStreamMock = jest.fn()
|
|
||||||
const blockBlobClientMock = jest.fn().mockImplementation(() => ({
|
|
||||||
uploadStream: uploadStreamMock
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('@azure/storage-blob', () => ({
|
|
||||||
BlobClient: jest.fn().mockImplementation(() => {
|
|
||||||
return {
|
|
||||||
getBlockBlobClient: blockBlobClientMock
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
|
|
||||||
const fixtures = {
|
|
||||||
uploadDirectory: path.join(__dirname, '_temp', 'plz-upload'),
|
|
||||||
files: [
|
|
||||||
{name: 'file1.txt', content: 'test 1 file content'},
|
|
||||||
{name: 'file2.txt', content: 'test 2 file content'},
|
|
||||||
{name: 'file3.txt', content: 'test 3 file content'},
|
|
||||||
{
|
|
||||||
name: 'real.txt',
|
|
||||||
content: 'from a symlink'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'relative.txt',
|
|
||||||
content: 'from a symlink',
|
|
||||||
symlink: 'real.txt',
|
|
||||||
relative: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'absolute.txt',
|
|
||||||
content: 'from a symlink',
|
|
||||||
symlink: 'real.txt',
|
|
||||||
relative: false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
backendIDs: {
|
|
||||||
workflowRunBackendId: '67dbcc20-e851-4452-a7c3-2cc0d2e0ec67',
|
|
||||||
workflowJobRunBackendId: '5f49179d-3386-4c38-85f7-00f8138facd0'
|
|
||||||
},
|
|
||||||
runtimeToken: 'test-token',
|
|
||||||
resultsServiceURL: 'http://results.local',
|
|
||||||
inputs: {
|
|
||||||
artifactName: 'test-artifact',
|
|
||||||
files: [
|
|
||||||
'/home/user/files/plz-upload/file1.txt',
|
|
||||||
'/home/user/files/plz-upload/file2.txt',
|
|
||||||
'/home/user/files/plz-upload/dir/file3.txt'
|
|
||||||
],
|
|
||||||
rootDirectory: '/home/user/files/plz-upload'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('upload-artifact', () => {
|
describe('upload-artifact', () => {
|
||||||
beforeAll(() => {
|
|
||||||
fs.mkdirSync(fixtures.uploadDirectory, {
|
|
||||||
recursive: true
|
|
||||||
})
|
|
||||||
|
|
||||||
for (const file of fixtures.files) {
|
|
||||||
if (file.symlink) {
|
|
||||||
let symlinkPath = file.symlink
|
|
||||||
if (!file.relative) {
|
|
||||||
symlinkPath = path.join(fixtures.uploadDirectory, file.symlink)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fs.existsSync(path.join(fixtures.uploadDirectory, file.name))) {
|
|
||||||
fs.symlinkSync(
|
|
||||||
symlinkPath,
|
|
||||||
path.join(fixtures.uploadDirectory, file.name),
|
|
||||||
'file'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.join(fixtures.uploadDirectory, file.name),
|
|
||||||
file.content
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
noopLogs()
|
noopLogs()
|
||||||
jest
|
|
||||||
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
|
||||||
.mockReturnValue()
|
|
||||||
jest
|
|
||||||
.spyOn(util, 'getBackendIdsFromToken')
|
|
||||||
.mockReturnValue(fixtures.backendIDs)
|
|
||||||
jest
|
|
||||||
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
|
|
||||||
.mockReturnValue(
|
|
||||||
fixtures.files.map(file => ({
|
|
||||||
sourcePath: path.join(fixtures.uploadDirectory, file.name),
|
|
||||||
destinationPath: file.name,
|
|
||||||
stats: new fs.Stats()
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
jest.spyOn(config, 'getRuntimeToken').mockReturnValue(fixtures.runtimeToken)
|
|
||||||
jest
|
|
||||||
.spyOn(config, 'getResultsServiceUrl')
|
|
||||||
.mockReturnValue(fixtures.resultsServiceURL)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
jest.restoreAllMocks()
|
jest.restoreAllMocks()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should reject if there are no files to upload', async () => {
|
it('should successfully upload an artifact', () => {
|
||||||
|
const mockDate = new Date('2020-01-01')
|
||||||
|
jest
|
||||||
|
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
||||||
|
.mockReturnValue()
|
||||||
jest
|
jest
|
||||||
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
|
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
|
||||||
.mockClear()
|
.mockReturnValue([
|
||||||
.mockReturnValue([])
|
{
|
||||||
|
sourcePath: '/home/user/files/plz-upload/file1.txt',
|
||||||
|
destinationPath: 'file1.txt'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourcePath: '/home/user/files/plz-upload/file2.txt',
|
||||||
|
destinationPath: 'file2.txt'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourcePath: '/home/user/files/plz-upload/dir/file3.txt',
|
||||||
|
destinationPath: 'dir/file3.txt'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
const uploadResp = uploadArtifact(
|
|
||||||
fixtures.inputs.artifactName,
|
|
||||||
fixtures.inputs.files,
|
|
||||||
fixtures.inputs.rootDirectory
|
|
||||||
)
|
|
||||||
await expect(uploadResp).rejects.toThrowError(FilesNotFoundError)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should reject if no backend IDs are found', async () => {
|
|
||||||
jest.spyOn(util, 'getBackendIdsFromToken').mockRestore()
|
|
||||||
|
|
||||||
const uploadResp = uploadArtifact(
|
|
||||||
fixtures.inputs.artifactName,
|
|
||||||
fixtures.inputs.files,
|
|
||||||
fixtures.inputs.rootDirectory
|
|
||||||
)
|
|
||||||
|
|
||||||
await expect(uploadResp).rejects.toThrow()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return false if the creation request fails', async () => {
|
|
||||||
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: '1234',
|
||||||
|
workflowJobRunBackendId: '5678'
|
||||||
|
})
|
||||||
|
jest
|
||||||
|
.spyOn(retention, 'getExpiration')
|
||||||
|
.mockReturnValue(Timestamp.fromDate(mockDate))
|
||||||
|
jest
|
||||||
|
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
|
||||||
|
.mockReturnValue(
|
||||||
|
Promise.resolve({
|
||||||
|
ok: true,
|
||||||
|
signedUploadUrl: 'https://signed-upload-url.com'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
jest.spyOn(blobUpload, 'uploadZipToBlobStorage').mockReturnValue(
|
||||||
|
Promise.resolve({
|
||||||
|
uploadSize: 1234,
|
||||||
|
sha256Hash: 'test-sha256-hash'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
jest
|
||||||
|
.spyOn(ArtifactServiceClientJSON.prototype, 'FinalizeArtifact')
|
||||||
|
.mockReturnValue(Promise.resolve({ok: true, artifactId: '1'}))
|
||||||
|
|
||||||
|
// ArtifactHttpClient mocks
|
||||||
|
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
|
||||||
|
jest
|
||||||
|
.spyOn(config, 'getResultsServiceUrl')
|
||||||
|
.mockReturnValue('https://test-url.com')
|
||||||
|
|
||||||
|
const uploadResp = uploadArtifact(
|
||||||
|
'test-artifact',
|
||||||
|
[
|
||||||
|
'/home/user/files/plz-upload/file1.txt',
|
||||||
|
'/home/user/files/plz-upload/file2.txt',
|
||||||
|
'/home/user/files/plz-upload/dir/file3.txt'
|
||||||
|
],
|
||||||
|
'/home/user/files/plz-upload'
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(uploadResp).resolves.toEqual({size: 1234, id: 1})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw an error if the root directory is invalid', () => {
|
||||||
|
jest
|
||||||
|
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
||||||
|
.mockImplementation(() => {
|
||||||
|
throw new Error('Invalid root directory')
|
||||||
|
})
|
||||||
|
|
||||||
|
const uploadResp = uploadArtifact(
|
||||||
|
'test-artifact',
|
||||||
|
[
|
||||||
|
'/home/user/files/plz-upload/file1.txt',
|
||||||
|
'/home/user/files/plz-upload/file2.txt',
|
||||||
|
'/home/user/files/plz-upload/dir/file3.txt'
|
||||||
|
],
|
||||||
|
'/home/user/files/plz-upload'
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(uploadResp).rejects.toThrow('Invalid root directory')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should reject if there are no files to upload', () => {
|
||||||
|
jest
|
||||||
|
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
||||||
|
.mockReturnValue()
|
||||||
|
jest
|
||||||
|
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
|
||||||
|
.mockReturnValue([])
|
||||||
|
|
||||||
|
const uploadResp = uploadArtifact(
|
||||||
|
'test-artifact',
|
||||||
|
[
|
||||||
|
'/home/user/files/plz-upload/file1.txt',
|
||||||
|
'/home/user/files/plz-upload/file2.txt',
|
||||||
|
'/home/user/files/plz-upload/dir/file3.txt'
|
||||||
|
],
|
||||||
|
'/home/user/files/plz-upload'
|
||||||
|
)
|
||||||
|
expect(uploadResp).rejects.toThrowError(FilesNotFoundError)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should reject if no backend IDs are found', () => {
|
||||||
|
jest
|
||||||
|
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
||||||
|
.mockReturnValue()
|
||||||
|
jest
|
||||||
|
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
|
||||||
|
.mockReturnValue([
|
||||||
|
{
|
||||||
|
sourcePath: '/home/user/files/plz-upload/file1.txt',
|
||||||
|
destinationPath: 'file1.txt'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourcePath: '/home/user/files/plz-upload/file2.txt',
|
||||||
|
destinationPath: 'file2.txt'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourcePath: '/home/user/files/plz-upload/dir/file3.txt',
|
||||||
|
destinationPath: 'dir/file3.txt'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(zip, 'createZipUploadStream')
|
||||||
|
.mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1)))
|
||||||
|
|
||||||
|
const uploadResp = uploadArtifact(
|
||||||
|
'test-artifact',
|
||||||
|
[
|
||||||
|
'/home/user/files/plz-upload/file1.txt',
|
||||||
|
'/home/user/files/plz-upload/file2.txt',
|
||||||
|
'/home/user/files/plz-upload/dir/file3.txt'
|
||||||
|
],
|
||||||
|
'/home/user/files/plz-upload'
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(uploadResp).rejects.toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return false if the creation request fails', () => {
|
||||||
|
const mockDate = new Date('2020-01-01')
|
||||||
|
jest
|
||||||
|
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
||||||
|
.mockReturnValue()
|
||||||
|
jest
|
||||||
|
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
|
||||||
|
.mockReturnValue([
|
||||||
|
{
|
||||||
|
sourcePath: '/home/user/files/plz-upload/file1.txt',
|
||||||
|
destinationPath: 'file1.txt'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourcePath: '/home/user/files/plz-upload/file2.txt',
|
||||||
|
destinationPath: 'file2.txt'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourcePath: '/home/user/files/plz-upload/dir/file3.txt',
|
||||||
|
destinationPath: 'dir/file3.txt'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(zip, 'createZipUploadStream')
|
||||||
|
.mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1)))
|
||||||
|
jest.spyOn(util, 'getBackendIdsFromToken').mockReturnValue({
|
||||||
|
workflowRunBackendId: '1234',
|
||||||
|
workflowJobRunBackendId: '5678'
|
||||||
|
})
|
||||||
|
jest
|
||||||
|
.spyOn(retention, 'getExpiration')
|
||||||
|
.mockReturnValue(Timestamp.fromDate(mockDate))
|
||||||
jest
|
jest
|
||||||
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
|
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
|
||||||
.mockReturnValue(Promise.resolve({ok: false, signedUploadUrl: ''}))
|
.mockReturnValue(Promise.resolve({ok: false, signedUploadUrl: ''}))
|
||||||
|
|
||||||
|
// ArtifactHttpClient mocks
|
||||||
|
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
|
||||||
|
jest
|
||||||
|
.spyOn(config, 'getResultsServiceUrl')
|
||||||
|
.mockReturnValue('https://test-url.com')
|
||||||
|
|
||||||
const uploadResp = uploadArtifact(
|
const uploadResp = uploadArtifact(
|
||||||
fixtures.inputs.artifactName,
|
'test-artifact',
|
||||||
fixtures.inputs.files,
|
[
|
||||||
fixtures.inputs.rootDirectory
|
'/home/user/files/plz-upload/file1.txt',
|
||||||
|
'/home/user/files/plz-upload/file2.txt',
|
||||||
|
'/home/user/files/plz-upload/dir/file3.txt'
|
||||||
|
],
|
||||||
|
'/home/user/files/plz-upload'
|
||||||
)
|
)
|
||||||
|
|
||||||
await expect(uploadResp).rejects.toThrow()
|
expect(uploadResp).rejects.toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return false if blob storage upload is unsuccessful', async () => {
|
it('should return false if blob storage upload is unsuccessful', () => {
|
||||||
|
const mockDate = new Date('2020-01-01')
|
||||||
|
jest
|
||||||
|
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
||||||
|
.mockReturnValue()
|
||||||
|
jest
|
||||||
|
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
|
||||||
|
.mockReturnValue([
|
||||||
|
{
|
||||||
|
sourcePath: '/home/user/files/plz-upload/file1.txt',
|
||||||
|
destinationPath: 'file1.txt'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourcePath: '/home/user/files/plz-upload/file2.txt',
|
||||||
|
destinationPath: 'file2.txt'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourcePath: '/home/user/files/plz-upload/dir/file3.txt',
|
||||||
|
destinationPath: 'dir/file3.txt'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
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: '1234',
|
||||||
|
workflowJobRunBackendId: '5678'
|
||||||
|
})
|
||||||
|
jest
|
||||||
|
.spyOn(retention, 'getExpiration')
|
||||||
|
.mockReturnValue(Timestamp.fromDate(mockDate))
|
||||||
jest
|
jest
|
||||||
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
|
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
|
||||||
.mockReturnValue(
|
.mockReturnValue(
|
||||||
@@ -180,19 +267,57 @@ describe('upload-artifact', () => {
|
|||||||
.spyOn(blobUpload, 'uploadZipToBlobStorage')
|
.spyOn(blobUpload, 'uploadZipToBlobStorage')
|
||||||
.mockReturnValue(Promise.reject(new Error('boom')))
|
.mockReturnValue(Promise.reject(new Error('boom')))
|
||||||
|
|
||||||
|
// ArtifactHttpClient mocks
|
||||||
|
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
|
||||||
|
jest
|
||||||
|
.spyOn(config, 'getResultsServiceUrl')
|
||||||
|
.mockReturnValue('https://test-url.com')
|
||||||
|
|
||||||
const uploadResp = uploadArtifact(
|
const uploadResp = uploadArtifact(
|
||||||
fixtures.inputs.artifactName,
|
'test-artifact',
|
||||||
fixtures.inputs.files,
|
[
|
||||||
fixtures.inputs.rootDirectory
|
'/home/user/files/plz-upload/file1.txt',
|
||||||
|
'/home/user/files/plz-upload/file2.txt',
|
||||||
|
'/home/user/files/plz-upload/dir/file3.txt'
|
||||||
|
],
|
||||||
|
'/home/user/files/plz-upload'
|
||||||
)
|
)
|
||||||
|
|
||||||
await expect(uploadResp).rejects.toThrow()
|
expect(uploadResp).rejects.toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should reject if finalize artifact fails', async () => {
|
it('should reject if finalize artifact fails', () => {
|
||||||
|
const mockDate = new Date('2020-01-01')
|
||||||
|
jest
|
||||||
|
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
||||||
|
.mockReturnValue()
|
||||||
|
jest
|
||||||
|
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
|
||||||
|
.mockReturnValue([
|
||||||
|
{
|
||||||
|
sourcePath: '/home/user/files/plz-upload/file1.txt',
|
||||||
|
destinationPath: 'file1.txt'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourcePath: '/home/user/files/plz-upload/file2.txt',
|
||||||
|
destinationPath: 'file2.txt'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourcePath: '/home/user/files/plz-upload/dir/file3.txt',
|
||||||
|
destinationPath: 'dir/file3.txt'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
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: '1234',
|
||||||
|
workflowJobRunBackendId: '5678'
|
||||||
|
})
|
||||||
|
jest
|
||||||
|
.spyOn(retention, 'getExpiration')
|
||||||
|
.mockReturnValue(Timestamp.fromDate(mockDate))
|
||||||
jest
|
jest
|
||||||
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
|
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
|
||||||
.mockReturnValue(
|
.mockReturnValue(
|
||||||
@@ -211,161 +336,112 @@ describe('upload-artifact', () => {
|
|||||||
.spyOn(ArtifactServiceClientJSON.prototype, 'FinalizeArtifact')
|
.spyOn(ArtifactServiceClientJSON.prototype, 'FinalizeArtifact')
|
||||||
.mockReturnValue(Promise.resolve({ok: false, artifactId: ''}))
|
.mockReturnValue(Promise.resolve({ok: false, artifactId: ''}))
|
||||||
|
|
||||||
|
// ArtifactHttpClient mocks
|
||||||
|
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
|
||||||
|
jest
|
||||||
|
.spyOn(config, 'getResultsServiceUrl')
|
||||||
|
.mockReturnValue('https://test-url.com')
|
||||||
|
|
||||||
const uploadResp = uploadArtifact(
|
const uploadResp = uploadArtifact(
|
||||||
fixtures.inputs.artifactName,
|
'test-artifact',
|
||||||
fixtures.inputs.files,
|
[
|
||||||
fixtures.inputs.rootDirectory
|
'/home/user/files/plz-upload/file1.txt',
|
||||||
|
'/home/user/files/plz-upload/file2.txt',
|
||||||
|
'/home/user/files/plz-upload/dir/file3.txt'
|
||||||
|
],
|
||||||
|
'/home/user/files/plz-upload'
|
||||||
)
|
)
|
||||||
|
|
||||||
await expect(uploadResp).rejects.toThrow()
|
expect(uploadResp).rejects.toThrow()
|
||||||
})
|
|
||||||
|
|
||||||
it('should successfully upload an artifact', async () => {
|
|
||||||
jest
|
|
||||||
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
|
|
||||||
.mockRestore()
|
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
|
|
||||||
.mockReturnValue(
|
|
||||||
Promise.resolve({
|
|
||||||
ok: true,
|
|
||||||
signedUploadUrl: 'https://signed-upload-url.local'
|
|
||||||
})
|
|
||||||
)
|
|
||||||
jest
|
|
||||||
.spyOn(ArtifactServiceClientJSON.prototype, 'FinalizeArtifact')
|
|
||||||
.mockReturnValue(
|
|
||||||
Promise.resolve({
|
|
||||||
ok: true,
|
|
||||||
artifactId: '1'
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
let loadedBytes = 0
|
|
||||||
const uploadedZip = path.join(
|
|
||||||
fixtures.uploadDirectory,
|
|
||||||
'..',
|
|
||||||
'uploaded.zip'
|
|
||||||
)
|
|
||||||
uploadStreamMock.mockImplementation(
|
|
||||||
async (
|
|
||||||
stream: NodeJS.ReadableStream,
|
|
||||||
bufferSize?: number,
|
|
||||||
maxConcurrency?: number,
|
|
||||||
options?: BlockBlobUploadStreamOptions
|
|
||||||
) => {
|
|
||||||
const {onProgress} = options || {}
|
|
||||||
|
|
||||||
if (fs.existsSync(uploadedZip)) {
|
|
||||||
fs.unlinkSync(uploadedZip)
|
|
||||||
}
|
|
||||||
const uploadedZipStream = fs.createWriteStream(uploadedZip)
|
|
||||||
|
|
||||||
onProgress?.({loadedBytes: 0})
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
stream.on('data', chunk => {
|
|
||||||
loadedBytes += chunk.length
|
|
||||||
uploadedZipStream.write(chunk)
|
|
||||||
onProgress?.({loadedBytes})
|
|
||||||
})
|
|
||||||
stream.on('end', () => {
|
|
||||||
onProgress?.({loadedBytes})
|
|
||||||
uploadedZipStream.end()
|
|
||||||
resolve({})
|
|
||||||
})
|
|
||||||
stream.on('error', err => {
|
|
||||||
reject(err)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const {id, size} = await uploadArtifact(
|
|
||||||
fixtures.inputs.artifactName,
|
|
||||||
fixtures.files.map(file =>
|
|
||||||
path.join(fixtures.uploadDirectory, file.name)
|
|
||||||
),
|
|
||||||
fixtures.uploadDirectory
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(id).toBe(1)
|
|
||||||
expect(size).toBe(loadedBytes)
|
|
||||||
|
|
||||||
const extractedDirectory = path.join(
|
|
||||||
fixtures.uploadDirectory,
|
|
||||||
'..',
|
|
||||||
'extracted'
|
|
||||||
)
|
|
||||||
if (fs.existsSync(extractedDirectory)) {
|
|
||||||
fs.rmdirSync(extractedDirectory, {recursive: true})
|
|
||||||
}
|
|
||||||
|
|
||||||
const extract = new Promise((resolve, reject) => {
|
|
||||||
fs.createReadStream(uploadedZip)
|
|
||||||
.pipe(unzip.Extract({path: extractedDirectory}))
|
|
||||||
.on('close', () => {
|
|
||||||
resolve(true)
|
|
||||||
})
|
|
||||||
.on('error', err => {
|
|
||||||
reject(err)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
await expect(extract).resolves.toBe(true)
|
|
||||||
for (const file of fixtures.files) {
|
|
||||||
const filePath = path.join(extractedDirectory, file.name)
|
|
||||||
expect(fs.existsSync(filePath)).toBe(true)
|
|
||||||
expect(fs.readFileSync(filePath, 'utf8')).toBe(file.content)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should throw an error uploading blob chunks get delayed', async () => {
|
it('should throw an error uploading blob chunks get delayed', async () => {
|
||||||
|
const mockDate = new Date('2020-01-01')
|
||||||
|
const dirPath = path.join(__dirname, `plz-upload`)
|
||||||
|
if (!fs.existsSync(dirPath)) {
|
||||||
|
fs.mkdirSync(dirPath, {recursive: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(path.join(dirPath, 'file1.txt'), 'test file content')
|
||||||
|
fs.writeFileSync(path.join(dirPath, 'file2.txt'), 'test file content')
|
||||||
|
|
||||||
|
fs.writeFileSync(path.join(dirPath, 'file3.txt'), 'test file content')
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
||||||
|
.mockReturnValue()
|
||||||
|
jest
|
||||||
|
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
|
||||||
|
.mockReturnValue([
|
||||||
|
{
|
||||||
|
sourcePath: path.join(dirPath, 'file1.txt'),
|
||||||
|
destinationPath: 'file1.txt'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourcePath: path.join(dirPath, 'file2.txt'),
|
||||||
|
destinationPath: 'file2.txt'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourcePath: path.join(dirPath, 'file3.txt'),
|
||||||
|
destinationPath: 'dir/file3.txt'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
jest.spyOn(util, 'getBackendIdsFromToken').mockReturnValue({
|
||||||
|
workflowRunBackendId: '1234',
|
||||||
|
workflowJobRunBackendId: '5678'
|
||||||
|
})
|
||||||
|
jest
|
||||||
|
.spyOn(retention, 'getExpiration')
|
||||||
|
.mockReturnValue(Timestamp.fromDate(mockDate))
|
||||||
jest
|
jest
|
||||||
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
|
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
|
||||||
.mockReturnValue(
|
.mockReturnValue(
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
ok: true,
|
ok: true,
|
||||||
signedUploadUrl: 'https://signed-upload-url.local'
|
signedUploadUrl: 'https://signed-upload-url.com'
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
jest
|
jest
|
||||||
.spyOn(ArtifactServiceClientJSON.prototype, 'FinalizeArtifact')
|
.spyOn(blobUpload, 'uploadZipToBlobStorage')
|
||||||
.mockReturnValue(
|
.mockReturnValue(Promise.reject(new Error('Upload progress stalled.')))
|
||||||
Promise.resolve({
|
|
||||||
ok: true,
|
// ArtifactHttpClient mocks
|
||||||
artifactId: '1'
|
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
|
||||||
})
|
|
||||||
)
|
|
||||||
jest
|
jest
|
||||||
.spyOn(config, 'getResultsServiceUrl')
|
.spyOn(config, 'getResultsServiceUrl')
|
||||||
.mockReturnValue('https://results.local')
|
.mockReturnValue('https://test-url.com')
|
||||||
|
|
||||||
jest.spyOn(config, 'getUploadChunkTimeout').mockReturnValue(2_000)
|
BlockBlobClient.prototype.uploadStream = jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation(
|
||||||
|
async (stream, bufferSize, maxConcurrency, options) => {
|
||||||
|
return new Promise<void>(resolve => {
|
||||||
|
// Call the onProgress callback with a progress event
|
||||||
|
options.onProgress({loadedBytes: 0})
|
||||||
|
|
||||||
uploadStreamMock.mockImplementation(
|
// Wait for 31 seconds before resolving the promise
|
||||||
async (
|
setTimeout(() => {
|
||||||
stream: NodeJS.ReadableStream,
|
// Call the onProgress callback again to simulate progress
|
||||||
bufferSize?: number,
|
options.onProgress({loadedBytes: 100})
|
||||||
maxConcurrency?: number,
|
|
||||||
options?: BlockBlobUploadStreamOptions
|
resolve()
|
||||||
) => {
|
}, 31000) // Delay longer than your timeout
|
||||||
const {onProgress, abortSignal} = options || {}
|
|
||||||
onProgress?.({loadedBytes: 0})
|
|
||||||
return new Promise(resolve => {
|
|
||||||
abortSignal?.addEventListener('abort', () => {
|
|
||||||
resolve({})
|
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
}
|
)
|
||||||
)
|
|
||||||
|
|
||||||
|
jest.mock('fs')
|
||||||
const uploadResp = uploadArtifact(
|
const uploadResp = uploadArtifact(
|
||||||
fixtures.inputs.artifactName,
|
'test-artifact',
|
||||||
fixtures.inputs.files,
|
[
|
||||||
fixtures.inputs.rootDirectory
|
'/home/user/files/plz-upload/file1.txt',
|
||||||
|
'/home/user/files/plz-upload/file2.txt',
|
||||||
|
'/home/user/files/plz-upload/dir/file3.txt'
|
||||||
|
],
|
||||||
|
'/home/user/files/plz-upload'
|
||||||
)
|
)
|
||||||
|
|
||||||
await expect(uploadResp).rejects.toThrow('Upload progress stalled.')
|
expect(uploadResp).rejects.toThrow('Upload progress stalled.')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -305,22 +305,4 @@ describe('Search', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Upload Specification - Includes symlinks', async () => {
|
|
||||||
const targetPath = path.join(root, 'link-dir', 'symlink-me.txt')
|
|
||||||
await fs.mkdir(path.dirname(targetPath), {recursive: true})
|
|
||||||
await fs.writeFile(targetPath, 'symlink file content')
|
|
||||||
|
|
||||||
const uploadPath = path.join(root, 'upload-dir', 'symlink.txt')
|
|
||||||
await fs.mkdir(path.dirname(uploadPath), {recursive: true})
|
|
||||||
await fs.symlink(targetPath, uploadPath, 'file')
|
|
||||||
|
|
||||||
const specifications = getUploadZipSpecification([uploadPath], root)
|
|
||||||
expect(specifications.length).toEqual(1)
|
|
||||||
expect(specifications[0].sourcePath).toEqual(uploadPath)
|
|
||||||
expect(specifications[0].destinationPath).toEqual(
|
|
||||||
path.join('/upload-dir', 'symlink.txt')
|
|
||||||
)
|
|
||||||
expect(specifications[0].stats.isSymbolicLink()).toBe(true)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
Generated
+12
-5
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/artifact",
|
"name": "@actions/artifact",
|
||||||
"version": "2.1.11",
|
"version": "2.1.7",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@actions/artifact",
|
"name": "@actions/artifact",
|
||||||
"version": "2.1.11",
|
"version": "2.1.7",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.10.0",
|
"@actions/core": "^1.10.0",
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
"@octokit/request-error": "^5.0.0",
|
"@octokit/request-error": "^5.0.0",
|
||||||
"@protobuf-ts/plugin": "^2.2.3-alpha.1",
|
"@protobuf-ts/plugin": "^2.2.3-alpha.1",
|
||||||
"archiver": "^7.0.1",
|
"archiver": "^7.0.1",
|
||||||
|
"crypto": "^1.0.1",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"twirp-ts": "^2.5.0",
|
"twirp-ts": "^2.5.0",
|
||||||
"unzip-stream": "^0.3.1"
|
"unzip-stream": "^0.3.1"
|
||||||
@@ -851,6 +852,12 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/crypto": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==",
|
||||||
|
"deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in."
|
||||||
|
},
|
||||||
"node_modules/delayed-stream": {
|
"node_modules/delayed-stream": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
@@ -1308,9 +1315,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/path-to-regexp": {
|
"node_modules/path-to-regexp": {
|
||||||
"version": "6.3.0",
|
"version": "6.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz",
|
||||||
"integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="
|
"integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw=="
|
||||||
},
|
},
|
||||||
"node_modules/prettier": {
|
"node_modules/prettier": {
|
||||||
"version": "2.8.8",
|
"version": "2.8.8",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/artifact",
|
"name": "@actions/artifact",
|
||||||
"version": "2.1.11",
|
"version": "2.1.7",
|
||||||
"preview": true,
|
"preview": true,
|
||||||
"description": "Actions artifact lib",
|
"description": "Actions artifact lib",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -50,6 +50,7 @@
|
|||||||
"@octokit/request-error": "^5.0.0",
|
"@octokit/request-error": "^5.0.0",
|
||||||
"@protobuf-ts/plugin": "^2.2.3-alpha.1",
|
"@protobuf-ts/plugin": "^2.2.3-alpha.1",
|
||||||
"archiver": "^7.0.1",
|
"archiver": "^7.0.1",
|
||||||
|
"crypto": "^1.0.1",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"twirp-ts": "^2.5.0",
|
"twirp-ts": "^2.5.0",
|
||||||
"unzip-stream": "^0.3.1"
|
"unzip-stream": "^0.3.1"
|
||||||
|
|||||||
@@ -30,10 +30,10 @@ export function isGhes(): boolean {
|
|||||||
|
|
||||||
const hostname = ghUrl.hostname.trimEnd().toUpperCase()
|
const hostname = ghUrl.hostname.trimEnd().toUpperCase()
|
||||||
const isGitHubHost = hostname === 'GITHUB.COM'
|
const isGitHubHost = hostname === 'GITHUB.COM'
|
||||||
const isGheHost = hostname.endsWith('.GHE.COM')
|
const isGheHost =
|
||||||
const isLocalHost = hostname.endsWith('.LOCALHOST')
|
hostname.endsWith('.GHE.COM') || hostname.endsWith('.GHE.LOCALHOST')
|
||||||
|
|
||||||
return !isGitHubHost && !isGheHost && !isLocalHost
|
return !isGitHubHost && !isGheHost
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getGitHubWorkspaceDir(): string {
|
export function getGitHubWorkspaceDir(): string {
|
||||||
@@ -57,7 +57,3 @@ export function getConcurrency(): number {
|
|||||||
const concurrency = 16 * numCPUs
|
const concurrency = 16 * numCPUs
|
||||||
return concurrency > 300 ? 300 : concurrency
|
return concurrency > 300 ? 300 : concurrency
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUploadChunkTimeout(): number {
|
|
||||||
return 300_000 // 5 minutes
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
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 {
|
import {getUploadChunkSize, getConcurrency} from '../shared/config'
|
||||||
getUploadChunkSize,
|
|
||||||
getConcurrency,
|
|
||||||
getUploadChunkTimeout
|
|
||||||
} 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'
|
||||||
@@ -29,26 +25,29 @@ export async function uploadZipToBlobStorage(
|
|||||||
): Promise<BlobUploadResponse> {
|
): Promise<BlobUploadResponse> {
|
||||||
let uploadByteCount = 0
|
let uploadByteCount = 0
|
||||||
let lastProgressTime = Date.now()
|
let lastProgressTime = Date.now()
|
||||||
const abortController = new AbortController()
|
let timeoutId: NodeJS.Timeout | undefined
|
||||||
|
|
||||||
const chunkTimer = async (interval: number): Promise<void> =>
|
const chunkTimer = (timeout: number): NodeJS.Timeout => {
|
||||||
new Promise((resolve, reject) => {
|
// clear the previous timeout
|
||||||
const timer = setInterval(() => {
|
if (timeoutId) {
|
||||||
if (Date.now() - lastProgressTime > interval) {
|
clearTimeout(timeoutId)
|
||||||
reject(new Error('Upload progress stalled.'))
|
}
|
||||||
}
|
|
||||||
}, interval)
|
|
||||||
|
|
||||||
abortController.signal.addEventListener('abort', () => {
|
|
||||||
clearInterval(timer)
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
const now = Date.now()
|
||||||
|
// if there's been more than 30 seconds since the
|
||||||
|
// last progress event, then we'll consider the upload stalled
|
||||||
|
if (now - lastProgressTime > timeout) {
|
||||||
|
throw new Error('Upload progress stalled.')
|
||||||
|
}
|
||||||
|
}, timeout)
|
||||||
|
return timeoutId
|
||||||
|
}
|
||||||
const maxConcurrency = getConcurrency()
|
const maxConcurrency = getConcurrency()
|
||||||
const bufferSize = getUploadChunkSize()
|
const bufferSize = getUploadChunkSize()
|
||||||
const blobClient = new BlobClient(authenticatedUploadURL)
|
const blobClient = new BlobClient(authenticatedUploadURL)
|
||||||
const blockBlobClient = blobClient.getBlockBlobClient()
|
const blockBlobClient = blobClient.getBlockBlobClient()
|
||||||
|
const timeoutDuration = 300000 // 30 seconds
|
||||||
|
|
||||||
core.debug(
|
core.debug(
|
||||||
`Uploading artifact zip to blob storage with maxConcurrency: ${maxConcurrency}, bufferSize: ${bufferSize}`
|
`Uploading artifact zip to blob storage with maxConcurrency: ${maxConcurrency}, bufferSize: ${bufferSize}`
|
||||||
@@ -57,13 +56,13 @@ export async function uploadZipToBlobStorage(
|
|||||||
const uploadCallback = (progress: TransferProgressEvent): void => {
|
const uploadCallback = (progress: TransferProgressEvent): void => {
|
||||||
core.info(`Uploaded bytes ${progress.loadedBytes}`)
|
core.info(`Uploaded bytes ${progress.loadedBytes}`)
|
||||||
uploadByteCount = progress.loadedBytes
|
uploadByteCount = progress.loadedBytes
|
||||||
|
chunkTimer(timeoutDuration)
|
||||||
lastProgressTime = Date.now()
|
lastProgressTime = Date.now()
|
||||||
}
|
}
|
||||||
|
|
||||||
const options: BlockBlobUploadStreamOptions = {
|
const options: BlockBlobUploadStreamOptions = {
|
||||||
blobHTTPHeaders: {blobContentType: 'zip'},
|
blobHTTPHeaders: {blobContentType: 'zip'},
|
||||||
onProgress: uploadCallback,
|
onProgress: uploadCallback
|
||||||
abortSignal: abortController.signal
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let sha256Hash: string | undefined = undefined
|
let sha256Hash: string | undefined = undefined
|
||||||
@@ -76,22 +75,24 @@ export async function uploadZipToBlobStorage(
|
|||||||
core.info('Beginning upload of artifact content to blob storage')
|
core.info('Beginning upload of artifact content to blob storage')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.race([
|
// Start the chunk timer
|
||||||
blockBlobClient.uploadStream(
|
timeoutId = chunkTimer(timeoutDuration)
|
||||||
uploadStream,
|
await blockBlobClient.uploadStream(
|
||||||
bufferSize,
|
uploadStream,
|
||||||
maxConcurrency,
|
bufferSize,
|
||||||
options
|
maxConcurrency,
|
||||||
),
|
options
|
||||||
chunkTimer(getUploadChunkTimeout())
|
)
|
||||||
])
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (NetworkError.isNetworkErrorCode(error?.code)) {
|
if (NetworkError.isNetworkErrorCode(error?.code)) {
|
||||||
throw new NetworkError(error?.code)
|
throw new NetworkError(error?.code)
|
||||||
}
|
}
|
||||||
throw error
|
throw error
|
||||||
} finally {
|
} finally {
|
||||||
abortController.abort()
|
// clear the timeout whether or not the upload completes
|
||||||
|
if (timeoutId) {
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
core.info('Finished uploading artifact content to blob storage!')
|
core.info('Finished uploading artifact content to blob storage!')
|
||||||
|
|||||||
@@ -13,12 +13,6 @@ export interface UploadZipSpecification {
|
|||||||
* The destination path in a zip for a file
|
* The destination path in a zip for a file
|
||||||
*/
|
*/
|
||||||
destinationPath: string
|
destinationPath: string
|
||||||
|
|
||||||
/**
|
|
||||||
* Information about the file
|
|
||||||
* https://nodejs.org/api/fs.html#class-fsstats
|
|
||||||
*/
|
|
||||||
stats: fs.Stats
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -81,11 +75,10 @@ export function getUploadZipSpecification(
|
|||||||
- file3.txt
|
- file3.txt
|
||||||
*/
|
*/
|
||||||
for (let file of filesToZip) {
|
for (let file of filesToZip) {
|
||||||
const stats = fs.lstatSync(file, {throwIfNoEntry: false})
|
if (!fs.existsSync(file)) {
|
||||||
if (!stats) {
|
|
||||||
throw new Error(`File ${file} does not exist`)
|
throw new Error(`File ${file} does not exist`)
|
||||||
}
|
}
|
||||||
if (!stats.isDirectory()) {
|
if (!fs.statSync(file).isDirectory()) {
|
||||||
// Normalize and resolve, this allows for either absolute or relative paths to be used
|
// Normalize and resolve, this allows for either absolute or relative paths to be used
|
||||||
file = normalize(file)
|
file = normalize(file)
|
||||||
file = resolve(file)
|
file = resolve(file)
|
||||||
@@ -101,8 +94,7 @@ export function getUploadZipSpecification(
|
|||||||
|
|
||||||
specification.push({
|
specification.push({
|
||||||
sourcePath: file,
|
sourcePath: file,
|
||||||
destinationPath: uploadPath,
|
destinationPath: uploadPath
|
||||||
stats
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// Empty directory
|
// Empty directory
|
||||||
@@ -111,8 +103,7 @@ export function getUploadZipSpecification(
|
|||||||
|
|
||||||
specification.push({
|
specification.push({
|
||||||
sourcePath: null,
|
sourcePath: null,
|
||||||
destinationPath: directoryPath,
|
destinationPath: directoryPath
|
||||||
stats
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as stream from 'stream'
|
import * as stream from 'stream'
|
||||||
import {realpath} from 'fs/promises'
|
|
||||||
import * as archiver from 'archiver'
|
import * as archiver from 'archiver'
|
||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
|
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'
|
||||||
|
|
||||||
@@ -43,14 +43,8 @@ export async function createZipUploadStream(
|
|||||||
|
|
||||||
for (const file of uploadSpecification) {
|
for (const file of uploadSpecification) {
|
||||||
if (file.sourcePath !== null) {
|
if (file.sourcePath !== null) {
|
||||||
// Check if symlink and resolve the source path
|
// Add a normal file to the zip
|
||||||
let sourcePath = file.sourcePath
|
zip.append(createReadStream(file.sourcePath), {
|
||||||
if (file.stats.isSymbolicLink()) {
|
|
||||||
sourcePath = await realpath(file.sourcePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the file to the zip
|
|
||||||
zip.file(sourcePath, {
|
|
||||||
name: file.destinationPath
|
name: file.destinationPath
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -63,8 +63,6 @@ export type AttestOptions = {
|
|||||||
// Sigstore instance to use for signing. Must be one of "public-good" or
|
// Sigstore instance to use for signing. Must be one of "public-good" or
|
||||||
// "github".
|
// "github".
|
||||||
sigstore?: 'public-good' | 'github'
|
sigstore?: 'public-good' | 'github'
|
||||||
// HTTP headers to include in request to attestations API.
|
|
||||||
headers?: {[header: string]: string | number | undefined}
|
|
||||||
// Whether to skip writing the attestation to the GH attestations API.
|
// Whether to skip writing the attestation to the GH attestations API.
|
||||||
skipWrite?: boolean
|
skipWrite?: boolean
|
||||||
}
|
}
|
||||||
@@ -115,8 +113,6 @@ export type AttestProvenanceOptions = {
|
|||||||
// Sigstore instance to use for signing. Must be one of "public-good" or
|
// Sigstore instance to use for signing. Must be one of "public-good" or
|
||||||
// "github".
|
// "github".
|
||||||
sigstore?: 'public-good' | 'github'
|
sigstore?: 'public-good' | 'github'
|
||||||
// HTTP headers to include in request to attestations API.
|
|
||||||
headers?: {[header: string]: string | number | undefined}
|
|
||||||
// Whether to skip writing the attestation to the GH attestations API.
|
// Whether to skip writing the attestation to the GH attestations API.
|
||||||
skipWrite?: boolean
|
skipWrite?: boolean
|
||||||
// Issuer URL responsible for minting the OIDC token from which the
|
// Issuer URL responsible for minting the OIDC token from which the
|
||||||
|
|||||||
@@ -1,42 +1,19 @@
|
|||||||
# @actions/attest Releases
|
# @actions/attest Releases
|
||||||
|
|
||||||
### 1.4.2
|
|
||||||
|
|
||||||
- Fix bug in `buildSLSAProvenancePredicate`/`attestProvenance` when generating provenance statement for enterprise account using customized OIDC issuer value [#1823](https://github.com/actions/toolkit/pull/1823)
|
|
||||||
### 1.4.1
|
|
||||||
|
|
||||||
- Bump @actions/http-client from 2.2.1 to 2.2.3 [#1805](https://github.com/actions/toolkit/pull/1805)
|
|
||||||
|
|
||||||
### 1.4.0
|
|
||||||
|
|
||||||
- Add new `headers` parameter to the `attest` and `attestProvenance` functions [#1790](https://github.com/actions/toolkit/pull/1790)
|
|
||||||
- Update `buildSLSAProvenancePredicate`/`attestProvenance` to automatically derive default OIDC issuer URL from current execution context [#1796](https://github.com/actions/toolkit/pull/1796)
|
|
||||||
### 1.3.1
|
|
||||||
|
|
||||||
- Fix bug with proxy support when retrieving JWKS for OIDC issuer [#1776](https://github.com/actions/toolkit/pull/1776)
|
|
||||||
|
|
||||||
### 1.3.0
|
|
||||||
|
|
||||||
- Dynamic construction of Sigstore API URLs [#1735](https://github.com/actions/toolkit/pull/1735)
|
|
||||||
- Switch to new GH provenance build type [#1745](https://github.com/actions/toolkit/pull/1745)
|
|
||||||
- Fetch existing Rekor entry on 409 conflict error [#1759](https://github.com/actions/toolkit/pull/1759)
|
|
||||||
- Bump @sigstore/bundle from 2.3.0 to 2.3.2 [#1738](https://github.com/actions/toolkit/pull/1738)
|
|
||||||
- Bump @sigstore/sign from 2.3.0 to 2.3.2 [#1738](https://github.com/actions/toolkit/pull/1738)
|
|
||||||
|
|
||||||
### 1.2.1
|
### 1.2.1
|
||||||
|
|
||||||
- Retry request on attestation persistence failure [#1725](https://github.com/actions/toolkit/pull/1725)
|
- Retry request on attestation persistence failure
|
||||||
|
|
||||||
### 1.2.0
|
### 1.2.0
|
||||||
|
|
||||||
- Generate attestations using the v0.3 Sigstore bundle format [#1701](https://github.com/actions/toolkit/pull/1701)
|
- Generate attestations using the v0.3 Sigstore bundle format.
|
||||||
- Bump @sigstore/bundle from 2.2.0 to 2.3.0 [#1701](https://github.com/actions/toolkit/pull/1701)
|
- Bump @sigstore/bundle from 2.2.0 to 2.3.0.
|
||||||
- Bump @sigstore/sign from 2.2.3 to 2.3.0 [#1701](https://github.com/actions/toolkit/pull/1701)
|
- Bump @sigstore/sign from 2.2.3 to 2.3.0.
|
||||||
- Remove dependency on make-fetch-happen [#1714](https://github.com/actions/toolkit/pull/1714)
|
- Remove dependency on make-fetch-happen
|
||||||
|
|
||||||
### 1.1.0
|
### 1.1.0
|
||||||
|
|
||||||
- Updates the `attestProvenance` function to retrieve a token from the GitHub OIDC provider and use the token claims to populate the provenance statement [#1693](https://github.com/actions/toolkit/pull/1693)
|
- Updates the `attestProvenance` function to retrieve a token from the GitHub OIDC provider and use the token claims to populate the provenance statement.
|
||||||
|
|
||||||
### 1.0.0
|
### 1.0.0
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ exports[`provenance functions buildSLSAProvenancePredicate returns a provenance
|
|||||||
{
|
{
|
||||||
"params": {
|
"params": {
|
||||||
"buildDefinition": {
|
"buildDefinition": {
|
||||||
"buildType": "https://actions.github.io/buildtypes/workflow/v1",
|
"buildType": "https://slsa-framework.github.io/github-actions-buildtypes/workflow/v1",
|
||||||
"externalParameters": {
|
"externalParameters": {
|
||||||
"workflow": {
|
"workflow": {
|
||||||
"path": ".github/workflows/main.yml",
|
"path": ".github/workflows/main.yml",
|
||||||
"ref": "main",
|
"ref": "main",
|
||||||
"repository": "https://foo.ghe.com/owner/repo",
|
"repository": "https://github.com/owner/repo",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"internalParameters": {
|
"internalParameters": {
|
||||||
@@ -17,7 +17,6 @@ exports[`provenance functions buildSLSAProvenancePredicate returns a provenance
|
|||||||
"event_name": "push",
|
"event_name": "push",
|
||||||
"repository_id": "repo-id",
|
"repository_id": "repo-id",
|
||||||
"repository_owner_id": "owner-id",
|
"repository_owner_id": "owner-id",
|
||||||
"runner_environment": "github-hosted",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"resolvedDependencies": [
|
"resolvedDependencies": [
|
||||||
@@ -25,16 +24,16 @@ exports[`provenance functions buildSLSAProvenancePredicate returns a provenance
|
|||||||
"digest": {
|
"digest": {
|
||||||
"gitCommit": "babca52ab0c93ae16539e5923cb0d7403b9a093b",
|
"gitCommit": "babca52ab0c93ae16539e5923cb0d7403b9a093b",
|
||||||
},
|
},
|
||||||
"uri": "git+https://foo.ghe.com/owner/repo@refs/heads/main",
|
"uri": "git+https://github.com/owner/repo@refs/heads/main",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"runDetails": {
|
"runDetails": {
|
||||||
"builder": {
|
"builder": {
|
||||||
"id": "https://foo.ghe.com/owner/workflows/.github/workflows/publish.yml@main",
|
"id": "https://github.com/actions/runner/github-hosted",
|
||||||
},
|
},
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"invocationId": "https://foo.ghe.com/owner/repo/actions/runs/run-id/attempts/run-attempt",
|
"invocationId": "https://github.com/owner/repo/actions/runs/run-id/attempts/run-attempt",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -45,8 +45,7 @@ describe('getIDTokenClaims', () => {
|
|||||||
sha: 'sha',
|
sha: 'sha',
|
||||||
repository: 'repo',
|
repository: 'repo',
|
||||||
event_name: 'push',
|
event_name: 'push',
|
||||||
job_workflow_ref: 'job_workflow_ref',
|
workflow_ref: 'main',
|
||||||
workflow_ref: 'workflow',
|
|
||||||
repository_id: '1',
|
repository_id: '1',
|
||||||
repository_owner_id: '1',
|
repository_owner_id: '1',
|
||||||
runner_environment: 'github-hosted',
|
runner_environment: 'github-hosted',
|
||||||
@@ -68,55 +67,6 @@ describe('getIDTokenClaims', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('when ID token is valid (w/ enterprise slug)', () => {
|
|
||||||
const claims = {
|
|
||||||
iss: `${issuer}/foo-bar`,
|
|
||||||
aud: audience,
|
|
||||||
ref: 'ref',
|
|
||||||
sha: 'sha',
|
|
||||||
repository: 'repo',
|
|
||||||
event_name: 'push',
|
|
||||||
job_workflow_ref: 'job_workflow_ref',
|
|
||||||
workflow_ref: 'workflow',
|
|
||||||
repository_id: '1',
|
|
||||||
repository_owner_id: '1',
|
|
||||||
runner_environment: 'github-hosted',
|
|
||||||
run_id: '1',
|
|
||||||
run_attempt: '1'
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const jwt = await new jose.SignJWT(claims)
|
|
||||||
.setProtectedHeader({alg: 'PS256'})
|
|
||||||
.sign(key.privateKey)
|
|
||||||
|
|
||||||
nock(issuer).get(tokenPath).query({audience}).reply(200, {value: jwt})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('returns the ID token claims', async () => {
|
|
||||||
const result = await getIDTokenClaims(issuer)
|
|
||||||
expect(result).toEqual(claims)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('when ID token is missing the "iss" claim', () => {
|
|
||||||
const claims = {
|
|
||||||
aud: audience
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const jwt = await new jose.SignJWT(claims)
|
|
||||||
.setProtectedHeader({alg: 'PS256'})
|
|
||||||
.sign(key.privateKey)
|
|
||||||
|
|
||||||
nock(issuer).get(tokenPath).query({audience}).reply(200, {value: jwt})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('throws an error', async () => {
|
|
||||||
await expect(getIDTokenClaims(issuer)).rejects.toThrow(/missing "iss"/i)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('when ID token is missing required claims', () => {
|
describe('when ID token is missing required claims', () => {
|
||||||
const claims = {
|
const claims = {
|
||||||
iss: issuer,
|
iss: issuer,
|
||||||
@@ -148,9 +98,7 @@ describe('getIDTokenClaims', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('throws an error', async () => {
|
it('throws an error', async () => {
|
||||||
await expect(getIDTokenClaims(issuer)).rejects.toThrow(
|
await expect(getIDTokenClaims(issuer)).rejects.toThrow(/issuer invalid/)
|
||||||
/unexpected "iss"/i
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -166,7 +114,7 @@ describe('getIDTokenClaims', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('throw an error', async () => {
|
it('throw an error', async () => {
|
||||||
await expect(getIDTokenClaims(issuer)).rejects.toThrow(/unexpected "aud"/)
|
await expect(getIDTokenClaims(issuer)).rejects.toThrow(/audience invalid/)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {attestProvenance, buildSLSAProvenancePredicate} from '../src/provenance'
|
|||||||
|
|
||||||
describe('provenance functions', () => {
|
describe('provenance functions', () => {
|
||||||
const originalEnv = process.env
|
const originalEnv = process.env
|
||||||
const issuer = 'https://token.actions.foo.ghe.com'
|
const issuer = 'https://example.com'
|
||||||
const audience = 'nobody'
|
const audience = 'nobody'
|
||||||
const jwksPath = '/.well-known/jwks.json'
|
const jwksPath = '/.well-known/jwks.json'
|
||||||
const tokenPath = '/token'
|
const tokenPath = '/token'
|
||||||
@@ -23,7 +23,6 @@ describe('provenance functions', () => {
|
|||||||
repository: 'owner/repo',
|
repository: 'owner/repo',
|
||||||
ref: 'refs/heads/main',
|
ref: 'refs/heads/main',
|
||||||
sha: 'babca52ab0c93ae16539e5923cb0d7403b9a093b',
|
sha: 'babca52ab0c93ae16539e5923cb0d7403b9a093b',
|
||||||
job_workflow_ref: 'owner/workflows/.github/workflows/publish.yml@main',
|
|
||||||
workflow_ref: 'owner/repo/.github/workflows/main.yml@main',
|
workflow_ref: 'owner/repo/.github/workflows/main.yml@main',
|
||||||
event_name: 'push',
|
event_name: 'push',
|
||||||
repository_id: 'repo-id',
|
repository_id: 'repo-id',
|
||||||
@@ -38,7 +37,7 @@ describe('provenance functions', () => {
|
|||||||
...originalEnv,
|
...originalEnv,
|
||||||
ACTIONS_ID_TOKEN_REQUEST_URL: `${issuer}${tokenPath}?`,
|
ACTIONS_ID_TOKEN_REQUEST_URL: `${issuer}${tokenPath}?`,
|
||||||
ACTIONS_ID_TOKEN_REQUEST_TOKEN: 'token',
|
ACTIONS_ID_TOKEN_REQUEST_TOKEN: 'token',
|
||||||
GITHUB_SERVER_URL: 'https://foo.ghe.com',
|
GITHUB_SERVER_URL: 'https://github.com',
|
||||||
GITHUB_REPOSITORY: claims.repository
|
GITHUB_REPOSITORY: claims.repository
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +67,7 @@ describe('provenance functions', () => {
|
|||||||
|
|
||||||
describe('buildSLSAProvenancePredicate', () => {
|
describe('buildSLSAProvenancePredicate', () => {
|
||||||
it('returns a provenance hydrated from an OIDC token', async () => {
|
it('returns a provenance hydrated from an OIDC token', async () => {
|
||||||
const predicate = await buildSLSAProvenancePredicate()
|
const predicate = await buildSLSAProvenancePredicate(issuer)
|
||||||
expect(predicate).toMatchSnapshot()
|
expect(predicate).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -96,9 +95,9 @@ describe('provenance functions', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('when using the github Sigstore instance', () => {
|
describe('when using the github Sigstore instance', () => {
|
||||||
beforeEach(async () => {
|
const {fulcioURL, tsaServerURL} = signingEndpoints('github')
|
||||||
const {fulcioURL, tsaServerURL} = signingEndpoints('github')
|
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
// Mock Sigstore
|
// Mock Sigstore
|
||||||
await mockFulcio({baseURL: fulcioURL, strict: false})
|
await mockFulcio({baseURL: fulcioURL, strict: false})
|
||||||
await mockTSA({baseURL: tsaServerURL})
|
await mockTSA({baseURL: tsaServerURL})
|
||||||
@@ -118,7 +117,8 @@ describe('provenance functions', () => {
|
|||||||
subjectName,
|
subjectName,
|
||||||
subjectDigest,
|
subjectDigest,
|
||||||
token: 'token',
|
token: 'token',
|
||||||
sigstore: 'github'
|
sigstore: 'github',
|
||||||
|
issuer
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(attestation).toBeDefined()
|
expect(attestation).toBeDefined()
|
||||||
@@ -145,7 +145,8 @@ describe('provenance functions', () => {
|
|||||||
const attestation = await attestProvenance({
|
const attestation = await attestProvenance({
|
||||||
subjectName,
|
subjectName,
|
||||||
subjectDigest,
|
subjectDigest,
|
||||||
token: 'token'
|
token: 'token',
|
||||||
|
issuer
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(attestation).toBeDefined()
|
expect(attestation).toBeDefined()
|
||||||
@@ -181,7 +182,8 @@ describe('provenance functions', () => {
|
|||||||
subjectName,
|
subjectName,
|
||||||
subjectDigest,
|
subjectDigest,
|
||||||
token: 'token',
|
token: 'token',
|
||||||
sigstore: 'public-good'
|
sigstore: 'public-good',
|
||||||
|
issuer
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(attestation).toBeDefined()
|
expect(attestation).toBeDefined()
|
||||||
@@ -208,7 +210,8 @@ describe('provenance functions', () => {
|
|||||||
const attestation = await attestProvenance({
|
const attestation = await attestProvenance({
|
||||||
subjectName,
|
subjectName,
|
||||||
subjectDigest,
|
subjectDigest,
|
||||||
token: 'token'
|
token: 'token',
|
||||||
|
issuer
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(attestation).toBeDefined()
|
expect(attestation).toBeDefined()
|
||||||
@@ -234,7 +237,8 @@ describe('provenance functions', () => {
|
|||||||
subjectDigest,
|
subjectDigest,
|
||||||
token: 'token',
|
token: 'token',
|
||||||
sigstore: 'public-good',
|
sigstore: 'public-good',
|
||||||
skipWrite: true
|
skipWrite: true,
|
||||||
|
issuer
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(attestation).toBeDefined()
|
expect(attestation).toBeDefined()
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ describe('writeAttestation', () => {
|
|||||||
const originalEnv = process.env
|
const originalEnv = process.env
|
||||||
const attestation = {foo: 'bar '}
|
const attestation = {foo: 'bar '}
|
||||||
const token = 'token'
|
const token = 'token'
|
||||||
const headers = {'X-GitHub-Foo': 'true'}
|
|
||||||
|
|
||||||
const mockAgent = new MockAgent()
|
const mockAgent = new MockAgent()
|
||||||
setGlobalDispatcher(mockAgent)
|
setGlobalDispatcher(mockAgent)
|
||||||
@@ -28,16 +27,14 @@ describe('writeAttestation', () => {
|
|||||||
.intercept({
|
.intercept({
|
||||||
path: '/repos/foo/bar/attestations',
|
path: '/repos/foo/bar/attestations',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {authorization: `token ${token}`, ...headers},
|
headers: {authorization: `token ${token}`},
|
||||||
body: JSON.stringify({bundle: attestation})
|
body: JSON.stringify({bundle: attestation})
|
||||||
})
|
})
|
||||||
.reply(201, {id: '123'})
|
.reply(201, {id: '123'})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('persists the attestation', async () => {
|
it('persists the attestation', async () => {
|
||||||
await expect(
|
await expect(writeAttestation(attestation, token)).resolves.toEqual('123')
|
||||||
writeAttestation(attestation, token, {headers})
|
|
||||||
).resolves.toEqual('123')
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Generated
+671
-218
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/attest",
|
"name": "@actions/attest",
|
||||||
"version": "1.4.2",
|
"version": "1.2.1",
|
||||||
"description": "Actions attestation lib",
|
"description": "Actions attestation lib",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"github",
|
"github",
|
||||||
@@ -35,20 +35,22 @@
|
|||||||
"url": "https://github.com/actions/toolkit/issues"
|
"url": "https://github.com/actions/toolkit/issues"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sigstore/mock": "^0.7.4",
|
"@sigstore/mock": "^0.6.5",
|
||||||
"@sigstore/rekor-types": "^2.0.0",
|
"@sigstore/rekor-types": "^2.0.0",
|
||||||
"@types/jsonwebtoken": "^9.0.6",
|
"@types/jsonwebtoken": "^9.0.6",
|
||||||
|
"jose": "^5.2.3",
|
||||||
"nock": "^13.5.1",
|
"nock": "^13.5.1",
|
||||||
"undici": "^5.28.4"
|
"undici": "^5.28.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.10.1",
|
"@actions/core": "^1.10.1",
|
||||||
"@actions/github": "^6.0.0",
|
"@actions/github": "^6.0.0",
|
||||||
"@actions/http-client": "^2.2.3",
|
"@actions/http-client": "^2.2.1",
|
||||||
"@octokit/plugin-retry": "^6.0.1",
|
"@octokit/plugin-retry": "^6.0.1",
|
||||||
"@sigstore/bundle": "^2.3.2",
|
"@sigstore/bundle": "^2.3.0",
|
||||||
"@sigstore/sign": "^2.3.2",
|
"@sigstore/sign": "^2.3.0",
|
||||||
"jose": "^5.2.3"
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"jwks-rsa": "^3.1.0"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"@octokit/plugin-retry": {
|
"@octokit/plugin-retry": {
|
||||||
|
|||||||
@@ -28,8 +28,6 @@ export type AttestOptions = {
|
|||||||
// Sigstore instance to use for signing. Must be one of "public-good" or
|
// Sigstore instance to use for signing. Must be one of "public-good" or
|
||||||
// "github".
|
// "github".
|
||||||
sigstore?: SigstoreInstance
|
sigstore?: SigstoreInstance
|
||||||
// HTTP headers to include in request to attestations API.
|
|
||||||
headers?: {[header: string]: string | number | undefined}
|
|
||||||
// Whether to skip writing the attestation to the GH attestations API.
|
// Whether to skip writing the attestation to the GH attestations API.
|
||||||
skipWrite?: boolean
|
skipWrite?: boolean
|
||||||
}
|
}
|
||||||
@@ -63,11 +61,7 @@ export async function attest(options: AttestOptions): Promise<Attestation> {
|
|||||||
// Store the attestation
|
// Store the attestation
|
||||||
let attestationID: string | undefined
|
let attestationID: string | undefined
|
||||||
if (options.skipWrite !== true) {
|
if (options.skipWrite !== true) {
|
||||||
attestationID = await writeAttestation(
|
attestationID = await writeAttestation(bundleToJSON(bundle), options.token)
|
||||||
bundleToJSON(bundle),
|
|
||||||
options.token,
|
|
||||||
{headers: options.headers}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return toAttestation(bundle, attestationID)
|
return toAttestation(bundle, attestationID)
|
||||||
|
|||||||
+46
-61
@@ -1,21 +1,16 @@
|
|||||||
import {getIDToken} from '@actions/core'
|
import {getIDToken} from '@actions/core'
|
||||||
import {HttpClient} from '@actions/http-client'
|
import {HttpClient} from '@actions/http-client'
|
||||||
import * as jose from 'jose'
|
import * as jwt from 'jsonwebtoken'
|
||||||
|
import jwks from 'jwks-rsa'
|
||||||
|
|
||||||
const OIDC_AUDIENCE = 'nobody'
|
const OIDC_AUDIENCE = 'nobody'
|
||||||
|
|
||||||
const VALID_SERVER_URLS = [
|
|
||||||
'https://github.com',
|
|
||||||
new RegExp('^https://[a-z0-9-]+\\.ghe\\.com$')
|
|
||||||
] as const
|
|
||||||
|
|
||||||
const REQUIRED_CLAIMS = [
|
const REQUIRED_CLAIMS = [
|
||||||
'iss',
|
'iss',
|
||||||
'ref',
|
'ref',
|
||||||
'sha',
|
'sha',
|
||||||
'repository',
|
'repository',
|
||||||
'event_name',
|
'event_name',
|
||||||
'job_workflow_ref',
|
|
||||||
'workflow_ref',
|
'workflow_ref',
|
||||||
'repository_id',
|
'repository_id',
|
||||||
'repository_owner_id',
|
'repository_owner_id',
|
||||||
@@ -30,8 +25,7 @@ type OIDCConfig = {
|
|||||||
jwks_uri: string
|
jwks_uri: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getIDTokenClaims = async (issuer?: string): Promise<ClaimSet> => {
|
export const getIDTokenClaims = async (issuer: string): Promise<ClaimSet> => {
|
||||||
issuer = issuer || getIssuer()
|
|
||||||
try {
|
try {
|
||||||
const token = await getIDToken(OIDC_AUDIENCE)
|
const token = await getIDToken(OIDC_AUDIENCE)
|
||||||
const claims = await decodeOIDCToken(token, issuer)
|
const claims = await decodeOIDCToken(token, issuer)
|
||||||
@@ -45,46 +39,55 @@ export const getIDTokenClaims = async (issuer?: string): Promise<ClaimSet> => {
|
|||||||
const decodeOIDCToken = async (
|
const decodeOIDCToken = async (
|
||||||
token: string,
|
token: string,
|
||||||
issuer: string
|
issuer: string
|
||||||
): Promise<jose.JWTPayload> => {
|
): Promise<jwt.JwtPayload> => {
|
||||||
// Verify and decode token
|
// Verify and decode token
|
||||||
const jwks = jose.createLocalJWKSet(await getJWKS(issuer))
|
return new Promise((resolve, reject) => {
|
||||||
const {payload} = await jose.jwtVerify(token, jwks, {
|
jwt.verify(
|
||||||
audience: OIDC_AUDIENCE
|
token,
|
||||||
|
getPublicKey(issuer),
|
||||||
|
{audience: OIDC_AUDIENCE, issuer},
|
||||||
|
(err, decoded) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err)
|
||||||
|
} else if (!decoded || typeof decoded === 'string') {
|
||||||
|
reject(new Error('No decoded token'))
|
||||||
|
} else {
|
||||||
|
resolve(decoded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!payload.iss) {
|
|
||||||
throw new Error('Missing "iss" claim')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that the issuer STARTS WITH the expected issuer URL to account for
|
|
||||||
// the fact that the value may include an enterprise-specific slug
|
|
||||||
if (!payload.iss.startsWith(issuer)) {
|
|
||||||
throw new Error(`Unexpected "iss" claim: ${payload.iss}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return payload
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getJWKS = async (issuer: string): Promise<jose.JSONWebKeySet> => {
|
// Returns a callback to locate the public key for the given JWT header. This
|
||||||
const client = new HttpClient('@actions/attest')
|
// involves two calls:
|
||||||
const config = await client.getJson<OIDCConfig>(
|
// 1. Fetch the OpenID configuration to get the JWKS URI.
|
||||||
`${issuer}/.well-known/openid-configuration`
|
// 2. Fetch the public key from the JWKS URI.
|
||||||
)
|
const getPublicKey =
|
||||||
|
(issuer: string): jwt.GetPublicKeyOrSecret =>
|
||||||
if (!config.result) {
|
(header: jwt.JwtHeader, callback: jwt.SigningKeyCallback) => {
|
||||||
throw new Error('No OpenID configuration found')
|
// Look up the JWKS URI from the issuer's OpenID configuration
|
||||||
|
new HttpClient('actions/attest')
|
||||||
|
.getJson<OIDCConfig>(`${issuer}/.well-known/openid-configuration`)
|
||||||
|
.then(data => {
|
||||||
|
if (!data.result) {
|
||||||
|
callback(new Error('No OpenID configuration found'))
|
||||||
|
} else {
|
||||||
|
// Fetch the public key from the JWKS URI
|
||||||
|
jwks({jwksUri: data.result.jwks_uri}).getSigningKey(
|
||||||
|
header.kid,
|
||||||
|
(err, key) => {
|
||||||
|
callback(err, key?.getPublicKey())
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
callback(err)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const jwks = await client.getJson<jose.JSONWebKeySet>(config.result.jwks_uri)
|
function assertClaimSet(claims: jwt.JwtPayload): asserts claims is ClaimSet {
|
||||||
|
|
||||||
if (!jwks.result) {
|
|
||||||
throw new Error('No JWKS found for issuer')
|
|
||||||
}
|
|
||||||
|
|
||||||
return jwks.result
|
|
||||||
}
|
|
||||||
|
|
||||||
function assertClaimSet(claims: jose.JWTPayload): asserts claims is ClaimSet {
|
|
||||||
const missingClaims: string[] = []
|
const missingClaims: string[] = []
|
||||||
|
|
||||||
for (const claim of REQUIRED_CLAIMS) {
|
for (const claim of REQUIRED_CLAIMS) {
|
||||||
@@ -97,21 +100,3 @@ function assertClaimSet(claims: jose.JWTPayload): asserts claims is ClaimSet {
|
|||||||
throw new Error(`Missing claims: ${missingClaims.join(', ')}`)
|
throw new Error(`Missing claims: ${missingClaims.join(', ')}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Derive the current OIDC issuer based on the server URL
|
|
||||||
function getIssuer(): string {
|
|
||||||
const serverURL = process.env.GITHUB_SERVER_URL || 'https://github.com'
|
|
||||||
|
|
||||||
// Ensure the server URL is a valid GitHub server URL
|
|
||||||
if (!VALID_SERVER_URLS.some(valid_url => serverURL.match(valid_url))) {
|
|
||||||
throw new Error(`Invalid server URL: ${serverURL}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
let host = new URL(serverURL).hostname
|
|
||||||
|
|
||||||
if (host === 'github.com') {
|
|
||||||
host = 'githubusercontent.com'
|
|
||||||
}
|
|
||||||
|
|
||||||
return `https://token.actions.${host}`
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,7 +3,12 @@ import {getIDTokenClaims} from './oidc'
|
|||||||
import type {Attestation, Predicate} from './shared.types'
|
import type {Attestation, Predicate} from './shared.types'
|
||||||
|
|
||||||
const SLSA_PREDICATE_V1_TYPE = 'https://slsa.dev/provenance/v1'
|
const SLSA_PREDICATE_V1_TYPE = 'https://slsa.dev/provenance/v1'
|
||||||
const GITHUB_BUILD_TYPE = 'https://actions.github.io/buildtypes/workflow/v1'
|
|
||||||
|
const GITHUB_BUILDER_ID_PREFIX = 'https://github.com/actions/runner'
|
||||||
|
const GITHUB_BUILD_TYPE =
|
||||||
|
'https://slsa-framework.github.io/github-actions-buildtypes/workflow/v1'
|
||||||
|
|
||||||
|
const DEFAULT_ISSUER = 'https://token.actions.githubusercontent.com'
|
||||||
|
|
||||||
export type AttestProvenanceOptions = Omit<
|
export type AttestProvenanceOptions = Omit<
|
||||||
AttestOptions,
|
AttestOptions,
|
||||||
@@ -22,7 +27,7 @@ export type AttestProvenanceOptions = Omit<
|
|||||||
* @returns The SLSA provenance predicate.
|
* @returns The SLSA provenance predicate.
|
||||||
*/
|
*/
|
||||||
export const buildSLSAProvenancePredicate = async (
|
export const buildSLSAProvenancePredicate = async (
|
||||||
issuer?: string
|
issuer: string = DEFAULT_ISSUER
|
||||||
): Promise<Predicate> => {
|
): Promise<Predicate> => {
|
||||||
const serverURL = process.env.GITHUB_SERVER_URL
|
const serverURL = process.env.GITHUB_SERVER_URL
|
||||||
const claims = await getIDTokenClaims(issuer)
|
const claims = await getIDTokenClaims(issuer)
|
||||||
@@ -50,8 +55,7 @@ export const buildSLSAProvenancePredicate = async (
|
|||||||
github: {
|
github: {
|
||||||
event_name: claims.event_name,
|
event_name: claims.event_name,
|
||||||
repository_id: claims.repository_id,
|
repository_id: claims.repository_id,
|
||||||
repository_owner_id: claims.repository_owner_id,
|
repository_owner_id: claims.repository_owner_id
|
||||||
runner_environment: claims.runner_environment
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
resolvedDependencies: [
|
resolvedDependencies: [
|
||||||
@@ -65,7 +69,7 @@ export const buildSLSAProvenancePredicate = async (
|
|||||||
},
|
},
|
||||||
runDetails: {
|
runDetails: {
|
||||||
builder: {
|
builder: {
|
||||||
id: `${serverURL}/${claims.job_workflow_ref}`
|
id: `${GITHUB_BUILDER_ID_PREFIX}/${claims.runner_environment}`
|
||||||
},
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
invocationId: `${serverURL}/${claims.repository}/actions/runs/${claims.run_id}/attempts/${claims.run_attempt}`
|
invocationId: `${serverURL}/${claims.repository}/actions/runs/${claims.run_id}/attempts/${claims.run_attempt}`
|
||||||
|
|||||||
@@ -87,7 +87,6 @@ const initBundleBuilder = (opts: SignOptions): BundleBuilder => {
|
|||||||
new RekorWitness({
|
new RekorWitness({
|
||||||
rekorBaseURL: opts.rekorURL,
|
rekorBaseURL: opts.rekorURL,
|
||||||
entryType: 'dsse',
|
entryType: 'dsse',
|
||||||
fetchOnConflict: true,
|
|
||||||
timeout,
|
timeout,
|
||||||
retry
|
retry
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import * as github from '@actions/github'
|
import * as github from '@actions/github'
|
||||||
import {retry} from '@octokit/plugin-retry'
|
import {retry} from '@octokit/plugin-retry'
|
||||||
import {RequestHeaders} from '@octokit/types'
|
|
||||||
|
|
||||||
const CREATE_ATTESTATION_REQUEST = 'POST /repos/{owner}/{repo}/attestations'
|
const CREATE_ATTESTATION_REQUEST = 'POST /repos/{owner}/{repo}/attestations'
|
||||||
const DEFAULT_RETRY_COUNT = 5
|
const DEFAULT_RETRY_COUNT = 5
|
||||||
|
|
||||||
export type WriteOptions = {
|
export type WriteOptions = {
|
||||||
retry?: number
|
retry?: number
|
||||||
headers?: RequestHeaders
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Writes an attestation to the repository's attestations endpoint.
|
* Writes an attestation to the repository's attestations endpoint.
|
||||||
@@ -28,7 +26,6 @@ export const writeAttestation = async (
|
|||||||
const response = await octokit.request(CREATE_ATTESTATION_REQUEST, {
|
const response = await octokit.request(CREATE_ATTESTATION_REQUEST, {
|
||||||
owner: github.context.repo.owner,
|
owner: github.context.repo.owner,
|
||||||
repo: github.context.repo.repo,
|
repo: github.context.repo.repo,
|
||||||
headers: options.headers,
|
|
||||||
data: {bundle: attestation}
|
data: {bundle: attestation}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
+75
@@ -2,10 +2,14 @@ import * as core from '@actions/core'
|
|||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import {saveCache} from '../src/cache'
|
import {saveCache} from '../src/cache'
|
||||||
import * as cacheHttpClient from '../src/internal/cacheHttpClient'
|
import * as cacheHttpClient from '../src/internal/cacheHttpClient'
|
||||||
|
import * as cacheTwirpClient from '../src/internal/cacheTwirpClient'
|
||||||
|
import {GetCacheBlobUploadURLResponse} from '../src/generated/results/api/v1/blobcache'
|
||||||
|
import {BlobCacheServiceClientJSON} from '../src/generated/results/api/v1/blobcache.twirp'
|
||||||
import * as cacheUtils from '../src/internal/cacheUtils'
|
import * as cacheUtils from '../src/internal/cacheUtils'
|
||||||
import {CacheFilename, CompressionMethod} from '../src/internal/constants'
|
import {CacheFilename, CompressionMethod} from '../src/internal/constants'
|
||||||
import * as tar from '../src/internal/tar'
|
import * as tar from '../src/internal/tar'
|
||||||
import {TypedResponse} from '@actions/http-client/lib/interfaces'
|
import {TypedResponse} from '@actions/http-client/lib/interfaces'
|
||||||
|
import * as uploadCache from '../src/internal/v2/upload-cache'
|
||||||
import {
|
import {
|
||||||
ReserveCacheResponse,
|
ReserveCacheResponse,
|
||||||
ITypedResponseWithError
|
ITypedResponseWithError
|
||||||
@@ -327,3 +331,74 @@ test('save with non existing path should not save cache', async () => {
|
|||||||
`Path Validation Error: Path(s) specified in the action for caching do(es) not exist, hence no cache is being saved.`
|
`Path Validation Error: Path(s) specified in the action for caching do(es) not exist, hence no cache is being saved.`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('throwaway test', async () => {
|
||||||
|
const filePath = 'node_modules'
|
||||||
|
const primaryKey = 'Linux-node-bb828da54c148048dd17899ba9fda624811cfb43'
|
||||||
|
const cachePaths = [path.resolve(filePath)]
|
||||||
|
|
||||||
|
const cacheSignedURL = 'https://container.blob.core.windows.net/cache/${primaryKey}?sig=1234'
|
||||||
|
const getCacheBlobUploadURL: GetCacheBlobUploadURLResponse = {
|
||||||
|
urls: [
|
||||||
|
{
|
||||||
|
key: primaryKey,
|
||||||
|
url: cacheSignedURL,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const cacheId = 4
|
||||||
|
const reserveCacheMock = jest
|
||||||
|
.spyOn(cacheHttpClient, 'reserveCache')
|
||||||
|
.mockImplementation(async () => {
|
||||||
|
const response: TypedResponse<ReserveCacheResponse> = {
|
||||||
|
statusCode: 500,
|
||||||
|
result: {cacheId},
|
||||||
|
headers: {}
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
})
|
||||||
|
|
||||||
|
const getCacheBlobUploadURLMock = jest
|
||||||
|
.spyOn(BlobCacheServiceClientJSON.prototype, 'GetCacheBlobUploadURL')
|
||||||
|
.mockResolvedValue(getCacheBlobUploadURL)
|
||||||
|
|
||||||
|
const uploadCacheMock = jest
|
||||||
|
.spyOn(uploadCache, 'UploadCacheFile')
|
||||||
|
.mockImplementation(async () => {
|
||||||
|
return {
|
||||||
|
status: 200
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const createTarMock = jest.spyOn(tar, 'createTar')
|
||||||
|
|
||||||
|
const saveCacheMock = jest.spyOn(cacheHttpClient, 'saveCache')
|
||||||
|
const compression = CompressionMethod.Zstd
|
||||||
|
const getCompressionMock = jest
|
||||||
|
.spyOn(cacheUtils, 'getCompressionMethod')
|
||||||
|
.mockReturnValue(Promise.resolve(compression))
|
||||||
|
|
||||||
|
await uploadCache.UploadCacheFile(getCacheBlobUploadURL, cachePaths[0])
|
||||||
|
await saveCache([filePath], primaryKey)
|
||||||
|
|
||||||
|
expect(reserveCacheMock).toHaveBeenCalledTimes(1)
|
||||||
|
expect(reserveCacheMock).toHaveBeenCalledWith(primaryKey, [filePath], {
|
||||||
|
cacheSize: undefined,
|
||||||
|
compressionMethod: compression,
|
||||||
|
enableCrossOsArchive: false
|
||||||
|
})
|
||||||
|
expect (getCacheBlobUploadURLMock).toHaveBeenCalledTimes(1)
|
||||||
|
const archiveFolder = '/foo/bar'
|
||||||
|
const archiveFile = path.join(archiveFolder, CacheFilename.Zstd)
|
||||||
|
expect(createTarMock).toHaveBeenCalledTimes(1)
|
||||||
|
expect(createTarMock).toHaveBeenCalledWith(
|
||||||
|
archiveFolder,
|
||||||
|
cachePaths,
|
||||||
|
compression
|
||||||
|
)
|
||||||
|
expect(uploadCacheMock).toHaveBeenCalledTimes(2)
|
||||||
|
expect(saveCacheMock).toHaveBeenCalledTimes(1)
|
||||||
|
expect(saveCacheMock).toHaveBeenCalledWith(cacheId, archiveFile, undefined)
|
||||||
|
expect(getCompressionMock).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
+2386
-1
File diff suppressed because it is too large
Load Diff
Vendored
+4
-1
@@ -38,6 +38,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.10.0",
|
"@actions/core": "^1.10.0",
|
||||||
|
"@actions/artifact": "^2.1.7",
|
||||||
"@actions/exec": "^1.0.1",
|
"@actions/exec": "^1.0.1",
|
||||||
"@actions/glob": "^0.1.0",
|
"@actions/glob": "^0.1.0",
|
||||||
"@actions/http-client": "^2.1.1",
|
"@actions/http-client": "^2.1.1",
|
||||||
@@ -45,10 +46,12 @@
|
|||||||
"@azure/abort-controller": "^1.1.0",
|
"@azure/abort-controller": "^1.1.0",
|
||||||
"@azure/ms-rest-js": "^2.6.0",
|
"@azure/ms-rest-js": "^2.6.0",
|
||||||
"@azure/storage-blob": "^12.13.0",
|
"@azure/storage-blob": "^12.13.0",
|
||||||
"semver": "^6.3.1"
|
"semver": "^6.3.1",
|
||||||
|
"uuid": "^3.3.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/semver": "^6.0.0",
|
"@types/semver": "^6.0.0",
|
||||||
|
"@types/uuid": "^3.4.5",
|
||||||
"typescript": "^5.2.2"
|
"typescript": "^5.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+150
-1
@@ -1,9 +1,25 @@
|
|||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import * as utils from './internal/cacheUtils'
|
import * as utils from './internal/cacheUtils'
|
||||||
|
import {CacheServiceVersion, CacheUrl} from './internal/constants'
|
||||||
import * as cacheHttpClient from './internal/cacheHttpClient'
|
import * as cacheHttpClient from './internal/cacheHttpClient'
|
||||||
|
import * as cacheTwirpClient from './internal/cacheTwirpClient'
|
||||||
import {createTar, extractTar, listTar} from './internal/tar'
|
import {createTar, extractTar, listTar} from './internal/tar'
|
||||||
import {DownloadOptions, UploadOptions} from './options'
|
import {DownloadOptions, UploadOptions} from './options'
|
||||||
|
import {
|
||||||
|
GetCacheBlobUploadURLRequest,
|
||||||
|
GetCacheBlobUploadURLResponse,
|
||||||
|
GetCachedBlobRequest,
|
||||||
|
GetCachedBlobResponse
|
||||||
|
} from './generated/results/api/v1/blobcache'
|
||||||
|
import {UploadCacheStream} from './internal/v2/upload-cache'
|
||||||
|
import {StreamExtract} from './internal/v2/download-cache'
|
||||||
|
import {
|
||||||
|
UploadZipSpecification,
|
||||||
|
getUploadZipSpecification
|
||||||
|
} from '@actions/artifact/lib/internal/upload/upload-zip-specification'
|
||||||
|
import {createZipUploadStream} from '@actions/artifact/lib/internal/upload/zip'
|
||||||
|
import {getBackendIdsFromToken, BackendIds} from '@actions/artifact/lib/internal/shared/util'
|
||||||
|
|
||||||
export class ValidationError extends Error {
|
export class ValidationError extends Error {
|
||||||
constructor(message: string) {
|
constructor(message: string) {
|
||||||
@@ -50,7 +66,7 @@ function checkKey(key: string): void {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export function isFeatureAvailable(): boolean {
|
export function isFeatureAvailable(): boolean {
|
||||||
return !!process.env['ACTIONS_CACHE_URL']
|
return !!CacheUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -72,6 +88,23 @@ export async function restoreCache(
|
|||||||
): Promise<string | undefined> {
|
): Promise<string | undefined> {
|
||||||
checkPaths(paths)
|
checkPaths(paths)
|
||||||
|
|
||||||
|
console.debug(`Cache Service Version: ${CacheServiceVersion}`)
|
||||||
|
switch (CacheServiceVersion) {
|
||||||
|
case "v2":
|
||||||
|
return await restoreCachev2(paths, primaryKey, restoreKeys, options, enableCrossOsArchive)
|
||||||
|
case "v1":
|
||||||
|
default:
|
||||||
|
return await restoreCachev1(paths, primaryKey, restoreKeys, options, enableCrossOsArchive)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function restoreCachev1(
|
||||||
|
paths: string[],
|
||||||
|
primaryKey: string,
|
||||||
|
restoreKeys?: string[],
|
||||||
|
options?: DownloadOptions,
|
||||||
|
enableCrossOsArchive = false
|
||||||
|
) {
|
||||||
restoreKeys = restoreKeys || []
|
restoreKeys = restoreKeys || []
|
||||||
const keys = [primaryKey, ...restoreKeys]
|
const keys = [primaryKey, ...restoreKeys]
|
||||||
|
|
||||||
@@ -153,6 +186,58 @@ export async function restoreCache(
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function restoreCachev2(
|
||||||
|
paths: string[],
|
||||||
|
primaryKey: string,
|
||||||
|
restoreKeys?: string[],
|
||||||
|
options?: DownloadOptions,
|
||||||
|
enableCrossOsArchive = false
|
||||||
|
) {
|
||||||
|
|
||||||
|
restoreKeys = restoreKeys || []
|
||||||
|
const keys = [primaryKey, ...restoreKeys]
|
||||||
|
|
||||||
|
core.debug('Resolved Keys:')
|
||||||
|
core.debug(JSON.stringify(keys))
|
||||||
|
|
||||||
|
if (keys.length > 10) {
|
||||||
|
throw new ValidationError(
|
||||||
|
`Key Validation Error: Keys are limited to a maximum of 10.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
for (const key of keys) {
|
||||||
|
checkKey(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// BackendIds are retrieved form the signed JWT
|
||||||
|
const backendIds: BackendIds = getBackendIdsFromToken()
|
||||||
|
const twirpClient = cacheTwirpClient.internalBlobCacheTwirpClient()
|
||||||
|
const getSignedDownloadURLRequest: GetCachedBlobRequest = {
|
||||||
|
workflowRunBackendId: backendIds.workflowRunBackendId,
|
||||||
|
workflowJobRunBackendId: backendIds.workflowJobRunBackendId,
|
||||||
|
keys: keys,
|
||||||
|
}
|
||||||
|
const signedDownloadURL: GetCachedBlobResponse = await twirpClient.GetCachedBlob(getSignedDownloadURLRequest)
|
||||||
|
core.info(`GetCachedBlobResponse: ${JSON.stringify(signedDownloadURL)}`)
|
||||||
|
|
||||||
|
if (signedDownloadURL.blobs.length === 0) {
|
||||||
|
// Cache not found
|
||||||
|
core.warning(`Cache not found for keys: ${keys.join(', ')}`)
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
core.info(`Cache hit for: ${signedDownloadURL.blobs[0].key}`)
|
||||||
|
core.info(`Starting download of artifact to: ${paths[0]}`)
|
||||||
|
await StreamExtract(signedDownloadURL.blobs[0].signedUrl, path.dirname(paths[0]))
|
||||||
|
core.info(`Artifact download completed successfully.`)
|
||||||
|
|
||||||
|
return keys[0]
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Unable to download and extract cache: ${error.message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves a list of files with the specified key
|
* Saves a list of files with the specified key
|
||||||
*
|
*
|
||||||
@@ -170,7 +255,23 @@ export async function saveCache(
|
|||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
checkPaths(paths)
|
checkPaths(paths)
|
||||||
checkKey(key)
|
checkKey(key)
|
||||||
|
|
||||||
|
console.debug(`Cache Service Version: ${CacheServiceVersion}`)
|
||||||
|
switch (CacheServiceVersion) {
|
||||||
|
case "v2":
|
||||||
|
return await saveCachev2(paths, key, options, enableCrossOsArchive)
|
||||||
|
case "v1":
|
||||||
|
default:
|
||||||
|
return await saveCachev1(paths, key, options, enableCrossOsArchive)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveCachev1(
|
||||||
|
paths: string[],
|
||||||
|
key: string,
|
||||||
|
options?: UploadOptions,
|
||||||
|
enableCrossOsArchive = false
|
||||||
|
): Promise<number> {
|
||||||
const compressionMethod = await utils.getCompressionMethod()
|
const compressionMethod = await utils.getCompressionMethod()
|
||||||
let cacheId = -1
|
let cacheId = -1
|
||||||
|
|
||||||
@@ -258,3 +359,51 @@ export async function saveCache(
|
|||||||
|
|
||||||
return cacheId
|
return cacheId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function saveCachev2(
|
||||||
|
paths: string[],
|
||||||
|
key: string,
|
||||||
|
options?: UploadOptions,
|
||||||
|
enableCrossOsArchive = false
|
||||||
|
): Promise<number> {
|
||||||
|
// BackendIds are retrieved form the signed JWT
|
||||||
|
const backendIds: BackendIds = getBackendIdsFromToken()
|
||||||
|
const twirpClient = cacheTwirpClient.internalBlobCacheTwirpClient()
|
||||||
|
const getSignedUploadURL: GetCacheBlobUploadURLRequest = {
|
||||||
|
workflowRunBackendId: backendIds.workflowRunBackendId,
|
||||||
|
workflowJobRunBackendId: backendIds.workflowJobRunBackendId,
|
||||||
|
organization: "github",
|
||||||
|
keys: [key],
|
||||||
|
}
|
||||||
|
const signedUploadURL: GetCacheBlobUploadURLResponse = await twirpClient.GetCacheBlobUploadURL(getSignedUploadURL)
|
||||||
|
core.info(`GetCacheBlobUploadURLResponse: ${JSON.stringify(signedUploadURL)}`)
|
||||||
|
|
||||||
|
// Archive
|
||||||
|
// We're going to handle 1 path fow now. This needs to be fixed to handle all
|
||||||
|
// paths passed in.
|
||||||
|
const rootDir = path.dirname(paths[0])
|
||||||
|
const zipSpecs: UploadZipSpecification[] = getUploadZipSpecification(paths, rootDir)
|
||||||
|
if (zipSpecs.length === 0) {
|
||||||
|
throw new Error(
|
||||||
|
`Error with zip specs: ${zipSpecs.flatMap(s => (s.sourcePath ? [s.sourcePath] : [])).join(', ')}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
const zipUploadStream = await createZipUploadStream(
|
||||||
|
zipSpecs,
|
||||||
|
6
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cache v2 upload
|
||||||
|
// inputs:
|
||||||
|
// - getSignedUploadURL
|
||||||
|
// - archivePath
|
||||||
|
core.info(`Saving Cache v2: ${paths[0]}`)
|
||||||
|
await UploadCacheStream(signedUploadURL.urls[0].url, zipUploadStream)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,290 @@
|
|||||||
|
// @generated by protobuf-ts 2.9.1 with parameter long_type_string,client_none,generate_dependencies
|
||||||
|
// @generated from protobuf file "google/protobuf/timestamp.proto" (package "google.protobuf", syntax proto3)
|
||||||
|
// tslint:disable
|
||||||
|
//
|
||||||
|
// Protocol Buffers - Google's data interchange format
|
||||||
|
// Copyright 2008 Google Inc. All rights reserved.
|
||||||
|
// https://developers.google.com/protocol-buffers/
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
//
|
||||||
|
import type { BinaryWriteOptions } from "@protobuf-ts/runtime";
|
||||||
|
import type { IBinaryWriter } from "@protobuf-ts/runtime";
|
||||||
|
import { WireType } from "@protobuf-ts/runtime";
|
||||||
|
import type { BinaryReadOptions } from "@protobuf-ts/runtime";
|
||||||
|
import type { IBinaryReader } from "@protobuf-ts/runtime";
|
||||||
|
import { UnknownFieldHandler } from "@protobuf-ts/runtime";
|
||||||
|
import type { PartialMessage } from "@protobuf-ts/runtime";
|
||||||
|
import { reflectionMergePartial } from "@protobuf-ts/runtime";
|
||||||
|
import { MESSAGE_TYPE } from "@protobuf-ts/runtime";
|
||||||
|
import { typeofJsonValue } from "@protobuf-ts/runtime";
|
||||||
|
import type { JsonValue } from "@protobuf-ts/runtime";
|
||||||
|
import type { JsonReadOptions } from "@protobuf-ts/runtime";
|
||||||
|
import type { JsonWriteOptions } from "@protobuf-ts/runtime";
|
||||||
|
import { PbLong } from "@protobuf-ts/runtime";
|
||||||
|
import { MessageType } from "@protobuf-ts/runtime";
|
||||||
|
/**
|
||||||
|
* A Timestamp represents a point in time independent of any time zone or local
|
||||||
|
* calendar, encoded as a count of seconds and fractions of seconds at
|
||||||
|
* nanosecond resolution. The count is relative to an epoch at UTC midnight on
|
||||||
|
* January 1, 1970, in the proleptic Gregorian calendar which extends the
|
||||||
|
* Gregorian calendar backwards to year one.
|
||||||
|
*
|
||||||
|
* All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
|
||||||
|
* second table is needed for interpretation, using a [24-hour linear
|
||||||
|
* smear](https://developers.google.com/time/smear).
|
||||||
|
*
|
||||||
|
* The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
|
||||||
|
* restricting to that range, we ensure that we can convert to and from [RFC
|
||||||
|
* 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
|
||||||
|
*
|
||||||
|
* # Examples
|
||||||
|
*
|
||||||
|
* Example 1: Compute Timestamp from POSIX `time()`.
|
||||||
|
*
|
||||||
|
* Timestamp timestamp;
|
||||||
|
* timestamp.set_seconds(time(NULL));
|
||||||
|
* timestamp.set_nanos(0);
|
||||||
|
*
|
||||||
|
* Example 2: Compute Timestamp from POSIX `gettimeofday()`.
|
||||||
|
*
|
||||||
|
* struct timeval tv;
|
||||||
|
* gettimeofday(&tv, NULL);
|
||||||
|
*
|
||||||
|
* Timestamp timestamp;
|
||||||
|
* timestamp.set_seconds(tv.tv_sec);
|
||||||
|
* timestamp.set_nanos(tv.tv_usec * 1000);
|
||||||
|
*
|
||||||
|
* Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
|
||||||
|
*
|
||||||
|
* FILETIME ft;
|
||||||
|
* GetSystemTimeAsFileTime(&ft);
|
||||||
|
* UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
|
||||||
|
*
|
||||||
|
* // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
|
||||||
|
* // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
|
||||||
|
* Timestamp timestamp;
|
||||||
|
* timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
|
||||||
|
* timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
|
||||||
|
*
|
||||||
|
* Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
|
||||||
|
*
|
||||||
|
* long millis = System.currentTimeMillis();
|
||||||
|
*
|
||||||
|
* Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
|
||||||
|
* .setNanos((int) ((millis % 1000) * 1000000)).build();
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Example 5: Compute Timestamp from Java `Instant.now()`.
|
||||||
|
*
|
||||||
|
* Instant now = Instant.now();
|
||||||
|
*
|
||||||
|
* Timestamp timestamp =
|
||||||
|
* Timestamp.newBuilder().setSeconds(now.getEpochSecond())
|
||||||
|
* .setNanos(now.getNano()).build();
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Example 6: Compute Timestamp from current time in Python.
|
||||||
|
*
|
||||||
|
* timestamp = Timestamp()
|
||||||
|
* timestamp.GetCurrentTime()
|
||||||
|
*
|
||||||
|
* # JSON Mapping
|
||||||
|
*
|
||||||
|
* In JSON format, the Timestamp type is encoded as a string in the
|
||||||
|
* [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the
|
||||||
|
* format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z"
|
||||||
|
* where {year} is always expressed using four digits while {month}, {day},
|
||||||
|
* {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional
|
||||||
|
* seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution),
|
||||||
|
* are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone
|
||||||
|
* is required. A proto3 JSON serializer should always use UTC (as indicated by
|
||||||
|
* "Z") when printing the Timestamp type and a proto3 JSON parser should be
|
||||||
|
* able to accept both UTC and other timezones (as indicated by an offset).
|
||||||
|
*
|
||||||
|
* For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past
|
||||||
|
* 01:30 UTC on January 15, 2017.
|
||||||
|
*
|
||||||
|
* In JavaScript, one can convert a Date object to this format using the
|
||||||
|
* standard
|
||||||
|
* [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
|
||||||
|
* method. In Python, a standard `datetime.datetime` object can be converted
|
||||||
|
* to this format using
|
||||||
|
* [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with
|
||||||
|
* the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use
|
||||||
|
* the Joda Time's [`ISODateTimeFormat.dateTime()`](
|
||||||
|
* http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D
|
||||||
|
* ) to obtain a formatter capable of generating timestamps in this format.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @generated from protobuf message google.protobuf.Timestamp
|
||||||
|
*/
|
||||||
|
export interface Timestamp {
|
||||||
|
/**
|
||||||
|
* Represents seconds of UTC time since Unix epoch
|
||||||
|
* 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
|
||||||
|
* 9999-12-31T23:59:59Z inclusive.
|
||||||
|
*
|
||||||
|
* @generated from protobuf field: int64 seconds = 1;
|
||||||
|
*/
|
||||||
|
seconds: string;
|
||||||
|
/**
|
||||||
|
* Non-negative fractions of a second at nanosecond resolution. Negative
|
||||||
|
* second values with fractions must still have non-negative nanos values
|
||||||
|
* that count forward in time. Must be from 0 to 999,999,999
|
||||||
|
* inclusive.
|
||||||
|
*
|
||||||
|
* @generated from protobuf field: int32 nanos = 2;
|
||||||
|
*/
|
||||||
|
nanos: number;
|
||||||
|
}
|
||||||
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
|
class Timestamp$Type extends MessageType<Timestamp> {
|
||||||
|
constructor() {
|
||||||
|
super("google.protobuf.Timestamp", [
|
||||||
|
{ no: 1, name: "seconds", kind: "scalar", T: 3 /*ScalarType.INT64*/ },
|
||||||
|
{ no: 2, name: "nanos", kind: "scalar", T: 5 /*ScalarType.INT32*/ }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Creates a new `Timestamp` for the current time.
|
||||||
|
*/
|
||||||
|
now(): Timestamp {
|
||||||
|
const msg = this.create();
|
||||||
|
const ms = Date.now();
|
||||||
|
msg.seconds = PbLong.from(Math.floor(ms / 1000)).toString();
|
||||||
|
msg.nanos = (ms % 1000) * 1000000;
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Converts a `Timestamp` to a JavaScript Date.
|
||||||
|
*/
|
||||||
|
toDate(message: Timestamp): Date {
|
||||||
|
return new Date(PbLong.from(message.seconds).toNumber() * 1000 + Math.ceil(message.nanos / 1000000));
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Converts a JavaScript Date to a `Timestamp`.
|
||||||
|
*/
|
||||||
|
fromDate(date: Date): Timestamp {
|
||||||
|
const msg = this.create();
|
||||||
|
const ms = date.getTime();
|
||||||
|
msg.seconds = PbLong.from(Math.floor(ms / 1000)).toString();
|
||||||
|
msg.nanos = (ms % 1000) * 1000000;
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* In JSON format, the `Timestamp` type is encoded as a string
|
||||||
|
* in the RFC 3339 format.
|
||||||
|
*/
|
||||||
|
internalJsonWrite(message: Timestamp, options: JsonWriteOptions): JsonValue {
|
||||||
|
let ms = PbLong.from(message.seconds).toNumber() * 1000;
|
||||||
|
if (ms < Date.parse("0001-01-01T00:00:00Z") || ms > Date.parse("9999-12-31T23:59:59Z"))
|
||||||
|
throw new Error("Unable to encode Timestamp to JSON. Must be from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive.");
|
||||||
|
if (message.nanos < 0)
|
||||||
|
throw new Error("Unable to encode invalid Timestamp to JSON. Nanos must not be negative.");
|
||||||
|
let z = "Z";
|
||||||
|
if (message.nanos > 0) {
|
||||||
|
let nanosStr = (message.nanos + 1000000000).toString().substring(1);
|
||||||
|
if (nanosStr.substring(3) === "000000")
|
||||||
|
z = "." + nanosStr.substring(0, 3) + "Z";
|
||||||
|
else if (nanosStr.substring(6) === "000")
|
||||||
|
z = "." + nanosStr.substring(0, 6) + "Z";
|
||||||
|
else
|
||||||
|
z = "." + nanosStr + "Z";
|
||||||
|
}
|
||||||
|
return new Date(ms).toISOString().replace(".000Z", z);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* In JSON format, the `Timestamp` type is encoded as a string
|
||||||
|
* in the RFC 3339 format.
|
||||||
|
*/
|
||||||
|
internalJsonRead(json: JsonValue, options: JsonReadOptions, target?: Timestamp): Timestamp {
|
||||||
|
if (typeof json !== "string")
|
||||||
|
throw new Error("Unable to parse Timestamp from JSON " + typeofJsonValue(json) + ".");
|
||||||
|
let matches = json.match(/^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(?:Z|\.([0-9]{3,9})Z|([+-][0-9][0-9]:[0-9][0-9]))$/);
|
||||||
|
if (!matches)
|
||||||
|
throw new Error("Unable to parse Timestamp from JSON. Invalid format.");
|
||||||
|
let ms = Date.parse(matches[1] + "-" + matches[2] + "-" + matches[3] + "T" + matches[4] + ":" + matches[5] + ":" + matches[6] + (matches[8] ? matches[8] : "Z"));
|
||||||
|
if (Number.isNaN(ms))
|
||||||
|
throw new Error("Unable to parse Timestamp from JSON. Invalid value.");
|
||||||
|
if (ms < Date.parse("0001-01-01T00:00:00Z") || ms > Date.parse("9999-12-31T23:59:59Z"))
|
||||||
|
throw new globalThis.Error("Unable to parse Timestamp from JSON. Must be from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive.");
|
||||||
|
if (!target)
|
||||||
|
target = this.create();
|
||||||
|
target.seconds = PbLong.from(ms / 1000).toString();
|
||||||
|
target.nanos = 0;
|
||||||
|
if (matches[7])
|
||||||
|
target.nanos = (parseInt("1" + matches[7] + "0".repeat(9 - matches[7].length)) - 1000000000);
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
create(value?: PartialMessage<Timestamp>): Timestamp {
|
||||||
|
const message = { seconds: "0", nanos: 0 };
|
||||||
|
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
||||||
|
if (value !== undefined)
|
||||||
|
reflectionMergePartial<Timestamp>(this, message, value);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: Timestamp): Timestamp {
|
||||||
|
let message = target ?? this.create(), end = reader.pos + length;
|
||||||
|
while (reader.pos < end) {
|
||||||
|
let [fieldNo, wireType] = reader.tag();
|
||||||
|
switch (fieldNo) {
|
||||||
|
case /* int64 seconds */ 1:
|
||||||
|
message.seconds = reader.int64().toString();
|
||||||
|
break;
|
||||||
|
case /* int32 nanos */ 2:
|
||||||
|
message.nanos = reader.int32();
|
||||||
|
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: Timestamp, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||||
|
/* int64 seconds = 1; */
|
||||||
|
if (message.seconds !== "0")
|
||||||
|
writer.tag(1, WireType.Varint).int64(message.seconds);
|
||||||
|
/* int32 nanos = 2; */
|
||||||
|
if (message.nanos !== 0)
|
||||||
|
writer.tag(2, WireType.Varint).int32(message.nanos);
|
||||||
|
let u = options.writeUnknownFields;
|
||||||
|
if (u !== false)
|
||||||
|
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated MessageType for protobuf message google.protobuf.Timestamp
|
||||||
|
*/
|
||||||
|
export const Timestamp = new Timestamp$Type();
|
||||||
@@ -0,0 +1,753 @@
|
|||||||
|
// @generated by protobuf-ts 2.9.1 with parameter long_type_string,client_none,generate_dependencies
|
||||||
|
// @generated from protobuf file "google/protobuf/wrappers.proto" (package "google.protobuf", syntax proto3)
|
||||||
|
// tslint:disable
|
||||||
|
//
|
||||||
|
// Protocol Buffers - Google's data interchange format
|
||||||
|
// Copyright 2008 Google Inc. All rights reserved.
|
||||||
|
// https://developers.google.com/protocol-buffers/
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Wrappers for primitive (non-message) types. These types are useful
|
||||||
|
// for embedding primitives in the `google.protobuf.Any` type and for places
|
||||||
|
// where we need to distinguish between the absence of a primitive
|
||||||
|
// typed field and its default value.
|
||||||
|
//
|
||||||
|
// These wrappers have no meaningful use within repeated fields as they lack
|
||||||
|
// the ability to detect presence on individual elements.
|
||||||
|
// These wrappers have no meaningful use within a map or a oneof since
|
||||||
|
// individual entries of a map or fields of a oneof can already detect presence.
|
||||||
|
//
|
||||||
|
import { ScalarType } from "@protobuf-ts/runtime";
|
||||||
|
import { LongType } from "@protobuf-ts/runtime";
|
||||||
|
import type { BinaryWriteOptions } from "@protobuf-ts/runtime";
|
||||||
|
import type { IBinaryWriter } from "@protobuf-ts/runtime";
|
||||||
|
import { WireType } from "@protobuf-ts/runtime";
|
||||||
|
import type { BinaryReadOptions } from "@protobuf-ts/runtime";
|
||||||
|
import type { IBinaryReader } from "@protobuf-ts/runtime";
|
||||||
|
import { UnknownFieldHandler } from "@protobuf-ts/runtime";
|
||||||
|
import type { PartialMessage } from "@protobuf-ts/runtime";
|
||||||
|
import { reflectionMergePartial } from "@protobuf-ts/runtime";
|
||||||
|
import { MESSAGE_TYPE } from "@protobuf-ts/runtime";
|
||||||
|
import type { JsonValue } from "@protobuf-ts/runtime";
|
||||||
|
import type { JsonReadOptions } from "@protobuf-ts/runtime";
|
||||||
|
import type { JsonWriteOptions } from "@protobuf-ts/runtime";
|
||||||
|
import { MessageType } from "@protobuf-ts/runtime";
|
||||||
|
/**
|
||||||
|
* Wrapper message for `double`.
|
||||||
|
*
|
||||||
|
* The JSON representation for `DoubleValue` is JSON number.
|
||||||
|
*
|
||||||
|
* @generated from protobuf message google.protobuf.DoubleValue
|
||||||
|
*/
|
||||||
|
export interface DoubleValue {
|
||||||
|
/**
|
||||||
|
* The double value.
|
||||||
|
*
|
||||||
|
* @generated from protobuf field: double value = 1;
|
||||||
|
*/
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Wrapper message for `float`.
|
||||||
|
*
|
||||||
|
* The JSON representation for `FloatValue` is JSON number.
|
||||||
|
*
|
||||||
|
* @generated from protobuf message google.protobuf.FloatValue
|
||||||
|
*/
|
||||||
|
export interface FloatValue {
|
||||||
|
/**
|
||||||
|
* The float value.
|
||||||
|
*
|
||||||
|
* @generated from protobuf field: float value = 1;
|
||||||
|
*/
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Wrapper message for `int64`.
|
||||||
|
*
|
||||||
|
* The JSON representation for `Int64Value` is JSON string.
|
||||||
|
*
|
||||||
|
* @generated from protobuf message google.protobuf.Int64Value
|
||||||
|
*/
|
||||||
|
export interface Int64Value {
|
||||||
|
/**
|
||||||
|
* The int64 value.
|
||||||
|
*
|
||||||
|
* @generated from protobuf field: int64 value = 1;
|
||||||
|
*/
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Wrapper message for `uint64`.
|
||||||
|
*
|
||||||
|
* The JSON representation for `UInt64Value` is JSON string.
|
||||||
|
*
|
||||||
|
* @generated from protobuf message google.protobuf.UInt64Value
|
||||||
|
*/
|
||||||
|
export interface UInt64Value {
|
||||||
|
/**
|
||||||
|
* The uint64 value.
|
||||||
|
*
|
||||||
|
* @generated from protobuf field: uint64 value = 1;
|
||||||
|
*/
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Wrapper message for `int32`.
|
||||||
|
*
|
||||||
|
* The JSON representation for `Int32Value` is JSON number.
|
||||||
|
*
|
||||||
|
* @generated from protobuf message google.protobuf.Int32Value
|
||||||
|
*/
|
||||||
|
export interface Int32Value {
|
||||||
|
/**
|
||||||
|
* The int32 value.
|
||||||
|
*
|
||||||
|
* @generated from protobuf field: int32 value = 1;
|
||||||
|
*/
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Wrapper message for `uint32`.
|
||||||
|
*
|
||||||
|
* The JSON representation for `UInt32Value` is JSON number.
|
||||||
|
*
|
||||||
|
* @generated from protobuf message google.protobuf.UInt32Value
|
||||||
|
*/
|
||||||
|
export interface UInt32Value {
|
||||||
|
/**
|
||||||
|
* The uint32 value.
|
||||||
|
*
|
||||||
|
* @generated from protobuf field: uint32 value = 1;
|
||||||
|
*/
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Wrapper message for `bool`.
|
||||||
|
*
|
||||||
|
* The JSON representation for `BoolValue` is JSON `true` and `false`.
|
||||||
|
*
|
||||||
|
* @generated from protobuf message google.protobuf.BoolValue
|
||||||
|
*/
|
||||||
|
export interface BoolValue {
|
||||||
|
/**
|
||||||
|
* The bool value.
|
||||||
|
*
|
||||||
|
* @generated from protobuf field: bool value = 1;
|
||||||
|
*/
|
||||||
|
value: boolean;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Wrapper message for `string`.
|
||||||
|
*
|
||||||
|
* The JSON representation for `StringValue` is JSON string.
|
||||||
|
*
|
||||||
|
* @generated from protobuf message google.protobuf.StringValue
|
||||||
|
*/
|
||||||
|
export interface StringValue {
|
||||||
|
/**
|
||||||
|
* The string value.
|
||||||
|
*
|
||||||
|
* @generated from protobuf field: string value = 1;
|
||||||
|
*/
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Wrapper message for `bytes`.
|
||||||
|
*
|
||||||
|
* The JSON representation for `BytesValue` is JSON string.
|
||||||
|
*
|
||||||
|
* @generated from protobuf message google.protobuf.BytesValue
|
||||||
|
*/
|
||||||
|
export interface BytesValue {
|
||||||
|
/**
|
||||||
|
* The bytes value.
|
||||||
|
*
|
||||||
|
* @generated from protobuf field: bytes value = 1;
|
||||||
|
*/
|
||||||
|
value: Uint8Array;
|
||||||
|
}
|
||||||
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
|
class DoubleValue$Type extends MessageType<DoubleValue> {
|
||||||
|
constructor() {
|
||||||
|
super("google.protobuf.DoubleValue", [
|
||||||
|
{ no: 1, name: "value", kind: "scalar", T: 1 /*ScalarType.DOUBLE*/ }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Encode `DoubleValue` to JSON number.
|
||||||
|
*/
|
||||||
|
internalJsonWrite(message: DoubleValue, options: JsonWriteOptions): JsonValue {
|
||||||
|
return this.refJsonWriter.scalar(2, message.value, "value", false, true);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Decode `DoubleValue` from JSON number.
|
||||||
|
*/
|
||||||
|
internalJsonRead(json: JsonValue, options: JsonReadOptions, target?: DoubleValue): DoubleValue {
|
||||||
|
if (!target)
|
||||||
|
target = this.create();
|
||||||
|
target.value = this.refJsonReader.scalar(json, 1, undefined, "value") as number;
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
create(value?: PartialMessage<DoubleValue>): DoubleValue {
|
||||||
|
const message = { value: 0 };
|
||||||
|
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
||||||
|
if (value !== undefined)
|
||||||
|
reflectionMergePartial<DoubleValue>(this, message, value);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: DoubleValue): DoubleValue {
|
||||||
|
let message = target ?? this.create(), end = reader.pos + length;
|
||||||
|
while (reader.pos < end) {
|
||||||
|
let [fieldNo, wireType] = reader.tag();
|
||||||
|
switch (fieldNo) {
|
||||||
|
case /* double value */ 1:
|
||||||
|
message.value = reader.double();
|
||||||
|
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: DoubleValue, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||||
|
/* double value = 1; */
|
||||||
|
if (message.value !== 0)
|
||||||
|
writer.tag(1, WireType.Bit64).double(message.value);
|
||||||
|
let u = options.writeUnknownFields;
|
||||||
|
if (u !== false)
|
||||||
|
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated MessageType for protobuf message google.protobuf.DoubleValue
|
||||||
|
*/
|
||||||
|
export const DoubleValue = new DoubleValue$Type();
|
||||||
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
|
class FloatValue$Type extends MessageType<FloatValue> {
|
||||||
|
constructor() {
|
||||||
|
super("google.protobuf.FloatValue", [
|
||||||
|
{ no: 1, name: "value", kind: "scalar", T: 2 /*ScalarType.FLOAT*/ }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Encode `FloatValue` to JSON number.
|
||||||
|
*/
|
||||||
|
internalJsonWrite(message: FloatValue, options: JsonWriteOptions): JsonValue {
|
||||||
|
return this.refJsonWriter.scalar(1, message.value, "value", false, true);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Decode `FloatValue` from JSON number.
|
||||||
|
*/
|
||||||
|
internalJsonRead(json: JsonValue, options: JsonReadOptions, target?: FloatValue): FloatValue {
|
||||||
|
if (!target)
|
||||||
|
target = this.create();
|
||||||
|
target.value = this.refJsonReader.scalar(json, 1, undefined, "value") as number;
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
create(value?: PartialMessage<FloatValue>): FloatValue {
|
||||||
|
const message = { value: 0 };
|
||||||
|
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
||||||
|
if (value !== undefined)
|
||||||
|
reflectionMergePartial<FloatValue>(this, message, value);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: FloatValue): FloatValue {
|
||||||
|
let message = target ?? this.create(), end = reader.pos + length;
|
||||||
|
while (reader.pos < end) {
|
||||||
|
let [fieldNo, wireType] = reader.tag();
|
||||||
|
switch (fieldNo) {
|
||||||
|
case /* float value */ 1:
|
||||||
|
message.value = reader.float();
|
||||||
|
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: FloatValue, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||||
|
/* float value = 1; */
|
||||||
|
if (message.value !== 0)
|
||||||
|
writer.tag(1, WireType.Bit32).float(message.value);
|
||||||
|
let u = options.writeUnknownFields;
|
||||||
|
if (u !== false)
|
||||||
|
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated MessageType for protobuf message google.protobuf.FloatValue
|
||||||
|
*/
|
||||||
|
export const FloatValue = new FloatValue$Type();
|
||||||
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
|
class Int64Value$Type extends MessageType<Int64Value> {
|
||||||
|
constructor() {
|
||||||
|
super("google.protobuf.Int64Value", [
|
||||||
|
{ no: 1, name: "value", kind: "scalar", T: 3 /*ScalarType.INT64*/ }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Encode `Int64Value` to JSON string.
|
||||||
|
*/
|
||||||
|
internalJsonWrite(message: Int64Value, options: JsonWriteOptions): JsonValue {
|
||||||
|
return this.refJsonWriter.scalar(ScalarType.INT64, message.value, "value", false, true);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Decode `Int64Value` from JSON string.
|
||||||
|
*/
|
||||||
|
internalJsonRead(json: JsonValue, options: JsonReadOptions, target?: Int64Value): Int64Value {
|
||||||
|
if (!target)
|
||||||
|
target = this.create();
|
||||||
|
target.value = this.refJsonReader.scalar(json, ScalarType.INT64, LongType.STRING, "value") as any;
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
create(value?: PartialMessage<Int64Value>): Int64Value {
|
||||||
|
const message = { value: "0" };
|
||||||
|
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
||||||
|
if (value !== undefined)
|
||||||
|
reflectionMergePartial<Int64Value>(this, message, value);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: Int64Value): Int64Value {
|
||||||
|
let message = target ?? this.create(), end = reader.pos + length;
|
||||||
|
while (reader.pos < end) {
|
||||||
|
let [fieldNo, wireType] = reader.tag();
|
||||||
|
switch (fieldNo) {
|
||||||
|
case /* int64 value */ 1:
|
||||||
|
message.value = reader.int64().toString();
|
||||||
|
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: Int64Value, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||||
|
/* int64 value = 1; */
|
||||||
|
if (message.value !== "0")
|
||||||
|
writer.tag(1, WireType.Varint).int64(message.value);
|
||||||
|
let u = options.writeUnknownFields;
|
||||||
|
if (u !== false)
|
||||||
|
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated MessageType for protobuf message google.protobuf.Int64Value
|
||||||
|
*/
|
||||||
|
export const Int64Value = new Int64Value$Type();
|
||||||
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
|
class UInt64Value$Type extends MessageType<UInt64Value> {
|
||||||
|
constructor() {
|
||||||
|
super("google.protobuf.UInt64Value", [
|
||||||
|
{ no: 1, name: "value", kind: "scalar", T: 4 /*ScalarType.UINT64*/ }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Encode `UInt64Value` to JSON string.
|
||||||
|
*/
|
||||||
|
internalJsonWrite(message: UInt64Value, options: JsonWriteOptions): JsonValue {
|
||||||
|
return this.refJsonWriter.scalar(ScalarType.UINT64, message.value, "value", false, true);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Decode `UInt64Value` from JSON string.
|
||||||
|
*/
|
||||||
|
internalJsonRead(json: JsonValue, options: JsonReadOptions, target?: UInt64Value): UInt64Value {
|
||||||
|
if (!target)
|
||||||
|
target = this.create();
|
||||||
|
target.value = this.refJsonReader.scalar(json, ScalarType.UINT64, LongType.STRING, "value") as any;
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
create(value?: PartialMessage<UInt64Value>): UInt64Value {
|
||||||
|
const message = { value: "0" };
|
||||||
|
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
||||||
|
if (value !== undefined)
|
||||||
|
reflectionMergePartial<UInt64Value>(this, message, value);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: UInt64Value): UInt64Value {
|
||||||
|
let message = target ?? this.create(), end = reader.pos + length;
|
||||||
|
while (reader.pos < end) {
|
||||||
|
let [fieldNo, wireType] = reader.tag();
|
||||||
|
switch (fieldNo) {
|
||||||
|
case /* uint64 value */ 1:
|
||||||
|
message.value = reader.uint64().toString();
|
||||||
|
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: UInt64Value, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||||
|
/* uint64 value = 1; */
|
||||||
|
if (message.value !== "0")
|
||||||
|
writer.tag(1, WireType.Varint).uint64(message.value);
|
||||||
|
let u = options.writeUnknownFields;
|
||||||
|
if (u !== false)
|
||||||
|
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated MessageType for protobuf message google.protobuf.UInt64Value
|
||||||
|
*/
|
||||||
|
export const UInt64Value = new UInt64Value$Type();
|
||||||
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
|
class Int32Value$Type extends MessageType<Int32Value> {
|
||||||
|
constructor() {
|
||||||
|
super("google.protobuf.Int32Value", [
|
||||||
|
{ no: 1, name: "value", kind: "scalar", T: 5 /*ScalarType.INT32*/ }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Encode `Int32Value` to JSON string.
|
||||||
|
*/
|
||||||
|
internalJsonWrite(message: Int32Value, options: JsonWriteOptions): JsonValue {
|
||||||
|
return this.refJsonWriter.scalar(5, message.value, "value", false, true);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Decode `Int32Value` from JSON string.
|
||||||
|
*/
|
||||||
|
internalJsonRead(json: JsonValue, options: JsonReadOptions, target?: Int32Value): Int32Value {
|
||||||
|
if (!target)
|
||||||
|
target = this.create();
|
||||||
|
target.value = this.refJsonReader.scalar(json, 5, undefined, "value") as number;
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
create(value?: PartialMessage<Int32Value>): Int32Value {
|
||||||
|
const message = { value: 0 };
|
||||||
|
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
||||||
|
if (value !== undefined)
|
||||||
|
reflectionMergePartial<Int32Value>(this, message, value);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: Int32Value): Int32Value {
|
||||||
|
let message = target ?? this.create(), end = reader.pos + length;
|
||||||
|
while (reader.pos < end) {
|
||||||
|
let [fieldNo, wireType] = reader.tag();
|
||||||
|
switch (fieldNo) {
|
||||||
|
case /* int32 value */ 1:
|
||||||
|
message.value = reader.int32();
|
||||||
|
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: Int32Value, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||||
|
/* int32 value = 1; */
|
||||||
|
if (message.value !== 0)
|
||||||
|
writer.tag(1, WireType.Varint).int32(message.value);
|
||||||
|
let u = options.writeUnknownFields;
|
||||||
|
if (u !== false)
|
||||||
|
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated MessageType for protobuf message google.protobuf.Int32Value
|
||||||
|
*/
|
||||||
|
export const Int32Value = new Int32Value$Type();
|
||||||
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
|
class UInt32Value$Type extends MessageType<UInt32Value> {
|
||||||
|
constructor() {
|
||||||
|
super("google.protobuf.UInt32Value", [
|
||||||
|
{ no: 1, name: "value", kind: "scalar", T: 13 /*ScalarType.UINT32*/ }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Encode `UInt32Value` to JSON string.
|
||||||
|
*/
|
||||||
|
internalJsonWrite(message: UInt32Value, options: JsonWriteOptions): JsonValue {
|
||||||
|
return this.refJsonWriter.scalar(13, message.value, "value", false, true);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Decode `UInt32Value` from JSON string.
|
||||||
|
*/
|
||||||
|
internalJsonRead(json: JsonValue, options: JsonReadOptions, target?: UInt32Value): UInt32Value {
|
||||||
|
if (!target)
|
||||||
|
target = this.create();
|
||||||
|
target.value = this.refJsonReader.scalar(json, 13, undefined, "value") as number;
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
create(value?: PartialMessage<UInt32Value>): UInt32Value {
|
||||||
|
const message = { value: 0 };
|
||||||
|
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
||||||
|
if (value !== undefined)
|
||||||
|
reflectionMergePartial<UInt32Value>(this, message, value);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: UInt32Value): UInt32Value {
|
||||||
|
let message = target ?? this.create(), end = reader.pos + length;
|
||||||
|
while (reader.pos < end) {
|
||||||
|
let [fieldNo, wireType] = reader.tag();
|
||||||
|
switch (fieldNo) {
|
||||||
|
case /* uint32 value */ 1:
|
||||||
|
message.value = reader.uint32();
|
||||||
|
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: UInt32Value, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||||
|
/* uint32 value = 1; */
|
||||||
|
if (message.value !== 0)
|
||||||
|
writer.tag(1, WireType.Varint).uint32(message.value);
|
||||||
|
let u = options.writeUnknownFields;
|
||||||
|
if (u !== false)
|
||||||
|
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated MessageType for protobuf message google.protobuf.UInt32Value
|
||||||
|
*/
|
||||||
|
export const UInt32Value = new UInt32Value$Type();
|
||||||
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
|
class BoolValue$Type extends MessageType<BoolValue> {
|
||||||
|
constructor() {
|
||||||
|
super("google.protobuf.BoolValue", [
|
||||||
|
{ no: 1, name: "value", kind: "scalar", T: 8 /*ScalarType.BOOL*/ }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Encode `BoolValue` to JSON bool.
|
||||||
|
*/
|
||||||
|
internalJsonWrite(message: BoolValue, options: JsonWriteOptions): JsonValue {
|
||||||
|
return message.value;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Decode `BoolValue` from JSON bool.
|
||||||
|
*/
|
||||||
|
internalJsonRead(json: JsonValue, options: JsonReadOptions, target?: BoolValue): BoolValue {
|
||||||
|
if (!target)
|
||||||
|
target = this.create();
|
||||||
|
target.value = this.refJsonReader.scalar(json, 8, undefined, "value") as boolean;
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
create(value?: PartialMessage<BoolValue>): BoolValue {
|
||||||
|
const message = { value: false };
|
||||||
|
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
||||||
|
if (value !== undefined)
|
||||||
|
reflectionMergePartial<BoolValue>(this, message, value);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: BoolValue): BoolValue {
|
||||||
|
let message = target ?? this.create(), end = reader.pos + length;
|
||||||
|
while (reader.pos < end) {
|
||||||
|
let [fieldNo, wireType] = reader.tag();
|
||||||
|
switch (fieldNo) {
|
||||||
|
case /* bool value */ 1:
|
||||||
|
message.value = reader.bool();
|
||||||
|
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: BoolValue, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||||
|
/* bool value = 1; */
|
||||||
|
if (message.value !== false)
|
||||||
|
writer.tag(1, WireType.Varint).bool(message.value);
|
||||||
|
let u = options.writeUnknownFields;
|
||||||
|
if (u !== false)
|
||||||
|
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated MessageType for protobuf message google.protobuf.BoolValue
|
||||||
|
*/
|
||||||
|
export const BoolValue = new BoolValue$Type();
|
||||||
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
|
class StringValue$Type extends MessageType<StringValue> {
|
||||||
|
constructor() {
|
||||||
|
super("google.protobuf.StringValue", [
|
||||||
|
{ no: 1, name: "value", kind: "scalar", T: 9 /*ScalarType.STRING*/ }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Encode `StringValue` to JSON string.
|
||||||
|
*/
|
||||||
|
internalJsonWrite(message: StringValue, options: JsonWriteOptions): JsonValue {
|
||||||
|
return message.value;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Decode `StringValue` from JSON string.
|
||||||
|
*/
|
||||||
|
internalJsonRead(json: JsonValue, options: JsonReadOptions, target?: StringValue): StringValue {
|
||||||
|
if (!target)
|
||||||
|
target = this.create();
|
||||||
|
target.value = this.refJsonReader.scalar(json, 9, undefined, "value") as string;
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
create(value?: PartialMessage<StringValue>): StringValue {
|
||||||
|
const message = { value: "" };
|
||||||
|
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
||||||
|
if (value !== undefined)
|
||||||
|
reflectionMergePartial<StringValue>(this, message, value);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: StringValue): StringValue {
|
||||||
|
let message = target ?? this.create(), end = reader.pos + length;
|
||||||
|
while (reader.pos < end) {
|
||||||
|
let [fieldNo, wireType] = reader.tag();
|
||||||
|
switch (fieldNo) {
|
||||||
|
case /* string value */ 1:
|
||||||
|
message.value = 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: StringValue, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||||
|
/* string value = 1; */
|
||||||
|
if (message.value !== "")
|
||||||
|
writer.tag(1, WireType.LengthDelimited).string(message.value);
|
||||||
|
let u = options.writeUnknownFields;
|
||||||
|
if (u !== false)
|
||||||
|
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated MessageType for protobuf message google.protobuf.StringValue
|
||||||
|
*/
|
||||||
|
export const StringValue = new StringValue$Type();
|
||||||
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
|
class BytesValue$Type extends MessageType<BytesValue> {
|
||||||
|
constructor() {
|
||||||
|
super("google.protobuf.BytesValue", [
|
||||||
|
{ no: 1, name: "value", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Encode `BytesValue` to JSON string.
|
||||||
|
*/
|
||||||
|
internalJsonWrite(message: BytesValue, options: JsonWriteOptions): JsonValue {
|
||||||
|
return this.refJsonWriter.scalar(12, message.value, "value", false, true);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Decode `BytesValue` from JSON string.
|
||||||
|
*/
|
||||||
|
internalJsonRead(json: JsonValue, options: JsonReadOptions, target?: BytesValue): BytesValue {
|
||||||
|
if (!target)
|
||||||
|
target = this.create();
|
||||||
|
target.value = this.refJsonReader.scalar(json, 12, undefined, "value") as Uint8Array;
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
create(value?: PartialMessage<BytesValue>): BytesValue {
|
||||||
|
const message = { value: new Uint8Array(0) };
|
||||||
|
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
||||||
|
if (value !== undefined)
|
||||||
|
reflectionMergePartial<BytesValue>(this, message, value);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: BytesValue): BytesValue {
|
||||||
|
let message = target ?? this.create(), end = reader.pos + length;
|
||||||
|
while (reader.pos < end) {
|
||||||
|
let [fieldNo, wireType] = reader.tag();
|
||||||
|
switch (fieldNo) {
|
||||||
|
case /* bytes value */ 1:
|
||||||
|
message.value = reader.bytes();
|
||||||
|
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: BytesValue, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||||
|
/* bytes value = 1; */
|
||||||
|
if (message.value.length)
|
||||||
|
writer.tag(1, WireType.LengthDelimited).bytes(message.value);
|
||||||
|
let u = options.writeUnknownFields;
|
||||||
|
if (u !== false)
|
||||||
|
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated MessageType for protobuf message google.protobuf.BytesValue
|
||||||
|
*/
|
||||||
|
export const BytesValue = new BytesValue$Type();
|
||||||
@@ -0,0 +1,513 @@
|
|||||||
|
// @generated by protobuf-ts 2.9.1 with parameter long_type_string,client_none,generate_dependencies
|
||||||
|
// @generated from protobuf file "results/api/v1/blobcache.proto" (package "github.actions.results.api.v1", syntax proto3)
|
||||||
|
// tslint:disable
|
||||||
|
import { ServiceType } from "@protobuf-ts/runtime-rpc";
|
||||||
|
import type { BinaryWriteOptions } from "@protobuf-ts/runtime";
|
||||||
|
import type { IBinaryWriter } from "@protobuf-ts/runtime";
|
||||||
|
import { WireType } from "@protobuf-ts/runtime";
|
||||||
|
import type { BinaryReadOptions } from "@protobuf-ts/runtime";
|
||||||
|
import type { IBinaryReader } from "@protobuf-ts/runtime";
|
||||||
|
import { UnknownFieldHandler } from "@protobuf-ts/runtime";
|
||||||
|
import type { PartialMessage } from "@protobuf-ts/runtime";
|
||||||
|
import { reflectionMergePartial } from "@protobuf-ts/runtime";
|
||||||
|
import { MESSAGE_TYPE } from "@protobuf-ts/runtime";
|
||||||
|
import { MessageType } from "@protobuf-ts/runtime";
|
||||||
|
import { Timestamp } from "../../../google/protobuf/timestamp";
|
||||||
|
/**
|
||||||
|
* @generated from protobuf message github.actions.results.api.v1.GetCachedBlobRequest
|
||||||
|
*/
|
||||||
|
export interface GetCachedBlobRequest {
|
||||||
|
/**
|
||||||
|
* Workflow run backend ID
|
||||||
|
*
|
||||||
|
* @generated from protobuf field: string workflow_run_backend_id = 1;
|
||||||
|
*/
|
||||||
|
workflowRunBackendId: string;
|
||||||
|
/**
|
||||||
|
* Workflow job run backend ID
|
||||||
|
*
|
||||||
|
* @generated from protobuf field: string workflow_job_run_backend_id = 2;
|
||||||
|
*/
|
||||||
|
workflowJobRunBackendId: string;
|
||||||
|
/**
|
||||||
|
* Key(s) of te blob(s) to be retrieved
|
||||||
|
*
|
||||||
|
* @generated from protobuf field: repeated string keys = 3;
|
||||||
|
*/
|
||||||
|
keys: string[];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated from protobuf message github.actions.results.api.v1.GetCachedBlobResponse
|
||||||
|
*/
|
||||||
|
export interface GetCachedBlobResponse {
|
||||||
|
/**
|
||||||
|
* List of blobs that match the requested keys
|
||||||
|
*
|
||||||
|
* @generated from protobuf field: repeated github.actions.results.api.v1.GetCachedBlobResponse.Blob blobs = 1;
|
||||||
|
*/
|
||||||
|
blobs: GetCachedBlobResponse_Blob[];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated from protobuf message github.actions.results.api.v1.GetCachedBlobResponse.Blob
|
||||||
|
*/
|
||||||
|
export interface GetCachedBlobResponse_Blob {
|
||||||
|
/**
|
||||||
|
* Key of the blob
|
||||||
|
*
|
||||||
|
* @generated from protobuf field: string key = 1;
|
||||||
|
*/
|
||||||
|
key: string;
|
||||||
|
/**
|
||||||
|
* Download url for the cached blob
|
||||||
|
*
|
||||||
|
* @generated from protobuf field: string signed_url = 2;
|
||||||
|
*/
|
||||||
|
signedUrl: string;
|
||||||
|
/**
|
||||||
|
* Version of the cached blob entry
|
||||||
|
*
|
||||||
|
* @generated from protobuf field: int32 version = 3;
|
||||||
|
*/
|
||||||
|
version: number;
|
||||||
|
/**
|
||||||
|
* Checksum of the blob
|
||||||
|
*
|
||||||
|
* @generated from protobuf field: string checksum = 4;
|
||||||
|
*/
|
||||||
|
checksum: string;
|
||||||
|
/**
|
||||||
|
* Timestamp for when the blob cache entry expires
|
||||||
|
*
|
||||||
|
* @generated from protobuf field: google.protobuf.Timestamp expires_at = 5;
|
||||||
|
*/
|
||||||
|
expiresAt?: Timestamp;
|
||||||
|
/**
|
||||||
|
* Timestamp for when the blob cache entry was created
|
||||||
|
*
|
||||||
|
* @generated from protobuf field: google.protobuf.Timestamp created_at = 6;
|
||||||
|
*/
|
||||||
|
createdAt?: Timestamp;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated from protobuf message github.actions.results.api.v1.GetCacheBlobUploadURLRequest
|
||||||
|
*/
|
||||||
|
export interface GetCacheBlobUploadURLRequest {
|
||||||
|
/**
|
||||||
|
* Workflow run backend ID
|
||||||
|
*
|
||||||
|
* @generated from protobuf field: string workflow_run_backend_id = 1;
|
||||||
|
*/
|
||||||
|
workflowRunBackendId: string;
|
||||||
|
/**
|
||||||
|
* Workflow job run backend ID
|
||||||
|
*
|
||||||
|
* @generated from protobuf field: string workflow_job_run_backend_id = 2;
|
||||||
|
*/
|
||||||
|
workflowJobRunBackendId: string;
|
||||||
|
/**
|
||||||
|
* / Owner of the blob(s) to be retrieved
|
||||||
|
*
|
||||||
|
* @generated from protobuf field: string organization = 3;
|
||||||
|
*/
|
||||||
|
organization: string;
|
||||||
|
/**
|
||||||
|
* Key(s) of te blob(s) to be retrieved
|
||||||
|
*
|
||||||
|
* @generated from protobuf field: repeated string keys = 4;
|
||||||
|
*/
|
||||||
|
keys: string[];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated from protobuf message github.actions.results.api.v1.GetCacheBlobUploadURLResponse
|
||||||
|
*/
|
||||||
|
export interface GetCacheBlobUploadURLResponse {
|
||||||
|
/**
|
||||||
|
* List of upload URLs that match the requested keys
|
||||||
|
*
|
||||||
|
* @generated from protobuf field: repeated github.actions.results.api.v1.GetCacheBlobUploadURLResponse.Url urls = 1;
|
||||||
|
*/
|
||||||
|
urls: GetCacheBlobUploadURLResponse_Url[];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated from protobuf message github.actions.results.api.v1.GetCacheBlobUploadURLResponse.Url
|
||||||
|
*/
|
||||||
|
export interface GetCacheBlobUploadURLResponse_Url {
|
||||||
|
/**
|
||||||
|
* Key of the blob
|
||||||
|
*
|
||||||
|
* @generated from protobuf field: string key = 1;
|
||||||
|
*/
|
||||||
|
key: string;
|
||||||
|
/**
|
||||||
|
* URL to the blob
|
||||||
|
*
|
||||||
|
* @generated from protobuf field: string url = 2;
|
||||||
|
*/
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
|
class GetCachedBlobRequest$Type extends MessageType<GetCachedBlobRequest> {
|
||||||
|
constructor() {
|
||||||
|
super("github.actions.results.api.v1.GetCachedBlobRequest", [
|
||||||
|
{ 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: "keys", kind: "scalar", repeat: 2 /*RepeatType.UNPACKED*/, T: 9 /*ScalarType.STRING*/ }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
create(value?: PartialMessage<GetCachedBlobRequest>): GetCachedBlobRequest {
|
||||||
|
const message = { workflowRunBackendId: "", workflowJobRunBackendId: "", keys: [] };
|
||||||
|
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
||||||
|
if (value !== undefined)
|
||||||
|
reflectionMergePartial<GetCachedBlobRequest>(this, message, value);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GetCachedBlobRequest): GetCachedBlobRequest {
|
||||||
|
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 /* repeated string keys */ 3:
|
||||||
|
message.keys.push(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: GetCachedBlobRequest, 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);
|
||||||
|
/* repeated string keys = 3; */
|
||||||
|
for (let i = 0; i < message.keys.length; i++)
|
||||||
|
writer.tag(3, WireType.LengthDelimited).string(message.keys[i]);
|
||||||
|
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.GetCachedBlobRequest
|
||||||
|
*/
|
||||||
|
export const GetCachedBlobRequest = new GetCachedBlobRequest$Type();
|
||||||
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
|
class GetCachedBlobResponse$Type extends MessageType<GetCachedBlobResponse> {
|
||||||
|
constructor() {
|
||||||
|
super("github.actions.results.api.v1.GetCachedBlobResponse", [
|
||||||
|
{ no: 1, name: "blobs", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => GetCachedBlobResponse_Blob }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
create(value?: PartialMessage<GetCachedBlobResponse>): GetCachedBlobResponse {
|
||||||
|
const message = { blobs: [] };
|
||||||
|
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
||||||
|
if (value !== undefined)
|
||||||
|
reflectionMergePartial<GetCachedBlobResponse>(this, message, value);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GetCachedBlobResponse): GetCachedBlobResponse {
|
||||||
|
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.GetCachedBlobResponse.Blob blobs */ 1:
|
||||||
|
message.blobs.push(GetCachedBlobResponse_Blob.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: GetCachedBlobResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||||
|
/* repeated github.actions.results.api.v1.GetCachedBlobResponse.Blob blobs = 1; */
|
||||||
|
for (let i = 0; i < message.blobs.length; i++)
|
||||||
|
GetCachedBlobResponse_Blob.internalBinaryWrite(message.blobs[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.GetCachedBlobResponse
|
||||||
|
*/
|
||||||
|
export const GetCachedBlobResponse = new GetCachedBlobResponse$Type();
|
||||||
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
|
class GetCachedBlobResponse_Blob$Type extends MessageType<GetCachedBlobResponse_Blob> {
|
||||||
|
constructor() {
|
||||||
|
super("github.actions.results.api.v1.GetCachedBlobResponse.Blob", [
|
||||||
|
{ no: 1, name: "key", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
||||||
|
{ no: 2, name: "signed_url", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
||||||
|
{ no: 3, name: "version", kind: "scalar", T: 5 /*ScalarType.INT32*/ },
|
||||||
|
{ no: 4, name: "checksum", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
||||||
|
{ no: 5, name: "expires_at", kind: "message", T: () => Timestamp },
|
||||||
|
{ no: 6, name: "created_at", kind: "message", T: () => Timestamp }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
create(value?: PartialMessage<GetCachedBlobResponse_Blob>): GetCachedBlobResponse_Blob {
|
||||||
|
const message = { key: "", signedUrl: "", version: 0, checksum: "" };
|
||||||
|
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
||||||
|
if (value !== undefined)
|
||||||
|
reflectionMergePartial<GetCachedBlobResponse_Blob>(this, message, value);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GetCachedBlobResponse_Blob): GetCachedBlobResponse_Blob {
|
||||||
|
let message = target ?? this.create(), end = reader.pos + length;
|
||||||
|
while (reader.pos < end) {
|
||||||
|
let [fieldNo, wireType] = reader.tag();
|
||||||
|
switch (fieldNo) {
|
||||||
|
case /* string key */ 1:
|
||||||
|
message.key = reader.string();
|
||||||
|
break;
|
||||||
|
case /* string signed_url */ 2:
|
||||||
|
message.signedUrl = reader.string();
|
||||||
|
break;
|
||||||
|
case /* int32 version */ 3:
|
||||||
|
message.version = reader.int32();
|
||||||
|
break;
|
||||||
|
case /* string checksum */ 4:
|
||||||
|
message.checksum = reader.string();
|
||||||
|
break;
|
||||||
|
case /* google.protobuf.Timestamp expires_at */ 5:
|
||||||
|
message.expiresAt = Timestamp.internalBinaryRead(reader, reader.uint32(), options, message.expiresAt);
|
||||||
|
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: GetCachedBlobResponse_Blob, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||||
|
/* string key = 1; */
|
||||||
|
if (message.key !== "")
|
||||||
|
writer.tag(1, WireType.LengthDelimited).string(message.key);
|
||||||
|
/* string signed_url = 2; */
|
||||||
|
if (message.signedUrl !== "")
|
||||||
|
writer.tag(2, WireType.LengthDelimited).string(message.signedUrl);
|
||||||
|
/* int32 version = 3; */
|
||||||
|
if (message.version !== 0)
|
||||||
|
writer.tag(3, WireType.Varint).int32(message.version);
|
||||||
|
/* string checksum = 4; */
|
||||||
|
if (message.checksum !== "")
|
||||||
|
writer.tag(4, WireType.LengthDelimited).string(message.checksum);
|
||||||
|
/* google.protobuf.Timestamp expires_at = 5; */
|
||||||
|
if (message.expiresAt)
|
||||||
|
Timestamp.internalBinaryWrite(message.expiresAt, writer.tag(5, WireType.LengthDelimited).fork(), options).join();
|
||||||
|
/* 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.GetCachedBlobResponse.Blob
|
||||||
|
*/
|
||||||
|
export const GetCachedBlobResponse_Blob = new GetCachedBlobResponse_Blob$Type();
|
||||||
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
|
class GetCacheBlobUploadURLRequest$Type extends MessageType<GetCacheBlobUploadURLRequest> {
|
||||||
|
constructor() {
|
||||||
|
super("github.actions.results.api.v1.GetCacheBlobUploadURLRequest", [
|
||||||
|
{ 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: "organization", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
||||||
|
{ no: 4, name: "keys", kind: "scalar", repeat: 2 /*RepeatType.UNPACKED*/, T: 9 /*ScalarType.STRING*/ }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
create(value?: PartialMessage<GetCacheBlobUploadURLRequest>): GetCacheBlobUploadURLRequest {
|
||||||
|
const message = { workflowRunBackendId: "", workflowJobRunBackendId: "", organization: "", keys: [] };
|
||||||
|
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
||||||
|
if (value !== undefined)
|
||||||
|
reflectionMergePartial<GetCacheBlobUploadURLRequest>(this, message, value);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GetCacheBlobUploadURLRequest): GetCacheBlobUploadURLRequest {
|
||||||
|
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 organization */ 3:
|
||||||
|
message.organization = reader.string();
|
||||||
|
break;
|
||||||
|
case /* repeated string keys */ 4:
|
||||||
|
message.keys.push(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: GetCacheBlobUploadURLRequest, 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 organization = 3; */
|
||||||
|
if (message.organization !== "")
|
||||||
|
writer.tag(3, WireType.LengthDelimited).string(message.organization);
|
||||||
|
/* repeated string keys = 4; */
|
||||||
|
for (let i = 0; i < message.keys.length; i++)
|
||||||
|
writer.tag(4, WireType.LengthDelimited).string(message.keys[i]);
|
||||||
|
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.GetCacheBlobUploadURLRequest
|
||||||
|
*/
|
||||||
|
export const GetCacheBlobUploadURLRequest = new GetCacheBlobUploadURLRequest$Type();
|
||||||
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
|
class GetCacheBlobUploadURLResponse$Type extends MessageType<GetCacheBlobUploadURLResponse> {
|
||||||
|
constructor() {
|
||||||
|
super("github.actions.results.api.v1.GetCacheBlobUploadURLResponse", [
|
||||||
|
{ no: 1, name: "urls", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => GetCacheBlobUploadURLResponse_Url }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
create(value?: PartialMessage<GetCacheBlobUploadURLResponse>): GetCacheBlobUploadURLResponse {
|
||||||
|
const message = { urls: [] };
|
||||||
|
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
||||||
|
if (value !== undefined)
|
||||||
|
reflectionMergePartial<GetCacheBlobUploadURLResponse>(this, message, value);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GetCacheBlobUploadURLResponse): GetCacheBlobUploadURLResponse {
|
||||||
|
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.GetCacheBlobUploadURLResponse.Url urls */ 1:
|
||||||
|
message.urls.push(GetCacheBlobUploadURLResponse_Url.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: GetCacheBlobUploadURLResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||||
|
/* repeated github.actions.results.api.v1.GetCacheBlobUploadURLResponse.Url urls = 1; */
|
||||||
|
for (let i = 0; i < message.urls.length; i++)
|
||||||
|
GetCacheBlobUploadURLResponse_Url.internalBinaryWrite(message.urls[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.GetCacheBlobUploadURLResponse
|
||||||
|
*/
|
||||||
|
export const GetCacheBlobUploadURLResponse = new GetCacheBlobUploadURLResponse$Type();
|
||||||
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
|
class GetCacheBlobUploadURLResponse_Url$Type extends MessageType<GetCacheBlobUploadURLResponse_Url> {
|
||||||
|
constructor() {
|
||||||
|
super("github.actions.results.api.v1.GetCacheBlobUploadURLResponse.Url", [
|
||||||
|
{ no: 1, name: "key", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
||||||
|
{ no: 2, name: "url", kind: "scalar", T: 9 /*ScalarType.STRING*/ }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
create(value?: PartialMessage<GetCacheBlobUploadURLResponse_Url>): GetCacheBlobUploadURLResponse_Url {
|
||||||
|
const message = { key: "", url: "" };
|
||||||
|
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
||||||
|
if (value !== undefined)
|
||||||
|
reflectionMergePartial<GetCacheBlobUploadURLResponse_Url>(this, message, value);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GetCacheBlobUploadURLResponse_Url): GetCacheBlobUploadURLResponse_Url {
|
||||||
|
let message = target ?? this.create(), end = reader.pos + length;
|
||||||
|
while (reader.pos < end) {
|
||||||
|
let [fieldNo, wireType] = reader.tag();
|
||||||
|
switch (fieldNo) {
|
||||||
|
case /* string key */ 1:
|
||||||
|
message.key = reader.string();
|
||||||
|
break;
|
||||||
|
case /* string url */ 2:
|
||||||
|
message.url = 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: GetCacheBlobUploadURLResponse_Url, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||||
|
/* string key = 1; */
|
||||||
|
if (message.key !== "")
|
||||||
|
writer.tag(1, WireType.LengthDelimited).string(message.key);
|
||||||
|
/* string url = 2; */
|
||||||
|
if (message.url !== "")
|
||||||
|
writer.tag(2, WireType.LengthDelimited).string(message.url);
|
||||||
|
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.GetCacheBlobUploadURLResponse.Url
|
||||||
|
*/
|
||||||
|
export const GetCacheBlobUploadURLResponse_Url = new GetCacheBlobUploadURLResponse_Url$Type();
|
||||||
|
/**
|
||||||
|
* @generated ServiceType for protobuf service github.actions.results.api.v1.BlobCacheService
|
||||||
|
*/
|
||||||
|
export const BlobCacheService = new ServiceType("github.actions.results.api.v1.BlobCacheService", [
|
||||||
|
{ name: "GetCachedBlob", options: {}, I: GetCachedBlobRequest, O: GetCachedBlobResponse },
|
||||||
|
{ name: "GetCacheBlobUploadURL", options: {}, I: GetCacheBlobUploadURLRequest, O: GetCacheBlobUploadURLResponse }
|
||||||
|
]);
|
||||||
@@ -0,0 +1,433 @@
|
|||||||
|
import {
|
||||||
|
TwirpContext,
|
||||||
|
TwirpServer,
|
||||||
|
RouterEvents,
|
||||||
|
TwirpError,
|
||||||
|
TwirpErrorCode,
|
||||||
|
Interceptor,
|
||||||
|
TwirpContentType,
|
||||||
|
chainInterceptors,
|
||||||
|
} from "twirp-ts";
|
||||||
|
import {
|
||||||
|
GetCachedBlobRequest,
|
||||||
|
GetCachedBlobResponse,
|
||||||
|
GetCacheBlobUploadURLRequest,
|
||||||
|
GetCacheBlobUploadURLResponse,
|
||||||
|
} from "./blobcache";
|
||||||
|
|
||||||
|
//==================================//
|
||||||
|
// Client Code //
|
||||||
|
//==================================//
|
||||||
|
|
||||||
|
interface Rpc {
|
||||||
|
request(
|
||||||
|
service: string,
|
||||||
|
method: string,
|
||||||
|
contentType: "application/json" | "application/protobuf",
|
||||||
|
data: object | Uint8Array
|
||||||
|
): Promise<object | Uint8Array>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BlobCacheServiceClient {
|
||||||
|
GetCachedBlob(request: GetCachedBlobRequest): Promise<GetCachedBlobResponse>;
|
||||||
|
GetCacheBlobUploadURL(
|
||||||
|
request: GetCacheBlobUploadURLRequest
|
||||||
|
): Promise<GetCacheBlobUploadURLResponse>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BlobCacheServiceClientJSON implements BlobCacheServiceClient {
|
||||||
|
private readonly rpc: Rpc;
|
||||||
|
constructor(rpc: Rpc) {
|
||||||
|
this.rpc = rpc;
|
||||||
|
this.GetCachedBlob.bind(this);
|
||||||
|
this.GetCacheBlobUploadURL.bind(this);
|
||||||
|
}
|
||||||
|
GetCachedBlob(request: GetCachedBlobRequest): Promise<GetCachedBlobResponse> {
|
||||||
|
const data = GetCachedBlobRequest.toJson(request, {
|
||||||
|
useProtoFieldName: true,
|
||||||
|
emitDefaultValues: false,
|
||||||
|
});
|
||||||
|
const promise = this.rpc.request(
|
||||||
|
"github.actions.results.api.v1.BlobCacheService",
|
||||||
|
"GetCachedBlob",
|
||||||
|
"application/json",
|
||||||
|
data as object
|
||||||
|
);
|
||||||
|
return promise.then((data) =>
|
||||||
|
GetCachedBlobResponse.fromJson(data as any, { ignoreUnknownFields: true })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
GetCacheBlobUploadURL(
|
||||||
|
request: GetCacheBlobUploadURLRequest
|
||||||
|
): Promise<GetCacheBlobUploadURLResponse> {
|
||||||
|
const data = GetCacheBlobUploadURLRequest.toJson(request, {
|
||||||
|
useProtoFieldName: true,
|
||||||
|
emitDefaultValues: false,
|
||||||
|
});
|
||||||
|
const promise = this.rpc.request(
|
||||||
|
"github.actions.results.api.v1.BlobCacheService",
|
||||||
|
"GetCacheBlobUploadURL",
|
||||||
|
"application/json",
|
||||||
|
data as object
|
||||||
|
);
|
||||||
|
return promise.then((data) =>
|
||||||
|
GetCacheBlobUploadURLResponse.fromJson(data as any, {
|
||||||
|
ignoreUnknownFields: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BlobCacheServiceClientProtobuf implements BlobCacheServiceClient {
|
||||||
|
private readonly rpc: Rpc;
|
||||||
|
constructor(rpc: Rpc) {
|
||||||
|
this.rpc = rpc;
|
||||||
|
this.GetCachedBlob.bind(this);
|
||||||
|
this.GetCacheBlobUploadURL.bind(this);
|
||||||
|
}
|
||||||
|
GetCachedBlob(request: GetCachedBlobRequest): Promise<GetCachedBlobResponse> {
|
||||||
|
const data = GetCachedBlobRequest.toBinary(request);
|
||||||
|
const promise = this.rpc.request(
|
||||||
|
"github.actions.results.api.v1.BlobCacheService",
|
||||||
|
"GetCachedBlob",
|
||||||
|
"application/protobuf",
|
||||||
|
data
|
||||||
|
);
|
||||||
|
return promise.then((data) =>
|
||||||
|
GetCachedBlobResponse.fromBinary(data as Uint8Array)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
GetCacheBlobUploadURL(
|
||||||
|
request: GetCacheBlobUploadURLRequest
|
||||||
|
): Promise<GetCacheBlobUploadURLResponse> {
|
||||||
|
const data = GetCacheBlobUploadURLRequest.toBinary(request);
|
||||||
|
const promise = this.rpc.request(
|
||||||
|
"github.actions.results.api.v1.BlobCacheService",
|
||||||
|
"GetCacheBlobUploadURL",
|
||||||
|
"application/protobuf",
|
||||||
|
data
|
||||||
|
);
|
||||||
|
return promise.then((data) =>
|
||||||
|
GetCacheBlobUploadURLResponse.fromBinary(data as Uint8Array)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================//
|
||||||
|
// Server Code //
|
||||||
|
//==================================//
|
||||||
|
|
||||||
|
export interface BlobCacheServiceTwirp<T extends TwirpContext = TwirpContext> {
|
||||||
|
GetCachedBlob(
|
||||||
|
ctx: T,
|
||||||
|
request: GetCachedBlobRequest
|
||||||
|
): Promise<GetCachedBlobResponse>;
|
||||||
|
GetCacheBlobUploadURL(
|
||||||
|
ctx: T,
|
||||||
|
request: GetCacheBlobUploadURLRequest
|
||||||
|
): Promise<GetCacheBlobUploadURLResponse>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum BlobCacheServiceMethod {
|
||||||
|
GetCachedBlob = "GetCachedBlob",
|
||||||
|
GetCacheBlobUploadURL = "GetCacheBlobUploadURL",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BlobCacheServiceMethodList = [
|
||||||
|
BlobCacheServiceMethod.GetCachedBlob,
|
||||||
|
BlobCacheServiceMethod.GetCacheBlobUploadURL,
|
||||||
|
];
|
||||||
|
|
||||||
|
export function createBlobCacheServiceServer<
|
||||||
|
T extends TwirpContext = TwirpContext
|
||||||
|
>(service: BlobCacheServiceTwirp<T>) {
|
||||||
|
return new TwirpServer<BlobCacheServiceTwirp, T>({
|
||||||
|
service,
|
||||||
|
packageName: "github.actions.results.api.v1",
|
||||||
|
serviceName: "BlobCacheService",
|
||||||
|
methodList: BlobCacheServiceMethodList,
|
||||||
|
matchRoute: matchBlobCacheServiceRoute,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchBlobCacheServiceRoute<T extends TwirpContext = TwirpContext>(
|
||||||
|
method: string,
|
||||||
|
events: RouterEvents<T>
|
||||||
|
) {
|
||||||
|
switch (method) {
|
||||||
|
case "GetCachedBlob":
|
||||||
|
return async (
|
||||||
|
ctx: T,
|
||||||
|
service: BlobCacheServiceTwirp,
|
||||||
|
data: Buffer,
|
||||||
|
interceptors?: Interceptor<
|
||||||
|
T,
|
||||||
|
GetCachedBlobRequest,
|
||||||
|
GetCachedBlobResponse
|
||||||
|
>[]
|
||||||
|
) => {
|
||||||
|
ctx = { ...ctx, methodName: "GetCachedBlob" };
|
||||||
|
await events.onMatch(ctx);
|
||||||
|
return handleBlobCacheServiceGetCachedBlobRequest(
|
||||||
|
ctx,
|
||||||
|
service,
|
||||||
|
data,
|
||||||
|
interceptors
|
||||||
|
);
|
||||||
|
};
|
||||||
|
case "GetCacheBlobUploadURL":
|
||||||
|
return async (
|
||||||
|
ctx: T,
|
||||||
|
service: BlobCacheServiceTwirp,
|
||||||
|
data: Buffer,
|
||||||
|
interceptors?: Interceptor<
|
||||||
|
T,
|
||||||
|
GetCacheBlobUploadURLRequest,
|
||||||
|
GetCacheBlobUploadURLResponse
|
||||||
|
>[]
|
||||||
|
) => {
|
||||||
|
ctx = { ...ctx, methodName: "GetCacheBlobUploadURL" };
|
||||||
|
await events.onMatch(ctx);
|
||||||
|
return handleBlobCacheServiceGetCacheBlobUploadURLRequest(
|
||||||
|
ctx,
|
||||||
|
service,
|
||||||
|
data,
|
||||||
|
interceptors
|
||||||
|
);
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
events.onNotFound();
|
||||||
|
const msg = `no handler found`;
|
||||||
|
throw new TwirpError(TwirpErrorCode.BadRoute, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBlobCacheServiceGetCachedBlobRequest<
|
||||||
|
T extends TwirpContext = TwirpContext
|
||||||
|
>(
|
||||||
|
ctx: T,
|
||||||
|
service: BlobCacheServiceTwirp,
|
||||||
|
data: Buffer,
|
||||||
|
interceptors?: Interceptor<T, GetCachedBlobRequest, GetCachedBlobResponse>[]
|
||||||
|
): Promise<string | Uint8Array> {
|
||||||
|
switch (ctx.contentType) {
|
||||||
|
case TwirpContentType.JSON:
|
||||||
|
return handleBlobCacheServiceGetCachedBlobJSON<T>(
|
||||||
|
ctx,
|
||||||
|
service,
|
||||||
|
data,
|
||||||
|
interceptors
|
||||||
|
);
|
||||||
|
case TwirpContentType.Protobuf:
|
||||||
|
return handleBlobCacheServiceGetCachedBlobProtobuf<T>(
|
||||||
|
ctx,
|
||||||
|
service,
|
||||||
|
data,
|
||||||
|
interceptors
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
const msg = "unexpected Content-Type";
|
||||||
|
throw new TwirpError(TwirpErrorCode.BadRoute, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBlobCacheServiceGetCacheBlobUploadURLRequest<
|
||||||
|
T extends TwirpContext = TwirpContext
|
||||||
|
>(
|
||||||
|
ctx: T,
|
||||||
|
service: BlobCacheServiceTwirp,
|
||||||
|
data: Buffer,
|
||||||
|
interceptors?: Interceptor<
|
||||||
|
T,
|
||||||
|
GetCacheBlobUploadURLRequest,
|
||||||
|
GetCacheBlobUploadURLResponse
|
||||||
|
>[]
|
||||||
|
): Promise<string | Uint8Array> {
|
||||||
|
switch (ctx.contentType) {
|
||||||
|
case TwirpContentType.JSON:
|
||||||
|
return handleBlobCacheServiceGetCacheBlobUploadURLJSON<T>(
|
||||||
|
ctx,
|
||||||
|
service,
|
||||||
|
data,
|
||||||
|
interceptors
|
||||||
|
);
|
||||||
|
case TwirpContentType.Protobuf:
|
||||||
|
return handleBlobCacheServiceGetCacheBlobUploadURLProtobuf<T>(
|
||||||
|
ctx,
|
||||||
|
service,
|
||||||
|
data,
|
||||||
|
interceptors
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
const msg = "unexpected Content-Type";
|
||||||
|
throw new TwirpError(TwirpErrorCode.BadRoute, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function handleBlobCacheServiceGetCachedBlobJSON<
|
||||||
|
T extends TwirpContext = TwirpContext
|
||||||
|
>(
|
||||||
|
ctx: T,
|
||||||
|
service: BlobCacheServiceTwirp,
|
||||||
|
data: Buffer,
|
||||||
|
interceptors?: Interceptor<T, GetCachedBlobRequest, GetCachedBlobResponse>[]
|
||||||
|
) {
|
||||||
|
let request: GetCachedBlobRequest;
|
||||||
|
let response: GetCachedBlobResponse;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const body = JSON.parse(data.toString() || "{}");
|
||||||
|
request = GetCachedBlobRequest.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,
|
||||||
|
GetCachedBlobRequest,
|
||||||
|
GetCachedBlobResponse
|
||||||
|
>;
|
||||||
|
response = await interceptor(ctx, request!, (ctx, inputReq) => {
|
||||||
|
return service.GetCachedBlob(ctx, inputReq);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
response = await service.GetCachedBlob(ctx, request!);
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(
|
||||||
|
GetCachedBlobResponse.toJson(response, {
|
||||||
|
useProtoFieldName: true,
|
||||||
|
emitDefaultValues: false,
|
||||||
|
}) as string
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleBlobCacheServiceGetCacheBlobUploadURLJSON<
|
||||||
|
T extends TwirpContext = TwirpContext
|
||||||
|
>(
|
||||||
|
ctx: T,
|
||||||
|
service: BlobCacheServiceTwirp,
|
||||||
|
data: Buffer,
|
||||||
|
interceptors?: Interceptor<
|
||||||
|
T,
|
||||||
|
GetCacheBlobUploadURLRequest,
|
||||||
|
GetCacheBlobUploadURLResponse
|
||||||
|
>[]
|
||||||
|
) {
|
||||||
|
let request: GetCacheBlobUploadURLRequest;
|
||||||
|
let response: GetCacheBlobUploadURLResponse;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const body = JSON.parse(data.toString() || "{}");
|
||||||
|
request = GetCacheBlobUploadURLRequest.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,
|
||||||
|
GetCacheBlobUploadURLRequest,
|
||||||
|
GetCacheBlobUploadURLResponse
|
||||||
|
>;
|
||||||
|
response = await interceptor(ctx, request!, (ctx, inputReq) => {
|
||||||
|
return service.GetCacheBlobUploadURL(ctx, inputReq);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
response = await service.GetCacheBlobUploadURL(ctx, request!);
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(
|
||||||
|
GetCacheBlobUploadURLResponse.toJson(response, {
|
||||||
|
useProtoFieldName: true,
|
||||||
|
emitDefaultValues: false,
|
||||||
|
}) as string
|
||||||
|
);
|
||||||
|
}
|
||||||
|
async function handleBlobCacheServiceGetCachedBlobProtobuf<
|
||||||
|
T extends TwirpContext = TwirpContext
|
||||||
|
>(
|
||||||
|
ctx: T,
|
||||||
|
service: BlobCacheServiceTwirp,
|
||||||
|
data: Buffer,
|
||||||
|
interceptors?: Interceptor<T, GetCachedBlobRequest, GetCachedBlobResponse>[]
|
||||||
|
) {
|
||||||
|
let request: GetCachedBlobRequest;
|
||||||
|
let response: GetCachedBlobResponse;
|
||||||
|
|
||||||
|
try {
|
||||||
|
request = GetCachedBlobRequest.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,
|
||||||
|
GetCachedBlobRequest,
|
||||||
|
GetCachedBlobResponse
|
||||||
|
>;
|
||||||
|
response = await interceptor(ctx, request!, (ctx, inputReq) => {
|
||||||
|
return service.GetCachedBlob(ctx, inputReq);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
response = await service.GetCachedBlob(ctx, request!);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Buffer.from(GetCachedBlobResponse.toBinary(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleBlobCacheServiceGetCacheBlobUploadURLProtobuf<
|
||||||
|
T extends TwirpContext = TwirpContext
|
||||||
|
>(
|
||||||
|
ctx: T,
|
||||||
|
service: BlobCacheServiceTwirp,
|
||||||
|
data: Buffer,
|
||||||
|
interceptors?: Interceptor<
|
||||||
|
T,
|
||||||
|
GetCacheBlobUploadURLRequest,
|
||||||
|
GetCacheBlobUploadURLResponse
|
||||||
|
>[]
|
||||||
|
) {
|
||||||
|
let request: GetCacheBlobUploadURLRequest;
|
||||||
|
let response: GetCacheBlobUploadURLResponse;
|
||||||
|
|
||||||
|
try {
|
||||||
|
request = GetCacheBlobUploadURLRequest.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,
|
||||||
|
GetCacheBlobUploadURLRequest,
|
||||||
|
GetCacheBlobUploadURLResponse
|
||||||
|
>;
|
||||||
|
response = await interceptor(ctx, request!, (ctx, inputReq) => {
|
||||||
|
return service.GetCacheBlobUploadURL(ctx, inputReq);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
response = await service.GetCacheBlobUploadURL(ctx, request!);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Buffer.from(GetCacheBlobUploadURLResponse.toBinary(response));
|
||||||
|
}
|
||||||
+3
-1
@@ -36,11 +36,12 @@ import {
|
|||||||
retryHttpClientResponse,
|
retryHttpClientResponse,
|
||||||
retryTypedResponse
|
retryTypedResponse
|
||||||
} from './requestUtils'
|
} from './requestUtils'
|
||||||
|
import {CacheUrl} from './constants'
|
||||||
|
|
||||||
const versionSalt = '1.0'
|
const versionSalt = '1.0'
|
||||||
|
|
||||||
function getCacheApiUrl(resource: string): string {
|
function getCacheApiUrl(resource: string): string {
|
||||||
const baseUrl: string = process.env['ACTIONS_CACHE_URL'] || ''
|
const baseUrl: string = CacheUrl || ''
|
||||||
if (!baseUrl) {
|
if (!baseUrl) {
|
||||||
throw new Error('Cache Service Url not found, unable to restore cache.')
|
throw new Error('Cache Service Url not found, unable to restore cache.')
|
||||||
}
|
}
|
||||||
@@ -111,6 +112,7 @@ export async function getCacheEntry(
|
|||||||
options?.compressionMethod,
|
options?.compressionMethod,
|
||||||
options?.enableCrossOsArchive
|
options?.enableCrossOsArchive
|
||||||
)
|
)
|
||||||
|
|
||||||
const resource = `cache?keys=${encodeURIComponent(
|
const resource = `cache?keys=${encodeURIComponent(
|
||||||
keys.join(',')
|
keys.join(',')
|
||||||
)}&version=${version}`
|
)}&version=${version}`
|
||||||
|
|||||||
+197
@@ -0,0 +1,197 @@
|
|||||||
|
import {HttpClient, HttpClientResponse, HttpCodes} from '@actions/http-client'
|
||||||
|
import {BearerCredentialHandler} from '@actions/http-client/lib/auth'
|
||||||
|
import {info, debug} from '@actions/core'
|
||||||
|
import {BlobCacheServiceClientJSON} from '../generated/results/api/v1/blobcache.twirp'
|
||||||
|
import {CacheUrl} from './constants'
|
||||||
|
import {getRuntimeToken} from './config'
|
||||||
|
// import {getUserAgentString} from './user-agent'
|
||||||
|
// import {NetworkError, UsageError} from './errors'
|
||||||
|
|
||||||
|
// The twirp http client must implement this interface
|
||||||
|
interface Rpc {
|
||||||
|
request(
|
||||||
|
service: string,
|
||||||
|
method: string,
|
||||||
|
contentType: 'application/json' | 'application/protobuf',
|
||||||
|
data: object | Uint8Array
|
||||||
|
): Promise<object | Uint8Array>
|
||||||
|
}
|
||||||
|
|
||||||
|
class BlobCacheServiceClient implements Rpc {
|
||||||
|
private httpClient: HttpClient
|
||||||
|
private baseUrl: string
|
||||||
|
private maxAttempts = 5
|
||||||
|
private baseRetryIntervalMilliseconds = 3000
|
||||||
|
private retryMultiplier = 1.5
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
userAgent: string,
|
||||||
|
maxAttempts?: number,
|
||||||
|
baseRetryIntervalMilliseconds?: number,
|
||||||
|
retryMultiplier?: number
|
||||||
|
) {
|
||||||
|
const token = getRuntimeToken()
|
||||||
|
this.baseUrl = CacheUrl
|
||||||
|
if (maxAttempts) {
|
||||||
|
this.maxAttempts = maxAttempts
|
||||||
|
}
|
||||||
|
if (baseRetryIntervalMilliseconds) {
|
||||||
|
this.baseRetryIntervalMilliseconds = baseRetryIntervalMilliseconds
|
||||||
|
}
|
||||||
|
if (retryMultiplier) {
|
||||||
|
this.retryMultiplier = retryMultiplier
|
||||||
|
}
|
||||||
|
|
||||||
|
this.httpClient = new HttpClient(userAgent, [
|
||||||
|
new BearerCredentialHandler(token)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function satisfies the Rpc interface. It is compatible with the JSON
|
||||||
|
// JSON generated client.
|
||||||
|
async request(
|
||||||
|
service: string,
|
||||||
|
method: string,
|
||||||
|
contentType: 'application/json' | 'application/protobuf',
|
||||||
|
data: object | Uint8Array
|
||||||
|
): Promise<object | Uint8Array> {
|
||||||
|
const url = new URL(`/twirp/${service}/${method}`, this.baseUrl).href
|
||||||
|
debug(`[Request] ${method} ${url}`)
|
||||||
|
const headers = {
|
||||||
|
'Content-Type': contentType
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const {body} = await this.retryableRequest(async () =>
|
||||||
|
this.httpClient.post(url, JSON.stringify(data), headers)
|
||||||
|
)
|
||||||
|
|
||||||
|
return body
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to ${method}: ${error.message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async retryableRequest(
|
||||||
|
operation: () => Promise<HttpClientResponse>
|
||||||
|
): Promise<{response: HttpClientResponse; body: object}> {
|
||||||
|
let attempt = 0
|
||||||
|
let errorMessage = ''
|
||||||
|
let rawBody = ''
|
||||||
|
while (attempt < this.maxAttempts) {
|
||||||
|
let isRetryable = false
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await operation()
|
||||||
|
const statusCode = response.message.statusCode
|
||||||
|
rawBody = await response.readBody()
|
||||||
|
debug(`[Response] - ${response.message.statusCode}`)
|
||||||
|
debug(`Headers: ${JSON.stringify(response.message.headers, null, 2)}`)
|
||||||
|
const body = JSON.parse(rawBody)
|
||||||
|
debug(`Body: ${JSON.stringify(body, null, 2)}`)
|
||||||
|
if (this.isSuccessStatusCode(statusCode)) {
|
||||||
|
return {response, body}
|
||||||
|
}
|
||||||
|
isRetryable = this.isRetryableHttpStatusCode(statusCode)
|
||||||
|
errorMessage = `Failed request: (${statusCode}) ${response.message.statusMessage}`
|
||||||
|
if (body.msg) {
|
||||||
|
// if (UsageError.isUsageErrorMessage(body.msg)) {
|
||||||
|
// throw new UsageError()
|
||||||
|
// }
|
||||||
|
|
||||||
|
errorMessage = `${errorMessage}: ${body.msg}`
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof SyntaxError) {
|
||||||
|
debug(`Raw Body: ${rawBody}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (error instanceof UsageError) {
|
||||||
|
// throw error
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (NetworkError.isNetworkErrorCode(error?.code)) {
|
||||||
|
// throw new NetworkError(error?.code)
|
||||||
|
// }
|
||||||
|
|
||||||
|
isRetryable = true
|
||||||
|
errorMessage = error.message
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isRetryable) {
|
||||||
|
throw new Error(`Received non-retryable error: ${errorMessage}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attempt + 1 === this.maxAttempts) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to make request after ${this.maxAttempts} attempts: ${errorMessage}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const retryTimeMilliseconds =
|
||||||
|
this.getExponentialRetryTimeMilliseconds(attempt)
|
||||||
|
info(
|
||||||
|
`Attempt ${attempt + 1} of ${
|
||||||
|
this.maxAttempts
|
||||||
|
} failed with error: ${errorMessage}. Retrying request in ${retryTimeMilliseconds} ms...`
|
||||||
|
)
|
||||||
|
await this.sleep(retryTimeMilliseconds)
|
||||||
|
attempt++
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Request failed`)
|
||||||
|
}
|
||||||
|
|
||||||
|
isSuccessStatusCode(statusCode?: number): boolean {
|
||||||
|
if (!statusCode) return false
|
||||||
|
return statusCode >= 200 && statusCode < 300
|
||||||
|
}
|
||||||
|
|
||||||
|
isRetryableHttpStatusCode(statusCode?: number): boolean {
|
||||||
|
if (!statusCode) return false
|
||||||
|
|
||||||
|
const retryableStatusCodes = [
|
||||||
|
HttpCodes.BadGateway,
|
||||||
|
HttpCodes.GatewayTimeout,
|
||||||
|
HttpCodes.InternalServerError,
|
||||||
|
HttpCodes.ServiceUnavailable,
|
||||||
|
HttpCodes.TooManyRequests
|
||||||
|
]
|
||||||
|
|
||||||
|
return retryableStatusCodes.includes(statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
async sleep(milliseconds: number): Promise<void> {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, milliseconds))
|
||||||
|
}
|
||||||
|
|
||||||
|
getExponentialRetryTimeMilliseconds(attempt: number): number {
|
||||||
|
if (attempt < 0) {
|
||||||
|
throw new Error('attempt should be a positive integer')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attempt === 0) {
|
||||||
|
return this.baseRetryIntervalMilliseconds
|
||||||
|
}
|
||||||
|
|
||||||
|
const minTime =
|
||||||
|
this.baseRetryIntervalMilliseconds * this.retryMultiplier ** attempt
|
||||||
|
const maxTime = minTime * this.retryMultiplier
|
||||||
|
|
||||||
|
// returns a random number between minTime and maxTime (exclusive)
|
||||||
|
return Math.trunc(Math.random() * (maxTime - minTime) + minTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function internalBlobCacheTwirpClient(options?: {
|
||||||
|
maxAttempts?: number
|
||||||
|
retryIntervalMs?: number
|
||||||
|
retryMultiplier?: number
|
||||||
|
}): BlobCacheServiceClientJSON {
|
||||||
|
const client = new BlobCacheServiceClient(
|
||||||
|
'actions/cache',
|
||||||
|
options?.maxAttempts,
|
||||||
|
options?.retryIntervalMs,
|
||||||
|
options?.retryMultiplier
|
||||||
|
)
|
||||||
|
return new BlobCacheServiceClientJSON(client)
|
||||||
|
}
|
||||||
+2
-2
@@ -2,11 +2,11 @@ import * as core from '@actions/core'
|
|||||||
import * as exec from '@actions/exec'
|
import * as exec from '@actions/exec'
|
||||||
import * as glob from '@actions/glob'
|
import * as glob from '@actions/glob'
|
||||||
import * as io from '@actions/io'
|
import * as io from '@actions/io'
|
||||||
import * as crypto from 'crypto'
|
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import * as semver from 'semver'
|
import * as semver from 'semver'
|
||||||
import * as util from 'util'
|
import * as util from 'util'
|
||||||
|
import {v4 as uuidV4} from 'uuid'
|
||||||
import {
|
import {
|
||||||
CacheFilename,
|
CacheFilename,
|
||||||
CompressionMethod,
|
CompressionMethod,
|
||||||
@@ -34,7 +34,7 @@ export async function createTempDirectory(): Promise<string> {
|
|||||||
tempDirectory = path.join(baseLocation, 'actions', 'temp')
|
tempDirectory = path.join(baseLocation, 'actions', 'temp')
|
||||||
}
|
}
|
||||||
|
|
||||||
const dest = path.join(tempDirectory, crypto.randomUUID())
|
const dest = path.join(tempDirectory, uuidV4())
|
||||||
await io.mkdirP(dest)
|
await io.mkdirP(dest)
|
||||||
return dest
|
return dest
|
||||||
}
|
}
|
||||||
|
|||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
export function getRuntimeToken(): string {
|
||||||
|
const token = process.env['ACTIONS_RUNTIME_TOKEN']
|
||||||
|
if (!token) {
|
||||||
|
throw new Error('Unable to get the ACTIONS_RUNTIME_TOKEN env variable')
|
||||||
|
}
|
||||||
|
return token
|
||||||
|
}
|
||||||
+4
@@ -36,3 +36,7 @@ export const SystemTarPathOnWindows = `${process.env['SYSTEMDRIVE']}\\Windows\\S
|
|||||||
export const TarFilename = 'cache.tar'
|
export const TarFilename = 'cache.tar'
|
||||||
|
|
||||||
export const ManifestFilename = 'manifest.txt'
|
export const ManifestFilename = 'manifest.txt'
|
||||||
|
|
||||||
|
// Cache Service Metadata
|
||||||
|
export const CacheUrl = `${process.env['ACTIONS_CACHE_URL_NEXT']} || ${process.env['ACTIONS_CACHE_URL']}`
|
||||||
|
export const CacheServiceVersion = `${process.env['ACTIONS_CACHE_URL_NEXT'] ? 'v2' : 'v1'}`
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import * as core from '@actions/core'
|
||||||
|
import * as httpClient from '@actions/http-client'
|
||||||
|
import unzip from 'unzip-stream'
|
||||||
|
const packageJson = require('../../../package.json')
|
||||||
|
|
||||||
|
export async function StreamExtract(url: string, directory: string): Promise<void> {
|
||||||
|
let retryCount = 0
|
||||||
|
while (retryCount < 5) {
|
||||||
|
try {
|
||||||
|
await streamExtractExternal(url, directory)
|
||||||
|
return
|
||||||
|
} catch (error) {
|
||||||
|
retryCount++
|
||||||
|
core.info(
|
||||||
|
`Failed to download cache after ${retryCount} retries due to ${error.message}. Retrying in 5 seconds...`
|
||||||
|
)
|
||||||
|
// wait 5 seconds before retrying
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 5000))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Cache download failed after ${retryCount} retries.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function streamExtractExternal(
|
||||||
|
url: string,
|
||||||
|
directory: string
|
||||||
|
): Promise<void> {
|
||||||
|
const client = new httpClient.HttpClient(`@actions/cache-${packageJson.version}`)
|
||||||
|
const response = await client.get(url)
|
||||||
|
if (response.message.statusCode !== 200) {
|
||||||
|
core.info(`Failed to download cache. HTTP status code: ${response.message.statusCode}`)
|
||||||
|
throw new Error(
|
||||||
|
`Unexpected HTTP response from blob storage: ${response.message.statusCode} ${response.message.statusMessage}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeout = 30 * 1000 // 30 seconds
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const timerFn = (): void => {
|
||||||
|
response.message.destroy(
|
||||||
|
new Error(`Blob storage chunk did not respond in ${timeout}ms`)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const timer = setTimeout(timerFn, timeout)
|
||||||
|
|
||||||
|
response.message
|
||||||
|
.on('data', () => {
|
||||||
|
timer.refresh()
|
||||||
|
})
|
||||||
|
.on('error', (error: Error) => {
|
||||||
|
core.info(
|
||||||
|
`response.message: Cache download failed: ${error.message}`
|
||||||
|
)
|
||||||
|
clearTimeout(timer)
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
.pipe(unzip.Extract({path: directory}))
|
||||||
|
.on('close', () => {
|
||||||
|
clearTimeout(timer)
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
.on('error', (error: Error) => {
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
+130
@@ -0,0 +1,130 @@
|
|||||||
|
import * as core from '@actions/core'
|
||||||
|
import {GetCacheBlobUploadURLResponse} from '../../generated/results/api/v1/blobcache'
|
||||||
|
import {ZipUploadStream} from '@actions/artifact/lib/internal/upload/zip'
|
||||||
|
import {NetworkError} from '@actions/artifact/'
|
||||||
|
import {TransferProgressEvent} from '@azure/core-http'
|
||||||
|
import * as stream from 'stream'
|
||||||
|
import * as crypto from 'crypto'
|
||||||
|
import {
|
||||||
|
BlobClient,
|
||||||
|
BlockBlobClient,
|
||||||
|
BlockBlobUploadStreamOptions,
|
||||||
|
BlockBlobParallelUploadOptions
|
||||||
|
} from '@azure/storage-blob'
|
||||||
|
|
||||||
|
export async function UploadCacheStream(
|
||||||
|
signedUploadURL: string,
|
||||||
|
zipUploadStream: ZipUploadStream
|
||||||
|
): Promise<{}> {
|
||||||
|
let uploadByteCount = 0
|
||||||
|
let lastProgressTime = Date.now()
|
||||||
|
let timeoutId: NodeJS.Timeout | undefined
|
||||||
|
|
||||||
|
const chunkTimer = (timeout: number): NodeJS.Timeout => {
|
||||||
|
// clear the previous timeout
|
||||||
|
if (timeoutId) {
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
const now = Date.now()
|
||||||
|
// if there's been more than 30 seconds since the
|
||||||
|
// last progress event, then we'll consider the upload stalled
|
||||||
|
if (now - lastProgressTime > timeout) {
|
||||||
|
throw new Error('Upload progress stalled.')
|
||||||
|
}
|
||||||
|
}, timeout)
|
||||||
|
return timeoutId
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxConcurrency = 32
|
||||||
|
const bufferSize = 8 * 1024 * 1024 // 8 MB Chunks
|
||||||
|
const blobClient = new BlobClient(signedUploadURL)
|
||||||
|
const blockBlobClient = blobClient.getBlockBlobClient()
|
||||||
|
const timeoutDuration = 300000 // 30 seconds
|
||||||
|
|
||||||
|
core.debug(
|
||||||
|
`Uploading cache zip to blob storage with maxConcurrency: ${maxConcurrency}, bufferSize: ${bufferSize}`
|
||||||
|
)
|
||||||
|
|
||||||
|
const uploadCallback = (progress: TransferProgressEvent): void => {
|
||||||
|
core.info(`Uploaded bytes ${progress.loadedBytes}`)
|
||||||
|
uploadByteCount = progress.loadedBytes
|
||||||
|
chunkTimer(timeoutDuration)
|
||||||
|
lastProgressTime = Date.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
const options: BlockBlobUploadStreamOptions = {
|
||||||
|
blobHTTPHeaders: {blobContentType: 'zip'},
|
||||||
|
onProgress: uploadCallback
|
||||||
|
}
|
||||||
|
|
||||||
|
let sha256Hash: string | undefined = undefined
|
||||||
|
const uploadStream = new stream.PassThrough()
|
||||||
|
const hashStream = crypto.createHash('sha256')
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
core.info('Beginning upload of cache to blob storage')
|
||||||
|
try {
|
||||||
|
// Start the chunk timer
|
||||||
|
timeoutId = chunkTimer(timeoutDuration)
|
||||||
|
await blockBlobClient.uploadStream(
|
||||||
|
uploadStream,
|
||||||
|
bufferSize,
|
||||||
|
maxConcurrency,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
if (NetworkError.isNetworkErrorCode(error?.code)) {
|
||||||
|
throw new NetworkError(error?.code)
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
} finally {
|
||||||
|
// clear the timeout whether or not the upload completes
|
||||||
|
if (timeoutId) {
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
core.info('Finished uploading cache content to blob storage!')
|
||||||
|
|
||||||
|
hashStream.end()
|
||||||
|
sha256Hash = hashStream.read() as string
|
||||||
|
core.info(`SHA256 hash of uploaded artifact zip is ${sha256Hash}`)
|
||||||
|
core.info(`Uploaded: ${uploadByteCount} bytes`)
|
||||||
|
|
||||||
|
if (uploadByteCount === 0) {
|
||||||
|
core.error(
|
||||||
|
`No data was uploaded to blob storage. Reported upload byte count is 0.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
uploadSize: uploadByteCount,
|
||||||
|
sha256Hash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function UploadCacheFile(
|
||||||
|
uploadURL: GetCacheBlobUploadURLResponse,
|
||||||
|
archivePath: string,
|
||||||
|
): Promise<{}> {
|
||||||
|
core.info(`Uploading ${archivePath} to: ${JSON.stringify(uploadURL)}`)
|
||||||
|
|
||||||
|
// Specify data transfer options
|
||||||
|
const uploadOptions: BlockBlobParallelUploadOptions = {
|
||||||
|
blockSize: 4 * 1024 * 1024, // 4 MiB max block size
|
||||||
|
concurrency: 2, // maximum number of parallel transfer workers
|
||||||
|
maxSingleShotSize: 8 * 1024 * 1024, // 8 MiB initial transfer size
|
||||||
|
};
|
||||||
|
|
||||||
|
// const blobClient: BlobClient = new BlobClient(uploadURL.urls[0])
|
||||||
|
const blobClient: BlobClient = new BlobClient(uploadURL.urls[0].url)
|
||||||
|
const blockBlobClient: BlockBlobClient = blobClient.getBlockBlobClient()
|
||||||
|
|
||||||
|
core.info(`BlobClient: ${JSON.stringify(blobClient)}`)
|
||||||
|
core.info(`blockBlobClient: ${JSON.stringify(blockBlobClient)}`)
|
||||||
|
|
||||||
|
return blockBlobClient.uploadFile(archivePath, uploadOptions);
|
||||||
|
}
|
||||||
@@ -1,12 +1,5 @@
|
|||||||
# @actions/core Releases
|
# @actions/core Releases
|
||||||
|
|
||||||
### 1.11.1
|
|
||||||
- Fix uses of `crypto.randomUUID` on Node 18 and earlier [#1842](https://github.com/actions/toolkit/pull/1842)
|
|
||||||
|
|
||||||
### 1.11.0
|
|
||||||
- Add platform info utilities [#1551](https://github.com/actions/toolkit/pull/1551)
|
|
||||||
- Remove dependency on `uuid` package [#1824](https://github.com/actions/toolkit/pull/1824)
|
|
||||||
|
|
||||||
### 1.10.1
|
### 1.10.1
|
||||||
- Fix error message reference in oidc utils [#1511](https://github.com/actions/toolkit/pull/1511)
|
- Fix error message reference in oidc utils [#1511](https://github.com/actions/toolkit/pull/1511)
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import * as path from 'path'
|
|||||||
import * as core from '../src/core'
|
import * as core from '../src/core'
|
||||||
import {HttpClient} from '@actions/http-client'
|
import {HttpClient} from '@actions/http-client'
|
||||||
import {toCommandProperties} from '../src/utils'
|
import {toCommandProperties} from '../src/utils'
|
||||||
|
import * as uuid from 'uuid'
|
||||||
|
|
||||||
|
jest.mock('uuid')
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/unbound-method */
|
/* eslint-disable @typescript-eslint/unbound-method */
|
||||||
|
|
||||||
@@ -46,23 +49,11 @@ const testEnvVars = {
|
|||||||
const UUID = '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'
|
const UUID = '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'
|
||||||
const DELIMITER = `ghadelimiter_${UUID}`
|
const DELIMITER = `ghadelimiter_${UUID}`
|
||||||
|
|
||||||
jest.mock('crypto', () => ({
|
|
||||||
...jest.requireActual('crypto'),
|
|
||||||
randomUUID: jest.fn(() => UUID)
|
|
||||||
}))
|
|
||||||
|
|
||||||
const TEMP_DIR = path.join(__dirname, '_temp')
|
|
||||||
|
|
||||||
describe('@actions/core', () => {
|
describe('@actions/core', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
const filePath = TEMP_DIR
|
const filePath = path.join(__dirname, `test`)
|
||||||
if (!fs.existsSync(filePath)) {
|
if (!fs.existsSync(filePath)) {
|
||||||
fs.mkdirSync(filePath)
|
fs.mkdirSync(filePath)
|
||||||
} else {
|
|
||||||
// Clear out the temp directory
|
|
||||||
for (const file of fs.readdirSync(filePath)) {
|
|
||||||
fs.unlinkSync(path.join(filePath, file))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -71,6 +62,10 @@ describe('@actions/core', () => {
|
|||||||
process.env[key] = testEnvVars[key as keyof typeof testEnvVars]
|
process.env[key] = testEnvVars[key as keyof typeof testEnvVars]
|
||||||
}
|
}
|
||||||
process.stdout.write = jest.fn()
|
process.stdout.write = jest.fn()
|
||||||
|
|
||||||
|
jest.spyOn(uuid, 'v4').mockImplementation(() => {
|
||||||
|
return UUID
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -146,7 +141,7 @@ describe('@actions/core', () => {
|
|||||||
`Unexpected input: value should not contain the delimiter "${DELIMITER}"`
|
`Unexpected input: value should not contain the delimiter "${DELIMITER}"`
|
||||||
)
|
)
|
||||||
|
|
||||||
const filePath = path.join(TEMP_DIR, command)
|
const filePath = path.join(__dirname, `test/${command}`)
|
||||||
fs.unlinkSync(filePath)
|
fs.unlinkSync(filePath)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -160,7 +155,7 @@ describe('@actions/core', () => {
|
|||||||
`Unexpected input: name should not contain the delimiter "${DELIMITER}"`
|
`Unexpected input: name should not contain the delimiter "${DELIMITER}"`
|
||||||
)
|
)
|
||||||
|
|
||||||
const filePath = path.join(TEMP_DIR, command)
|
const filePath = path.join(__dirname, `test/${command}`)
|
||||||
fs.unlinkSync(filePath)
|
fs.unlinkSync(filePath)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -352,7 +347,7 @@ describe('@actions/core', () => {
|
|||||||
`Unexpected input: value should not contain the delimiter "${DELIMITER}"`
|
`Unexpected input: value should not contain the delimiter "${DELIMITER}"`
|
||||||
)
|
)
|
||||||
|
|
||||||
const filePath = path.join(TEMP_DIR, command)
|
const filePath = path.join(__dirname, `test/${command}`)
|
||||||
fs.unlinkSync(filePath)
|
fs.unlinkSync(filePath)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -366,7 +361,7 @@ describe('@actions/core', () => {
|
|||||||
`Unexpected input: name should not contain the delimiter "${DELIMITER}"`
|
`Unexpected input: name should not contain the delimiter "${DELIMITER}"`
|
||||||
)
|
)
|
||||||
|
|
||||||
const filePath = path.join(TEMP_DIR, command)
|
const filePath = path.join(__dirname, `test/${command}`)
|
||||||
fs.unlinkSync(filePath)
|
fs.unlinkSync(filePath)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -590,7 +585,7 @@ describe('@actions/core', () => {
|
|||||||
`Unexpected input: value should not contain the delimiter "${DELIMITER}"`
|
`Unexpected input: value should not contain the delimiter "${DELIMITER}"`
|
||||||
)
|
)
|
||||||
|
|
||||||
const filePath = path.join(TEMP_DIR, command)
|
const filePath = path.join(__dirname, `test/${command}`)
|
||||||
fs.unlinkSync(filePath)
|
fs.unlinkSync(filePath)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -604,7 +599,7 @@ describe('@actions/core', () => {
|
|||||||
`Unexpected input: name should not contain the delimiter "${DELIMITER}"`
|
`Unexpected input: name should not contain the delimiter "${DELIMITER}"`
|
||||||
)
|
)
|
||||||
|
|
||||||
const filePath = path.join(TEMP_DIR, command)
|
const filePath = path.join(__dirname, `test/${command}`)
|
||||||
fs.unlinkSync(filePath)
|
fs.unlinkSync(filePath)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -646,7 +641,7 @@ function assertWriteCalls(calls: string[]): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createFileCommandFile(command: string): void {
|
function createFileCommandFile(command: string): void {
|
||||||
const filePath = path.join(__dirname, `_temp/${command}`)
|
const filePath = path.join(__dirname, `test/${command}`)
|
||||||
process.env[`GITHUB_${command}`] = filePath
|
process.env[`GITHUB_${command}`] = filePath
|
||||||
fs.appendFileSync(filePath, '', {
|
fs.appendFileSync(filePath, '', {
|
||||||
encoding: 'utf8'
|
encoding: 'utf8'
|
||||||
@@ -654,7 +649,7 @@ function createFileCommandFile(command: string): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function verifyFileCommand(command: string, expectedContents: string): void {
|
function verifyFileCommand(command: string, expectedContents: string): void {
|
||||||
const filePath = path.join(__dirname, `_temp/${command}`)
|
const filePath = path.join(__dirname, `test/${command}`)
|
||||||
const contents = fs.readFileSync(filePath, 'utf8')
|
const contents = fs.readFileSync(filePath, 'utf8')
|
||||||
try {
|
try {
|
||||||
expect(contents).toEqual(expectedContents)
|
expect(contents).toEqual(expectedContents)
|
||||||
|
|||||||
Generated
+37
-10
@@ -1,19 +1,21 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/core",
|
"name": "@actions/core",
|
||||||
"version": "1.11.1",
|
"version": "1.10.1",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@actions/core",
|
"name": "@actions/core",
|
||||||
"version": "1.11.1",
|
"version": "1.10.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/exec": "^1.1.1",
|
"@actions/exec": "^1.1.1",
|
||||||
"@actions/http-client": "^2.0.1"
|
"@actions/http-client": "^2.0.1",
|
||||||
|
"uuid": "^8.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^16.18.112"
|
"@types/node": "^12.0.2",
|
||||||
|
"@types/uuid": "^8.3.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/exec": {
|
"node_modules/@actions/exec": {
|
||||||
@@ -38,9 +40,15 @@
|
|||||||
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="
|
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "16.18.112",
|
"version": "12.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.112.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.2.tgz",
|
||||||
"integrity": "sha512-EKrbKUGJROm17+dY/gMi31aJlGLJ75e1IkTojt9n6u+hnaTBDs+M1bIdOawpk2m6YUAXq/R2W0SxCng1tndHCg==",
|
"integrity": "sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/@types/uuid": {
|
||||||
|
"version": "8.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
|
||||||
|
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/tunnel": {
|
"node_modules/tunnel": {
|
||||||
@@ -50,6 +58,14 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
|
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "8.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||||
|
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/bin/uuid"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -75,15 +91,26 @@
|
|||||||
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="
|
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="
|
||||||
},
|
},
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "16.18.112",
|
"version": "12.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.112.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.2.tgz",
|
||||||
"integrity": "sha512-EKrbKUGJROm17+dY/gMi31aJlGLJ75e1IkTojt9n6u+hnaTBDs+M1bIdOawpk2m6YUAXq/R2W0SxCng1tndHCg==",
|
"integrity": "sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@types/uuid": {
|
||||||
|
"version": "8.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
|
||||||
|
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"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",
|
||||||
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
|
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
|
||||||
|
},
|
||||||
|
"uuid": {
|
||||||
|
"version": "8.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||||
|
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/core",
|
"name": "@actions/core",
|
||||||
"version": "1.11.1",
|
"version": "1.10.1",
|
||||||
"description": "Actions core lib",
|
"description": "Actions core lib",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"github",
|
"github",
|
||||||
@@ -37,9 +37,11 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/exec": "^1.1.1",
|
"@actions/exec": "^1.1.1",
|
||||||
"@actions/http-client": "^2.0.1"
|
"@actions/http-client": "^2.0.1",
|
||||||
|
"uuid": "^8.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^16.18.112"
|
"@types/node": "^12.0.2",
|
||||||
|
"@types/uuid": "^8.3.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,9 +3,9 @@
|
|||||||
// We use any as a valid input type
|
// We use any as a valid input type
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
import * as crypto from 'crypto'
|
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import * as os from 'os'
|
import * as os from 'os'
|
||||||
|
import {v4 as uuidv4} from 'uuid'
|
||||||
import {toCommandValue} from './utils'
|
import {toCommandValue} from './utils'
|
||||||
|
|
||||||
export function issueFileCommand(command: string, message: any): void {
|
export function issueFileCommand(command: string, message: any): void {
|
||||||
@@ -25,7 +25,7 @@ export function issueFileCommand(command: string, message: any): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function prepareKeyValueMessage(key: string, value: any): string {
|
export function prepareKeyValueMessage(key: string, value: any): string {
|
||||||
const delimiter = `ghadelimiter_${crypto.randomUUID()}`
|
const delimiter = `ghadelimiter_${uuidv4()}`
|
||||||
const convertedValue = toCommandValue(value)
|
const convertedValue = toCommandValue(value)
|
||||||
|
|
||||||
// These should realistically never happen, but just in case someone finds a
|
// These should realistically never happen, but just in case someone finds a
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
# @actions/glob Releases
|
# @actions/glob Releases
|
||||||
|
|
||||||
### 0.5.0
|
|
||||||
- Added `excludeHiddenFiles` option, which is disabled by default to preserve existing behavior [#1791: Add glob option to ignore hidden files](https://github.com/actions/toolkit/pull/1791)
|
|
||||||
|
|
||||||
### 0.4.0
|
### 0.4.0
|
||||||
- Pass in the current workspace as a parameter to HashFiles [#1318](https://github.com/actions/toolkit/pull/1318)
|
- Pass in the current workspace as a parameter to HashFiles [#1318](https://github.com/actions/toolkit/pull/1318)
|
||||||
|
|
||||||
|
|||||||
@@ -708,7 +708,7 @@ describe('globber', () => {
|
|||||||
expect(itemPaths).toEqual([])
|
expect(itemPaths).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns hidden files by default', async () => {
|
it('returns hidden files', async () => {
|
||||||
// Create the following layout:
|
// Create the following layout:
|
||||||
// <root>
|
// <root>
|
||||||
// <root>/.emptyFolder
|
// <root>/.emptyFolder
|
||||||
@@ -734,26 +734,6 @@ describe('globber', () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('ignores hidden files when excludeHiddenFiles is set', async () => {
|
|
||||||
// Create the following layout:
|
|
||||||
// <root>
|
|
||||||
// <root>/.emptyFolder
|
|
||||||
// <root>/.file
|
|
||||||
// <root>/.folder
|
|
||||||
// <root>/.folder/file
|
|
||||||
const root = path.join(getTestTemp(), 'ignores-hidden-files')
|
|
||||||
await createHiddenDirectory(path.join(root, '.emptyFolder'))
|
|
||||||
await createHiddenDirectory(path.join(root, '.folder'))
|
|
||||||
await createHiddenFile(path.join(root, '.file'), 'test .file content')
|
|
||||||
await fs.writeFile(
|
|
||||||
path.join(root, '.folder', 'file'),
|
|
||||||
'test .folder/file content'
|
|
||||||
)
|
|
||||||
|
|
||||||
const itemPaths = await glob(root, {excludeHiddenFiles: true})
|
|
||||||
expect(itemPaths).toEqual([root])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('returns normalized paths', async () => {
|
it('returns normalized paths', async () => {
|
||||||
// Create the following layout:
|
// Create the following layout:
|
||||||
// <root>/hello/world.txt
|
// <root>/hello/world.txt
|
||||||
|
|||||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/glob",
|
"name": "@actions/glob",
|
||||||
"version": "0.5.0",
|
"version": "0.4.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"description": "Actions glob lib",
|
"description": "Actions glob lib",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/glob",
|
"name": "@actions/glob",
|
||||||
"version": "0.5.0",
|
"version": "0.4.0",
|
||||||
"preview": true,
|
"preview": true,
|
||||||
"description": "Actions glob lib",
|
"description": "Actions glob lib",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ export function getOptions(copy?: GlobOptions): GlobOptions {
|
|||||||
followSymbolicLinks: true,
|
followSymbolicLinks: true,
|
||||||
implicitDescendants: true,
|
implicitDescendants: true,
|
||||||
matchDirectories: true,
|
matchDirectories: true,
|
||||||
omitBrokenSymbolicLinks: true,
|
omitBrokenSymbolicLinks: true
|
||||||
excludeHiddenFiles: false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (copy) {
|
if (copy) {
|
||||||
@@ -33,11 +32,6 @@ export function getOptions(copy?: GlobOptions): GlobOptions {
|
|||||||
result.omitBrokenSymbolicLinks = copy.omitBrokenSymbolicLinks
|
result.omitBrokenSymbolicLinks = copy.omitBrokenSymbolicLinks
|
||||||
core.debug(`omitBrokenSymbolicLinks '${result.omitBrokenSymbolicLinks}'`)
|
core.debug(`omitBrokenSymbolicLinks '${result.omitBrokenSymbolicLinks}'`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof copy.excludeHiddenFiles === 'boolean') {
|
|
||||||
result.excludeHiddenFiles = copy.excludeHiddenFiles
|
|
||||||
core.debug(`excludeHiddenFiles '${result.excludeHiddenFiles}'`)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -36,13 +36,4 @@ export interface GlobOptions {
|
|||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
omitBrokenSymbolicLinks?: boolean
|
omitBrokenSymbolicLinks?: boolean
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates whether to exclude hidden files (files and directories starting with a `.`).
|
|
||||||
* This does not apply to Windows files and directories with the hidden attribute unless
|
|
||||||
* they are also prefixed with a `.`.
|
|
||||||
*
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
excludeHiddenFiles?: boolean
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,11 +128,6 @@ export class DefaultGlobber implements Globber {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hidden file or directory?
|
|
||||||
if (options.excludeHiddenFiles && path.basename(item.path).match(/^\./)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Directory
|
// Directory
|
||||||
if (stats.isDirectory()) {
|
if (stats.isDirectory()) {
|
||||||
// Matched
|
// Matched
|
||||||
|
|||||||
@@ -1,14 +1,5 @@
|
|||||||
## Releases
|
## Releases
|
||||||
|
|
||||||
## 2.2.3
|
|
||||||
- Fixed an issue where proxy username and password were not handled correctly [#1799](https://github.com/actions/toolkit/pull/1799)
|
|
||||||
|
|
||||||
## 2.2.2
|
|
||||||
- Better handling of url encoded usernames and passwords in proxy config [#1782](https://github.com/actions/toolkit/pull/1782)
|
|
||||||
|
|
||||||
## 2.2.1
|
|
||||||
- Make sure RequestOptions.keepAlive is applied properly on node20 runtime [#1572](https://github.com/actions/toolkit/pull/1572)
|
|
||||||
|
|
||||||
## 2.2.0
|
## 2.2.0
|
||||||
- Add function to return proxy agent dispatcher for compatibility with latest octokit packages [#1547](https://github.com/actions/toolkit/pull/1547)
|
- Add function to return proxy agent dispatcher for compatibility with latest octokit packages [#1547](https://github.com/actions/toolkit/pull/1547)
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ describe('basics', () => {
|
|||||||
// "user-agent": "typed-test-client-tests"
|
// "user-agent": "typed-test-client-tests"
|
||||||
// },
|
// },
|
||||||
// "origin": "173.95.152.44",
|
// "origin": "173.95.152.44",
|
||||||
// "url": "http://postman-echo.com/get"
|
// "url": "https://postman-echo.com/get"
|
||||||
// }
|
// }
|
||||||
|
|
||||||
it('does basic http get request', async () => {
|
it('does basic http get request', async () => {
|
||||||
@@ -63,17 +63,16 @@ describe('basics', () => {
|
|||||||
expect(obj.headers['user-agent']).toBeFalsy()
|
expect(obj.headers['user-agent']).toBeFalsy()
|
||||||
})
|
})
|
||||||
|
|
||||||
/* TODO write a mock rather then relying on a third party
|
|
||||||
it('does basic https get request', async () => {
|
it('does basic https get request', async () => {
|
||||||
const res: httpm.HttpClientResponse = await _http.get(
|
const res: httpm.HttpClientResponse = await _http.get(
|
||||||
'http://postman-echo.com/get'
|
'https://postman-echo.com/get'
|
||||||
)
|
)
|
||||||
expect(res.message.statusCode).toBe(200)
|
expect(res.message.statusCode).toBe(200)
|
||||||
const body: string = await res.readBody()
|
const body: string = await res.readBody()
|
||||||
const obj = JSON.parse(body)
|
const obj = JSON.parse(body)
|
||||||
expect(obj.url).toBe('http://postman-echo.com/get')
|
expect(obj.url).toBe('https://postman-echo.com/get')
|
||||||
})
|
})
|
||||||
*/
|
|
||||||
it('does basic http get request with default headers', async () => {
|
it('does basic http get request with default headers', async () => {
|
||||||
const http: httpm.HttpClient = new httpm.HttpClient(
|
const http: httpm.HttpClient = new httpm.HttpClient(
|
||||||
'http-client-tests',
|
'http-client-tests',
|
||||||
@@ -126,12 +125,12 @@ describe('basics', () => {
|
|||||||
it('pipes a get request', async () => {
|
it('pipes a get request', async () => {
|
||||||
return new Promise<void>(async resolve => {
|
return new Promise<void>(async resolve => {
|
||||||
const file = fs.createWriteStream(sampleFilePath)
|
const file = fs.createWriteStream(sampleFilePath)
|
||||||
;(await _http.get('http://postman-echo.com/get')).message
|
;(await _http.get('https://postman-echo.com/get')).message
|
||||||
.pipe(file)
|
.pipe(file)
|
||||||
.on('close', () => {
|
.on('close', () => {
|
||||||
const body: string = fs.readFileSync(sampleFilePath).toString()
|
const body: string = fs.readFileSync(sampleFilePath).toString()
|
||||||
const obj = JSON.parse(body)
|
const obj = JSON.parse(body)
|
||||||
expect(obj.url).toBe('http://postman-echo.com/get')
|
expect(obj.url).toBe('https://postman-echo.com/get')
|
||||||
resolve()
|
resolve()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -139,32 +138,32 @@ describe('basics', () => {
|
|||||||
|
|
||||||
it('does basic get request with redirects', async () => {
|
it('does basic get request with redirects', async () => {
|
||||||
const res: httpm.HttpClientResponse = await _http.get(
|
const res: httpm.HttpClientResponse = await _http.get(
|
||||||
`http://postman-echo.com/redirect-to?url=${encodeURIComponent(
|
`https://postman-echo.com/redirect-to?url=${encodeURIComponent(
|
||||||
'http://postman-echo.com/get'
|
'https://postman-echo.com/get'
|
||||||
)}`
|
)}`
|
||||||
)
|
)
|
||||||
expect(res.message.statusCode).toBe(200)
|
expect(res.message.statusCode).toBe(200)
|
||||||
const body: string = await res.readBody()
|
const body: string = await res.readBody()
|
||||||
const obj = JSON.parse(body)
|
const obj = JSON.parse(body)
|
||||||
expect(obj.url).toBe('http://postman-echo.com/get')
|
expect(obj.url).toBe('https://postman-echo.com/get')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('does basic get request with redirects (303)', async () => {
|
it('does basic get request with redirects (303)', async () => {
|
||||||
const res: httpm.HttpClientResponse = await _http.get(
|
const res: httpm.HttpClientResponse = await _http.get(
|
||||||
`http://postman-echo.com/redirect-to?url=${encodeURIComponent(
|
`https://postman-echo.com/redirect-to?url=${encodeURIComponent(
|
||||||
'http://postman-echo.com/get'
|
'https://postman-echo.com/get'
|
||||||
)}&status_code=303`
|
)}&status_code=303`
|
||||||
)
|
)
|
||||||
expect(res.message.statusCode).toBe(200)
|
expect(res.message.statusCode).toBe(200)
|
||||||
const body: string = await res.readBody()
|
const body: string = await res.readBody()
|
||||||
const obj = JSON.parse(body)
|
const obj = JSON.parse(body)
|
||||||
expect(obj.url).toBe('http://postman-echo.com/get')
|
expect(obj.url).toBe('https://postman-echo.com/get')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns 404 for not found get request on redirect', async () => {
|
it('returns 404 for not found get request on redirect', async () => {
|
||||||
const res: httpm.HttpClientResponse = await _http.get(
|
const res: httpm.HttpClientResponse = await _http.get(
|
||||||
`http://postman-echo.com/redirect-to?url=${encodeURIComponent(
|
`https://postman-echo.com/redirect-to?url=${encodeURIComponent(
|
||||||
'http://postman-echo.com/status/404'
|
'https://postman-echo.com/status/404'
|
||||||
)}&status_code=303`
|
)}&status_code=303`
|
||||||
)
|
)
|
||||||
expect(res.message.statusCode).toBe(404)
|
expect(res.message.statusCode).toBe(404)
|
||||||
@@ -178,8 +177,8 @@ describe('basics', () => {
|
|||||||
{allowRedirects: false}
|
{allowRedirects: false}
|
||||||
)
|
)
|
||||||
const res: httpm.HttpClientResponse = await http.get(
|
const res: httpm.HttpClientResponse = await http.get(
|
||||||
`http://postman-echo.com/redirect-to?url=${encodeURIComponent(
|
`https://postman-echo.com/redirect-to?url=${encodeURIComponent(
|
||||||
'http://postman-echo.com/get'
|
'https://postman-echo.com/get'
|
||||||
)}`
|
)}`
|
||||||
)
|
)
|
||||||
expect(res.message.statusCode).toBe(302)
|
expect(res.message.statusCode).toBe(302)
|
||||||
@@ -192,8 +191,8 @@ describe('basics', () => {
|
|||||||
authorization: 'shhh'
|
authorization: 'shhh'
|
||||||
}
|
}
|
||||||
const res: httpm.HttpClientResponse = await _http.get(
|
const res: httpm.HttpClientResponse = await _http.get(
|
||||||
`http://postman-echo.com/redirect-to?url=${encodeURIComponent(
|
`https://postman-echo.com/redirect-to?url=${encodeURIComponent(
|
||||||
'http://www.postman-echo.com/get'
|
'https://www.postman-echo.com/get'
|
||||||
)}`,
|
)}`,
|
||||||
headers
|
headers
|
||||||
)
|
)
|
||||||
@@ -205,7 +204,7 @@ describe('basics', () => {
|
|||||||
expect(obj.headers[httpm.Headers.Accept]).toBe('application/json')
|
expect(obj.headers[httpm.Headers.Accept]).toBe('application/json')
|
||||||
expect(obj.headers['Authorization']).toBeUndefined()
|
expect(obj.headers['Authorization']).toBeUndefined()
|
||||||
expect(obj.headers['authorization']).toBeUndefined()
|
expect(obj.headers['authorization']).toBeUndefined()
|
||||||
expect(obj.url).toBe('http://www.postman-echo.com/get')
|
expect(obj.url).toBe('https://www.postman-echo.com/get')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('does not pass Auth with diff hostname redirects', async () => {
|
it('does not pass Auth with diff hostname redirects', async () => {
|
||||||
@@ -214,8 +213,8 @@ describe('basics', () => {
|
|||||||
Authorization: 'shhh'
|
Authorization: 'shhh'
|
||||||
}
|
}
|
||||||
const res: httpm.HttpClientResponse = await _http.get(
|
const res: httpm.HttpClientResponse = await _http.get(
|
||||||
`http://postman-echo.com/redirect-to?url=${encodeURIComponent(
|
`https://postman-echo.com/redirect-to?url=${encodeURIComponent(
|
||||||
'http://www.postman-echo.com/get'
|
'https://www.postman-echo.com/get'
|
||||||
)}`,
|
)}`,
|
||||||
headers
|
headers
|
||||||
)
|
)
|
||||||
@@ -227,7 +226,7 @@ describe('basics', () => {
|
|||||||
expect(obj.headers[httpm.Headers.Accept]).toBe('application/json')
|
expect(obj.headers[httpm.Headers.Accept]).toBe('application/json')
|
||||||
expect(obj.headers['Authorization']).toBeUndefined()
|
expect(obj.headers['Authorization']).toBeUndefined()
|
||||||
expect(obj.headers['authorization']).toBeUndefined()
|
expect(obj.headers['authorization']).toBeUndefined()
|
||||||
expect(obj.url).toBe('http://www.postman-echo.com/get')
|
expect(obj.url).toBe('https://www.postman-echo.com/get')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('does basic head request', async () => {
|
it('does basic head request', async () => {
|
||||||
@@ -290,11 +289,11 @@ describe('basics', () => {
|
|||||||
|
|
||||||
it('gets a json object', async () => {
|
it('gets a json object', async () => {
|
||||||
const jsonObj = await _http.getJson<HttpBinData>(
|
const jsonObj = await _http.getJson<HttpBinData>(
|
||||||
'http://postman-echo.com/get'
|
'https://postman-echo.com/get'
|
||||||
)
|
)
|
||||||
expect(jsonObj.statusCode).toBe(200)
|
expect(jsonObj.statusCode).toBe(200)
|
||||||
expect(jsonObj.result).toBeDefined()
|
expect(jsonObj.result).toBeDefined()
|
||||||
expect(jsonObj.result?.url).toBe('http://postman-echo.com/get')
|
expect(jsonObj.result?.url).toBe('https://postman-echo.com/get')
|
||||||
expect(jsonObj.result?.headers[httpm.Headers.Accept]).toBe(
|
expect(jsonObj.result?.headers[httpm.Headers.Accept]).toBe(
|
||||||
httpm.MediaTypes.ApplicationJson
|
httpm.MediaTypes.ApplicationJson
|
||||||
)
|
)
|
||||||
@@ -305,7 +304,7 @@ describe('basics', () => {
|
|||||||
|
|
||||||
it('getting a non existent json object returns null', async () => {
|
it('getting a non existent json object returns null', async () => {
|
||||||
const jsonObj = await _http.getJson<HttpBinData>(
|
const jsonObj = await _http.getJson<HttpBinData>(
|
||||||
'http://postman-echo.com/status/404'
|
'https://postman-echo.com/status/404'
|
||||||
)
|
)
|
||||||
expect(jsonObj.statusCode).toBe(404)
|
expect(jsonObj.statusCode).toBe(404)
|
||||||
expect(jsonObj.result).toBeNull()
|
expect(jsonObj.result).toBeNull()
|
||||||
@@ -314,12 +313,12 @@ describe('basics', () => {
|
|||||||
it('posts a json object', async () => {
|
it('posts a json object', async () => {
|
||||||
const res = {name: 'foo'}
|
const res = {name: 'foo'}
|
||||||
const restRes = await _http.postJson<HttpBinData>(
|
const restRes = await _http.postJson<HttpBinData>(
|
||||||
'http://postman-echo.com/post',
|
'https://postman-echo.com/post',
|
||||||
res
|
res
|
||||||
)
|
)
|
||||||
expect(restRes.statusCode).toBe(200)
|
expect(restRes.statusCode).toBe(200)
|
||||||
expect(restRes.result).toBeDefined()
|
expect(restRes.result).toBeDefined()
|
||||||
expect(restRes.result?.url).toBe('http://postman-echo.com/post')
|
expect(restRes.result?.url).toBe('https://postman-echo.com/post')
|
||||||
expect(restRes.result?.json.name).toBe('foo')
|
expect(restRes.result?.json.name).toBe('foo')
|
||||||
expect(restRes.result?.headers[httpm.Headers.Accept]).toBe(
|
expect(restRes.result?.headers[httpm.Headers.Accept]).toBe(
|
||||||
httpm.MediaTypes.ApplicationJson
|
httpm.MediaTypes.ApplicationJson
|
||||||
@@ -335,12 +334,12 @@ describe('basics', () => {
|
|||||||
it('puts a json object', async () => {
|
it('puts a json object', async () => {
|
||||||
const res = {name: 'foo'}
|
const res = {name: 'foo'}
|
||||||
const restRes = await _http.putJson<HttpBinData>(
|
const restRes = await _http.putJson<HttpBinData>(
|
||||||
'http://postman-echo.com/put',
|
'https://postman-echo.com/put',
|
||||||
res
|
res
|
||||||
)
|
)
|
||||||
expect(restRes.statusCode).toBe(200)
|
expect(restRes.statusCode).toBe(200)
|
||||||
expect(restRes.result).toBeDefined()
|
expect(restRes.result).toBeDefined()
|
||||||
expect(restRes.result?.url).toBe('http://postman-echo.com/put')
|
expect(restRes.result?.url).toBe('https://postman-echo.com/put')
|
||||||
expect(restRes.result?.json.name).toBe('foo')
|
expect(restRes.result?.json.name).toBe('foo')
|
||||||
|
|
||||||
expect(restRes.result?.headers[httpm.Headers.Accept]).toBe(
|
expect(restRes.result?.headers[httpm.Headers.Accept]).toBe(
|
||||||
@@ -357,12 +356,12 @@ describe('basics', () => {
|
|||||||
it('patch a json object', async () => {
|
it('patch a json object', async () => {
|
||||||
const res = {name: 'foo'}
|
const res = {name: 'foo'}
|
||||||
const restRes = await _http.patchJson<HttpBinData>(
|
const restRes = await _http.patchJson<HttpBinData>(
|
||||||
'http://postman-echo.com/patch',
|
'https://postman-echo.com/patch',
|
||||||
res
|
res
|
||||||
)
|
)
|
||||||
expect(restRes.statusCode).toBe(200)
|
expect(restRes.statusCode).toBe(200)
|
||||||
expect(restRes.result).toBeDefined()
|
expect(restRes.result).toBeDefined()
|
||||||
expect(restRes.result?.url).toBe('http://postman-echo.com/patch')
|
expect(restRes.result?.url).toBe('https://postman-echo.com/patch')
|
||||||
expect(restRes.result?.json.name).toBe('foo')
|
expect(restRes.result?.json.name).toBe('foo')
|
||||||
expect(restRes.result?.headers[httpm.Headers.Accept]).toBe(
|
expect(restRes.result?.headers[httpm.Headers.Accept]).toBe(
|
||||||
httpm.MediaTypes.ApplicationJson
|
httpm.MediaTypes.ApplicationJson
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ describe('headers', () => {
|
|||||||
it('preserves existing headers on getJson', async () => {
|
it('preserves existing headers on getJson', async () => {
|
||||||
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
|
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
|
||||||
let jsonObj = await _http.getJson<any>(
|
let jsonObj = await _http.getJson<any>(
|
||||||
'http://postman-echo.com/get',
|
'https://postman-echo.com/get',
|
||||||
additionalHeaders
|
additionalHeaders
|
||||||
)
|
)
|
||||||
expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('foo')
|
expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('foo')
|
||||||
@@ -26,7 +26,7 @@ describe('headers', () => {
|
|||||||
[httpm.Headers.Accept]: 'baz'
|
[httpm.Headers.Accept]: 'baz'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jsonObj = await httpWithHeaders.getJson<any>('http://postman-echo.com/get')
|
jsonObj = await httpWithHeaders.getJson<any>('https://postman-echo.com/get')
|
||||||
expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('baz')
|
expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('baz')
|
||||||
expect(jsonObj.headers[httpm.Headers.ContentType]).toContain(
|
expect(jsonObj.headers[httpm.Headers.ContentType]).toContain(
|
||||||
httpm.MediaTypes.ApplicationJson
|
httpm.MediaTypes.ApplicationJson
|
||||||
@@ -36,7 +36,7 @@ describe('headers', () => {
|
|||||||
it('preserves existing headers on postJson', async () => {
|
it('preserves existing headers on postJson', async () => {
|
||||||
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
|
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
|
||||||
let jsonObj = await _http.postJson<any>(
|
let jsonObj = await _http.postJson<any>(
|
||||||
'http://postman-echo.com/post',
|
'https://postman-echo.com/post',
|
||||||
{},
|
{},
|
||||||
additionalHeaders
|
additionalHeaders
|
||||||
)
|
)
|
||||||
@@ -52,7 +52,7 @@ describe('headers', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
jsonObj = await httpWithHeaders.postJson<any>(
|
jsonObj = await httpWithHeaders.postJson<any>(
|
||||||
'http://postman-echo.com/post',
|
'https://postman-echo.com/post',
|
||||||
{}
|
{}
|
||||||
)
|
)
|
||||||
expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('baz')
|
expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('baz')
|
||||||
@@ -64,7 +64,7 @@ describe('headers', () => {
|
|||||||
it('preserves existing headers on putJson', async () => {
|
it('preserves existing headers on putJson', async () => {
|
||||||
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
|
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
|
||||||
let jsonObj = await _http.putJson<any>(
|
let jsonObj = await _http.putJson<any>(
|
||||||
'http://postman-echo.com/put',
|
'https://postman-echo.com/put',
|
||||||
{},
|
{},
|
||||||
additionalHeaders
|
additionalHeaders
|
||||||
)
|
)
|
||||||
@@ -80,7 +80,7 @@ describe('headers', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
jsonObj = await httpWithHeaders.putJson<any>(
|
jsonObj = await httpWithHeaders.putJson<any>(
|
||||||
'http://postman-echo.com/put',
|
'https://postman-echo.com/put',
|
||||||
{}
|
{}
|
||||||
)
|
)
|
||||||
expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('baz')
|
expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('baz')
|
||||||
@@ -92,7 +92,7 @@ describe('headers', () => {
|
|||||||
it('preserves existing headers on patchJson', async () => {
|
it('preserves existing headers on patchJson', async () => {
|
||||||
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
|
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
|
||||||
let jsonObj = await _http.patchJson<any>(
|
let jsonObj = await _http.patchJson<any>(
|
||||||
'http://postman-echo.com/patch',
|
'https://postman-echo.com/patch',
|
||||||
{},
|
{},
|
||||||
additionalHeaders
|
additionalHeaders
|
||||||
)
|
)
|
||||||
@@ -108,7 +108,7 @@ describe('headers', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
jsonObj = await httpWithHeaders.patchJson<any>(
|
jsonObj = await httpWithHeaders.patchJson<any>(
|
||||||
'http://postman-echo.com/patch',
|
'https://postman-echo.com/patch',
|
||||||
{}
|
{}
|
||||||
)
|
)
|
||||||
expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('baz')
|
expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('baz')
|
||||||
|
|||||||
@@ -222,33 +222,30 @@ describe('proxy', () => {
|
|||||||
expect(_proxyConnects).toHaveLength(0)
|
expect(_proxyConnects).toHaveLength(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO mock this out so we don't rely on a third party
|
|
||||||
/*
|
|
||||||
it('HttpClient does basic https get request through proxy', async () => {
|
it('HttpClient does basic https get request through proxy', async () => {
|
||||||
process.env['https_proxy'] = _proxyUrl
|
process.env['https_proxy'] = _proxyUrl
|
||||||
const httpClient = new httpm.HttpClient()
|
const httpClient = new httpm.HttpClient()
|
||||||
const res: httpm.HttpClientResponse = await httpClient.get(
|
const res: httpm.HttpClientResponse = await httpClient.get(
|
||||||
'http://postman-echo.com/get'
|
'https://postman-echo.com/get'
|
||||||
)
|
)
|
||||||
expect(res.message.statusCode).toBe(200)
|
expect(res.message.statusCode).toBe(200)
|
||||||
const body: string = await res.readBody()
|
const body: string = await res.readBody()
|
||||||
const obj = JSON.parse(body)
|
const obj = JSON.parse(body)
|
||||||
expect(obj.url).toBe('http://postman-echo.com/get')
|
expect(obj.url).toBe('https://postman-echo.com/get')
|
||||||
expect(_proxyConnects).toEqual(['postman-echo.com:443'])
|
expect(_proxyConnects).toEqual(['postman-echo.com:443'])
|
||||||
})
|
})
|
||||||
*/
|
|
||||||
|
|
||||||
it('HttpClient does basic http get request when bypass proxy', async () => {
|
it('HttpClient does basic https get request when bypass proxy', async () => {
|
||||||
process.env['http_proxy'] = _proxyUrl
|
process.env['https_proxy'] = _proxyUrl
|
||||||
process.env['no_proxy'] = 'postman-echo.com'
|
process.env['no_proxy'] = 'postman-echo.com'
|
||||||
const httpClient = new httpm.HttpClient()
|
const httpClient = new httpm.HttpClient()
|
||||||
const res: httpm.HttpClientResponse = await httpClient.get(
|
const res: httpm.HttpClientResponse = await httpClient.get(
|
||||||
'http://postman-echo.com/get'
|
'https://postman-echo.com/get'
|
||||||
)
|
)
|
||||||
expect(res.message.statusCode).toBe(200)
|
expect(res.message.statusCode).toBe(200)
|
||||||
const body: string = await res.readBody()
|
const body: string = await res.readBody()
|
||||||
const obj = JSON.parse(body)
|
const obj = JSON.parse(body)
|
||||||
expect(obj.url).toBe('http://postman-echo.com/get')
|
expect(obj.url).toBe('https://postman-echo.com/get')
|
||||||
expect(_proxyConnects).toHaveLength(0)
|
expect(_proxyConnects).toHaveLength(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -307,18 +304,6 @@ 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 with URIencoding', async () => {
|
|
||||||
process.env['https_proxy'] =
|
|
||||||
'http://user%40github.com:p%[email protected]:8080'
|
|
||||||
const httpClient = new httpm.HttpClient()
|
|
||||||
const agent: any = httpClient.getAgent('https://some-url')
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log(agent)
|
|
||||||
expect(agent.proxyOptions.host).toBe('127.0.0.1')
|
|
||||||
expect(agent.proxyOptions.port).toBe('8080')
|
|
||||||
expect(agent.proxyOptions.proxyAuth).toBe('[email protected]:p@ssword')
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
function _clearVars(): void {
|
function _clearVars(): void {
|
||||||
|
|||||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/http-client",
|
"name": "@actions/http-client",
|
||||||
"version": "2.2.3",
|
"version": "2.2.1",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/http-client",
|
"name": "@actions/http-client",
|
||||||
"version": "2.2.3",
|
"version": "2.2.1",
|
||||||
"description": "Actions Http Client",
|
"description": "Actions Http Client",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"github",
|
"github",
|
||||||
|
|||||||
@@ -726,9 +726,7 @@ export class HttpClient {
|
|||||||
uri: proxyUrl.href,
|
uri: proxyUrl.href,
|
||||||
pipelining: !this._keepAlive ? 0 : 1,
|
pipelining: !this._keepAlive ? 0 : 1,
|
||||||
...((proxyUrl.username || proxyUrl.password) && {
|
...((proxyUrl.username || proxyUrl.password) && {
|
||||||
token: `Basic ${Buffer.from(
|
token: `${proxyUrl.username}:${proxyUrl.password}`
|
||||||
`${proxyUrl.username}:${proxyUrl.password}`
|
|
||||||
).toString('base64')}`
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
this._proxyAgentDispatcher = proxyAgent
|
this._proxyAgentDispatcher = proxyAgent
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ export function getProxyUrl(reqUrl: URL): URL | undefined {
|
|||||||
|
|
||||||
if (proxyVar) {
|
if (proxyVar) {
|
||||||
try {
|
try {
|
||||||
return new DecodedURL(proxyVar)
|
return new URL(proxyVar)
|
||||||
} catch {
|
} catch {
|
||||||
if (!proxyVar.startsWith('http://') && !proxyVar.startsWith('https://'))
|
if (!proxyVar.startsWith('http://') && !proxyVar.startsWith('https://'))
|
||||||
return new DecodedURL(`http://${proxyVar}`)
|
return new URL(`http://${proxyVar}`)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return undefined
|
return undefined
|
||||||
@@ -87,22 +87,3 @@ function isLoopbackAddress(host: string): boolean {
|
|||||||
hostLower.startsWith('[0:0:0:0:0:0:0:1]')
|
hostLower.startsWith('[0:0:0:0:0:0:0:1]')
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
class DecodedURL extends URL {
|
|
||||||
private _decodedUsername: string
|
|
||||||
private _decodedPassword: string
|
|
||||||
|
|
||||||
constructor(url: string | URL, base?: string | URL) {
|
|
||||||
super(url, base)
|
|
||||||
this._decodedUsername = decodeURIComponent(super.username)
|
|
||||||
this._decodedPassword = decodeURIComponent(super.password)
|
|
||||||
}
|
|
||||||
|
|
||||||
get username(): string {
|
|
||||||
return this._decodedUsername
|
|
||||||
}
|
|
||||||
|
|
||||||
get password(): string {
|
|
||||||
return this._decodedPassword
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Generated
+47
-1
@@ -13,11 +13,13 @@
|
|||||||
"@actions/exec": "^1.0.0",
|
"@actions/exec": "^1.0.0",
|
||||||
"@actions/http-client": "^2.0.1",
|
"@actions/http-client": "^2.0.1",
|
||||||
"@actions/io": "^1.1.1",
|
"@actions/io": "^1.1.1",
|
||||||
"semver": "^6.1.0"
|
"semver": "^6.1.0",
|
||||||
|
"uuid": "^3.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/nock": "^11.1.0",
|
"@types/nock": "^11.1.0",
|
||||||
"@types/semver": "^6.0.0",
|
"@types/semver": "^6.0.0",
|
||||||
|
"@types/uuid": "^3.4.4",
|
||||||
"nock": "^13.2.9"
|
"nock": "^13.2.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -69,12 +71,27 @@
|
|||||||
"nock": "*"
|
"nock": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "12.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.0.tgz",
|
||||||
|
"integrity": "sha512-vqcj1MVm2Sla4PpMfYKh1MyDN4D2f/mPIZD7RdAGqEsbE+JxfeqQHHVbRDQ0Nqn8i73gJa1HQ1Pu3+nH4Q0Yiw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/semver": {
|
"node_modules/@types/semver": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-6.0.1.tgz",
|
||||||
"integrity": "sha512-ffCdcrEE5h8DqVxinQjo+2d1q+FV5z7iNtPofw3JsrltSoSVlOGaW0rY8XxtO9XukdTn8TaCGWmk2VFGhI70mg==",
|
"integrity": "sha512-ffCdcrEE5h8DqVxinQjo+2d1q+FV5z7iNtPofw3JsrltSoSVlOGaW0rY8XxtO9XukdTn8TaCGWmk2VFGhI70mg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/uuid": {
|
||||||
|
"version": "3.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.5.tgz",
|
||||||
|
"integrity": "sha512-MNL15wC3EKyw1VLF+RoVO4hJJdk9t/Hlv3rt1OL65Qvuadm4BYo6g9ZJQqoq7X8NBFSsQXgAujWciovh2lpVjA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.3.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
@@ -149,6 +166,15 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
|
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==",
|
||||||
|
"deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "bin/uuid"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -198,12 +224,27 @@
|
|||||||
"nock": "*"
|
"nock": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/node": {
|
||||||
|
"version": "12.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.0.tgz",
|
||||||
|
"integrity": "sha512-vqcj1MVm2Sla4PpMfYKh1MyDN4D2f/mPIZD7RdAGqEsbE+JxfeqQHHVbRDQ0Nqn8i73gJa1HQ1Pu3+nH4Q0Yiw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@types/semver": {
|
"@types/semver": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-6.0.1.tgz",
|
||||||
"integrity": "sha512-ffCdcrEE5h8DqVxinQjo+2d1q+FV5z7iNtPofw3JsrltSoSVlOGaW0rY8XxtO9XukdTn8TaCGWmk2VFGhI70mg==",
|
"integrity": "sha512-ffCdcrEE5h8DqVxinQjo+2d1q+FV5z7iNtPofw3JsrltSoSVlOGaW0rY8XxtO9XukdTn8TaCGWmk2VFGhI70mg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@types/uuid": {
|
||||||
|
"version": "3.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.5.tgz",
|
||||||
|
"integrity": "sha512-MNL15wC3EKyw1VLF+RoVO4hJJdk9t/Hlv3rt1OL65Qvuadm4BYo6g9ZJQqoq7X8NBFSsQXgAujWciovh2lpVjA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "4.3.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
@@ -258,6 +299,11 @@
|
|||||||
"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",
|
||||||
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
|
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
|
||||||
|
},
|
||||||
|
"uuid": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,11 +40,13 @@
|
|||||||
"@actions/exec": "^1.0.0",
|
"@actions/exec": "^1.0.0",
|
||||||
"@actions/http-client": "^2.0.1",
|
"@actions/http-client": "^2.0.1",
|
||||||
"@actions/io": "^1.1.1",
|
"@actions/io": "^1.1.1",
|
||||||
"semver": "^6.1.0"
|
"semver": "^6.1.0",
|
||||||
|
"uuid": "^3.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/nock": "^11.1.0",
|
"@types/nock": "^11.1.0",
|
||||||
"@types/semver": "^6.0.0",
|
"@types/semver": "^6.0.0",
|
||||||
|
"@types/uuid": "^3.4.4",
|
||||||
"nock": "^13.2.9"
|
"nock": "^13.2.9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import * as io from '@actions/io'
|
import * as io from '@actions/io'
|
||||||
import * as crypto from 'crypto'
|
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import * as mm from './manifest'
|
import * as mm from './manifest'
|
||||||
import * as os from 'os'
|
import * as os from 'os'
|
||||||
@@ -11,6 +10,7 @@ import * as stream from 'stream'
|
|||||||
import * as util from 'util'
|
import * as util from 'util'
|
||||||
import {ok} from 'assert'
|
import {ok} from 'assert'
|
||||||
import {OutgoingHttpHeaders} from 'http'
|
import {OutgoingHttpHeaders} from 'http'
|
||||||
|
import uuidV4 from 'uuid/v4'
|
||||||
import {exec} from '@actions/exec/lib/exec'
|
import {exec} from '@actions/exec/lib/exec'
|
||||||
import {ExecOptions} from '@actions/exec/lib/interfaces'
|
import {ExecOptions} from '@actions/exec/lib/interfaces'
|
||||||
import {RetryHelper} from './retry-helper'
|
import {RetryHelper} from './retry-helper'
|
||||||
@@ -41,7 +41,7 @@ export async function downloadTool(
|
|||||||
auth?: string,
|
auth?: string,
|
||||||
headers?: OutgoingHttpHeaders
|
headers?: OutgoingHttpHeaders
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
dest = dest || path.join(_getTempDirectory(), crypto.randomUUID())
|
dest = dest || path.join(_getTempDirectory(), uuidV4())
|
||||||
await io.mkdirP(path.dirname(dest))
|
await io.mkdirP(path.dirname(dest))
|
||||||
core.debug(`Downloading ${url}`)
|
core.debug(`Downloading ${url}`)
|
||||||
core.debug(`Destination ${dest}`)
|
core.debug(`Destination ${dest}`)
|
||||||
@@ -651,7 +651,7 @@ export async function findFromManifest(
|
|||||||
async function _createExtractFolder(dest?: string): Promise<string> {
|
async function _createExtractFolder(dest?: string): Promise<string> {
|
||||||
if (!dest) {
|
if (!dest) {
|
||||||
// create a temp dir
|
// create a temp dir
|
||||||
dest = path.join(_getTempDirectory(), crypto.randomUUID())
|
dest = path.join(_getTempDirectory(), uuidV4())
|
||||||
}
|
}
|
||||||
await io.mkdirP(dest)
|
await io.mkdirP(dest)
|
||||||
return dest
|
return dest
|
||||||
|
|||||||
Reference in New Issue
Block a user