Compare commits
75
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42b0077729 | ||
|
|
bcaba45bb9 | ||
|
|
da5e926162 | ||
|
|
6e1d7543c4 | ||
|
|
67c383759c | ||
|
|
4f54b861cf | ||
|
|
e7f45861f6 | ||
|
|
ce603525f8 | ||
|
|
38f54e9d73 | ||
|
|
c5f3463c0b | ||
|
|
96ef8d5d33 | ||
|
|
84d3cef541 | ||
|
|
a926fff385 | ||
|
|
a2a8a724c2 | ||
|
|
a8fa53b609 | ||
|
|
23039a4345 | ||
|
|
4778aebf5b | ||
|
|
6eff4e928d | ||
|
|
2c4f0f555e | ||
|
|
dea3595881 | ||
|
|
4c878a66e7 | ||
|
|
2f2738e4ae | ||
|
|
2d065559f7 | ||
|
|
2da528819c | ||
|
|
90ee020ccd | ||
|
|
c14e304555 | ||
|
|
a2852cee6f | ||
|
|
6d94ad125b | ||
|
|
d02a834fe3 | ||
|
|
0f23ae1184 | ||
|
|
5288db3953 | ||
|
|
bc893bf1b4 | ||
|
|
9322468ef0 | ||
|
|
b6c87ceea7 | ||
|
|
17c4cab8b2 | ||
|
|
59593338a6 | ||
|
|
0a0e70d1cd | ||
|
|
c9e825e841 | ||
|
|
31a6086649 | ||
|
|
d2d69999e3 | ||
|
|
6ac9cbfda9 | ||
|
|
d597cf234f | ||
|
|
a768aa30c7 | ||
|
|
e4c0440c3a | ||
|
|
26b62e0fd5 | ||
|
|
73f526b642 | ||
|
|
077846ed34 | ||
|
|
180b75bf01 | ||
|
|
f33a3f4748 | ||
|
|
97e4fcfcd5 | ||
|
|
fe0c0de7db | ||
|
|
c7de68f215 | ||
|
|
66343faec4 | ||
|
|
fd88cbe6df | ||
|
|
bef1fc5f67 | ||
|
|
18751738a8 | ||
|
|
b1f55c6942 | ||
|
|
9299663297 | ||
|
|
088b9761b4 | ||
|
|
d97edf7f61 | ||
|
|
06482c6da1 | ||
|
|
7441cc7b8b | ||
|
|
ca87c5e124 | ||
|
|
97629b8767 | ||
|
|
8e29fb2e30 | ||
|
|
560ae4debd | ||
|
|
fda9d58d06 | ||
|
|
6defe19df1 | ||
|
|
6e4aebbd4e | ||
|
|
14eee6f54a | ||
|
|
e71ddb9e5a | ||
|
|
13d981a863 | ||
|
|
4090623397 | ||
|
|
4617da254f | ||
|
|
1f22f9faf9 |
Generated
+32
-15
@@ -4925,13 +4925,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "1.7.4",
|
"version": "1.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
|
||||||
"integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
|
"integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.6",
|
"follow-redirects": "^1.15.0",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
"proxy-from-env": "^1.1.0"
|
"proxy-from-env": "^1.1.0"
|
||||||
}
|
}
|
||||||
@@ -5176,12 +5175,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/braces": {
|
"node_modules/braces": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fill-range": "^7.1.1"
|
"fill-range": "^7.0.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@@ -6401,9 +6400,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/ejs": {
|
"node_modules/ejs": {
|
||||||
"version": "3.1.10",
|
"version": "3.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
|
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz",
|
||||||
"integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
|
"integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jake": "^10.8.5"
|
"jake": "^10.8.5"
|
||||||
@@ -7458,9 +7457,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fill-range": {
|
"node_modules/fill-range": {
|
||||||
"version": "7.1.1",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"to-regex-range": "^5.0.1"
|
"to-regex-range": "^5.0.1"
|
||||||
@@ -9016,6 +9015,24 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jackspeak": {
|
||||||
|
"version": "2.3.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
|
||||||
|
"integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@isaacs/cliui": "^8.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@pkgjs/parseargs": "^0.11.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/jake": {
|
"node_modules/jake": {
|
||||||
"version": "10.8.7",
|
"version": "10.8.7",
|
||||||
"resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz",
|
"resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz",
|
||||||
|
|||||||
@@ -1,18 +1,5 @@
|
|||||||
# @actions/artifact Releases
|
# @actions/artifact Releases
|
||||||
|
|
||||||
### 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
|
|
||||||
|
|
||||||
- Update unzip-stream dependency and reverted to using `unzip.Extract()`
|
|
||||||
|
|
||||||
### 2.1.6
|
### 2.1.6
|
||||||
|
|
||||||
- Will retry on invalid request responses.
|
- Will retry on invalid request responses.
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -200,12 +200,14 @@ describe('download-artifact', () => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const response = await downloadArtifactPublic(
|
await expect(
|
||||||
|
downloadArtifactPublic(
|
||||||
fixtures.artifactID,
|
fixtures.artifactID,
|
||||||
fixtures.repositoryOwner,
|
fixtures.repositoryOwner,
|
||||||
fixtures.repositoryName,
|
fixtures.repositoryName,
|
||||||
fixtures.token
|
fixtures.token
|
||||||
)
|
)
|
||||||
|
).rejects.toBeInstanceOf(Error)
|
||||||
|
|
||||||
expect(downloadArtifactMock).toHaveBeenCalledWith({
|
expect(downloadArtifactMock).toHaveBeenCalledWith({
|
||||||
owner: fixtures.repositoryOwner,
|
owner: fixtures.repositoryOwner,
|
||||||
@@ -221,16 +223,6 @@ describe('download-artifact', () => {
|
|||||||
expect(mockGetArtifactMalicious).toHaveBeenCalledWith(
|
expect(mockGetArtifactMalicious).toHaveBeenCalledWith(
|
||||||
fixtures.blobStorageUrl
|
fixtures.blobStorageUrl
|
||||||
)
|
)
|
||||||
|
|
||||||
// ensure path traversal was not possible
|
|
||||||
expect(
|
|
||||||
fs.existsSync(path.join(fixtures.workspaceDir, 'x/etc/hosts'))
|
|
||||||
).toBe(true)
|
|
||||||
expect(
|
|
||||||
fs.existsSync(path.join(fixtures.workspaceDir, 'y/etc/hosts'))
|
|
||||||
).toBe(true)
|
|
||||||
|
|
||||||
expect(response.downloadPath).toBe(fixtures.workspaceDir)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should successfully download an artifact to user defined path', async () => {
|
it('should successfully download an artifact to user defined path', async () => {
|
||||||
|
|||||||
@@ -1,137 +1,267 @@
|
|||||||
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 {
|
||||||
import {BlockBlobUploadStreamOptions} from '@azure/storage-blob'
|
FilesNotFoundError,
|
||||||
import * as fs from 'fs'
|
InvalidResponseError
|
||||||
import * as path from 'path'
|
} from '../src/internal/shared/errors'
|
||||||
|
class NodeJSError extends Error {
|
||||||
|
code: string
|
||||||
|
|
||||||
const uploadStreamMock = jest.fn()
|
constructor(message?: string, code?: string) {
|
||||||
const blockBlobClientMock = jest.fn().mockImplementation(() => ({
|
super(message) // Pass the message to the Error constructor
|
||||||
uploadStream: uploadStreamMock
|
this.code = code || ''
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('@azure/storage-blob', () => ({
|
|
||||||
BlobClient: jest.fn().mockImplementation(() => {
|
|
||||||
return {
|
|
||||||
getBlockBlobClient: blockBlobClientMock
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
|
|
||||||
const fixtures = {
|
|
||||||
uploadDirectory: path.join(__dirname, '_temp', 'plz-upload'),
|
|
||||||
files: [
|
|
||||||
['file1.txt', 'test 1 file content'],
|
|
||||||
['file2.txt', 'test 2 file content'],
|
|
||||||
['file3.txt', 'test 3 file content']
|
|
||||||
],
|
|
||||||
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(() => {
|
|
||||||
if (!fs.existsSync(fixtures.uploadDirectory)) {
|
|
||||||
fs.mkdirSync(fixtures.uploadDirectory, {recursive: true})
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [file, content] of fixtures.files) {
|
|
||||||
fs.writeFileSync(path.join(fixtures.uploadDirectory, 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[0]),
|
|
||||||
destinationPath: file[0]
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
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(
|
||||||
@@ -144,19 +274,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(
|
||||||
@@ -175,113 +343,120 @@ describe('upload-artifact', () => {
|
|||||||
.spyOn(ArtifactServiceClientJSON.prototype, 'FinalizeArtifact')
|
.spyOn(ArtifactServiceClientJSON.prototype, 'FinalizeArtifact')
|
||||||
.mockReturnValue(Promise.resolve({ok: false, artifactId: ''}))
|
.mockReturnValue(Promise.resolve({ok: false, artifactId: ''}))
|
||||||
|
|
||||||
const uploadResp = uploadArtifact(
|
// ArtifactHttpClient mocks
|
||||||
fixtures.inputs.artifactName,
|
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
|
||||||
fixtures.inputs.files,
|
|
||||||
fixtures.inputs.rootDirectory
|
|
||||||
)
|
|
||||||
|
|
||||||
await expect(uploadResp).rejects.toThrow()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should successfully upload an artifact', async () => {
|
|
||||||
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'
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
uploadStreamMock.mockImplementation(
|
|
||||||
async (
|
|
||||||
stream: NodeJS.ReadableStream,
|
|
||||||
bufferSize?: number,
|
|
||||||
maxConcurrency?: number,
|
|
||||||
options?: BlockBlobUploadStreamOptions
|
|
||||||
) => {
|
|
||||||
const {onProgress, abortSignal} = options || {}
|
|
||||||
|
|
||||||
onProgress?.({loadedBytes: 0})
|
|
||||||
|
|
||||||
return new Promise(resolve => {
|
|
||||||
const timerId = setTimeout(() => {
|
|
||||||
onProgress?.({loadedBytes: 256})
|
|
||||||
resolve({})
|
|
||||||
}, 1_000)
|
|
||||||
abortSignal?.addEventListener('abort', () => {
|
|
||||||
clearTimeout(timerId)
|
|
||||||
resolve({})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const {id, size} = await uploadArtifact(
|
|
||||||
fixtures.inputs.artifactName,
|
|
||||||
fixtures.inputs.files,
|
|
||||||
fixtures.inputs.rootDirectory
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(id).toBe(1)
|
|
||||||
expect(size).toBe(256)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should throw an error uploading blob chunks get delayed', async () => {
|
|
||||||
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'
|
|
||||||
})
|
|
||||||
)
|
|
||||||
jest
|
jest
|
||||||
.spyOn(config, 'getResultsServiceUrl')
|
.spyOn(config, 'getResultsServiceUrl')
|
||||||
.mockReturnValue('https://results.local')
|
.mockReturnValue('https://test-url.com')
|
||||||
|
|
||||||
jest.spyOn(config, 'getUploadChunkTimeout').mockReturnValue(2_000)
|
|
||||||
|
|
||||||
uploadStreamMock.mockImplementation(
|
|
||||||
async (
|
|
||||||
stream: NodeJS.ReadableStream,
|
|
||||||
bufferSize?: number,
|
|
||||||
maxConcurrency?: number,
|
|
||||||
options?: BlockBlobUploadStreamOptions
|
|
||||||
) => {
|
|
||||||
const {onProgress, abortSignal} = options || {}
|
|
||||||
onProgress?.({loadedBytes: 0})
|
|
||||||
return new Promise(resolve => {
|
|
||||||
abortSignal?.addEventListener('abort', () => {
|
|
||||||
resolve({})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
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()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('should respond with non-successful callback on different zipstream lifecycle methods', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
noopLogs()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.restoreAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle ENOENT error', async () => {
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
const mockZipStream = {
|
||||||
|
entry: jest.fn((source, data, callback) => {
|
||||||
|
const err = (new NodeJSError(
|
||||||
|
"ENOENT: no such file or directory, open '/home/user/files/plz-upload/file1.txt'"
|
||||||
|
).code = 'ENOENT')
|
||||||
|
callback(null, err)
|
||||||
|
}),
|
||||||
|
pipe: jest.fn(),
|
||||||
|
on: jest.fn(),
|
||||||
|
finalize: jest.fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
jest.mock('zip-stream', () => {
|
||||||
|
return {
|
||||||
|
default: jest.fn().mockImplementation(() => mockZipStream)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(zip, 'createZipUploadStream')
|
||||||
|
.mockReturnValue(
|
||||||
|
Promise.reject(
|
||||||
|
new NodeJSError(
|
||||||
|
"ENOENT: no such file or directory, open '/home/user/files/plz-upload/file1.txt'"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
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).rejects.toThrowError(InvalidResponseError)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Generated
+341
-34
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/artifact",
|
"name": "@actions/artifact",
|
||||||
"version": "2.1.9",
|
"version": "2.1.5",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@actions/artifact",
|
"name": "@actions/artifact",
|
||||||
"version": "2.1.9",
|
"version": "2.1.5",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.10.0",
|
"@actions/core": "^1.10.0",
|
||||||
@@ -18,14 +18,15 @@
|
|||||||
"@octokit/plugin-retry": "^3.0.9",
|
"@octokit/plugin-retry": "^3.0.9",
|
||||||
"@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": "^5.3.1",
|
||||||
|
"async": "^3.2.5",
|
||||||
"crypto": "^1.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",
|
||||||
|
"zip-stream": "^6.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/archiver": "^5.3.2",
|
|
||||||
"@types/unzip-stream": "^0.3.4",
|
"@types/unzip-stream": "^0.3.4",
|
||||||
"typedoc": "^0.25.4",
|
"typedoc": "^0.25.4",
|
||||||
"typedoc-plugin-markdown": "^3.17.1",
|
"typedoc-plugin-markdown": "^3.17.1",
|
||||||
@@ -443,15 +444,6 @@
|
|||||||
"@protobuf-ts/runtime": "^2.9.1"
|
"@protobuf-ts/runtime": "^2.9.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/archiver": {
|
|
||||||
"version": "5.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-5.3.2.tgz",
|
|
||||||
"integrity": "sha512-IctHreBuWE5dvBDz/0WeKtyVKVRs4h75IblxOACL92wU66v+HGAfEYAOyXkOFphvRJMhuXdI9huDXpX0FC6lCw==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@types/readdir-glob": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.4.9",
|
"version": "20.4.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.9.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.9.tgz",
|
||||||
@@ -479,15 +471,6 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/readdir-glob": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/readdir-glob/-/readdir-glob-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-ImM6TmoF8bgOwvehGviEj3tRdRBbQujr1N+0ypaln/GWjaerOB26jb93vsRHmdMtvVQZQebOlqt2HROark87mQ==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/tunnel": {
|
"node_modules/@types/tunnel": {
|
||||||
"version": "0.0.3",
|
"version": "0.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/tunnel/-/tunnel-0.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/tunnel/-/tunnel-0.0.3.tgz",
|
||||||
@@ -621,10 +604,69 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/archiver/node_modules/compress-commons": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==",
|
||||||
|
"dependencies": {
|
||||||
|
"buffer-crc32": "^0.2.13",
|
||||||
|
"crc32-stream": "^4.0.2",
|
||||||
|
"normalize-path": "^3.0.0",
|
||||||
|
"readable-stream": "^3.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/archiver/node_modules/crc32-stream": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz",
|
||||||
|
"integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==",
|
||||||
|
"dependencies": {
|
||||||
|
"crc-32": "^1.2.0",
|
||||||
|
"readable-stream": "^3.4.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/archiver/node_modules/zip-stream": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"archiver-utils": "^3.0.4",
|
||||||
|
"compress-commons": "^4.1.2",
|
||||||
|
"readable-stream": "^3.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/archiver/node_modules/zip-stream/node_modules/archiver-utils": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==",
|
||||||
|
"dependencies": {
|
||||||
|
"glob": "^7.2.3",
|
||||||
|
"graceful-fs": "^4.2.0",
|
||||||
|
"lazystream": "^1.0.0",
|
||||||
|
"lodash.defaults": "^4.2.0",
|
||||||
|
"lodash.difference": "^4.5.0",
|
||||||
|
"lodash.flatten": "^4.4.0",
|
||||||
|
"lodash.isplainobject": "^4.0.6",
|
||||||
|
"lodash.union": "^4.6.0",
|
||||||
|
"normalize-path": "^3.0.0",
|
||||||
|
"readable-stream": "^3.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/async": {
|
"node_modules/async": {
|
||||||
"version": "3.2.4",
|
"version": "3.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz",
|
||||||
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ=="
|
"integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg=="
|
||||||
},
|
},
|
||||||
"node_modules/asynckit": {
|
"node_modules/asynckit": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
@@ -806,6 +848,44 @@
|
|||||||
"node": ">= 14"
|
"node": ">= 14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/compress-commons/node_modules/buffer": {
|
||||||
|
"version": "6.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||||
|
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"base64-js": "^1.3.1",
|
||||||
|
"ieee754": "^1.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/compress-commons/node_modules/readable-stream": {
|
||||||
|
"version": "4.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz",
|
||||||
|
"integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==",
|
||||||
|
"dependencies": {
|
||||||
|
"abort-controller": "^3.0.0",
|
||||||
|
"buffer": "^6.0.3",
|
||||||
|
"events": "^3.3.0",
|
||||||
|
"process": "^0.11.10",
|
||||||
|
"string_decoder": "^1.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/concat-map": {
|
"node_modules/concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
@@ -839,6 +919,44 @@
|
|||||||
"node": ">= 14"
|
"node": ">= 14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/crc32-stream/node_modules/buffer": {
|
||||||
|
"version": "6.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||||
|
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"base64-js": "^1.3.1",
|
||||||
|
"ieee754": "^1.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/crc32-stream/node_modules/readable-stream": {
|
||||||
|
"version": "4.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz",
|
||||||
|
"integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==",
|
||||||
|
"dependencies": {
|
||||||
|
"abort-controller": "^3.0.0",
|
||||||
|
"buffer": "^6.0.3",
|
||||||
|
"events": "^3.3.0",
|
||||||
|
"process": "^0.11.10",
|
||||||
|
"string_decoder": "^1.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.3",
|
"version": "7.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||||
@@ -893,6 +1011,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
||||||
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
|
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/end-of-stream": {
|
||||||
|
"version": "1.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||||
|
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"once": "^1.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/event-target-shim": {
|
"node_modules/event-target-shim": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
|
||||||
@@ -909,11 +1035,6 @@
|
|||||||
"node": ">=0.8.x"
|
"node": ">=0.8.x"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fast-fifo": {
|
|
||||||
"version": "1.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
|
|
||||||
"integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="
|
|
||||||
},
|
|
||||||
"node_modules/foreground-child": {
|
"node_modules/foreground-child": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
|
||||||
@@ -1576,6 +1697,94 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tar-stream": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"eastasianwidth": "^0.2.0",
|
||||||
|
"emoji-regex": "^9.2.2",
|
||||||
|
"strip-ansi": "^7.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/string-width-cjs": {
|
||||||
|
"name": "string-width",
|
||||||
|
"version": "4.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||||
|
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||||
|
"dependencies": {
|
||||||
|
"emoji-regex": "^8.0.0",
|
||||||
|
"is-fullwidth-code-point": "^3.0.0",
|
||||||
|
"strip-ansi": "^6.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/string-width-cjs/node_modules/ansi-regex": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/string-width-cjs/node_modules/emoji-regex": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
||||||
|
},
|
||||||
|
"node_modules/string-width-cjs/node_modules/strip-ansi": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^5.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/strip-ansi": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^6.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/strip-ansi-cjs": {
|
||||||
|
"name": "strip-ansi",
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^5.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tar-stream": {
|
"node_modules/tar-stream": {
|
||||||
"version": "3.1.7",
|
"version": "3.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz",
|
||||||
@@ -1738,9 +1947,9 @@
|
|||||||
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
|
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
|
||||||
},
|
},
|
||||||
"node_modules/unzip-stream": {
|
"node_modules/unzip-stream": {
|
||||||
"version": "0.3.4",
|
"version": "0.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/unzip-stream/-/unzip-stream-0.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/unzip-stream/-/unzip-stream-0.3.1.tgz",
|
||||||
"integrity": "sha512-PyofABPVv+d7fL7GOpusx7eRT9YETY2X04PhwbSipdj6bMxVCFJrr+nm0Mxqbf9hUiTin/UsnuFWBXlDZFy0Cw==",
|
"integrity": "sha512-RzaGXLNt+CW+T41h1zl6pGz3EaeVhYlK+rdAap+7DxW5kqsqePO8kRtWPaCiVqdhZc86EctSPVYNix30YOMzmw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"binary": "^0.3.0",
|
"binary": "^0.3.0",
|
||||||
"mkdirp": "^0.5.1"
|
"mkdirp": "^0.5.1"
|
||||||
@@ -1934,6 +2143,104 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 14"
|
"node": ">= 14"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zip-stream/node_modules/archiver-utils": {
|
||||||
|
"version": "5.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz",
|
||||||
|
"integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==",
|
||||||
|
"dependencies": {
|
||||||
|
"glob": "^10.0.0",
|
||||||
|
"graceful-fs": "^4.2.0",
|
||||||
|
"is-stream": "^2.0.1",
|
||||||
|
"lazystream": "^1.0.0",
|
||||||
|
"lodash": "^4.17.15",
|
||||||
|
"normalize-path": "^3.0.0",
|
||||||
|
"readable-stream": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zip-stream/node_modules/brace-expansion": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||||
|
"dependencies": {
|
||||||
|
"balanced-match": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zip-stream/node_modules/buffer": {
|
||||||
|
"version": "6.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||||
|
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"base64-js": "^1.3.1",
|
||||||
|
"ieee754": "^1.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zip-stream/node_modules/glob": {
|
||||||
|
"version": "10.3.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
|
||||||
|
"integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
|
||||||
|
"dependencies": {
|
||||||
|
"foreground-child": "^3.1.0",
|
||||||
|
"jackspeak": "^2.3.5",
|
||||||
|
"minimatch": "^9.0.1",
|
||||||
|
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
|
||||||
|
"path-scurry": "^1.10.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"glob": "dist/esm/bin.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16 || 14 >=14.17"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zip-stream/node_modules/minimatch": {
|
||||||
|
"version": "9.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
|
||||||
|
"integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
|
||||||
|
"dependencies": {
|
||||||
|
"brace-expansion": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16 || 14 >=14.17"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zip-stream/node_modules/readable-stream": {
|
||||||
|
"version": "4.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz",
|
||||||
|
"integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==",
|
||||||
|
"dependencies": {
|
||||||
|
"abort-controller": "^3.0.0",
|
||||||
|
"buffer": "^6.0.3",
|
||||||
|
"events": "^3.3.0",
|
||||||
|
"process": "^0.11.10",
|
||||||
|
"string_decoder": "^1.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/artifact",
|
"name": "@actions/artifact",
|
||||||
"version": "2.1.9",
|
"version": "2.1.6",
|
||||||
"preview": true,
|
"preview": true,
|
||||||
"description": "Actions artifact lib",
|
"description": "Actions artifact lib",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -49,14 +49,15 @@
|
|||||||
"@octokit/plugin-retry": "^3.0.9",
|
"@octokit/plugin-retry": "^3.0.9",
|
||||||
"@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": "^5.3.1",
|
||||||
|
"async": "^3.2.5",
|
||||||
"crypto": "^1.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",
|
||||||
|
"zip-stream": "^6.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/archiver": "^5.3.2",
|
|
||||||
"@types/unzip-stream": "^0.3.4",
|
"@types/unzip-stream": "^0.3.4",
|
||||||
"typedoc": "^0.25.4",
|
"typedoc": "^0.25.4",
|
||||||
"typedoc-plugin-markdown": "^3.17.1",
|
"typedoc-plugin-markdown": "^3.17.1",
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import fs from 'fs/promises'
|
import fs from 'fs/promises'
|
||||||
|
import * as stream from 'stream'
|
||||||
|
import {createWriteStream} from 'fs'
|
||||||
|
import * as path from 'path'
|
||||||
import * as github from '@actions/github'
|
import * as github from '@actions/github'
|
||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import * as httpClient from '@actions/http-client'
|
import * as httpClient from '@actions/http-client'
|
||||||
@@ -44,6 +47,11 @@ async function streamExtract(url: string, directory: string): Promise<void> {
|
|||||||
await streamExtractExternal(url, directory)
|
await streamExtractExternal(url, directory)
|
||||||
return
|
return
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error.message.includes('Malformed extraction path')) {
|
||||||
|
throw new Error(
|
||||||
|
`Artifact download failed with unretryable error: ${error.message}`
|
||||||
|
)
|
||||||
|
}
|
||||||
retryCount++
|
retryCount++
|
||||||
core.debug(
|
core.debug(
|
||||||
`Failed to download artifact after ${retryCount} retries due to ${error.message}. Retrying in 5 seconds...`
|
`Failed to download artifact after ${retryCount} retries due to ${error.message}. Retrying in 5 seconds...`
|
||||||
@@ -78,6 +86,8 @@ export async function streamExtractExternal(
|
|||||||
}
|
}
|
||||||
const timer = setTimeout(timerFn, timeout)
|
const timer = setTimeout(timerFn, timeout)
|
||||||
|
|
||||||
|
const createdDirectories = new Set<string>()
|
||||||
|
createdDirectories.add(directory)
|
||||||
response.message
|
response.message
|
||||||
.on('data', () => {
|
.on('data', () => {
|
||||||
timer.refresh()
|
timer.refresh()
|
||||||
@@ -89,8 +99,46 @@ export async function streamExtractExternal(
|
|||||||
clearTimeout(timer)
|
clearTimeout(timer)
|
||||||
reject(error)
|
reject(error)
|
||||||
})
|
})
|
||||||
.pipe(unzip.Extract({path: directory}))
|
.pipe(unzip.Parse())
|
||||||
.on('close', () => {
|
.pipe(
|
||||||
|
new stream.Transform({
|
||||||
|
objectMode: true,
|
||||||
|
transform: async (entry, _, callback) => {
|
||||||
|
const fullPath = path.normalize(path.join(directory, entry.path))
|
||||||
|
if (!directory.endsWith(path.sep)) {
|
||||||
|
directory += path.sep
|
||||||
|
}
|
||||||
|
if (!fullPath.startsWith(directory)) {
|
||||||
|
reject(new Error(`Malformed extraction path: ${fullPath}`))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.type === 'Directory') {
|
||||||
|
if (!createdDirectories.has(fullPath)) {
|
||||||
|
createdDirectories.add(fullPath)
|
||||||
|
await resolveOrCreateDirectory(fullPath).then(() => {
|
||||||
|
entry.autodrain()
|
||||||
|
callback()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
entry.autodrain()
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
core.info(`Extracting artifact entry: ${fullPath}`)
|
||||||
|
if (!createdDirectories.has(path.dirname(fullPath))) {
|
||||||
|
createdDirectories.add(path.dirname(fullPath))
|
||||||
|
await resolveOrCreateDirectory(path.dirname(fullPath))
|
||||||
|
}
|
||||||
|
|
||||||
|
const writeStream = createWriteStream(fullPath)
|
||||||
|
writeStream.on('finish', callback)
|
||||||
|
writeStream.on('error', reject)
|
||||||
|
entry.pipe(writeStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.on('finish', async () => {
|
||||||
clearTimeout(timer)
|
clearTimeout(timer)
|
||||||
resolve()
|
resolve()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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)
|
||||||
|
await blockBlobClient.uploadStream(
|
||||||
uploadStream,
|
uploadStream,
|
||||||
bufferSize,
|
bufferSize,
|
||||||
maxConcurrency,
|
maxConcurrency,
|
||||||
options
|
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!')
|
||||||
|
|||||||
@@ -67,18 +67,25 @@ export async function uploadArtifact(
|
|||||||
'CreateArtifact: response from backend was not ok'
|
'CreateArtifact: response from backend was not ok'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
// Create the zipupload stream for use in blob upload
|
||||||
const zipUploadStream = await createZipUploadStream(
|
const zipUploadStream = await createZipUploadStream(
|
||||||
zipSpecification,
|
zipSpecification,
|
||||||
options?.compressionLevel
|
options?.compressionLevel
|
||||||
|
).catch(err => {
|
||||||
|
throw new InvalidResponseError(
|
||||||
|
`createZipUploadStream: response from backend was not ok: ${err}`
|
||||||
)
|
)
|
||||||
|
})
|
||||||
|
|
||||||
// Upload zip to blob storage
|
// Upload zip to blob storage
|
||||||
const uploadResult = await uploadZipToBlobStorage(
|
const uploadResult = await uploadZipToBlobStorage(
|
||||||
createArtifactResp.signedUploadUrl,
|
createArtifactResp.signedUploadUrl,
|
||||||
zipUploadStream
|
zipUploadStream
|
||||||
|
).catch(err => {
|
||||||
|
throw new InvalidResponseError(
|
||||||
|
`uploadZipToBlobStorage: response blob was not ok: ${err}`
|
||||||
)
|
)
|
||||||
|
})
|
||||||
// finalize the artifact
|
// finalize the artifact
|
||||||
const finalizeArtifactReq: FinalizeArtifactRequest = {
|
const finalizeArtifactReq: FinalizeArtifactRequest = {
|
||||||
workflowRunBackendId: backendIds.workflowRunBackendId,
|
workflowRunBackendId: backendIds.workflowRunBackendId,
|
||||||
@@ -86,15 +93,12 @@ export async function uploadArtifact(
|
|||||||
name,
|
name,
|
||||||
size: uploadResult.uploadSize ? uploadResult.uploadSize.toString() : '0'
|
size: uploadResult.uploadSize ? uploadResult.uploadSize.toString() : '0'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uploadResult.sha256Hash) {
|
if (uploadResult.sha256Hash) {
|
||||||
finalizeArtifactReq.hash = StringValue.create({
|
finalizeArtifactReq.hash = StringValue.create({
|
||||||
value: `sha256:${uploadResult.sha256Hash}`
|
value: `sha256:${uploadResult.sha256Hash}`
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
core.info(`Finalizing artifact upload`)
|
core.info(`Finalizing artifact upload`)
|
||||||
|
|
||||||
const finalizeArtifactResp =
|
const finalizeArtifactResp =
|
||||||
await artifactClient.FinalizeArtifact(finalizeArtifactReq)
|
await artifactClient.FinalizeArtifact(finalizeArtifactReq)
|
||||||
if (!finalizeArtifactResp.ok) {
|
if (!finalizeArtifactResp.ok) {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import * as stream from 'stream'
|
import * as stream from 'stream'
|
||||||
import * as archiver from 'archiver'
|
import * as ZipStream from 'zip-stream'
|
||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
|
import async from 'async'
|
||||||
|
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'
|
||||||
|
|
||||||
@@ -29,31 +31,57 @@ export async function createZipUploadStream(
|
|||||||
`Creating Artifact archive with compressionLevel: ${compressionLevel}`
|
`Creating Artifact archive with compressionLevel: ${compressionLevel}`
|
||||||
)
|
)
|
||||||
|
|
||||||
const zip = archiver.create('zip', {
|
const zlibOptions = {
|
||||||
highWaterMark: getUploadChunkSize(),
|
zlib: {
|
||||||
zlib: {level: compressionLevel}
|
level: compressionLevel,
|
||||||
})
|
bufferSize: getUploadChunkSize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const zip = new ZipStream.default(zlibOptions)
|
||||||
|
|
||||||
|
const bufferSize = getUploadChunkSize()
|
||||||
|
const zipUploadStream = new ZipUploadStream(bufferSize)
|
||||||
|
zip.pipe(zipUploadStream)
|
||||||
// register callbacks for various events during the zip lifecycle
|
// register callbacks for various events during the zip lifecycle
|
||||||
zip.on('error', zipErrorCallback)
|
zip.on('error', zipErrorCallback)
|
||||||
zip.on('warning', zipWarningCallback)
|
zip.on('warning', zipWarningCallback)
|
||||||
zip.on('finish', zipFinishCallback)
|
zip.on('finish', zipFinishCallback)
|
||||||
zip.on('end', zipEndCallback)
|
zip.on('end', zipEndCallback)
|
||||||
|
const addFileToZip = (
|
||||||
for (const file of uploadSpecification) {
|
file: UploadZipSpecification,
|
||||||
|
callback: (error?: Error) => void
|
||||||
|
): void => {
|
||||||
if (file.sourcePath !== null) {
|
if (file.sourcePath !== null) {
|
||||||
// Add a normal file to the zip
|
zip.entry(
|
||||||
zip.file(file.sourcePath, {
|
createReadStream(file.sourcePath),
|
||||||
name: file.destinationPath
|
{name: file.destinationPath},
|
||||||
})
|
(error: unknown) => {
|
||||||
|
if (error) {
|
||||||
|
callback(error as Error) // Cast the error object to the Error type
|
||||||
|
return
|
||||||
|
}
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
// Add a directory to the zip
|
zip.entry('', {name: file.destinationPath}, (error: unknown) => {
|
||||||
zip.append('', {name: file.destinationPath})
|
if (error) {
|
||||||
|
callback(error as Error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
callback()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const bufferSize = getUploadChunkSize()
|
async.eachSeries(uploadSpecification, addFileToZip, (error: unknown) => {
|
||||||
const zipUploadStream = new ZipUploadStream(bufferSize)
|
if (error) {
|
||||||
|
core.error('Failed to add a file to the zip:')
|
||||||
|
core.info(error.toString()) // Convert error to string
|
||||||
|
return
|
||||||
|
}
|
||||||
|
zip.finalize() // Finalize the archive once all files have been added
|
||||||
|
})
|
||||||
|
|
||||||
core.debug(
|
core.debug(
|
||||||
`Zip write high watermark value ${zipUploadStream.writableHighWaterMark}`
|
`Zip write high watermark value ${zipUploadStream.writableHighWaterMark}`
|
||||||
@@ -62,9 +90,6 @@ export async function createZipUploadStream(
|
|||||||
`Zip read high watermark value ${zipUploadStream.readableHighWaterMark}`
|
`Zip read high watermark value ${zipUploadStream.readableHighWaterMark}`
|
||||||
)
|
)
|
||||||
|
|
||||||
zip.pipe(zipUploadStream)
|
|
||||||
zip.finalize()
|
|
||||||
|
|
||||||
return zipUploadStream
|
return zipUploadStream
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +100,6 @@ const zipErrorCallback = (error: any): void => {
|
|||||||
|
|
||||||
throw new Error('An error has occurred during zip creation for the artifact')
|
throw new Error('An error has occurred during zip creation for the artifact')
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const zipWarningCallback = (error: any): void => {
|
const zipWarningCallback = (error: any): void => {
|
||||||
if (error.code === 'ENOENT') {
|
if (error.code === 'ENOENT') {
|
||||||
|
|||||||
@@ -12,9 +12,6 @@ Once the attestation has been created and signed, it will be uploaded to the GH
|
|||||||
attestations API and associated with the repository from which the workflow was
|
attestations API and associated with the repository from which the workflow was
|
||||||
initiated.
|
initiated.
|
||||||
|
|
||||||
See [Using artifact attestations to establish provenance for builds](https://docs.github.com/en/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds)
|
|
||||||
for more information on artifact attestations.
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### `attest`
|
### `attest`
|
||||||
@@ -63,8 +60,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 +110,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,25 +1,5 @@
|
|||||||
# @actions/attest Releases
|
# @actions/attest Releases
|
||||||
|
|
||||||
### 1.4.0
|
|
||||||
|
|
||||||
- Add new `headers` parameter to the `attest` and `attestProvenance` functions.
|
|
||||||
|
|
||||||
### 1.3.1
|
|
||||||
|
|
||||||
- Fix bug with proxy support when retrieving JWKS for OIDC issuer
|
|
||||||
|
|
||||||
### 1.3.0
|
|
||||||
|
|
||||||
- Dynamic construction of Sigstore API URLs
|
|
||||||
- Switch to new GH provenance build type
|
|
||||||
- Fetch existing Rekor entry on 409 conflict error
|
|
||||||
- Bump @sigstore/bundle from 2.3.0 to 2.3.2
|
|
||||||
- Bump @sigstore/sign from 2.3.0 to 2.3.2
|
|
||||||
|
|
||||||
### 1.2.1
|
|
||||||
|
|
||||||
- Retry request on attestation persistence failure
|
|
||||||
|
|
||||||
### 1.2.0
|
### 1.2.0
|
||||||
|
|
||||||
- Generate attestations using the v0.3 Sigstore bundle format.
|
- Generate attestations using the v0.3 Sigstore bundle format.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ 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",
|
||||||
@@ -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": [
|
||||||
@@ -31,7 +30,7 @@ exports[`provenance functions buildSLSAProvenancePredicate returns a provenance
|
|||||||
},
|
},
|
||||||
"runDetails": {
|
"runDetails": {
|
||||||
"builder": {
|
"builder": {
|
||||||
"id": "https://github.com/owner/workflows/.github/workflows/publish.yml@main",
|
"id": "https://github.com/actions/runner/github-hosted",
|
||||||
},
|
},
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"invocationId": "https://github.com/owner/repo/actions/runs/run-id/attempts/run-attempt",
|
"invocationId": "https://github.com/owner/repo/actions/runs/run-id/attempts/run-attempt",
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
import {signingEndpoints} from '../src/endpoints'
|
|
||||||
|
|
||||||
describe('signingEndpoints', () => {
|
|
||||||
const originalEnv = process.env
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
process.env = originalEnv
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('when using github.com', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
process.env = {
|
|
||||||
...originalEnv,
|
|
||||||
GITHUB_SERVER_URL: 'https://github.com'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
it('returns expected endpoints', async () => {
|
|
||||||
const endpoints = signingEndpoints('github')
|
|
||||||
|
|
||||||
expect(endpoints.fulcioURL).toEqual('https://fulcio.githubapp.com')
|
|
||||||
expect(endpoints.tsaServerURL).toEqual('https://timestamp.githubapp.com')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('when using custom domain', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
process.env = {
|
|
||||||
...originalEnv,
|
|
||||||
GITHUB_SERVER_URL: 'https://foo.bar.com'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
it('returns a expected endpoints', async () => {
|
|
||||||
const endpoints = signingEndpoints('github')
|
|
||||||
|
|
||||||
expect(endpoints.fulcioURL).toEqual('https://fulcio.foo.bar.com')
|
|
||||||
expect(endpoints.tsaServerURL).toEqual('https://timestamp.foo.bar.com')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -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',
|
||||||
@@ -99,7 +98,7 @@ describe('getIDTokenClaims', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('throws an error', async () => {
|
it('throws an error', async () => {
|
||||||
await expect(getIDTokenClaims(issuer)).rejects.toThrow(/unexpected "iss"/)
|
await expect(getIDTokenClaims(issuer)).rejects.toThrow(/issuer invalid/)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -115,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/)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {mockFulcio, mockRekor, mockTSA} from '@sigstore/mock'
|
|||||||
import * as jose from 'jose'
|
import * as jose from 'jose'
|
||||||
import nock from 'nock'
|
import nock from 'nock'
|
||||||
import {MockAgent, setGlobalDispatcher} from 'undici'
|
import {MockAgent, setGlobalDispatcher} from 'undici'
|
||||||
import {SIGSTORE_PUBLIC_GOOD, signingEndpoints} from '../src/endpoints'
|
import {SIGSTORE_GITHUB, SIGSTORE_PUBLIC_GOOD} from '../src/endpoints'
|
||||||
import {attestProvenance, buildSLSAProvenancePredicate} from '../src/provenance'
|
import {attestProvenance, buildSLSAProvenancePredicate} from '../src/provenance'
|
||||||
|
|
||||||
describe('provenance functions', () => {
|
describe('provenance functions', () => {
|
||||||
@@ -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',
|
||||||
@@ -96,7 +95,7 @@ describe('provenance functions', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('when using the github Sigstore instance', () => {
|
describe('when using the github Sigstore instance', () => {
|
||||||
const {fulcioURL, tsaServerURL} = signingEndpoints('github')
|
const {fulcioURL, tsaServerURL} = SIGSTORE_GITHUB
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
// Mock Sigstore
|
// Mock Sigstore
|
||||||
|
|||||||
@@ -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')
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -55,39 +52,7 @@ describe('writeAttestation', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('throws an error', async () => {
|
it('throws an error', async () => {
|
||||||
await expect(
|
await expect(writeAttestation(attestation, token)).rejects.toThrow(/oops/)
|
||||||
writeAttestation(attestation, token, {retry: 0})
|
|
||||||
).rejects.toThrow(/oops/)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('when the api call fails but succeeds on retry', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
const pool = mockAgent.get('https://api.github.com')
|
|
||||||
|
|
||||||
pool
|
|
||||||
.intercept({
|
|
||||||
path: '/repos/foo/bar/attestations',
|
|
||||||
method: 'POST',
|
|
||||||
headers: {authorization: `token ${token}`},
|
|
||||||
body: JSON.stringify({bundle: attestation})
|
|
||||||
})
|
|
||||||
.reply(500, 'oops')
|
|
||||||
.times(1)
|
|
||||||
|
|
||||||
pool
|
|
||||||
.intercept({
|
|
||||||
path: '/repos/foo/bar/attestations',
|
|
||||||
method: 'POST',
|
|
||||||
headers: {authorization: `token ${token}`},
|
|
||||||
body: JSON.stringify({bundle: attestation})
|
|
||||||
})
|
|
||||||
.reply(201, {id: '123'})
|
|
||||||
.times(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('persists the attestation', async () => {
|
|
||||||
await expect(writeAttestation(attestation, token)).resolves.toEqual('123')
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Generated
+714
-438
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/attest",
|
"name": "@actions/attest",
|
||||||
"version": "1.4.0",
|
"version": "1.2.0",
|
||||||
"description": "Actions attestation lib",
|
"description": "Actions attestation lib",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"github",
|
"github",
|
||||||
@@ -35,9 +35,10 @@
|
|||||||
"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"
|
||||||
},
|
},
|
||||||
@@ -45,14 +46,9 @@
|
|||||||
"@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.1",
|
"@actions/http-client": "^2.2.1",
|
||||||
"@octokit/plugin-retry": "^6.0.1",
|
"@sigstore/bundle": "^2.3.0",
|
||||||
"@sigstore/bundle": "^2.3.2",
|
"@sigstore/sign": "^2.3.0",
|
||||||
"@sigstore/sign": "^2.3.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"jose": "^5.2.3"
|
"jwks-rsa": "^3.1.0"
|
||||||
},
|
|
||||||
"overrides": {
|
|
||||||
"@octokit/plugin-retry": {
|
|
||||||
"@octokit/core": "^5.2.0"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ const GITHUB_ID = 'github'
|
|||||||
const FULCIO_PUBLIC_GOOD_URL = 'https://fulcio.sigstore.dev'
|
const FULCIO_PUBLIC_GOOD_URL = 'https://fulcio.sigstore.dev'
|
||||||
const REKOR_PUBLIC_GOOD_URL = 'https://rekor.sigstore.dev'
|
const REKOR_PUBLIC_GOOD_URL = 'https://rekor.sigstore.dev'
|
||||||
|
|
||||||
|
const FULCIO_INTERNAL_URL = 'https://fulcio.githubapp.com'
|
||||||
|
const TSA_INTERNAL_URL = 'https://timestamp.githubapp.com'
|
||||||
|
|
||||||
export type SigstoreInstance = typeof PUBLIC_GOOD_ID | typeof GITHUB_ID
|
export type SigstoreInstance = typeof PUBLIC_GOOD_ID | typeof GITHUB_ID
|
||||||
|
|
||||||
export type Endpoints = {
|
export type Endpoints = {
|
||||||
@@ -19,6 +22,11 @@ export const SIGSTORE_PUBLIC_GOOD: Endpoints = {
|
|||||||
rekorURL: REKOR_PUBLIC_GOOD_URL
|
rekorURL: REKOR_PUBLIC_GOOD_URL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const SIGSTORE_GITHUB: Endpoints = {
|
||||||
|
fulcioURL: FULCIO_INTERNAL_URL,
|
||||||
|
tsaServerURL: TSA_INTERNAL_URL
|
||||||
|
}
|
||||||
|
|
||||||
export const signingEndpoints = (sigstore?: SigstoreInstance): Endpoints => {
|
export const signingEndpoints = (sigstore?: SigstoreInstance): Endpoints => {
|
||||||
let instance: SigstoreInstance
|
let instance: SigstoreInstance
|
||||||
|
|
||||||
@@ -37,19 +45,6 @@ export const signingEndpoints = (sigstore?: SigstoreInstance): Endpoints => {
|
|||||||
case PUBLIC_GOOD_ID:
|
case PUBLIC_GOOD_ID:
|
||||||
return SIGSTORE_PUBLIC_GOOD
|
return SIGSTORE_PUBLIC_GOOD
|
||||||
case GITHUB_ID:
|
case GITHUB_ID:
|
||||||
return buildGitHubEndpoints()
|
return SIGSTORE_GITHUB
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildGitHubEndpoints(): Endpoints {
|
|
||||||
const serverURL = process.env.GITHUB_SERVER_URL || 'https://github.com'
|
|
||||||
let host = new URL(serverURL).hostname
|
|
||||||
|
|
||||||
if (host === 'github.com') {
|
|
||||||
host = 'githubapp.com'
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
fulcioURL: `https://fulcio.${host}`,
|
|
||||||
tsaServerURL: `https://timestamp.${host}`
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+44
-26
@@ -1,6 +1,7 @@
|
|||||||
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'
|
||||||
|
|
||||||
@@ -10,7 +11,6 @@ const REQUIRED_CLAIMS = [
|
|||||||
'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',
|
||||||
@@ -39,37 +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,
|
||||||
issuer
|
getPublicKey(issuer),
|
||||||
})
|
{audience: OIDC_AUDIENCE, issuer},
|
||||||
|
(err, decoded) => {
|
||||||
return payload
|
if (err) {
|
||||||
|
reject(err)
|
||||||
|
} else if (!decoded || typeof decoded === 'string') {
|
||||||
|
reject(new Error('No decoded token'))
|
||||||
|
} else {
|
||||||
|
resolve(decoded)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getJWKS = async (issuer: string): Promise<jose.JSONWebKeySet> => {
|
|
||||||
const client = new HttpClient('@actions/attest')
|
|
||||||
const config = await client.getJson<OIDCConfig>(
|
|
||||||
`${issuer}/.well-known/openid-configuration`
|
|
||||||
)
|
)
|
||||||
|
})
|
||||||
if (!config.result) {
|
|
||||||
throw new Error('No OpenID configuration found')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const jwks = await client.getJson<jose.JSONWebKeySet>(config.result.jwks_uri)
|
// Returns a callback to locate the public key for the given JWT header. This
|
||||||
|
// involves two calls:
|
||||||
if (!jwks.result) {
|
// 1. Fetch the OpenID configuration to get the JWKS URI.
|
||||||
throw new Error('No JWKS found for issuer')
|
// 2. Fetch the public key from the JWKS URI.
|
||||||
|
const getPublicKey =
|
||||||
|
(issuer: string): jwt.GetPublicKeyOrSecret =>
|
||||||
|
(header: jwt.JwtHeader, callback: jwt.SigningKeyCallback) => {
|
||||||
|
// 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)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return jwks.result
|
function assertClaimSet(claims: jwt.JwtPayload): asserts claims is ClaimSet {
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ 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'
|
const DEFAULT_ISSUER = 'https://token.actions.githubusercontent.com'
|
||||||
|
|
||||||
@@ -52,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: [
|
||||||
@@ -67,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,14 +1,7 @@
|
|||||||
import * as github from '@actions/github'
|
import * as github from '@actions/github'
|
||||||
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
|
|
||||||
|
|
||||||
export type WriteOptions = {
|
|
||||||
retry?: number
|
|
||||||
headers?: RequestHeaders
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* Writes an attestation to the repository's attestations endpoint.
|
* Writes an attestation to the repository's attestations endpoint.
|
||||||
* @param attestation - The attestation to write.
|
* @param attestation - The attestation to write.
|
||||||
@@ -18,17 +11,14 @@ export type WriteOptions = {
|
|||||||
*/
|
*/
|
||||||
export const writeAttestation = async (
|
export const writeAttestation = async (
|
||||||
attestation: unknown,
|
attestation: unknown,
|
||||||
token: string,
|
token: string
|
||||||
options: WriteOptions = {}
|
|
||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
const retries = options.retry ?? DEFAULT_RETRY_COUNT
|
const octokit = github.getOctokit(token)
|
||||||
const octokit = github.getOctokit(token, {retry: {retries}}, retry)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
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}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user