Compare commits
96 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4eaf5d56fb | |||
| 0a588c33a3 | |||
| 8360baed2c | |||
| 5c3e1c231d | |||
| fe8d95a8fc | |||
| b2c6bee10a | |||
| eb88fce3c0 | |||
| a7aa89a929 | |||
| d7dd89f52b | |||
| 3da67ac4cb | |||
| 0bab3623f4 | |||
| af75719a1e | |||
| d9212ff45b | |||
| 2b58973dac | |||
| 4631854e0f | |||
| 09e9478907 | |||
| ea81280a4d | |||
| 1f8d7b5a64 | |||
| 1c03cd3284 | |||
| 1162975200 | |||
| 3ceb264e9b | |||
| 619566e5b8 | |||
| 547e30cada | |||
| 22e5d95310 | |||
| 1c86c4c890 | |||
| c7ec4073b7 | |||
| d0f4aae179 | |||
| dac801e6b9 | |||
| 33891d9aef | |||
| cca2b1808b | |||
| 5d9c674092 | |||
| aa1968c9e9 | |||
| f55900670f | |||
| 0a94a783ee | |||
| 9c6e7d8265 | |||
| 5afccaa9db | |||
| 0c1cb726c3 | |||
| f0b00fd201 | |||
| ff90431d27 | |||
| a2adaa856b | |||
| 662a937248 | |||
| 330dc0b5b8 | |||
| 58dfa1c4ac | |||
| 456cf5a97f | |||
| 7965cc3c7d | |||
| f541fb1ac9 | |||
| a6114b695e | |||
| 885469e8ce | |||
| 962ff70002 | |||
| 8071504f3c | |||
| 9df74283c2 | |||
| 4831d7a53b | |||
| 53a752919b | |||
| c45ad60078 | |||
| f7330892f1 | |||
| 1322acbcca | |||
| bdacfc4c65 | |||
| 4564768940 | |||
| a31b7eca9e | |||
| 9167ce1f3a | |||
| 11601c0d2d | |||
| b9414eecb3 | |||
| 243a8bba07 | |||
| c5e1af5dc3 | |||
| c9af6bb1b3 | |||
| bf4ce74a0f | |||
| db21627995 | |||
| bb2f39337d | |||
| dc4b4dab1d | |||
| 8df94d9879 | |||
| c5035362ab | |||
| 439eaced07 | |||
| 51dc07a106 | |||
| 36b8c66aec | |||
| aa29345ae8 | |||
| e1a7863be6 | |||
| c507914181 | |||
| a65bca60a1 | |||
| a1b068ec31 | |||
| 6e33c78c3d | |||
| 9ac66375a0 | |||
| ddd04b6997 | |||
| 566ea66979 | |||
| 0d74e9080a | |||
| 8dc2d6eb6a | |||
| 3bd746139f | |||
| f915ace085 | |||
| 1bafbed467 | |||
| 98549fbf21 | |||
| b33912b7cc | |||
| cac7db2d19 | |||
| 1c367e0a26 | |||
| 09e59b9a5c | |||
| fecf6cdd59 | |||
| 2b97eb3192 | |||
| ed490dc20d |
+2
-1
@@ -1,3 +1,4 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
packages/*/node_modules/
|
packages/*/node_modules/
|
||||||
packages/*/lib/
|
packages/*/lib/
|
||||||
|
packages/glob/__tests__/_temp
|
||||||
+18
-6
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"plugins": ["jest", "@typescript-eslint"],
|
"plugins": ["jest", "@typescript-eslint"],
|
||||||
"extends": ["plugin:github/es6"],
|
"extends": ["plugin:github/recommended"],
|
||||||
"parser": "@typescript-eslint/parser",
|
"parser": "@typescript-eslint/parser",
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": 9,
|
"ecmaVersion": 9,
|
||||||
@@ -9,20 +9,34 @@
|
|||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"eslint-comments/no-use": "off",
|
"eslint-comments/no-use": "off",
|
||||||
|
"github/no-then": "off",
|
||||||
"import/no-namespace": "off",
|
"import/no-namespace": "off",
|
||||||
|
"no-shadow": "off",
|
||||||
"no-unused-vars": "off",
|
"no-unused-vars": "off",
|
||||||
|
"no-undef": "off",
|
||||||
"@typescript-eslint/no-unused-vars": "error",
|
"@typescript-eslint/no-unused-vars": "error",
|
||||||
"@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}],
|
"@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}],
|
||||||
"@typescript-eslint/no-require-imports": "error",
|
"@typescript-eslint/no-require-imports": "error",
|
||||||
"@typescript-eslint/array-type": "error",
|
"@typescript-eslint/array-type": "error",
|
||||||
"@typescript-eslint/await-thenable": "error",
|
"@typescript-eslint/await-thenable": "error",
|
||||||
"@typescript-eslint/ban-ts-ignore": "error",
|
"@typescript-eslint/ban-ts-comment": "error",
|
||||||
"camelcase": "off",
|
"camelcase": "off",
|
||||||
"@typescript-eslint/camelcase": "off",
|
"@typescript-eslint/camelcase": "off",
|
||||||
"@typescript-eslint/class-name-casing": "error",
|
"@typescript-eslint/consistent-type-assertions": "off",
|
||||||
"@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}],
|
"@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}],
|
||||||
"@typescript-eslint/func-call-spacing": ["error", "never"],
|
"@typescript-eslint/func-call-spacing": ["error", "never"],
|
||||||
"@typescript-eslint/generic-type-naming": ["error", "^[A-Z][A-Za-z]*$"],
|
"@typescript-eslint/naming-convention": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"format": null,
|
||||||
|
"filter": {
|
||||||
|
// you can expand this regex as you find more cases that require quoting that you want to allow
|
||||||
|
"regex": "^[A-Z][A-Za-z]*$",
|
||||||
|
"match": true
|
||||||
|
},
|
||||||
|
"selector": "memberLike"
|
||||||
|
}
|
||||||
|
],
|
||||||
"@typescript-eslint/no-array-constructor": "error",
|
"@typescript-eslint/no-array-constructor": "error",
|
||||||
"@typescript-eslint/no-empty-interface": "error",
|
"@typescript-eslint/no-empty-interface": "error",
|
||||||
"@typescript-eslint/no-explicit-any": "error",
|
"@typescript-eslint/no-explicit-any": "error",
|
||||||
@@ -32,7 +46,6 @@
|
|||||||
"@typescript-eslint/no-misused-new": "error",
|
"@typescript-eslint/no-misused-new": "error",
|
||||||
"@typescript-eslint/no-namespace": "error",
|
"@typescript-eslint/no-namespace": "error",
|
||||||
"@typescript-eslint/no-non-null-assertion": "warn",
|
"@typescript-eslint/no-non-null-assertion": "warn",
|
||||||
"@typescript-eslint/no-object-literal-type-assertion": "error",
|
|
||||||
"@typescript-eslint/no-unnecessary-qualifier": "error",
|
"@typescript-eslint/no-unnecessary-qualifier": "error",
|
||||||
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
||||||
"@typescript-eslint/no-useless-constructor": "error",
|
"@typescript-eslint/no-useless-constructor": "error",
|
||||||
@@ -40,7 +53,6 @@
|
|||||||
"@typescript-eslint/prefer-for-of": "warn",
|
"@typescript-eslint/prefer-for-of": "warn",
|
||||||
"@typescript-eslint/prefer-function-type": "warn",
|
"@typescript-eslint/prefer-function-type": "warn",
|
||||||
"@typescript-eslint/prefer-includes": "error",
|
"@typescript-eslint/prefer-includes": "error",
|
||||||
"@typescript-eslint/prefer-interface": "error",
|
|
||||||
"@typescript-eslint/prefer-string-starts-ends-with": "error",
|
"@typescript-eslint/prefer-string-starts-ends-with": "error",
|
||||||
"@typescript-eslint/promise-function-async": "error",
|
"@typescript-eslint/promise-function-async": "error",
|
||||||
"@typescript-eslint/require-array-sort-compare": "error",
|
"@typescript-eslint/require-array-sort-compare": "error",
|
||||||
|
|||||||
@@ -31,8 +31,9 @@ jobs:
|
|||||||
- name: Bootstrap
|
- name: Bootstrap
|
||||||
run: npm run bootstrap
|
run: npm run bootstrap
|
||||||
|
|
||||||
# - name: audit tools #disabled while we wait for https://github.com/actions/toolkit/issues/539
|
- name: audit tools
|
||||||
# run: npm audit --audit-level=moderate
|
# `|| npm audit` to pretty-print the output if vulnerabilies are found after filtering.
|
||||||
|
run: npm audit --audit-level=moderate --json | scripts/audit-allow-list || npm audit --audit-level=moderate
|
||||||
|
|
||||||
- name: audit packages
|
- name: audit packages
|
||||||
run: npm run audit-all
|
run: npm run audit-all
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ name: "Code Scanning - Action"
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
pull_request:
|
pull_request:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 0 * * 0'
|
- cron: '0 0 * * 0'
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
@@ -6,7 +6,7 @@ Problem Matchers are a way to scan the output of actions for a specified regex p
|
|||||||
|
|
||||||
Currently, GitHub Actions limit the annotation count in a workflow run.
|
Currently, GitHub Actions limit the annotation count in a workflow run.
|
||||||
|
|
||||||
- 10 warning annotations and 10 error annotations per step
|
- 10 warning annotations, 10 error annotations, and 10 notice annotations per step
|
||||||
- 50 annotations per job (sum of annotations from all the steps)
|
- 50 annotations per job (sum of annotations from all the steps)
|
||||||
- 50 annotations per run (separate from the job annotations, these annotations aren’t created by users)
|
- 50 annotations per run (separate from the job annotations, these annotations aren’t created by users)
|
||||||
|
|
||||||
@@ -144,6 +144,6 @@ Use ECMAScript regular expression syntax when testing patterns.
|
|||||||
|
|
||||||
### File property getting dropped
|
### File property getting dropped
|
||||||
|
|
||||||
[Enable debug logging](https://help.github.com/en/actions/configuring-and-managing-workflows/managing-a-workflow-run#enabling-debug-logging) to determine why the file is getting dropped.
|
[Enable debug logging](https://docs.github.com/en/actions/managing-workflow-runs/enabling-debug-logging) to determine why the file is getting dropped.
|
||||||
|
|
||||||
This usually happens when the file does not exist or is not under the workflow repo.
|
This usually happens when the file does not exist or is not under the workflow repo.
|
||||||
|
|||||||
Generated
+32146
-12842
File diff suppressed because it is too large
Load Diff
+13
-12
@@ -9,24 +9,25 @@
|
|||||||
"format": "prettier --write packages/**/*.ts",
|
"format": "prettier --write packages/**/*.ts",
|
||||||
"format-check": "prettier --check packages/**/*.ts",
|
"format-check": "prettier --check packages/**/*.ts",
|
||||||
"lint": "eslint packages/**/*.ts",
|
"lint": "eslint packages/**/*.ts",
|
||||||
|
"lint-fix": "eslint packages/**/*.ts --fix",
|
||||||
"new-package": "scripts/create-package",
|
"new-package": "scripts/create-package",
|
||||||
"test": "jest --testTimeout 10000"
|
"test": "jest --testTimeout 10000"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^24.0.11",
|
"@types/jest": "^24.9.1",
|
||||||
"@types/node": "^12.12.47",
|
"@types/node": "^12.20.13",
|
||||||
"@types/signale": "^1.2.1",
|
"@types/signale": "^1.4.1",
|
||||||
"@typescript-eslint/parser": "^2.2.7",
|
"@typescript-eslint/parser": "^4.0.0",
|
||||||
"concurrently": "^4.1.0",
|
"concurrently": "^6.1.0",
|
||||||
"eslint": "^5.16.0",
|
"eslint": "^7.23.0",
|
||||||
"eslint-plugin-github": "^2.0.0",
|
"eslint-plugin-github": "^4.1.3",
|
||||||
"eslint-plugin-jest": "^22.5.1",
|
"eslint-plugin-jest": "^22.21.0",
|
||||||
"flow-bin": "^0.115.0",
|
"flow-bin": "^0.115.0",
|
||||||
"jest": "^25.1.0",
|
"jest": "^26.6.3",
|
||||||
"jest-circus": "^24.7.1",
|
"jest-circus": "^24.9.0",
|
||||||
"lerna": "^3.18.4",
|
"lerna": "^4.0.0",
|
||||||
"prettier": "^1.19.1",
|
"prettier": "^1.19.1",
|
||||||
"ts-jest": "^25.4.0",
|
"ts-jest": "^26.5.6",
|
||||||
"typescript": "^3.9.9"
|
"typescript": "^3.9.9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,3 +58,6 @@
|
|||||||
|
|
||||||
- Bump @actions/http-client to version 1.0.11 to fix proxy related issues during artifact upload and download
|
- Bump @actions/http-client to version 1.0.11 to fix proxy related issues during artifact upload and download
|
||||||
|
|
||||||
|
### 0.5.2
|
||||||
|
|
||||||
|
- Add HTTP 500 as a retryable status code for artifact upload and download.
|
||||||
@@ -71,7 +71,7 @@ describe('Download Tests', () => {
|
|||||||
setupFailedResponse()
|
setupFailedResponse()
|
||||||
const downloadHttpClient = new DownloadHttpClient()
|
const downloadHttpClient = new DownloadHttpClient()
|
||||||
expect(downloadHttpClient.listArtifacts()).rejects.toThrow(
|
expect(downloadHttpClient.listArtifacts()).rejects.toThrow(
|
||||||
'List Artifacts failed: Artifact service responded with 500'
|
'List Artifacts failed: Artifact service responded with 400'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -113,7 +113,7 @@ describe('Download Tests', () => {
|
|||||||
configVariables.getRuntimeUrl()
|
configVariables.getRuntimeUrl()
|
||||||
)
|
)
|
||||||
).rejects.toThrow(
|
).rejects.toThrow(
|
||||||
`Get Container Items failed: Artifact service responded with 500`
|
`Get Container Items failed: Artifact service responded with 400`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -166,7 +166,7 @@ describe('Download Tests', () => {
|
|||||||
it('Test retryable status codes during artifact download', async () => {
|
it('Test retryable status codes during artifact download', async () => {
|
||||||
// The first http response should return a retryable status call while the subsequent call should return a 200 so
|
// The first http response should return a retryable status call while the subsequent call should return a 200 so
|
||||||
// the download should successfully finish
|
// the download should successfully finish
|
||||||
const retryableStatusCodes = [429, 502, 503, 504]
|
const retryableStatusCodes = [429, 500, 502, 503, 504]
|
||||||
for (const statusCode of retryableStatusCodes) {
|
for (const statusCode of retryableStatusCodes) {
|
||||||
const fileContents = Buffer.from('try, try again\n', defaultEncoding)
|
const fileContents = Buffer.from('try, try again\n', defaultEncoding)
|
||||||
const targetPath = path.join(root, `FileC-${statusCode}.txt`)
|
const targetPath = path.join(root, `FileC-${statusCode}.txt`)
|
||||||
@@ -357,7 +357,7 @@ describe('Download Tests', () => {
|
|||||||
plaintext: Buffer | string
|
plaintext: Buffer | string
|
||||||
): Promise<Buffer> {
|
): Promise<Buffer> {
|
||||||
if (isGzip) {
|
if (isGzip) {
|
||||||
return <Buffer>await promisify(gzip)(plaintext)
|
return await promisify(gzip)(plaintext)
|
||||||
} else if (typeof plaintext === 'string') {
|
} else if (typeof plaintext === 'string') {
|
||||||
return Buffer.from(plaintext, defaultEncoding)
|
return Buffer.from(plaintext, defaultEncoding)
|
||||||
} else {
|
} else {
|
||||||
@@ -468,7 +468,7 @@ describe('Download Tests', () => {
|
|||||||
function setupFailedResponse(): void {
|
function setupFailedResponse(): void {
|
||||||
jest.spyOn(HttpClient.prototype, 'get').mockImplementationOnce(async () => {
|
jest.spyOn(HttpClient.prototype, 'get').mockImplementationOnce(async () => {
|
||||||
const mockMessage = new http.IncomingMessage(new net.Socket())
|
const mockMessage = new http.IncomingMessage(new net.Socket())
|
||||||
mockMessage.statusCode = 500
|
mockMessage.statusCode = 400
|
||||||
return new Promise<HttpClientResponse>(resolve => {
|
return new Promise<HttpClientResponse>(resolve => {
|
||||||
resolve({
|
resolve({
|
||||||
message: mockMessage,
|
message: mockMessage,
|
||||||
|
|||||||
@@ -107,8 +107,8 @@ test('retry fails after exhausting retries', async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('retry fails after non-retryable status code', async () => {
|
test('retry fails after non-retryable status code', async () => {
|
||||||
await testRetry([500, 200], {
|
await testRetry([400, 200], {
|
||||||
responseCode: 500,
|
responseCode: 400,
|
||||||
errorMessage: 'test failed: Artifact service responded with 500'
|
errorMessage: 'test failed: Artifact service responded with 400'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/artifact",
|
"name": "@actions/artifact",
|
||||||
"version": "0.5.1",
|
"version": "0.5.2",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/artifact",
|
"name": "@actions/artifact",
|
||||||
"version": "0.5.1",
|
"version": "0.5.2",
|
||||||
"preview": true,
|
"preview": true,
|
||||||
"description": "Actions artifact lib",
|
"description": "Actions artifact lib",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export async function retry(
|
|||||||
throw Error(`${name} failed: ${errorMessage}`)
|
throw Error(`${name} failed: ${errorMessage}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function retryHttpClientRequest<T>(
|
export async function retryHttpClientRequest(
|
||||||
name: string,
|
name: string,
|
||||||
method: () => Promise<IHttpClientResponse>,
|
method: () => Promise<IHttpClientResponse>,
|
||||||
customErrorMessages: Map<number, string> = new Map(),
|
customErrorMessages: Map<number, string> = new Map(),
|
||||||
|
|||||||
@@ -72,8 +72,9 @@ export function isRetryableStatusCode(statusCode: number | undefined): boolean {
|
|||||||
|
|
||||||
const retryableStatusCodes = [
|
const retryableStatusCodes = [
|
||||||
HttpCodes.BadGateway,
|
HttpCodes.BadGateway,
|
||||||
HttpCodes.ServiceUnavailable,
|
|
||||||
HttpCodes.GatewayTimeout,
|
HttpCodes.GatewayTimeout,
|
||||||
|
HttpCodes.InternalServerError,
|
||||||
|
HttpCodes.ServiceUnavailable,
|
||||||
HttpCodes.TooManyRequests,
|
HttpCodes.TooManyRequests,
|
||||||
413 // Payload Too Large
|
413 // Payload Too Large
|
||||||
]
|
]
|
||||||
|
|||||||
+4
-3
@@ -87,7 +87,7 @@ test('download progress tracked correctly', () => {
|
|||||||
expect(progress.isDone()).toBe(true)
|
expect(progress.isDone()).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('display timer works correctly', () => {
|
test('display timer works correctly', done => {
|
||||||
const progress = new DownloadProgress(1000)
|
const progress = new DownloadProgress(1000)
|
||||||
|
|
||||||
const infoMock = jest.spyOn(core, 'info')
|
const infoMock = jest.spyOn(core, 'info')
|
||||||
@@ -103,6 +103,7 @@ test('display timer works correctly', () => {
|
|||||||
const test2 = (): void => {
|
const test2 = (): void => {
|
||||||
check()
|
check()
|
||||||
expect(progress.timeoutHandle).toBeUndefined()
|
expect(progress.timeoutHandle).toBeUndefined()
|
||||||
|
done()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the progress is displayed, stop the timer, and call test2.
|
// Validate the progress is displayed, stop the timer, and call test2.
|
||||||
@@ -112,7 +113,7 @@ test('display timer works correctly', () => {
|
|||||||
progress.stopDisplayTimer()
|
progress.stopDisplayTimer()
|
||||||
progress.setReceivedBytes(1000)
|
progress.setReceivedBytes(1000)
|
||||||
|
|
||||||
setTimeout(() => test2(), 100)
|
setTimeout(() => test2(), 500)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the timer, update the received bytes, and call test1.
|
// Start the timer, update the received bytes, and call test1.
|
||||||
@@ -122,7 +123,7 @@ test('display timer works correctly', () => {
|
|||||||
|
|
||||||
progress.setReceivedBytes(500)
|
progress.setReceivedBytes(500)
|
||||||
|
|
||||||
setTimeout(() => test1(), 100)
|
setTimeout(() => test1(), 500)
|
||||||
}
|
}
|
||||||
|
|
||||||
start()
|
start()
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ async function handleResponse(
|
|||||||
response: ITestResponse | undefined
|
response: ITestResponse | undefined
|
||||||
): Promise<ITestResponse> {
|
): Promise<ITestResponse> {
|
||||||
if (!response) {
|
if (!response) {
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
fail('Retry method called too many times')
|
fail('Retry method called too many times')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ beforeAll(() => {
|
|||||||
jest.spyOn(core, 'warning').mockImplementation(() => {})
|
jest.spyOn(core, 'warning').mockImplementation(() => {})
|
||||||
jest.spyOn(core, 'error').mockImplementation(() => {})
|
jest.spyOn(core, 'error').mockImplementation(() => {})
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
|
||||||
jest.spyOn(cacheUtils, 'getCacheFileName').mockImplementation(cm => {
|
jest.spyOn(cacheUtils, 'getCacheFileName').mockImplementation(cm => {
|
||||||
const actualUtils = jest.requireActual('../src/internal/cacheUtils')
|
const actualUtils = jest.requireActual('../src/internal/cacheUtils')
|
||||||
return actualUtils.getCacheFileName(cm)
|
return actualUtils.getCacheFileName(cm)
|
||||||
|
|||||||
-1
@@ -17,7 +17,6 @@ beforeAll(() => {
|
|||||||
jest.spyOn(core, 'warning').mockImplementation(() => {})
|
jest.spyOn(core, 'warning').mockImplementation(() => {})
|
||||||
jest.spyOn(core, 'error').mockImplementation(() => {})
|
jest.spyOn(core, 'error').mockImplementation(() => {})
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
|
||||||
jest.spyOn(cacheUtils, 'getCacheFileName').mockImplementation(cm => {
|
jest.spyOn(cacheUtils, 'getCacheFileName').mockImplementation(cm => {
|
||||||
const actualUtils = jest.requireActual('../src/internal/cacheUtils')
|
const actualUtils = jest.requireActual('../src/internal/cacheUtils')
|
||||||
return actualUtils.getCacheFileName(cm)
|
return actualUtils.getCacheFileName(cm)
|
||||||
|
|||||||
+4
-26
@@ -59,7 +59,6 @@
|
|||||||
"debug": "^4.1.0",
|
"debug": "^4.1.0",
|
||||||
"gensync": "^1.0.0-beta.1",
|
"gensync": "^1.0.0-beta.1",
|
||||||
"json5": "^2.1.2",
|
"json5": "^2.1.2",
|
||||||
"lodash": "^4.17.13",
|
|
||||||
"resolve": "^1.3.2",
|
"resolve": "^1.3.2",
|
||||||
"semver": "^5.4.1",
|
"semver": "^5.4.1",
|
||||||
"source-map": "^0.5.0"
|
"source-map": "^0.5.0"
|
||||||
@@ -79,7 +78,6 @@
|
|||||||
"requires": {
|
"requires": {
|
||||||
"@babel/types": "^7.9.0",
|
"@babel/types": "^7.9.0",
|
||||||
"jsesc": "^2.5.1",
|
"jsesc": "^2.5.1",
|
||||||
"lodash": "^4.17.13",
|
|
||||||
"source-map": "^0.5.0"
|
"source-map": "^0.5.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -134,8 +132,7 @@
|
|||||||
"@babel/helper-simple-access": "^7.8.3",
|
"@babel/helper-simple-access": "^7.8.3",
|
||||||
"@babel/helper-split-export-declaration": "^7.8.3",
|
"@babel/helper-split-export-declaration": "^7.8.3",
|
||||||
"@babel/template": "^7.8.6",
|
"@babel/template": "^7.8.6",
|
||||||
"@babel/types": "^7.9.0",
|
"@babel/types": "^7.9.0"
|
||||||
"lodash": "^4.17.13"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/helper-optimise-call-expression": {
|
"@babel/helper-optimise-call-expression": {
|
||||||
@@ -293,8 +290,7 @@
|
|||||||
"@babel/parser": "^7.9.0",
|
"@babel/parser": "^7.9.0",
|
||||||
"@babel/types": "^7.9.0",
|
"@babel/types": "^7.9.0",
|
||||||
"debug": "^4.1.0",
|
"debug": "^4.1.0",
|
||||||
"globals": "^11.1.0",
|
"globals": "^11.1.0"
|
||||||
"lodash": "^4.17.13"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/types": {
|
"@babel/types": {
|
||||||
@@ -303,7 +299,6 @@
|
|||||||
"integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==",
|
"integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/helper-validator-identifier": "^7.9.0",
|
"@babel/helper-validator-identifier": "^7.9.0",
|
||||||
"lodash": "^4.17.13",
|
|
||||||
"to-fast-properties": "^2.0.0"
|
"to-fast-properties": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2557,7 +2552,6 @@
|
|||||||
"whatwg-encoding": "^1.0.5",
|
"whatwg-encoding": "^1.0.5",
|
||||||
"whatwg-mimetype": "^2.3.0",
|
"whatwg-mimetype": "^2.3.0",
|
||||||
"whatwg-url": "^7.0.0",
|
"whatwg-url": "^7.0.0",
|
||||||
"ws": "^7.0.0",
|
|
||||||
"xml-name-validator": "^3.0.0"
|
"xml-name-validator": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2632,11 +2626,6 @@
|
|||||||
"p-locate": "^4.1.0"
|
"p-locate": "^4.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lodash": {
|
|
||||||
"version": "4.17.15",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
|
||||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
|
|
||||||
},
|
|
||||||
"lodash.memoize": {
|
"lodash.memoize": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||||
@@ -3202,10 +3191,7 @@
|
|||||||
"request-promise-core": {
|
"request-promise-core": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz",
|
||||||
"integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==",
|
"integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ=="
|
||||||
"requires": {
|
|
||||||
"lodash": "^4.17.15"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"request-promise-native": {
|
"request-promise-native": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
@@ -3734,10 +3720,7 @@
|
|||||||
"string-similarity": {
|
"string-similarity": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-1.1.0.tgz",
|
||||||
"integrity": "sha1-PGZJiFikZex8QMfYFzm72ZWQSRQ=",
|
"integrity": "sha1-PGZJiFikZex8QMfYFzm72ZWQSRQ="
|
||||||
"requires": {
|
|
||||||
"lodash": "^4.13.1"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"string-width": {
|
"string-width": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
@@ -4184,11 +4167,6 @@
|
|||||||
"typedarray-to-buffer": "^3.1.5"
|
"typedarray-to-buffer": "^3.1.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ws": {
|
|
||||||
"version": "7.2.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.2.3.tgz",
|
|
||||||
"integrity": "sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ=="
|
|
||||||
},
|
|
||||||
"xml-name-validator": {
|
"xml-name-validator": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz",
|
||||||
|
|||||||
Vendored
+25
-16
@@ -166,24 +166,33 @@ export async function saveCache(
|
|||||||
|
|
||||||
core.debug(`Archive Path: ${archivePath}`)
|
core.debug(`Archive Path: ${archivePath}`)
|
||||||
|
|
||||||
await createTar(archiveFolder, cachePaths, compressionMethod)
|
try {
|
||||||
if (core.isDebug()) {
|
await createTar(archiveFolder, cachePaths, compressionMethod)
|
||||||
await listTar(archivePath, compressionMethod)
|
if (core.isDebug()) {
|
||||||
}
|
await listTar(archivePath, compressionMethod)
|
||||||
|
}
|
||||||
|
|
||||||
const fileSizeLimit = 5 * 1024 * 1024 * 1024 // 5GB per repo limit
|
const fileSizeLimit = 5 * 1024 * 1024 * 1024 // 5GB per repo limit
|
||||||
const archiveFileSize = utils.getArchiveFileSizeInBytes(archivePath)
|
const archiveFileSize = utils.getArchiveFileSizeInBytes(archivePath)
|
||||||
core.debug(`File Size: ${archiveFileSize}`)
|
core.debug(`File Size: ${archiveFileSize}`)
|
||||||
if (archiveFileSize > fileSizeLimit) {
|
if (archiveFileSize > fileSizeLimit) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Cache size of ~${Math.round(
|
`Cache size of ~${Math.round(
|
||||||
archiveFileSize / (1024 * 1024)
|
archiveFileSize / (1024 * 1024)
|
||||||
)} MB (${archiveFileSize} B) is over the 5GB limit, not saving cache.`
|
)} MB (${archiveFileSize} B) is over the 5GB limit, not saving cache.`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
core.debug(`Saving Cache (ID: ${cacheId})`)
|
core.debug(`Saving Cache (ID: ${cacheId})`)
|
||||||
await cacheHttpClient.saveCache(cacheId, archivePath, options)
|
await cacheHttpClient.saveCache(cacheId, archivePath, options)
|
||||||
|
} finally {
|
||||||
|
// Try to delete the archive to save space
|
||||||
|
try {
|
||||||
|
await utils.unlinkFile(archivePath)
|
||||||
|
} catch (error) {
|
||||||
|
core.debug(`Failed to delete archive: ${error}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return cacheId
|
return cacheId
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -133,7 +133,7 @@ export class DownloadProgress {
|
|||||||
*
|
*
|
||||||
* @param delayInMs the delay between each write
|
* @param delayInMs the delay between each write
|
||||||
*/
|
*/
|
||||||
startDisplayTimer(delayInMs: number = 1000): void {
|
startDisplayTimer(delayInMs = 1000): void {
|
||||||
const displayCallback = (): void => {
|
const displayCallback = (): void => {
|
||||||
this.display()
|
this.display()
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -120,7 +120,7 @@ export async function retryTypedResponse<T>(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function retryHttpClientResponse<T>(
|
export async function retryHttpClientResponse(
|
||||||
name: string,
|
name: string,
|
||||||
method: () => Promise<IHttpClientResponse>,
|
method: () => Promise<IHttpClientResponse>,
|
||||||
maxAttempts = DefaultRetryAttempts,
|
maxAttempts = DefaultRetryAttempts,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ Outputs can be set with `setOutput` which makes them available to be mapped into
|
|||||||
```js
|
```js
|
||||||
const myInput = core.getInput('inputName', { required: true });
|
const myInput = core.getInput('inputName', { required: true });
|
||||||
const myBooleanInput = core.getBooleanInput('booleanInputName', { required: true });
|
const myBooleanInput = core.getBooleanInput('booleanInputName', { required: true });
|
||||||
|
const myMultilineInput = core.getMultilineInput('multilineInputName', { required: true });
|
||||||
core.setOutput('outputKey', 'outputVal');
|
core.setOutput('outputKey', 'outputVal');
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -91,6 +92,8 @@ try {
|
|||||||
|
|
||||||
// Do stuff
|
// Do stuff
|
||||||
core.info('Output to the actions build log')
|
core.info('Output to the actions build log')
|
||||||
|
|
||||||
|
core.notice('This is a message that will also emit an annotation')
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
core.error(`Error ${err}, action may still succeed though`);
|
core.error(`Error ${err}, action may still succeed though`);
|
||||||
@@ -114,6 +117,54 @@ const result = await core.group('Do something async', async () => {
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Annotations
|
||||||
|
|
||||||
|
This library has 3 methods that will produce [annotations](https://docs.github.com/en/rest/reference/checks#create-a-check-run).
|
||||||
|
```js
|
||||||
|
core.error('This is a bad error. This will also fail the build.')
|
||||||
|
|
||||||
|
core.warning('Something went wrong, but it\'s not bad enough to fail the build.')
|
||||||
|
|
||||||
|
core.notice('Something happened that you might want to know about.')
|
||||||
|
```
|
||||||
|
|
||||||
|
These will surface to the UI in the Actions page and on Pull Requests. They look something like this:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
These annotations can also be attached to particular lines and columns of your source files to show exactly where a problem is occuring.
|
||||||
|
|
||||||
|
These options are:
|
||||||
|
```typescript
|
||||||
|
export interface AnnotationProperties {
|
||||||
|
/**
|
||||||
|
* A title for the annotation.
|
||||||
|
*/
|
||||||
|
title?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The start line for the annotation.
|
||||||
|
*/
|
||||||
|
startLine?: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The end line for the annotation. Defaults to `startLine` when `startLine` is provided.
|
||||||
|
*/
|
||||||
|
endLine?: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The start column for the annotation. Cannot be sent when `startLine` and `endLine` are different values.
|
||||||
|
*/
|
||||||
|
startColumn?: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The start column for the annotation. Cannot be sent when `startLine` and `endLine` are different values.
|
||||||
|
* Defaults to `startColumn` when `startColumn` is provided.
|
||||||
|
*/
|
||||||
|
endColumn?: number
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
#### Styling output
|
#### Styling output
|
||||||
|
|
||||||
Colored output is supported in the Action logs via standard [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code). 3/4 bit, 8 bit and 24 bit colors are all supported.
|
Colored output is supported in the Action logs via standard [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code). 3/4 bit, 8 bit and 24 bit colors are all supported.
|
||||||
@@ -206,3 +257,51 @@ var pid = core.getState("pidToKill");
|
|||||||
|
|
||||||
process.kill(pid);
|
process.kill(pid);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### OIDC Token
|
||||||
|
|
||||||
|
You can use these methods to interact with the GitHub OIDC provider and get a JWT ID token which would help to get access token from third party cloud providers.
|
||||||
|
|
||||||
|
**Method Name**: getIDToken()
|
||||||
|
|
||||||
|
**Inputs**
|
||||||
|
|
||||||
|
audience : optional
|
||||||
|
|
||||||
|
**Outputs**
|
||||||
|
|
||||||
|
A [JWT](https://jwt.io/) ID Token
|
||||||
|
|
||||||
|
In action's `main.ts`:
|
||||||
|
```js
|
||||||
|
const core = require('@actions/core');
|
||||||
|
async function getIDTokenAction(): Promise<void> {
|
||||||
|
|
||||||
|
const audience = core.getInput('audience', {required: false})
|
||||||
|
|
||||||
|
const id_token1 = await core.getIDToken() // ID Token with default audience
|
||||||
|
const id_token2 = await core.getIDToken(audience) // ID token with custom audience
|
||||||
|
|
||||||
|
// this id_token can be used to get access token from third party cloud providers
|
||||||
|
}
|
||||||
|
getIDTokenAction()
|
||||||
|
```
|
||||||
|
|
||||||
|
In action's `actions.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: 'GetIDToken'
|
||||||
|
description: 'Get ID token from Github OIDC provider'
|
||||||
|
inputs:
|
||||||
|
audience:
|
||||||
|
description: 'Audience for which the ID token is intended for'
|
||||||
|
required: false
|
||||||
|
outputs:
|
||||||
|
id_token1:
|
||||||
|
description: 'ID token obtained from OIDC provider'
|
||||||
|
id_token2:
|
||||||
|
description: 'ID token obtained from OIDC provider'
|
||||||
|
runs:
|
||||||
|
using: 'node12'
|
||||||
|
main: 'dist/index.js'
|
||||||
|
```
|
||||||
@@ -1,5 +1,19 @@
|
|||||||
# @actions/core Releases
|
# @actions/core Releases
|
||||||
|
|
||||||
|
### 1.6.0
|
||||||
|
- [Added OIDC Client function `getIDToken`](https://github.com/actions/toolkit/pull/919)
|
||||||
|
- [Added `file` parameter to `AnnotationProperties`](https://github.com/actions/toolkit/pull/896)
|
||||||
|
|
||||||
|
### 1.5.0
|
||||||
|
- [Added support for notice annotations and more annotation fields](https://github.com/actions/toolkit/pull/855)
|
||||||
|
|
||||||
|
### 1.4.0
|
||||||
|
- [Added the `getMultilineInput` function](https://github.com/actions/toolkit/pull/829)
|
||||||
|
|
||||||
|
### 1.3.0
|
||||||
|
- [Added the trimWhitespace option to getInput](https://github.com/actions/toolkit/pull/802)
|
||||||
|
- [Added the getBooleanInput function](https://github.com/actions/toolkit/pull/725)
|
||||||
|
|
||||||
### 1.2.7
|
### 1.2.7
|
||||||
- [Prepend newline for set-output](https://github.com/actions/toolkit/pull/772)
|
- [Prepend newline for set-output](https://github.com/actions/toolkit/pull/772)
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import * as fs from 'fs'
|
|||||||
import * as os from 'os'
|
import * as os from 'os'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import * as core from '../src/core'
|
import * as core from '../src/core'
|
||||||
|
import {HttpClient} from '@actions/http-client'
|
||||||
|
import {toCommandProperties} from '../src/utils'
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/unbound-method */
|
/* eslint-disable @typescript-eslint/unbound-method */
|
||||||
|
|
||||||
@@ -27,6 +29,9 @@ const testEnvVars = {
|
|||||||
INPUT_BOOLEAN_INPUT_FALSE2: 'False',
|
INPUT_BOOLEAN_INPUT_FALSE2: 'False',
|
||||||
INPUT_BOOLEAN_INPUT_FALSE3: 'FALSE',
|
INPUT_BOOLEAN_INPUT_FALSE3: 'FALSE',
|
||||||
INPUT_WRONG_BOOLEAN_INPUT: 'wrong',
|
INPUT_WRONG_BOOLEAN_INPUT: 'wrong',
|
||||||
|
INPUT_WITH_TRAILING_WHITESPACE: ' some val ',
|
||||||
|
|
||||||
|
INPUT_MY_INPUT_LIST: 'val1\nval2\nval3',
|
||||||
|
|
||||||
// Save inputs
|
// Save inputs
|
||||||
STATE_TEST_1: 'state_val',
|
STATE_TEST_1: 'state_val',
|
||||||
@@ -165,6 +170,30 @@ describe('@actions/core', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('getMultilineInput works', () => {
|
||||||
|
expect(core.getMultilineInput('my input list')).toEqual([
|
||||||
|
'val1',
|
||||||
|
'val2',
|
||||||
|
'val3'
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('getInput trims whitespace by default', () => {
|
||||||
|
expect(core.getInput('with trailing whitespace')).toBe('some val')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('getInput trims whitespace when option is explicitly true', () => {
|
||||||
|
expect(
|
||||||
|
core.getInput('with trailing whitespace', {trimWhitespace: true})
|
||||||
|
).toBe('some val')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('getInput does not trim whitespace when option is false', () => {
|
||||||
|
expect(
|
||||||
|
core.getInput('with trailing whitespace', {trimWhitespace: false})
|
||||||
|
).toBe(' some val ')
|
||||||
|
})
|
||||||
|
|
||||||
it('getInput gets non-required boolean input', () => {
|
it('getInput gets non-required boolean input', () => {
|
||||||
expect(core.getBooleanInput('boolean input')).toBe(true)
|
expect(core.getBooleanInput('boolean input')).toBe(true)
|
||||||
})
|
})
|
||||||
@@ -242,6 +271,20 @@ describe('@actions/core', () => {
|
|||||||
assertWriteCalls([`::error::Error: ${message}${os.EOL}`])
|
assertWriteCalls([`::error::Error: ${message}${os.EOL}`])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('error handles parameters correctly', () => {
|
||||||
|
const message = 'this is my error message'
|
||||||
|
core.error(new Error(message), {
|
||||||
|
title: 'A title',
|
||||||
|
startColumn: 1,
|
||||||
|
endColumn: 2,
|
||||||
|
startLine: 5,
|
||||||
|
endLine: 5
|
||||||
|
})
|
||||||
|
assertWriteCalls([
|
||||||
|
`::error title=A title,line=5,endLine=5,col=1,endColumn=2::Error: ${message}${os.EOL}`
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
it('warning sets the correct message', () => {
|
it('warning sets the correct message', () => {
|
||||||
core.warning('Warning')
|
core.warning('Warning')
|
||||||
assertWriteCalls([`::warning::Warning${os.EOL}`])
|
assertWriteCalls([`::warning::Warning${os.EOL}`])
|
||||||
@@ -258,6 +301,38 @@ describe('@actions/core', () => {
|
|||||||
assertWriteCalls([`::warning::Error: ${message}${os.EOL}`])
|
assertWriteCalls([`::warning::Error: ${message}${os.EOL}`])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('warning handles parameters correctly', () => {
|
||||||
|
const message = 'this is my error message'
|
||||||
|
core.warning(new Error(message), {
|
||||||
|
title: 'A title',
|
||||||
|
startColumn: 1,
|
||||||
|
endColumn: 2,
|
||||||
|
startLine: 5,
|
||||||
|
endLine: 5
|
||||||
|
})
|
||||||
|
assertWriteCalls([
|
||||||
|
`::warning title=A title,line=5,endLine=5,col=1,endColumn=2::Error: ${message}${os.EOL}`
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('annotations map field names correctly', () => {
|
||||||
|
const commandProperties = toCommandProperties({
|
||||||
|
title: 'A title',
|
||||||
|
startColumn: 1,
|
||||||
|
endColumn: 2,
|
||||||
|
startLine: 5,
|
||||||
|
endLine: 5
|
||||||
|
})
|
||||||
|
expect(commandProperties.title).toBe('A title')
|
||||||
|
expect(commandProperties.col).toBe(1)
|
||||||
|
expect(commandProperties.endColumn).toBe(2)
|
||||||
|
expect(commandProperties.line).toBe(5)
|
||||||
|
expect(commandProperties.endLine).toBe(5)
|
||||||
|
|
||||||
|
expect(commandProperties.startColumn).toBeUndefined()
|
||||||
|
expect(commandProperties.startLine).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
it('startGroup starts a new group', () => {
|
it('startGroup starts a new group', () => {
|
||||||
core.startGroup('my-group')
|
core.startGroup('my-group')
|
||||||
assertWriteCalls([`::group::my-group${os.EOL}`])
|
assertWriteCalls([`::group::my-group${os.EOL}`])
|
||||||
@@ -360,3 +435,20 @@ function verifyFileCommand(command: string, expectedContents: string): void {
|
|||||||
fs.unlinkSync(filePath)
|
fs.unlinkSync(filePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getTokenEndPoint(): string {
|
||||||
|
return 'https://vstoken.actions.githubusercontent.com/.well-known/openid-configuration'
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('oidc-client-tests', () => {
|
||||||
|
it('Get Http Client', async () => {
|
||||||
|
const http = new HttpClient('actions/oidc-client')
|
||||||
|
expect(http).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('HTTP get request to get token endpoint', async () => {
|
||||||
|
const http = new HttpClient('actions/oidc-client')
|
||||||
|
const res = await http.get(getTokenEndPoint())
|
||||||
|
expect(res.message.statusCode).toBe(200)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
Generated
+50
-2
@@ -1,14 +1,62 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/core",
|
"name": "@actions/core",
|
||||||
"version": "1.2.7",
|
"version": "1.6.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "@actions/core",
|
||||||
|
"version": "1.6.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@actions/http-client": "^1.0.11"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^12.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@actions/http-client": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
|
||||||
|
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
|
||||||
|
"dependencies": {
|
||||||
|
"tunnel": "0.0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "12.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.2.tgz",
|
||||||
|
"integrity": "sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/tunnel": {
|
||||||
|
"version": "0.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
||||||
|
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@actions/http-client": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
|
||||||
|
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
|
||||||
|
"requires": {
|
||||||
|
"tunnel": "0.0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "12.0.2",
|
"version": "12.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.2.tgz",
|
||||||
"integrity": "sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA==",
|
"integrity": "sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
|
},
|
||||||
|
"tunnel": {
|
||||||
|
"version": "0.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
||||||
|
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/core",
|
"name": "@actions/core",
|
||||||
"version": "1.2.7",
|
"version": "1.6.0",
|
||||||
"description": "Actions core lib",
|
"description": "Actions core lib",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"github",
|
"github",
|
||||||
@@ -35,6 +35,9 @@
|
|||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/actions/toolkit/issues"
|
"url": "https://github.com/actions/toolkit/issues"
|
||||||
},
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@actions/http-client": "^1.0.11"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^12.0.2"
|
"@types/node": "^12.0.2"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {toCommandValue} from './utils'
|
|||||||
// We use any as a valid input type
|
// We use any as a valid input type
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
interface CommandProperties {
|
export interface CommandProperties {
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ export function issueCommand(
|
|||||||
process.stdout.write(cmd.toString() + os.EOL)
|
process.stdout.write(cmd.toString() + os.EOL)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function issue(name: string, message: string = ''): void {
|
export function issue(name: string, message = ''): void {
|
||||||
issueCommand(name, {}, message)
|
issueCommand(name, {}, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+105
-7
@@ -1,16 +1,21 @@
|
|||||||
import {issue, issueCommand} from './command'
|
import {issue, issueCommand} from './command'
|
||||||
import {issueCommand as issueFileCommand} from './file-command'
|
import {issueCommand as issueFileCommand} from './file-command'
|
||||||
import {toCommandValue} from './utils'
|
import {toCommandProperties, toCommandValue} from './utils'
|
||||||
|
|
||||||
import * as os from 'os'
|
import * as os from 'os'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
|
|
||||||
|
import {OidcClient} from './oidc-utils'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for getInput options
|
* Interface for getInput options
|
||||||
*/
|
*/
|
||||||
export interface InputOptions {
|
export interface InputOptions {
|
||||||
/** Optional. Whether the input is required. If required and not present, will throw. Defaults to false */
|
/** Optional. Whether the input is required. If required and not present, will throw. Defaults to false */
|
||||||
required?: boolean
|
required?: boolean
|
||||||
|
|
||||||
|
/** Optional. Whether leading/trailing whitespace will be trimmed for the input. Defaults to true */
|
||||||
|
trimWhitespace?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,6 +33,38 @@ export enum ExitCode {
|
|||||||
Failure = 1
|
Failure = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional properties that can be sent with annotatation commands (notice, error, and warning)
|
||||||
|
* See: https://docs.github.com/en/rest/reference/checks#create-a-check-run for more information about annotations.
|
||||||
|
*/
|
||||||
|
export interface AnnotationProperties {
|
||||||
|
/**
|
||||||
|
* A title for the annotation.
|
||||||
|
*/
|
||||||
|
title?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The start line for the annotation.
|
||||||
|
*/
|
||||||
|
startLine?: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The end line for the annotation. Defaults to `startLine` when `startLine` is provided.
|
||||||
|
*/
|
||||||
|
endLine?: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The start column for the annotation. Cannot be sent when `startLine` and `endLine` are different values.
|
||||||
|
*/
|
||||||
|
startColumn?: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The start column for the annotation. Cannot be sent when `startLine` and `endLine` are different values.
|
||||||
|
* Defaults to `startColumn` when `startColumn` is provided.
|
||||||
|
*/
|
||||||
|
endColumn?: number
|
||||||
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
//-----------------------------------------------------------------------
|
||||||
// Variables
|
// Variables
|
||||||
//-----------------------------------------------------------------------
|
//-----------------------------------------------------------------------
|
||||||
@@ -75,7 +112,9 @@ export function addPath(inputPath: string): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the value of an input. The value is also trimmed.
|
* Gets the value of an input.
|
||||||
|
* Unless trimWhitespace is set to false in InputOptions, the value is also trimmed.
|
||||||
|
* Returns an empty string if the value is not defined.
|
||||||
*
|
*
|
||||||
* @param name name of the input to get
|
* @param name name of the input to get
|
||||||
* @param options optional. See InputOptions.
|
* @param options optional. See InputOptions.
|
||||||
@@ -88,9 +127,32 @@ export function getInput(name: string, options?: InputOptions): string {
|
|||||||
throw new Error(`Input required and not supplied: ${name}`)
|
throw new Error(`Input required and not supplied: ${name}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options && options.trimWhitespace === false) {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
return val.trim()
|
return val.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the values of an multiline input. Each value is also trimmed.
|
||||||
|
*
|
||||||
|
* @param name name of the input to get
|
||||||
|
* @param options optional. See InputOptions.
|
||||||
|
* @returns string[]
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export function getMultilineInput(
|
||||||
|
name: string,
|
||||||
|
options?: InputOptions
|
||||||
|
): string[] {
|
||||||
|
const inputs: string[] = getInput(name, options)
|
||||||
|
.split('\n')
|
||||||
|
.filter(x => x !== '')
|
||||||
|
|
||||||
|
return inputs
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the input value of the boolean type in the YAML 1.2 "core schema" specification.
|
* Gets the input value of the boolean type in the YAML 1.2 "core schema" specification.
|
||||||
* Support boolean input list: `true | True | TRUE | false | False | FALSE` .
|
* Support boolean input list: `true | True | TRUE | false | False | FALSE` .
|
||||||
@@ -171,17 +233,49 @@ export function debug(message: string): void {
|
|||||||
/**
|
/**
|
||||||
* Adds an error issue
|
* Adds an error issue
|
||||||
* @param message error issue message. Errors will be converted to string via toString()
|
* @param message error issue message. Errors will be converted to string via toString()
|
||||||
|
* @param properties optional properties to add to the annotation.
|
||||||
*/
|
*/
|
||||||
export function error(message: string | Error): void {
|
export function error(
|
||||||
issue('error', message instanceof Error ? message.toString() : message)
|
message: string | Error,
|
||||||
|
properties: AnnotationProperties = {}
|
||||||
|
): void {
|
||||||
|
issueCommand(
|
||||||
|
'error',
|
||||||
|
toCommandProperties(properties),
|
||||||
|
message instanceof Error ? message.toString() : message
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds an warning issue
|
* Adds a warning issue
|
||||||
* @param message warning issue message. Errors will be converted to string via toString()
|
* @param message warning issue message. Errors will be converted to string via toString()
|
||||||
|
* @param properties optional properties to add to the annotation.
|
||||||
*/
|
*/
|
||||||
export function warning(message: string | Error): void {
|
export function warning(
|
||||||
issue('warning', message instanceof Error ? message.toString() : message)
|
message: string | Error,
|
||||||
|
properties: AnnotationProperties = {}
|
||||||
|
): void {
|
||||||
|
issueCommand(
|
||||||
|
'warning',
|
||||||
|
toCommandProperties(properties),
|
||||||
|
message instanceof Error ? message.toString() : message
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a notice issue
|
||||||
|
* @param message notice issue message. Errors will be converted to string via toString()
|
||||||
|
* @param properties optional properties to add to the annotation.
|
||||||
|
*/
|
||||||
|
export function notice(
|
||||||
|
message: string | Error,
|
||||||
|
properties: AnnotationProperties = {}
|
||||||
|
): void {
|
||||||
|
issueCommand(
|
||||||
|
'notice',
|
||||||
|
toCommandProperties(properties),
|
||||||
|
message instanceof Error ? message.toString() : message
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -256,3 +350,7 @@ export function saveState(name: string, value: any): void {
|
|||||||
export function getState(name: string): string {
|
export function getState(name: string): string {
|
||||||
return process.env[`STATE_${name}`] || ''
|
return process.env[`STATE_${name}`] || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getIDToken(aud?: string): Promise<string> {
|
||||||
|
return await OidcClient.getIDToken(aud)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-extraneous-class */
|
||||||
|
import * as actions_http_client from '@actions/http-client'
|
||||||
|
import {IRequestOptions} from '@actions/http-client/interfaces'
|
||||||
|
import {HttpClient} from '@actions/http-client'
|
||||||
|
import {BearerCredentialHandler} from '@actions/http-client/auth'
|
||||||
|
import {debug, setSecret} from './core'
|
||||||
|
interface TokenResponse {
|
||||||
|
value?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OidcClient {
|
||||||
|
private static createHttpClient(
|
||||||
|
allowRetry = true,
|
||||||
|
maxRetry = 10
|
||||||
|
): actions_http_client.HttpClient {
|
||||||
|
const requestOptions: IRequestOptions = {
|
||||||
|
allowRetries: allowRetry,
|
||||||
|
maxRetries: maxRetry
|
||||||
|
}
|
||||||
|
|
||||||
|
return new HttpClient(
|
||||||
|
'actions/oidc-client',
|
||||||
|
[new BearerCredentialHandler(OidcClient.getRequestToken())],
|
||||||
|
requestOptions
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getRequestToken(): string {
|
||||||
|
const token = process.env['ACTIONS_ID_TOKEN_REQUEST_TOKEN']
|
||||||
|
if (!token) {
|
||||||
|
throw new Error(
|
||||||
|
'Unable to get ACTIONS_ID_TOKEN_REQUEST_TOKEN env variable'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getIDTokenUrl(): string {
|
||||||
|
const runtimeUrl = process.env['ACTIONS_ID_TOKEN_REQUEST_URL']
|
||||||
|
if (!runtimeUrl) {
|
||||||
|
throw new Error('Unable to get ACTIONS_ID_TOKEN_REQUEST_URL env variable')
|
||||||
|
}
|
||||||
|
return runtimeUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async getCall(id_token_url: string): Promise<string> {
|
||||||
|
const httpclient = OidcClient.createHttpClient()
|
||||||
|
|
||||||
|
const res = await httpclient
|
||||||
|
.getJson<TokenResponse>(id_token_url)
|
||||||
|
.catch(error => {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to get ID Token. \n
|
||||||
|
Error Code : ${error.statusCode}\n
|
||||||
|
Error Message: ${error.result.message}`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const id_token = res.result?.value
|
||||||
|
if (!id_token) {
|
||||||
|
throw new Error('Response json body do not have ID Token field')
|
||||||
|
}
|
||||||
|
return id_token
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getIDToken(audience?: string): Promise<string> {
|
||||||
|
try {
|
||||||
|
// New ID Token is requested from action service
|
||||||
|
let id_token_url: string = OidcClient.getIDTokenUrl()
|
||||||
|
if (audience) {
|
||||||
|
const encodedAudience = encodeURIComponent(audience)
|
||||||
|
id_token_url = `${id_token_url}&audience=${encodedAudience}`
|
||||||
|
}
|
||||||
|
|
||||||
|
debug(`ID token url is ${id_token_url}`)
|
||||||
|
|
||||||
|
const id_token = await OidcClient.getCall(id_token_url)
|
||||||
|
setSecret(id_token)
|
||||||
|
return id_token
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Error message: ${error.message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
// We use any as a valid input type
|
// We use any as a valid input type
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
import {AnnotationProperties} from './core'
|
||||||
|
import {CommandProperties} from './command'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sanitizes an input into a string so it can be passed into issueCommand safely
|
* Sanitizes an input into a string so it can be passed into issueCommand safely
|
||||||
* @param input input to sanitize into a string
|
* @param input input to sanitize into a string
|
||||||
@@ -13,3 +16,25 @@ export function toCommandValue(input: any): string {
|
|||||||
}
|
}
|
||||||
return JSON.stringify(input)
|
return JSON.stringify(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param annotationProperties
|
||||||
|
* @returns The command properties to send with the actual annotation command
|
||||||
|
* See IssueCommandProperties: https://github.com/actions/runner/blob/main/src/Runner.Worker/ActionCommandManager.cs#L646
|
||||||
|
*/
|
||||||
|
export function toCommandProperties(
|
||||||
|
annotationProperties: AnnotationProperties
|
||||||
|
): CommandProperties {
|
||||||
|
if (!Object.keys(annotationProperties).length) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: annotationProperties.title,
|
||||||
|
line: annotationProperties.startLine,
|
||||||
|
endLine: annotationProperties.endLine,
|
||||||
|
col: annotationProperties.startColumn,
|
||||||
|
endColumn: annotationProperties.endColumn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @actions/exec Releases
|
# @actions/exec Releases
|
||||||
|
|
||||||
|
### 1.1.0
|
||||||
|
|
||||||
|
- [Fix stdline dropping large output](https://github.com/actions/toolkit/pull/773)
|
||||||
|
- [Add getExecOutput function](https://github.com/actions/toolkit/pull/814)
|
||||||
|
- [Better error for bad cwd](https://github.com/actions/toolkit/pull/793)
|
||||||
|
|
||||||
### 1.0.3
|
### 1.0.3
|
||||||
|
|
||||||
- [Add \"types\" to package.json](https://github.com/actions/toolkit/pull/221)
|
- [Add \"types\" to package.json](https://github.com/actions/toolkit/pull/221)
|
||||||
|
|||||||
@@ -286,6 +286,31 @@ describe('@actions/exec', () => {
|
|||||||
expect(stderrCalled).toBeTruthy()
|
expect(stderrCalled).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Handles large stdline', async () => {
|
||||||
|
const stdlinePath: string = path.join(
|
||||||
|
__dirname,
|
||||||
|
'scripts',
|
||||||
|
'stdlineoutput.js'
|
||||||
|
)
|
||||||
|
const nodePath: string = await io.which('node', true)
|
||||||
|
|
||||||
|
const _testExecOptions = getExecOptions()
|
||||||
|
let largeLine = ''
|
||||||
|
_testExecOptions.listeners = {
|
||||||
|
stdline: (line: string) => {
|
||||||
|
largeLine = line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const exitCode = await exec.exec(
|
||||||
|
`"${nodePath}"`,
|
||||||
|
[stdlinePath],
|
||||||
|
_testExecOptions
|
||||||
|
)
|
||||||
|
expect(exitCode).toBe(0)
|
||||||
|
expect(Buffer.byteLength(largeLine)).toEqual(2 ** 16 + 1)
|
||||||
|
})
|
||||||
|
|
||||||
it('Handles stdin shell', async () => {
|
it('Handles stdin shell', async () => {
|
||||||
let command: string
|
let command: string
|
||||||
if (IS_WINDOWS) {
|
if (IS_WINDOWS) {
|
||||||
@@ -538,6 +563,22 @@ describe('@actions/exec', () => {
|
|||||||
expect(output.trim()).toBe(`args[0]: "hello"${os.EOL}args[1]: "world"`)
|
expect(output.trim()).toBe(`args[0]: "hello"${os.EOL}args[1]: "world"`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Exec roots throws friendly error when bad cwd is specified', async () => {
|
||||||
|
const execOptions = getExecOptions()
|
||||||
|
execOptions.cwd = 'nonexistent/path'
|
||||||
|
|
||||||
|
await expect(exec.exec('ls', ['-all'], execOptions)).rejects.toThrowError(
|
||||||
|
`The cwd: ${execOptions.cwd} does not exist!`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Exec roots does not throw when valid cwd is provided', async () => {
|
||||||
|
const execOptions = getExecOptions()
|
||||||
|
execOptions.cwd = './'
|
||||||
|
|
||||||
|
await expect(exec.exec('ls', ['-all'], execOptions)).resolves.toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
it('Exec roots relative tool path using rooted options.cwd', async () => {
|
it('Exec roots relative tool path using rooted options.cwd', async () => {
|
||||||
let command: string
|
let command: string
|
||||||
if (IS_WINDOWS) {
|
if (IS_WINDOWS) {
|
||||||
@@ -620,6 +661,165 @@ describe('@actions/exec', () => {
|
|||||||
expect(output.trim()).toBe(`args[0]: "hello"${os.EOL}args[1]: "world"`)
|
expect(output.trim()).toBe(`args[0]: "hello"${os.EOL}args[1]: "world"`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('correctly outputs for getExecOutput', async () => {
|
||||||
|
const stdErrPath: string = path.join(
|
||||||
|
__dirname,
|
||||||
|
'scripts',
|
||||||
|
'stderroutput.js'
|
||||||
|
)
|
||||||
|
const stdOutPath: string = path.join(
|
||||||
|
__dirname,
|
||||||
|
'scripts',
|
||||||
|
'stdoutoutput.js'
|
||||||
|
)
|
||||||
|
const nodePath: string = await io.which('node', true)
|
||||||
|
|
||||||
|
const {exitCode: exitCodeOut, stdout} = await exec.getExecOutput(
|
||||||
|
`"${nodePath}"`,
|
||||||
|
[stdOutPath],
|
||||||
|
getExecOptions()
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(exitCodeOut).toBe(0)
|
||||||
|
expect(stdout).toBe('this is output to stdout')
|
||||||
|
|
||||||
|
const {exitCode: exitCodeErr, stderr} = await exec.getExecOutput(
|
||||||
|
`"${nodePath}"`,
|
||||||
|
[stdErrPath],
|
||||||
|
getExecOptions()
|
||||||
|
)
|
||||||
|
expect(exitCodeErr).toBe(0)
|
||||||
|
expect(stderr).toBe('this is output to stderr')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('correctly outputs for getExecOutput with additional listeners', async () => {
|
||||||
|
const stdErrPath: string = path.join(
|
||||||
|
__dirname,
|
||||||
|
'scripts',
|
||||||
|
'stderroutput.js'
|
||||||
|
)
|
||||||
|
const stdOutPath: string = path.join(
|
||||||
|
__dirname,
|
||||||
|
'scripts',
|
||||||
|
'stdoutoutput.js'
|
||||||
|
)
|
||||||
|
|
||||||
|
const nodePath: string = await io.which('node', true)
|
||||||
|
let listenerOut = ''
|
||||||
|
|
||||||
|
const {exitCode: exitCodeOut, stdout} = await exec.getExecOutput(
|
||||||
|
`"${nodePath}"`,
|
||||||
|
[stdOutPath],
|
||||||
|
{
|
||||||
|
...getExecOptions(),
|
||||||
|
listeners: {
|
||||||
|
stdout: data => {
|
||||||
|
listenerOut = data.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(exitCodeOut).toBe(0)
|
||||||
|
expect(stdout).toBe('this is output to stdout')
|
||||||
|
expect(listenerOut).toBe('this is output to stdout')
|
||||||
|
|
||||||
|
let listenerErr = ''
|
||||||
|
const {exitCode: exitCodeErr, stderr} = await exec.getExecOutput(
|
||||||
|
`"${nodePath}"`,
|
||||||
|
[stdErrPath],
|
||||||
|
{
|
||||||
|
...getExecOptions(),
|
||||||
|
listeners: {
|
||||||
|
stderr: data => {
|
||||||
|
listenerErr = data.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(exitCodeErr).toBe(0)
|
||||||
|
expect(stderr).toBe('this is output to stderr')
|
||||||
|
expect(listenerErr).toBe('this is output to stderr')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('correctly outputs for getExecOutput when total size exceeds buffer size', async () => {
|
||||||
|
const stdErrPath: string = path.join(
|
||||||
|
__dirname,
|
||||||
|
'scripts',
|
||||||
|
'stderroutput.js'
|
||||||
|
)
|
||||||
|
const stdOutPath: string = path.join(
|
||||||
|
__dirname,
|
||||||
|
'scripts',
|
||||||
|
'stdoutoutputlarge.js'
|
||||||
|
)
|
||||||
|
|
||||||
|
const nodePath: string = await io.which('node', true)
|
||||||
|
let listenerOut = ''
|
||||||
|
|
||||||
|
const {exitCode: exitCodeOut, stdout} = await exec.getExecOutput(
|
||||||
|
`"${nodePath}"`,
|
||||||
|
[stdOutPath],
|
||||||
|
{
|
||||||
|
...getExecOptions(),
|
||||||
|
listeners: {
|
||||||
|
stdout: data => {
|
||||||
|
listenerOut += data.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(exitCodeOut).toBe(0)
|
||||||
|
expect(Buffer.byteLength(stdout || '', 'utf8')).toBe(2 ** 25)
|
||||||
|
expect(Buffer.byteLength(listenerOut, 'utf8')).toBe(2 ** 25)
|
||||||
|
|
||||||
|
let listenerErr = ''
|
||||||
|
const {exitCode: exitCodeErr, stderr} = await exec.getExecOutput(
|
||||||
|
`"${nodePath}"`,
|
||||||
|
[stdErrPath],
|
||||||
|
{
|
||||||
|
...getExecOptions(),
|
||||||
|
listeners: {
|
||||||
|
stderr: data => {
|
||||||
|
listenerErr = data.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(exitCodeErr).toBe(0)
|
||||||
|
expect(stderr).toBe('this is output to stderr')
|
||||||
|
expect(listenerErr).toBe('this is output to stderr')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('correctly outputs for getExecOutput with multi-byte characters', async () => {
|
||||||
|
const stdOutPath: string = path.join(
|
||||||
|
__dirname,
|
||||||
|
'scripts',
|
||||||
|
'stdoutputspecial.js'
|
||||||
|
)
|
||||||
|
|
||||||
|
const nodePath: string = await io.which('node', true)
|
||||||
|
let numStdOutBufferCalls = 0
|
||||||
|
const {exitCode: exitCodeOut, stdout} = await exec.getExecOutput(
|
||||||
|
`"${nodePath}"`,
|
||||||
|
[stdOutPath],
|
||||||
|
{
|
||||||
|
...getExecOptions(),
|
||||||
|
listeners: {
|
||||||
|
stdout: () => {
|
||||||
|
numStdOutBufferCalls += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(exitCodeOut).toBe(0)
|
||||||
|
//one call for each half of the © character, ensuring it was actually split and not sent together
|
||||||
|
expect(numStdOutBufferCalls).toBe(2)
|
||||||
|
expect(stdout).toBe('©')
|
||||||
|
})
|
||||||
|
|
||||||
if (IS_WINDOWS) {
|
if (IS_WINDOWS) {
|
||||||
it('Exec roots relative tool path using process.cwd (Windows path separator)', async () => {
|
it('Exec roots relative tool path using process.cwd (Windows path separator)', async () => {
|
||||||
let exitCode: number
|
let exitCode: number
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
//Default highWaterMark for readable stream buffers us 64K (2^16)
|
||||||
|
//so we go over that to get more than a buffer's worth
|
||||||
|
const os = require('os')
|
||||||
|
|
||||||
|
process.stdout.write('a'.repeat(2**16 + 1) + os.EOL);
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
//Default highWaterMark for readable stream buffers us 64K (2^16)
|
||||||
|
//so we go over that to get more than a buffer's worth
|
||||||
|
process.stdout.write('a\n'.repeat(2**24));
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
//first half of © character
|
||||||
|
process.stdout.write(Buffer.from([0xC2]), (err) => {
|
||||||
|
//write in the callback so that the second byte is sent separately
|
||||||
|
setTimeout(() => {
|
||||||
|
process.stdout.write(Buffer.from([0xA9])) //second half of © character
|
||||||
|
}, 5000)
|
||||||
|
|
||||||
|
})
|
||||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/exec",
|
"name": "@actions/exec",
|
||||||
"version": "1.0.4",
|
"version": "1.1.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/exec",
|
"name": "@actions/exec",
|
||||||
"version": "1.0.4",
|
"version": "1.1.0",
|
||||||
"description": "Actions exec lib",
|
"description": "Actions exec lib",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"github",
|
"github",
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import {ExecOptions} from './interfaces'
|
import {StringDecoder} from 'string_decoder'
|
||||||
|
import {ExecOptions, ExecOutput, ExecListeners} from './interfaces'
|
||||||
import * as tr from './toolrunner'
|
import * as tr from './toolrunner'
|
||||||
|
|
||||||
export {ExecOptions}
|
export {ExecOptions, ExecOutput, ExecListeners}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exec a command.
|
* Exec a command.
|
||||||
@@ -28,3 +29,62 @@ export async function exec(
|
|||||||
const runner: tr.ToolRunner = new tr.ToolRunner(toolPath, args, options)
|
const runner: tr.ToolRunner = new tr.ToolRunner(toolPath, args, options)
|
||||||
return runner.exec()
|
return runner.exec()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exec a command and get the output.
|
||||||
|
* Output will be streamed to the live console.
|
||||||
|
* Returns promise with the exit code and collected stdout and stderr
|
||||||
|
*
|
||||||
|
* @param commandLine command to execute (can include additional args). Must be correctly escaped.
|
||||||
|
* @param args optional arguments for tool. Escaping is handled by the lib.
|
||||||
|
* @param options optional exec options. See ExecOptions
|
||||||
|
* @returns Promise<ExecOutput> exit code, stdout, and stderr
|
||||||
|
*/
|
||||||
|
|
||||||
|
export async function getExecOutput(
|
||||||
|
commandLine: string,
|
||||||
|
args?: string[],
|
||||||
|
options?: ExecOptions
|
||||||
|
): Promise<ExecOutput> {
|
||||||
|
let stdout = ''
|
||||||
|
let stderr = ''
|
||||||
|
|
||||||
|
//Using string decoder covers the case where a mult-byte character is split
|
||||||
|
const stdoutDecoder = new StringDecoder('utf8')
|
||||||
|
const stderrDecoder = new StringDecoder('utf8')
|
||||||
|
|
||||||
|
const originalStdoutListener = options?.listeners?.stdout
|
||||||
|
const originalStdErrListener = options?.listeners?.stderr
|
||||||
|
|
||||||
|
const stdErrListener = (data: Buffer): void => {
|
||||||
|
stderr += stderrDecoder.write(data)
|
||||||
|
if (originalStdErrListener) {
|
||||||
|
originalStdErrListener(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const stdOutListener = (data: Buffer): void => {
|
||||||
|
stdout += stdoutDecoder.write(data)
|
||||||
|
if (originalStdoutListener) {
|
||||||
|
originalStdoutListener(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const listeners: ExecListeners = {
|
||||||
|
...options?.listeners,
|
||||||
|
stdout: stdOutListener,
|
||||||
|
stderr: stdErrListener
|
||||||
|
}
|
||||||
|
|
||||||
|
const exitCode = await exec(commandLine, args, {...options, listeners})
|
||||||
|
|
||||||
|
//flush any remaining characters
|
||||||
|
stdout += stdoutDecoder.end()
|
||||||
|
stderr += stderrDecoder.end()
|
||||||
|
|
||||||
|
return {
|
||||||
|
exitCode,
|
||||||
|
stdout,
|
||||||
|
stderr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -34,15 +34,39 @@ export interface ExecOptions {
|
|||||||
input?: Buffer
|
input?: Buffer
|
||||||
|
|
||||||
/** optional. Listeners for output. Callback functions that will be called on these events */
|
/** optional. Listeners for output. Callback functions that will be called on these events */
|
||||||
listeners?: {
|
listeners?: ExecListeners
|
||||||
stdout?: (data: Buffer) => void
|
}
|
||||||
|
|
||||||
stderr?: (data: Buffer) => void
|
/**
|
||||||
|
* Interface for the output of getExecOutput()
|
||||||
stdline?: (data: string) => void
|
*/
|
||||||
|
export interface ExecOutput {
|
||||||
errline?: (data: string) => void
|
/**The exit code of the process */
|
||||||
|
exitCode: number
|
||||||
debug?: (data: string) => void
|
|
||||||
}
|
/**The entire stdout of the process as a string */
|
||||||
|
stdout: string
|
||||||
|
|
||||||
|
/**The entire stderr of the process as a string */
|
||||||
|
stderr: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The user defined listeners for an exec call
|
||||||
|
*/
|
||||||
|
export interface ExecListeners {
|
||||||
|
/** A call back for each buffer of stdout */
|
||||||
|
stdout?: (data: Buffer) => void
|
||||||
|
|
||||||
|
/** A call back for each buffer of stderr */
|
||||||
|
stderr?: (data: Buffer) => void
|
||||||
|
|
||||||
|
/** A call back for each line of stdout */
|
||||||
|
stdline?: (data: string) => void
|
||||||
|
|
||||||
|
/** A call back for each line of stderr */
|
||||||
|
errline?: (data: string) => void
|
||||||
|
|
||||||
|
/** A call back for each debug log */
|
||||||
|
debug?: (data: string) => void
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export class ToolRunner extends events.EventEmitter {
|
|||||||
data: Buffer,
|
data: Buffer,
|
||||||
strBuffer: string,
|
strBuffer: string,
|
||||||
onLine: (line: string) => void
|
onLine: (line: string) => void
|
||||||
): void {
|
): string {
|
||||||
try {
|
try {
|
||||||
let s = strBuffer + data.toString()
|
let s = strBuffer + data.toString()
|
||||||
let n = s.indexOf(os.EOL)
|
let n = s.indexOf(os.EOL)
|
||||||
@@ -98,10 +98,12 @@ export class ToolRunner extends events.EventEmitter {
|
|||||||
n = s.indexOf(os.EOL)
|
n = s.indexOf(os.EOL)
|
||||||
}
|
}
|
||||||
|
|
||||||
strBuffer = s
|
return s
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// streaming lines to console is best effort. Don't fail a build.
|
// streaming lines to console is best effort. Don't fail a build.
|
||||||
this._debug(`error processing line. Failed with error ${err}`)
|
this._debug(`error processing line. Failed with error ${err}`)
|
||||||
|
|
||||||
|
return ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,7 +416,7 @@ export class ToolRunner extends events.EventEmitter {
|
|||||||
// otherwise verify it exists (add extension on Windows if necessary)
|
// otherwise verify it exists (add extension on Windows if necessary)
|
||||||
this.toolPath = await io.which(this.toolPath, true)
|
this.toolPath = await io.which(this.toolPath, true)
|
||||||
|
|
||||||
return new Promise<number>((resolve, reject) => {
|
return new Promise<number>(async (resolve, reject) => {
|
||||||
this._debug(`exec tool: ${this.toolPath}`)
|
this._debug(`exec tool: ${this.toolPath}`)
|
||||||
this._debug('arguments:')
|
this._debug('arguments:')
|
||||||
for (const arg of this.args) {
|
for (const arg of this.args) {
|
||||||
@@ -433,6 +435,10 @@ export class ToolRunner extends events.EventEmitter {
|
|||||||
this._debug(message)
|
this._debug(message)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (this.options.cwd && !(await ioUtil.exists(this.options.cwd))) {
|
||||||
|
return reject(new Error(`The cwd: ${this.options.cwd} does not exist!`))
|
||||||
|
}
|
||||||
|
|
||||||
const fileName = this._getSpawnFileName()
|
const fileName = this._getSpawnFileName()
|
||||||
const cp = child.spawn(
|
const cp = child.spawn(
|
||||||
fileName,
|
fileName,
|
||||||
@@ -440,7 +446,7 @@ export class ToolRunner extends events.EventEmitter {
|
|||||||
this._getSpawnOptions(this.options, fileName)
|
this._getSpawnOptions(this.options, fileName)
|
||||||
)
|
)
|
||||||
|
|
||||||
const stdbuffer = ''
|
let stdbuffer = ''
|
||||||
if (cp.stdout) {
|
if (cp.stdout) {
|
||||||
cp.stdout.on('data', (data: Buffer) => {
|
cp.stdout.on('data', (data: Buffer) => {
|
||||||
if (this.options.listeners && this.options.listeners.stdout) {
|
if (this.options.listeners && this.options.listeners.stdout) {
|
||||||
@@ -451,15 +457,19 @@ export class ToolRunner extends events.EventEmitter {
|
|||||||
optionsNonNull.outStream.write(data)
|
optionsNonNull.outStream.write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
this._processLineBuffer(data, stdbuffer, (line: string) => {
|
stdbuffer = this._processLineBuffer(
|
||||||
if (this.options.listeners && this.options.listeners.stdline) {
|
data,
|
||||||
this.options.listeners.stdline(line)
|
stdbuffer,
|
||||||
|
(line: string) => {
|
||||||
|
if (this.options.listeners && this.options.listeners.stdline) {
|
||||||
|
this.options.listeners.stdline(line)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const errbuffer = ''
|
let errbuffer = ''
|
||||||
if (cp.stderr) {
|
if (cp.stderr) {
|
||||||
cp.stderr.on('data', (data: Buffer) => {
|
cp.stderr.on('data', (data: Buffer) => {
|
||||||
state.processStderr = true
|
state.processStderr = true
|
||||||
@@ -478,11 +488,15 @@ export class ToolRunner extends events.EventEmitter {
|
|||||||
s.write(data)
|
s.write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
this._processLineBuffer(data, errbuffer, (line: string) => {
|
errbuffer = this._processLineBuffer(
|
||||||
if (this.options.listeners && this.options.listeners.errline) {
|
data,
|
||||||
this.options.listeners.errline(line)
|
errbuffer,
|
||||||
|
(line: string) => {
|
||||||
|
if (this.options.listeners && this.options.listeners.errline) {
|
||||||
|
this.options.listeners.errline(line)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -615,13 +629,13 @@ class ExecState extends events.EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
processClosed: boolean = false // tracks whether the process has exited and stdio is closed
|
processClosed = false // tracks whether the process has exited and stdio is closed
|
||||||
processError: string = ''
|
processError = ''
|
||||||
processExitCode: number = 0
|
processExitCode = 0
|
||||||
processExited: boolean = false // tracks whether the process has exited
|
processExited = false // tracks whether the process has exited
|
||||||
processStderr: boolean = false // tracks whether stderr was written to
|
processStderr = false // tracks whether stderr was written to
|
||||||
private delay = 10000 // 10 seconds
|
private delay = 10000 // 10 seconds
|
||||||
private done: boolean = false
|
private done = false
|
||||||
private options: im.ExecOptions
|
private options: im.ExecOptions
|
||||||
private timeout: NodeJS.Timer | null = null
|
private timeout: NodeJS.Timer | null = null
|
||||||
private toolPath: string
|
private toolPath: string
|
||||||
|
|||||||
@@ -59,18 +59,19 @@ const newIssue = await octokit.rest.issues.create({
|
|||||||
|
|
||||||
## Webhook payload typescript definitions
|
## Webhook payload typescript definitions
|
||||||
|
|
||||||
The npm module `@octokit/webhooks` provides type definitions for the response payloads. You can cast the payload to these types for better type information.
|
The npm module `@octokit/webhooks-definitions` provides type definitions for the response payloads. You can cast the payload to these types for better type information.
|
||||||
|
|
||||||
First, install the npm module `npm install @octokit/webhooks`
|
First, install the npm module `npm install @octokit/webhooks-definitions`
|
||||||
|
|
||||||
Then, assert the type based on the eventName
|
Then, assert the type based on the eventName
|
||||||
```ts
|
```ts
|
||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import * as github from '@actions/github'
|
import * as github from '@actions/github'
|
||||||
import * as Webhooks from '@octokit/webhooks'
|
import {PushEvent} from '@octokit/webhooks-definitions/schema'
|
||||||
|
|
||||||
if (github.context.eventName === 'push') {
|
if (github.context.eventName === 'push') {
|
||||||
const pushPayload = github.context.payload as Webhooks.WebhookPayloadPush
|
const pushPayload = github.context.payload as PushEvent
|
||||||
core.info(`The head commit is: ${pushPayload.head}`)
|
core.info(`The head commit is: ${pushPayload.head_commit}`)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
### 5.0.0
|
### 5.0.0
|
||||||
- [Update @actions/github to include latest octokit definitions](https://github.com/actions/toolkit/pull/783)
|
- [Update @actions/github to include latest octokit definitions](https://github.com/actions/toolkit/pull/783)
|
||||||
|
- [Add urls to context](https://github.com/actions/toolkit/pull/794)
|
||||||
|
|
||||||
### 4.0.0
|
### 4.0.0
|
||||||
- [Add execution state information to context](https://github.com/actions/toolkit/pull/499)
|
- [Add execution state information to context](https://github.com/actions/toolkit/pull/499)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import * as path from 'path'
|
|||||||
import {Context} from '../src/context'
|
import {Context} from '../src/context'
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||||
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
|
|
||||||
describe('@actions/context', () => {
|
describe('@actions/context', () => {
|
||||||
let context: Context
|
let context: Context
|
||||||
|
|||||||
Generated
+1431
-1203
File diff suppressed because it is too large
Load Diff
@@ -44,7 +44,7 @@
|
|||||||
"@octokit/plugin-rest-endpoint-methods": "^5.1.1"
|
"@octokit/plugin-rest-endpoint-methods": "^5.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jest": "^25.1.0",
|
"jest": "^26.6.3",
|
||||||
"proxy": "^1.0.1"
|
"proxy": "^1.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ export class Context {
|
|||||||
job: string
|
job: string
|
||||||
runNumber: number
|
runNumber: number
|
||||||
runId: number
|
runId: number
|
||||||
|
apiUrl: string
|
||||||
|
serverUrl: string
|
||||||
|
graphqlUrl: string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hydrate the context from the environment
|
* Hydrate the context from the environment
|
||||||
@@ -43,6 +46,10 @@ export class Context {
|
|||||||
this.job = process.env.GITHUB_JOB as string
|
this.job = process.env.GITHUB_JOB as string
|
||||||
this.runNumber = parseInt(process.env.GITHUB_RUN_NUMBER as string, 10)
|
this.runNumber = parseInt(process.env.GITHUB_RUN_NUMBER as string, 10)
|
||||||
this.runId = parseInt(process.env.GITHUB_RUN_ID as string, 10)
|
this.runId = parseInt(process.env.GITHUB_RUN_ID as string, 10)
|
||||||
|
this.apiUrl = process.env.GITHUB_API_URL ?? `https://api.github.com`
|
||||||
|
this.serverUrl = process.env.GITHUB_SERVER_URL ?? `https://github.com`
|
||||||
|
this.graphqlUrl =
|
||||||
|
process.env.GITHUB_GRAPHQL_URL ?? `https://api.github.com/graphql`
|
||||||
}
|
}
|
||||||
|
|
||||||
get issue(): {owner: string; repo: string; number: number} {
|
get issue(): {owner: string; repo: string; number: number} {
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
# @actions/glob Releases
|
# @actions/glob Releases
|
||||||
|
|
||||||
|
### 0.2.0
|
||||||
|
- [Added the hashFiles function to Glob](https://github.com/actions/toolkit/pull/830)
|
||||||
|
- [Added an option to filter out directories](https://github.com/actions/toolkit/pull/728)
|
||||||
|
|
||||||
|
### 0.1.2
|
||||||
|
|
||||||
|
- [Fix bug where files were matched incorrectly](https://github.com/actions/toolkit/pull/805)
|
||||||
|
|
||||||
|
### 0.1.1
|
||||||
|
|
||||||
|
- Update @actions/core version
|
||||||
### 0.1.0
|
### 0.1.0
|
||||||
|
|
||||||
- Initial release
|
- Initial release
|
||||||
|
|
||||||
### 0.1.1
|
|
||||||
|
|
||||||
- Update @actions/core version
|
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
import * as io from '../../io/src/io'
|
||||||
|
import * as path from 'path'
|
||||||
|
import {hashFiles} from '../src/glob'
|
||||||
|
import {promises as fs} from 'fs'
|
||||||
|
|
||||||
|
const IS_WINDOWS = process.platform === 'win32'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These test focus on the ability of globber to find files
|
||||||
|
* and not on the pattern matching aspect
|
||||||
|
*/
|
||||||
|
describe('globber', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
await io.rmRF(getTestTemp())
|
||||||
|
})
|
||||||
|
|
||||||
|
it('basic hashfiles test', async () => {
|
||||||
|
const root = path.join(getTestTemp(), 'basic-hashfiles')
|
||||||
|
await fs.mkdir(path.join(root), {recursive: true})
|
||||||
|
await fs.writeFile(path.join(root, 'test.txt'), 'test file content')
|
||||||
|
const hash = await hashFiles(`${root}/*`)
|
||||||
|
expect(hash).toEqual(
|
||||||
|
'd8a411e8f8643821bed189e627ff57151918aa554c00c10b31c693ab2dded273'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('basic hashfiles no match should return empty string', async () => {
|
||||||
|
const root = path.join(getTestTemp(), 'empty-hashfiles')
|
||||||
|
const hash = await hashFiles(`${root}/*`)
|
||||||
|
expect(hash).toEqual('')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('followSymbolicLinks defaults to true', async () => {
|
||||||
|
const root = path.join(
|
||||||
|
getTestTemp(),
|
||||||
|
'defaults-to-follow-symbolic-links-true'
|
||||||
|
)
|
||||||
|
await fs.mkdir(path.join(root, 'realdir'), {recursive: true})
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(root, 'realdir', 'file.txt'),
|
||||||
|
'test file content'
|
||||||
|
)
|
||||||
|
await createSymlinkDir(
|
||||||
|
path.join(root, 'realdir'),
|
||||||
|
path.join(root, 'symDir')
|
||||||
|
)
|
||||||
|
const testPath = path.join(root, `symDir`)
|
||||||
|
const hash = await hashFiles(testPath)
|
||||||
|
expect(hash).toEqual(
|
||||||
|
'd8a411e8f8643821bed189e627ff57151918aa554c00c10b31c693ab2dded273'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('followSymbolicLinks set to true', async () => {
|
||||||
|
const root = path.join(getTestTemp(), 'set-to-true')
|
||||||
|
await fs.mkdir(path.join(root, 'realdir'), {recursive: true})
|
||||||
|
await fs.writeFile(path.join(root, 'realdir', 'file'), 'test file content')
|
||||||
|
await createSymlinkDir(
|
||||||
|
path.join(root, 'realdir'),
|
||||||
|
path.join(root, 'symDir')
|
||||||
|
)
|
||||||
|
const testPath = path.join(root, `symDir`)
|
||||||
|
const hash = await hashFiles(testPath, {followSymbolicLinks: true})
|
||||||
|
expect(hash).toEqual(
|
||||||
|
'd8a411e8f8643821bed189e627ff57151918aa554c00c10b31c693ab2dded273'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('followSymbolicLinks set to false', async () => {
|
||||||
|
// Create the following layout:
|
||||||
|
// <root>
|
||||||
|
// <root>/folder-a
|
||||||
|
// <root>/folder-a/file
|
||||||
|
// <root>/symDir -> <root>/folder-a
|
||||||
|
const root = path.join(getTestTemp(), 'set-to-false')
|
||||||
|
await fs.mkdir(path.join(root, 'realdir'), {recursive: true})
|
||||||
|
await fs.writeFile(path.join(root, 'realdir', 'file'), 'test file content')
|
||||||
|
await createSymlinkDir(
|
||||||
|
path.join(root, 'realdir'),
|
||||||
|
path.join(root, 'symDir')
|
||||||
|
)
|
||||||
|
const testPath = path.join(root, 'symdir')
|
||||||
|
const hash = await hashFiles(testPath, {followSymbolicLinks: false})
|
||||||
|
expect(hash).toEqual('')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('multipath test basic', async () => {
|
||||||
|
// Create the following layout:
|
||||||
|
// <root>
|
||||||
|
// <root>/folder-a
|
||||||
|
// <root>/folder-a/file
|
||||||
|
// <root>/symDir -> <root>/folder-a
|
||||||
|
const root = path.join(getTestTemp(), 'set-to-false')
|
||||||
|
await fs.mkdir(path.join(root, 'dir1'), {recursive: true})
|
||||||
|
await fs.mkdir(path.join(root, 'dir2'), {recursive: true})
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(root, 'dir1', 'testfile1.txt'),
|
||||||
|
'test file content'
|
||||||
|
)
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(root, 'dir2', 'testfile2.txt'),
|
||||||
|
'test file content'
|
||||||
|
)
|
||||||
|
const testPath = `${path.join(root, 'dir1')}\n${path.join(root, 'dir2')}`
|
||||||
|
const hash = await hashFiles(testPath)
|
||||||
|
expect(hash).toEqual(
|
||||||
|
'4e911ea5824830b6a3ec096c7833d5af8381c189ffaa825c3503a5333a73eadc'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function getTestTemp(): string {
|
||||||
|
return path.join(__dirname, '_temp', 'hash_files')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a symlink directory on OSX/Linux, and a junction point directory on Windows.
|
||||||
|
* A symlink directory is not created on Windows since it requires an elevated context.
|
||||||
|
*/
|
||||||
|
async function createSymlinkDir(real: string, link: string): Promise<void> {
|
||||||
|
if (IS_WINDOWS) {
|
||||||
|
await fs.symlink(real, link, 'junction')
|
||||||
|
} else {
|
||||||
|
await fs.symlink(real, link)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -97,6 +97,41 @@ describe('globber', () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('defaults to matchDirectories=true', async () => {
|
||||||
|
// Create the following layout:
|
||||||
|
// <root>
|
||||||
|
// <root>/folder-a
|
||||||
|
// <root>/folder-a/file
|
||||||
|
const root = path.join(getTestTemp(), 'defaults-to-match-directories-true')
|
||||||
|
await fs.mkdir(path.join(root, 'folder-a'), {recursive: true})
|
||||||
|
await fs.writeFile(path.join(root, 'folder-a', 'file'), 'test file content')
|
||||||
|
|
||||||
|
const itemPaths = await glob(root, {})
|
||||||
|
expect(itemPaths).toEqual([
|
||||||
|
root,
|
||||||
|
path.join(root, 'folder-a'),
|
||||||
|
path.join(root, 'folder-a', 'file')
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not match file with trailing slash when implicitDescendants=true', async () => {
|
||||||
|
// Create the following layout:
|
||||||
|
// <root>
|
||||||
|
// <root>/file
|
||||||
|
const root = path.join(
|
||||||
|
getTestTemp(),
|
||||||
|
'defaults-to-implicit-descendants-true'
|
||||||
|
)
|
||||||
|
|
||||||
|
const filePath = path.join(root, 'file')
|
||||||
|
|
||||||
|
await fs.mkdir(root, {recursive: true})
|
||||||
|
await fs.writeFile(filePath, 'test file content')
|
||||||
|
|
||||||
|
const itemPaths = await glob(`${filePath}/`, {})
|
||||||
|
expect(itemPaths).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
it('defaults to omitBrokenSymbolicLinks=true', async () => {
|
it('defaults to omitBrokenSymbolicLinks=true', async () => {
|
||||||
// Create the following layout:
|
// Create the following layout:
|
||||||
// <root>
|
// <root>
|
||||||
@@ -343,6 +378,34 @@ describe('globber', () => {
|
|||||||
expect(itemPaths).toEqual([])
|
expect(itemPaths).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('does not return directories when match directories false', async () => {
|
||||||
|
// Create the following layout:
|
||||||
|
// <root>/file-1
|
||||||
|
// <root>/dir-1
|
||||||
|
// <root>/dir-1/file-2
|
||||||
|
// <root>/dir-1/dir-2
|
||||||
|
// <root>/dir-1/dir-2/file-3
|
||||||
|
const root = path.join(
|
||||||
|
getTestTemp(),
|
||||||
|
'does-not-return-directories-when-match-directories-false'
|
||||||
|
)
|
||||||
|
await fs.mkdir(path.join(root, 'dir-1', 'dir-2'), {recursive: true})
|
||||||
|
await fs.writeFile(path.join(root, 'file-1'), '')
|
||||||
|
await fs.writeFile(path.join(root, 'dir-1', 'file-2'), '')
|
||||||
|
await fs.writeFile(path.join(root, 'dir-1', 'dir-2', 'file-3'), '')
|
||||||
|
|
||||||
|
const pattern = `${root}${path.sep}**`
|
||||||
|
expect(
|
||||||
|
await glob(pattern, {
|
||||||
|
matchDirectories: false
|
||||||
|
})
|
||||||
|
).toEqual([
|
||||||
|
path.join(root, 'dir-1', 'dir-2', 'file-3'),
|
||||||
|
path.join(root, 'dir-1', 'file-2'),
|
||||||
|
path.join(root, 'file-1')
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
it('does not search paths that are not partial matches', async () => {
|
it('does not search paths that are not partial matches', async () => {
|
||||||
// Create the following layout:
|
// Create the following layout:
|
||||||
// <root>
|
// <root>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ describe('pattern', () => {
|
|||||||
it('escapes homedir', async () => {
|
it('escapes homedir', async () => {
|
||||||
const home = path.join(getTestTemp(), 'home-with-[and]')
|
const home = path.join(getTestTemp(), 'home-with-[and]')
|
||||||
await fs.mkdir(home, {recursive: true})
|
await fs.mkdir(home, {recursive: true})
|
||||||
const pattern = new Pattern('~/m*', undefined, home)
|
const pattern = new Pattern('~/m*', false, undefined, home)
|
||||||
|
|
||||||
expect(pattern.searchPath).toBe(home)
|
expect(pattern.searchPath).toBe(home)
|
||||||
expect(pattern.match(path.join(home, 'match'))).toBeTruthy()
|
expect(pattern.match(path.join(home, 'match'))).toBeTruthy()
|
||||||
|
|||||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/glob",
|
"name": "@actions/glob",
|
||||||
"version": "0.1.1",
|
"version": "0.2.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/glob",
|
"name": "@actions/glob",
|
||||||
"version": "0.1.1",
|
"version": "0.2.0",
|
||||||
"preview": true,
|
"preview": true,
|
||||||
"description": "Actions glob lib",
|
"description": "Actions glob lib",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import {Globber, DefaultGlobber} from './internal-globber'
|
import {Globber, DefaultGlobber} from './internal-globber'
|
||||||
import {GlobOptions} from './internal-glob-options'
|
import {GlobOptions} from './internal-glob-options'
|
||||||
|
import {HashFileOptions} from './internal-hash-file-options'
|
||||||
|
import {hashFiles as _hashFiles} from './internal-hash-files'
|
||||||
|
|
||||||
export {Globber, GlobOptions}
|
export {Globber, GlobOptions}
|
||||||
|
|
||||||
@@ -15,3 +17,21 @@ export async function create(
|
|||||||
): Promise<Globber> {
|
): Promise<Globber> {
|
||||||
return await DefaultGlobber.create(patterns, options)
|
return await DefaultGlobber.create(patterns, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the sha256 hash of a glob
|
||||||
|
*
|
||||||
|
* @param patterns Patterns separated by newlines
|
||||||
|
* @param options Glob options
|
||||||
|
*/
|
||||||
|
export async function hashFiles(
|
||||||
|
patterns: string,
|
||||||
|
options?: HashFileOptions
|
||||||
|
): Promise<string> {
|
||||||
|
let followSymbolicLinks = true
|
||||||
|
if (options && typeof options.followSymbolicLinks === 'boolean') {
|
||||||
|
followSymbolicLinks = options.followSymbolicLinks
|
||||||
|
}
|
||||||
|
const globber = await create(patterns, {followSymbolicLinks})
|
||||||
|
return _hashFiles(globber)
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export function getOptions(copy?: GlobOptions): GlobOptions {
|
|||||||
const result: GlobOptions = {
|
const result: GlobOptions = {
|
||||||
followSymbolicLinks: true,
|
followSymbolicLinks: true,
|
||||||
implicitDescendants: true,
|
implicitDescendants: true,
|
||||||
|
matchDirectories: true,
|
||||||
omitBrokenSymbolicLinks: true
|
omitBrokenSymbolicLinks: true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,6 +23,11 @@ export function getOptions(copy?: GlobOptions): GlobOptions {
|
|||||||
core.debug(`implicitDescendants '${result.implicitDescendants}'`)
|
core.debug(`implicitDescendants '${result.implicitDescendants}'`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof copy.matchDirectories === 'boolean') {
|
||||||
|
result.matchDirectories = copy.matchDirectories
|
||||||
|
core.debug(`matchDirectories '${result.matchDirectories}'`)
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof copy.omitBrokenSymbolicLinks === 'boolean') {
|
if (typeof copy.omitBrokenSymbolicLinks === 'boolean') {
|
||||||
result.omitBrokenSymbolicLinks = copy.omitBrokenSymbolicLinks
|
result.omitBrokenSymbolicLinks = copy.omitBrokenSymbolicLinks
|
||||||
core.debug(`omitBrokenSymbolicLinks '${result.omitBrokenSymbolicLinks}'`)
|
core.debug(`omitBrokenSymbolicLinks '${result.omitBrokenSymbolicLinks}'`)
|
||||||
|
|||||||
@@ -21,6 +21,14 @@ export interface GlobOptions {
|
|||||||
*/
|
*/
|
||||||
implicitDescendants?: boolean
|
implicitDescendants?: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether matching directories should be included in the
|
||||||
|
* result set.
|
||||||
|
*
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
matchDirectories?: boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates whether broken symbolic should be ignored and omitted from the
|
* Indicates whether broken symbolic should be ignored and omitted from the
|
||||||
* result set. Otherwise an error will be thrown.
|
* result set. Otherwise an error will be thrown.
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ export class DefaultGlobber implements Globber {
|
|||||||
async *globGenerator(): AsyncGenerator<string, void> {
|
async *globGenerator(): AsyncGenerator<string, void> {
|
||||||
// Fill in defaults options
|
// Fill in defaults options
|
||||||
const options = globOptionsHelper.getOptions(this.options)
|
const options = globOptionsHelper.getOptions(this.options)
|
||||||
|
|
||||||
// Implicit descendants?
|
// Implicit descendants?
|
||||||
const patterns: Pattern[] = []
|
const patterns: Pattern[] = []
|
||||||
for (const pattern of this.patterns) {
|
for (const pattern of this.patterns) {
|
||||||
@@ -77,12 +76,13 @@ export class DefaultGlobber implements Globber {
|
|||||||
pattern.segments[pattern.segments.length - 1] !== '**')
|
pattern.segments[pattern.segments.length - 1] !== '**')
|
||||||
) {
|
) {
|
||||||
patterns.push(
|
patterns.push(
|
||||||
new Pattern(pattern.negate, pattern.segments.concat('**'))
|
new Pattern(pattern.negate, true, pattern.segments.concat('**'))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push the search paths
|
// Push the search paths
|
||||||
|
|
||||||
const stack: SearchState[] = []
|
const stack: SearchState[] = []
|
||||||
for (const searchPath of patternHelper.getSearchPaths(patterns)) {
|
for (const searchPath of patternHelper.getSearchPaths(patterns)) {
|
||||||
core.debug(`Search path '${searchPath}'`)
|
core.debug(`Search path '${searchPath}'`)
|
||||||
@@ -131,7 +131,7 @@ export class DefaultGlobber implements Globber {
|
|||||||
// Directory
|
// Directory
|
||||||
if (stats.isDirectory()) {
|
if (stats.isDirectory()) {
|
||||||
// Matched
|
// Matched
|
||||||
if (match & MatchKind.Directory) {
|
if (match & MatchKind.Directory && options.matchDirectories) {
|
||||||
yield item.path
|
yield item.path
|
||||||
}
|
}
|
||||||
// Descend?
|
// Descend?
|
||||||
@@ -180,6 +180,7 @@ export class DefaultGlobber implements Globber {
|
|||||||
}
|
}
|
||||||
|
|
||||||
result.searchPaths.push(...patternHelper.getSearchPaths(result.patterns))
|
result.searchPaths.push(...patternHelper.getSearchPaths(result.patterns))
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Options to control globbing behavior
|
||||||
|
*/
|
||||||
|
export interface HashFileOptions {
|
||||||
|
/**
|
||||||
|
* Indicates whether to follow symbolic links. Generally should set to false
|
||||||
|
* when deleting files.
|
||||||
|
*
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
followSymbolicLinks?: boolean
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import * as crypto from 'crypto'
|
||||||
|
import * as core from '@actions/core'
|
||||||
|
import * as fs from 'fs'
|
||||||
|
import * as stream from 'stream'
|
||||||
|
import * as util from 'util'
|
||||||
|
import * as path from 'path'
|
||||||
|
import {Globber} from './glob'
|
||||||
|
|
||||||
|
export async function hashFiles(globber: Globber): Promise<string> {
|
||||||
|
let hasMatch = false
|
||||||
|
const githubWorkspace = process.env['GITHUB_WORKSPACE'] ?? process.cwd()
|
||||||
|
const result = crypto.createHash('sha256')
|
||||||
|
let count = 0
|
||||||
|
for await (const file of globber.globGenerator()) {
|
||||||
|
core.debug(file)
|
||||||
|
if (!file.startsWith(`${githubWorkspace}${path.sep}`)) {
|
||||||
|
core.debug(`Ignore '${file}' since it is not under GITHUB_WORKSPACE.`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (fs.statSync(file).isDirectory()) {
|
||||||
|
core.debug(`Skip directory '${file}'.`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const hash = crypto.createHash('sha256')
|
||||||
|
const pipeline = util.promisify(stream.pipeline)
|
||||||
|
await pipeline(fs.createReadStream(file), hash)
|
||||||
|
result.write(hash.digest())
|
||||||
|
count++
|
||||||
|
if (!hasMatch) {
|
||||||
|
hasMatch = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.end()
|
||||||
|
|
||||||
|
if (hasMatch) {
|
||||||
|
core.debug(`Found ${count} files to hash.`)
|
||||||
|
return result.digest('hex')
|
||||||
|
} else {
|
||||||
|
core.debug(`No matches found for glob`)
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,15 +43,27 @@ export class Pattern {
|
|||||||
*/
|
*/
|
||||||
private readonly rootRegExp: RegExp
|
private readonly rootRegExp: RegExp
|
||||||
|
|
||||||
/* eslint-disable no-dupe-class-members */
|
/**
|
||||||
// Disable no-dupe-class-members due to false positive for method overload
|
* Indicates that the pattern is implicitly added as opposed to user specified.
|
||||||
// https://github.com/typescript-eslint/typescript-eslint/issues/291
|
*/
|
||||||
|
private readonly isImplicitPattern: boolean
|
||||||
|
|
||||||
constructor(pattern: string)
|
constructor(pattern: string)
|
||||||
constructor(pattern: string, segments: undefined, homedir: string)
|
constructor(
|
||||||
constructor(negate: boolean, segments: string[])
|
pattern: string,
|
||||||
|
isImplicitPattern: boolean,
|
||||||
|
segments: undefined,
|
||||||
|
homedir: string
|
||||||
|
)
|
||||||
|
constructor(
|
||||||
|
negate: boolean,
|
||||||
|
isImplicitPattern: boolean,
|
||||||
|
segments: string[],
|
||||||
|
homedir?: string
|
||||||
|
)
|
||||||
constructor(
|
constructor(
|
||||||
patternOrNegate: string | boolean,
|
patternOrNegate: string | boolean,
|
||||||
|
isImplicitPattern = false,
|
||||||
segments?: string[],
|
segments?: string[],
|
||||||
homedir?: string
|
homedir?: string
|
||||||
) {
|
) {
|
||||||
@@ -107,6 +119,8 @@ export class Pattern {
|
|||||||
IS_WINDOWS ? 'i' : ''
|
IS_WINDOWS ? 'i' : ''
|
||||||
)
|
)
|
||||||
|
|
||||||
|
this.isImplicitPattern = isImplicitPattern
|
||||||
|
|
||||||
// Create minimatch
|
// Create minimatch
|
||||||
const minimatchOptions: IMinimatchOptions = {
|
const minimatchOptions: IMinimatchOptions = {
|
||||||
dot: true,
|
dot: true,
|
||||||
@@ -132,7 +146,7 @@ export class Pattern {
|
|||||||
// Append a trailing slash. Otherwise Minimatch will not match the directory immediately
|
// Append a trailing slash. Otherwise Minimatch will not match the directory immediately
|
||||||
// preceding the globstar. For example, given the pattern `/foo/**`, Minimatch returns
|
// preceding the globstar. For example, given the pattern `/foo/**`, Minimatch returns
|
||||||
// false for `/foo` but returns true for `/foo/`. Append a trailing slash to handle that quirk.
|
// false for `/foo` but returns true for `/foo/`. Append a trailing slash to handle that quirk.
|
||||||
if (!itemPath.endsWith(path.sep)) {
|
if (!itemPath.endsWith(path.sep) && this.isImplicitPattern === false) {
|
||||||
// Note, this is safe because the constructor ensures the pattern has an absolute root.
|
// Note, this is safe because the constructor ensures the pattern has an absolute root.
|
||||||
// For example, formats like C: and C:foo on Windows are resolved to an absolute root.
|
// For example, formats like C: and C:foo on Windows are resolved to an absolute root.
|
||||||
itemPath = `${itemPath}${path.sep}`
|
itemPath = `${itemPath}${path.sep}`
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
# @actions/io Releases
|
# @actions/io Releases
|
||||||
|
|
||||||
|
### 1.1.1
|
||||||
|
- [Fixed a bug where we incorrectly escaped paths for rmrf](https://github.com/actions/toolkit/pull/828)
|
||||||
|
|
||||||
### 1.1.0
|
### 1.1.0
|
||||||
|
|
||||||
- Add `findInPath` method to locate all matching executables in the system path
|
- Add `findInPath` method to locate all matching executables in the system path
|
||||||
|
|||||||
@@ -556,6 +556,45 @@ describe('rmRF', () => {
|
|||||||
await assertNotExists(symlinkFile)
|
await assertNotExists(symlinkFile)
|
||||||
await assertNotExists(outerDirectory)
|
await assertNotExists(outerDirectory)
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
it('correctly escapes % on windows', async () => {
|
||||||
|
const root: string = path.join(getTestTemp(), 'rmRF_escape_test_win')
|
||||||
|
const directory: string = path.join(root, '%test%')
|
||||||
|
await io.mkdirP(root)
|
||||||
|
await io.mkdirP(directory)
|
||||||
|
const oldEnv = process.env['test']
|
||||||
|
process.env['test'] = 'thisshouldnotresolve'
|
||||||
|
|
||||||
|
await io.rmRF(directory)
|
||||||
|
await assertNotExists(directory)
|
||||||
|
process.env['test'] = oldEnv
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should throw for invalid characters', async () => {
|
||||||
|
const root: string = path.join(getTestTemp(), 'rmRF_invalidChar_Windows')
|
||||||
|
const errorString =
|
||||||
|
'File path must not contain `*`, `"`, `<`, `>` or `|` on Windows'
|
||||||
|
await expect(io.rmRF(path.join(root, '"'))).rejects.toHaveProperty(
|
||||||
|
'message',
|
||||||
|
errorString
|
||||||
|
)
|
||||||
|
await expect(io.rmRF(path.join(root, '<'))).rejects.toHaveProperty(
|
||||||
|
'message',
|
||||||
|
errorString
|
||||||
|
)
|
||||||
|
await expect(io.rmRF(path.join(root, '>'))).rejects.toHaveProperty(
|
||||||
|
'message',
|
||||||
|
errorString
|
||||||
|
)
|
||||||
|
await expect(io.rmRF(path.join(root, '|'))).rejects.toHaveProperty(
|
||||||
|
'message',
|
||||||
|
errorString
|
||||||
|
)
|
||||||
|
await expect(io.rmRF(path.join(root, '*'))).rejects.toHaveProperty(
|
||||||
|
'message',
|
||||||
|
errorString
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
it('removes symlink folder with missing source using rmRF', async () => {
|
it('removes symlink folder with missing source using rmRF', async () => {
|
||||||
|
|||||||
Generated
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/io",
|
"name": "@actions/io",
|
||||||
"version": "1.1.0",
|
"version": "1.1.1",
|
||||||
"lockfileVersion": 1
|
"lockfileVersion": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/io",
|
"name": "@actions/io",
|
||||||
"version": "1.1.0",
|
"version": "1.1.1",
|
||||||
"description": "Actions io lib",
|
"description": "Actions io lib",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"github",
|
"github",
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export async function exists(fsPath: string): Promise<boolean> {
|
|||||||
|
|
||||||
export async function isDirectory(
|
export async function isDirectory(
|
||||||
fsPath: string,
|
fsPath: string,
|
||||||
useStat: boolean = false
|
useStat = false
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const stats = useStat ? await stat(fsPath) : await lstat(fsPath)
|
const stats = useStat ? await stat(fsPath) : await lstat(fsPath)
|
||||||
return stats.isDirectory()
|
return stats.isDirectory()
|
||||||
@@ -166,3 +166,8 @@ function isUnixExecutable(stats: fs.Stats): boolean {
|
|||||||
((stats.mode & 64) > 0 && stats.uid === process.getuid())
|
((stats.mode & 64) > 0 && stats.uid === process.getuid())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the path of cmd.exe in windows
|
||||||
|
export function getCmdPath(): string {
|
||||||
|
return process.env['COMSPEC'] ?? `cmd.exe`
|
||||||
|
}
|
||||||
|
|||||||
+17
-3
@@ -5,6 +5,7 @@ import {promisify} from 'util'
|
|||||||
import * as ioUtil from './io-util'
|
import * as ioUtil from './io-util'
|
||||||
|
|
||||||
const exec = promisify(childProcess.exec)
|
const exec = promisify(childProcess.exec)
|
||||||
|
const execFile = promisify(childProcess.execFile)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for cp/mv options
|
* Interface for cp/mv options
|
||||||
@@ -117,11 +118,24 @@ export async function rmRF(inputPath: string): Promise<void> {
|
|||||||
if (ioUtil.IS_WINDOWS) {
|
if (ioUtil.IS_WINDOWS) {
|
||||||
// Node doesn't provide a delete operation, only an unlink function. This means that if the file is being used by another
|
// Node doesn't provide a delete operation, only an unlink function. This means that if the file is being used by another
|
||||||
// program (e.g. antivirus), it won't be deleted. To address this, we shell out the work to rd/del.
|
// program (e.g. antivirus), it won't be deleted. To address this, we shell out the work to rd/del.
|
||||||
|
|
||||||
|
// Check for invalid characters
|
||||||
|
// https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
|
||||||
|
if (/[*"<>|]/.test(inputPath)) {
|
||||||
|
throw new Error(
|
||||||
|
'File path must not contain `*`, `"`, `<`, `>` or `|` on Windows'
|
||||||
|
)
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
|
const cmdPath = ioUtil.getCmdPath()
|
||||||
if (await ioUtil.isDirectory(inputPath, true)) {
|
if (await ioUtil.isDirectory(inputPath, true)) {
|
||||||
await exec(`rd /s /q "${inputPath}"`)
|
await exec(`${cmdPath} /s /c "rd /s /q "%inputPath%""`, {
|
||||||
|
env: {inputPath}
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
await exec(`del /f /a "${inputPath}"`)
|
await exec(`${cmdPath} /s /c "del /f /a "%inputPath%""`, {
|
||||||
|
env: {inputPath}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// if you try to delete a file that doesn't exist, desired result is achieved
|
// if you try to delete a file that doesn't exist, desired result is achieved
|
||||||
@@ -149,7 +163,7 @@ export async function rmRF(inputPath: string): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isDir) {
|
if (isDir) {
|
||||||
await exec(`rm -rf "${inputPath}"`)
|
await execFile(`rm`, [`-rf`, `${inputPath}`])
|
||||||
} else {
|
} else {
|
||||||
await ioUtil.unlink(inputPath)
|
await ioUtil.unlink(inputPath)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
# @actions/tool-cache Releases
|
# @actions/tool-cache Releases
|
||||||
|
|
||||||
|
### 1.7.1
|
||||||
|
- [Fallback to os-releases file to get linux version](https://github.com/actions/toolkit/pull/594)
|
||||||
|
- [Update to latest @actions/io verison](https://github.com/actions/toolkit/pull/838)
|
||||||
|
|
||||||
|
### 1.7.0
|
||||||
|
- [Allow arbirtary headers when downloading tools to the tc](https://github.com/actions/toolkit/pull/530)
|
||||||
|
- [Export `isExplicitVersion` and `evaluateVersions` functions](https://github.com/actions/toolkit/pull/796)
|
||||||
|
- [Force overwrite on default when extracted compressed files](https://github.com/actions/toolkit/pull/807)
|
||||||
|
|
||||||
### 1.6.1
|
### 1.6.1
|
||||||
- [Update @actions/core version](https://github.com/actions/toolkit/pull/636)
|
- [Update @actions/core version](https://github.com/actions/toolkit/pull/636)
|
||||||
|
|
||||||
### 1.6.0
|
### 1.6.0
|
||||||
- [Add extractXar function to extract XAR files](https://github.com/actions/toolkit/pull/207)
|
- [Add extractXar function to extract XAR files](https://github.com/actions/toolkit/pull/207)
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ describe('@actions/tool-cache-manifest', () => {
|
|||||||
expect(file?.filename).toBe('sometool-1.2.3-linux-x64.tar.gz')
|
expect(file?.filename).toBe('sometool-1.2.3-linux-x64.tar.gz')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can match with linux platform version spec', async () => {
|
it('can match with linux platform version spec from lsb-release', async () => {
|
||||||
os.platform = 'linux'
|
os.platform = 'linux'
|
||||||
os.arch = 'x64'
|
os.arch = 'x64'
|
||||||
|
|
||||||
@@ -150,6 +150,48 @@ describe('@actions/tool-cache-manifest', () => {
|
|||||||
expect(file?.filename).toBe('sometool-1.2.4-ubuntu1804-x64.tar.gz')
|
expect(file?.filename).toBe('sometool-1.2.4-ubuntu1804-x64.tar.gz')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('can match with linux platform version spec from os-release', async () => {
|
||||||
|
os.platform = 'linux'
|
||||||
|
os.arch = 'x64'
|
||||||
|
|
||||||
|
readLsbSpy.mockImplementation(() => {
|
||||||
|
return `NAME="Ubuntu"
|
||||||
|
VERSION="18.04.5 LTS (Bionic Beaver)"
|
||||||
|
ID=ubuntu
|
||||||
|
ID_LIKE=debian
|
||||||
|
PRETTY_NAME="Ubuntu 18.04.5 LTS"
|
||||||
|
VERSION_ID="18.04"
|
||||||
|
HOME_URL="https://www.ubuntu.com/"
|
||||||
|
SUPPORT_URL="https://help.ubuntu.com/"
|
||||||
|
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
|
||||||
|
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
|
||||||
|
VERSION_CODENAME=bionic
|
||||||
|
UBUNTU_CODENAME=bionic`
|
||||||
|
})
|
||||||
|
|
||||||
|
const manifest: mm.IToolRelease[] | null = await tc.getManifestFromRepo(
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
fakeToken
|
||||||
|
)
|
||||||
|
const release: tc.IToolRelease | undefined = await tc.findFromManifest(
|
||||||
|
'1.2.4',
|
||||||
|
true,
|
||||||
|
manifest
|
||||||
|
)
|
||||||
|
expect(release).toBeDefined()
|
||||||
|
expect(release?.version).toBe('1.2.4')
|
||||||
|
expect(release?.files.length).toBe(1)
|
||||||
|
const file = release?.files[0]
|
||||||
|
expect(file).toBeDefined()
|
||||||
|
expect(file?.arch).toBe('x64')
|
||||||
|
expect(file?.platform).toBe('linux')
|
||||||
|
expect(file?.download_url).toBe(
|
||||||
|
'https://github.com/actions/sometool/releases/tag/1.2.4-20200402.6/sometool-1.2.4-ubuntu1804-x64.tar.gz'
|
||||||
|
)
|
||||||
|
expect(file?.filename).toBe('sometool-1.2.4-ubuntu1804-x64.tar.gz')
|
||||||
|
})
|
||||||
|
|
||||||
it('can match with darwin platform version spec', async () => {
|
it('can match with darwin platform version spec', async () => {
|
||||||
os.platform = 'darwin'
|
os.platform = 'darwin'
|
||||||
os.arch = 'x64'
|
os.arch = 'x64'
|
||||||
|
|||||||
@@ -122,11 +122,9 @@ describe('@actions/tool-cache', function() {
|
|||||||
|
|
||||||
setResponseMessageFactory(() => {
|
setResponseMessageFactory(() => {
|
||||||
const readStream = new stream.Readable()
|
const readStream = new stream.Readable()
|
||||||
/* eslint-disable @typescript-eslint/unbound-method */
|
|
||||||
readStream._read = () => {
|
readStream._read = () => {
|
||||||
readStream.destroy(new Error('uh oh'))
|
readStream.destroy(new Error('uh oh'))
|
||||||
}
|
}
|
||||||
/* eslint-enable @typescript-eslint/unbound-method */
|
|
||||||
return readStream
|
return readStream
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -149,7 +147,6 @@ describe('@actions/tool-cache', function() {
|
|||||||
.get('/retries-error-from-response-message-stream')
|
.get('/retries-error-from-response-message-stream')
|
||||||
.reply(200, {})
|
.reply(200, {})
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/unbound-method */
|
|
||||||
let attempt = 1
|
let attempt = 1
|
||||||
setResponseMessageFactory(() => {
|
setResponseMessageFactory(() => {
|
||||||
const readStream = new stream.Readable()
|
const readStream = new stream.Readable()
|
||||||
@@ -170,7 +167,6 @@ describe('@actions/tool-cache', function() {
|
|||||||
|
|
||||||
return readStream
|
return readStream
|
||||||
})
|
})
|
||||||
/* eslint-enable @typescript-eslint/unbound-method */
|
|
||||||
|
|
||||||
const downPath = await tc.downloadTool(
|
const downPath = await tc.downloadTool(
|
||||||
'http://example.com/retries-error-from-response-message-stream'
|
'http://example.com/retries-error-from-response-message-stream'
|
||||||
@@ -243,6 +239,10 @@ describe('@actions/tool-cache', function() {
|
|||||||
const _7zFile: string = path.join(tempDir, 'test.7z')
|
const _7zFile: string = path.join(tempDir, 'test.7z')
|
||||||
await io.cp(path.join(__dirname, 'data', 'test.7z'), _7zFile)
|
await io.cp(path.join(__dirname, 'data', 'test.7z'), _7zFile)
|
||||||
|
|
||||||
|
const destDir = path.join(tempDir, 'destination')
|
||||||
|
await io.mkdirP(destDir)
|
||||||
|
fs.writeFileSync(path.join(destDir, 'file.txt'), 'overwriteMe')
|
||||||
|
|
||||||
// extract/cache
|
// extract/cache
|
||||||
const extPath: string = await tc.extract7z(_7zFile)
|
const extPath: string = await tc.extract7z(_7zFile)
|
||||||
await tc.cacheDir(extPath, 'my-7z-contents', '1.1.0')
|
await tc.cacheDir(extPath, 'my-7z-contents', '1.1.0')
|
||||||
@@ -251,6 +251,9 @@ describe('@actions/tool-cache', function() {
|
|||||||
expect(fs.existsSync(toolPath)).toBeTruthy()
|
expect(fs.existsSync(toolPath)).toBeTruthy()
|
||||||
expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy()
|
expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy()
|
||||||
expect(fs.existsSync(path.join(toolPath, 'file.txt'))).toBeTruthy()
|
expect(fs.existsSync(path.join(toolPath, 'file.txt'))).toBeTruthy()
|
||||||
|
expect(fs.readFileSync(path.join(toolPath, 'file.txt'), 'utf8')).toBe(
|
||||||
|
'file.txt contents'
|
||||||
|
)
|
||||||
expect(
|
expect(
|
||||||
fs.existsSync(path.join(toolPath, 'file-with-ç-character.txt'))
|
fs.existsSync(path.join(toolPath, 'file-with-ç-character.txt'))
|
||||||
).toBeTruthy()
|
).toBeTruthy()
|
||||||
@@ -347,6 +350,22 @@ describe('@actions/tool-cache', function() {
|
|||||||
await io.rmRF(tempDir)
|
await io.rmRF(tempDir)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
it.each(['pwsh', 'powershell'])(
|
||||||
|
'unzip properly fails with bad path (%s)',
|
||||||
|
async powershellTool => {
|
||||||
|
const originalPath = process.env['PATH']
|
||||||
|
try {
|
||||||
|
if (powershellTool === 'powershell' && IS_WINDOWS) {
|
||||||
|
//remove pwsh from PATH temporarily to test fallback case
|
||||||
|
process.env['PATH'] = removePWSHFromPath(process.env['PATH'])
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect(tc.extractZip('badPath')).rejects.toThrow()
|
||||||
|
} finally {
|
||||||
|
process.env['PATH'] = originalPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
} else if (IS_MAC) {
|
} else if (IS_MAC) {
|
||||||
it('extract .xar', async () => {
|
it('extract .xar', async () => {
|
||||||
const tempDir = path.join(tempPath, 'test-install.xar')
|
const tempDir = path.join(tempPath, 'test-install.xar')
|
||||||
@@ -360,14 +379,21 @@ describe('@actions/tool-cache', function() {
|
|||||||
cwd: sourcePath
|
cwd: sourcePath
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const destDir = path.join(tempDir, 'destination')
|
||||||
|
await io.mkdirP(destDir)
|
||||||
|
fs.writeFileSync(path.join(destDir, 'file.txt'), 'overwriteMe')
|
||||||
|
|
||||||
// extract/cache
|
// extract/cache
|
||||||
const extPath: string = await tc.extractXar(targetPath, undefined, '-x')
|
const extPath: string = await tc.extractXar(targetPath, destDir, ['-x'])
|
||||||
await tc.cacheDir(extPath, 'my-xar-contents', '1.1.0')
|
await tc.cacheDir(extPath, 'my-xar-contents', '1.1.0')
|
||||||
const toolPath: string = tc.find('my-xar-contents', '1.1.0')
|
const toolPath: string = tc.find('my-xar-contents', '1.1.0')
|
||||||
|
|
||||||
expect(fs.existsSync(toolPath)).toBeTruthy()
|
expect(fs.existsSync(toolPath)).toBeTruthy()
|
||||||
expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy()
|
expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy()
|
||||||
expect(fs.existsSync(path.join(toolPath, 'file.txt'))).toBeTruthy()
|
expect(fs.existsSync(path.join(toolPath, 'file.txt'))).toBeTruthy()
|
||||||
|
expect(fs.readFileSync(path.join(toolPath, 'file.txt'), 'utf8')).toBe(
|
||||||
|
'file.txt contents'
|
||||||
|
)
|
||||||
expect(
|
expect(
|
||||||
fs.existsSync(path.join(toolPath, 'file-with-ç-character.txt'))
|
fs.existsSync(path.join(toolPath, 'file-with-ç-character.txt'))
|
||||||
).toBeTruthy()
|
).toBeTruthy()
|
||||||
@@ -462,14 +488,23 @@ describe('@actions/tool-cache', function() {
|
|||||||
const _tgzFile: string = path.join(tempDir, 'test.tar.gz')
|
const _tgzFile: string = path.join(tempDir, 'test.tar.gz')
|
||||||
await io.cp(path.join(__dirname, 'data', 'test.tar.gz'), _tgzFile)
|
await io.cp(path.join(__dirname, 'data', 'test.tar.gz'), _tgzFile)
|
||||||
|
|
||||||
|
//Create file to overwrite
|
||||||
|
const destDir = path.join(tempDir, 'extract-dest')
|
||||||
|
await io.rmRF(destDir)
|
||||||
|
await io.mkdirP(destDir)
|
||||||
|
fs.writeFileSync(path.join(destDir, 'file.txt'), 'overwriteMe')
|
||||||
|
|
||||||
// extract/cache
|
// extract/cache
|
||||||
const extPath: string = await tc.extractTar(_tgzFile)
|
const extPath: string = await tc.extractTar(_tgzFile, destDir)
|
||||||
await tc.cacheDir(extPath, 'my-tgz-contents', '1.1.0')
|
await tc.cacheDir(extPath, 'my-tgz-contents', '1.1.0')
|
||||||
const toolPath: string = tc.find('my-tgz-contents', '1.1.0')
|
const toolPath: string = tc.find('my-tgz-contents', '1.1.0')
|
||||||
|
|
||||||
expect(fs.existsSync(toolPath)).toBeTruthy()
|
expect(fs.existsSync(toolPath)).toBeTruthy()
|
||||||
expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy()
|
expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy()
|
||||||
expect(fs.existsSync(path.join(toolPath, 'file.txt'))).toBeTruthy()
|
expect(fs.existsSync(path.join(toolPath, 'file.txt'))).toBeTruthy()
|
||||||
|
expect(fs.readFileSync(path.join(toolPath, 'file.txt'), 'utf8')).toBe(
|
||||||
|
'file.txt contents'
|
||||||
|
)
|
||||||
expect(
|
expect(
|
||||||
fs.existsSync(path.join(toolPath, 'file-with-ç-character.txt'))
|
fs.existsSync(path.join(toolPath, 'file-with-ç-character.txt'))
|
||||||
).toBeTruthy()
|
).toBeTruthy()
|
||||||
@@ -500,6 +535,9 @@ describe('@actions/tool-cache', function() {
|
|||||||
expect(fs.existsSync(toolPath)).toBeTruthy()
|
expect(fs.existsSync(toolPath)).toBeTruthy()
|
||||||
expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy()
|
expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy()
|
||||||
expect(fs.existsSync(path.join(toolPath, 'file.txt'))).toBeTruthy()
|
expect(fs.existsSync(path.join(toolPath, 'file.txt'))).toBeTruthy()
|
||||||
|
expect(fs.readFileSync(path.join(toolPath, 'file.txt'), 'utf8')).toBe(
|
||||||
|
'file.txt contents'
|
||||||
|
)
|
||||||
expect(
|
expect(
|
||||||
fs.existsSync(path.join(toolPath, 'file-with-ç-character.txt'))
|
fs.existsSync(path.join(toolPath, 'file-with-ç-character.txt'))
|
||||||
).toBeTruthy()
|
).toBeTruthy()
|
||||||
@@ -520,8 +558,14 @@ describe('@actions/tool-cache', function() {
|
|||||||
const _txzFile: string = path.join(tempDir, 'test.tar.xz')
|
const _txzFile: string = path.join(tempDir, 'test.tar.xz')
|
||||||
await io.cp(path.join(__dirname, 'data', 'test.tar.xz'), _txzFile)
|
await io.cp(path.join(__dirname, 'data', 'test.tar.xz'), _txzFile)
|
||||||
|
|
||||||
|
//Create file to overwrite
|
||||||
|
const destDir = path.join(tempDir, 'extract-dest')
|
||||||
|
await io.rmRF(destDir)
|
||||||
|
await io.mkdirP(destDir)
|
||||||
|
fs.writeFileSync(path.join(destDir, 'file.txt'), 'overwriteMe')
|
||||||
|
|
||||||
// extract/cache
|
// extract/cache
|
||||||
const extPath: string = await tc.extractTar(_txzFile, undefined, 'x')
|
const extPath: string = await tc.extractTar(_txzFile, destDir, 'x')
|
||||||
await tc.cacheDir(extPath, 'my-txz-contents', '1.1.0')
|
await tc.cacheDir(extPath, 'my-txz-contents', '1.1.0')
|
||||||
const toolPath: string = tc.find('my-txz-contents', '1.1.0')
|
const toolPath: string = tc.find('my-txz-contents', '1.1.0')
|
||||||
|
|
||||||
@@ -534,58 +578,70 @@ describe('@actions/tool-cache', function() {
|
|||||||
).toBe('foo/hello: world')
|
).toBe('foo/hello: world')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('installs a zip and finds it', async () => {
|
it.each(['pwsh', 'powershell'])(
|
||||||
const tempDir = path.join(__dirname, 'test-install-zip')
|
'installs a zip and finds it (%s)',
|
||||||
try {
|
async powershellTool => {
|
||||||
await io.mkdirP(tempDir)
|
const tempDir = path.join(__dirname, 'test-install-zip')
|
||||||
|
const originalPath = process.env['PATH']
|
||||||
|
try {
|
||||||
|
await io.mkdirP(tempDir)
|
||||||
|
|
||||||
// stage the layout for a zip file:
|
// stage the layout for a zip file:
|
||||||
// file.txt
|
// file.txt
|
||||||
// folder/nested-file.txt
|
// folder/nested-file.txt
|
||||||
const stagingDir = path.join(tempDir, 'zip-staging')
|
const stagingDir = path.join(tempDir, 'zip-staging')
|
||||||
await io.mkdirP(path.join(stagingDir, 'folder'))
|
await io.mkdirP(path.join(stagingDir, 'folder'))
|
||||||
fs.writeFileSync(path.join(stagingDir, 'file.txt'), '')
|
fs.writeFileSync(path.join(stagingDir, 'file.txt'), '')
|
||||||
fs.writeFileSync(path.join(stagingDir, 'folder', 'nested-file.txt'), '')
|
fs.writeFileSync(path.join(stagingDir, 'folder', 'nested-file.txt'), '')
|
||||||
|
|
||||||
// create the zip
|
// create the zip
|
||||||
const zipFile = path.join(tempDir, 'test.zip')
|
const zipFile = path.join(tempDir, 'test.zip')
|
||||||
await io.rmRF(zipFile)
|
await io.rmRF(zipFile)
|
||||||
if (IS_WINDOWS) {
|
if (IS_WINDOWS) {
|
||||||
const escapedStagingPath = stagingDir.replace(/'/g, "''") // double-up single quotes
|
const escapedStagingPath = stagingDir.replace(/'/g, "''") // double-up single quotes
|
||||||
const escapedZipFile = zipFile.replace(/'/g, "''")
|
const escapedZipFile = zipFile.replace(/'/g, "''")
|
||||||
const powershellPath =
|
const powershellPath =
|
||||||
(await io.which('pwsh', false)) ||
|
(await io.which('pwsh', false)) ||
|
||||||
(await io.which('powershell', true))
|
(await io.which('powershell', true))
|
||||||
const args = [
|
const args = [
|
||||||
'-NoLogo',
|
'-NoLogo',
|
||||||
'-Sta',
|
'-Sta',
|
||||||
'-NoProfile',
|
'-NoProfile',
|
||||||
'-NonInteractive',
|
'-NonInteractive',
|
||||||
'-ExecutionPolicy',
|
'-ExecutionPolicy',
|
||||||
'Unrestricted',
|
'Unrestricted',
|
||||||
'-Command',
|
'-Command',
|
||||||
`$ErrorActionPreference = 'Stop' ; Add-Type -AssemblyName System.IO.Compression.FileSystem ; [System.IO.Compression.ZipFile]::CreateFromDirectory('${escapedStagingPath}', '${escapedZipFile}')`
|
`$ErrorActionPreference = 'Stop' ; Add-Type -AssemblyName System.IO.Compression.FileSystem ; [System.IO.Compression.ZipFile]::CreateFromDirectory('${escapedStagingPath}', '${escapedZipFile}')`
|
||||||
]
|
]
|
||||||
await exec.exec(`"${powershellPath}"`, args)
|
await exec.exec(`"${powershellPath}"`, args)
|
||||||
} else {
|
} else {
|
||||||
const zipPath: string = await io.which('zip', true)
|
const zipPath: string = await io.which('zip', true)
|
||||||
await exec.exec(`"${zipPath}`, [zipFile, '-r', '.'], {cwd: stagingDir})
|
await exec.exec(`"${zipPath}`, [zipFile, '-r', '.'], {
|
||||||
|
cwd: stagingDir
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (powershellTool === 'powershell' && IS_WINDOWS) {
|
||||||
|
//remove pwsh from PATH temporarily to test fallback case
|
||||||
|
process.env['PATH'] = removePWSHFromPath(process.env['PATH'])
|
||||||
|
}
|
||||||
|
|
||||||
|
const extPath: string = await tc.extractZip(zipFile)
|
||||||
|
await tc.cacheDir(extPath, 'foo', '1.1.0')
|
||||||
|
const toolPath: string = tc.find('foo', '1.1.0')
|
||||||
|
|
||||||
|
expect(fs.existsSync(toolPath)).toBeTruthy()
|
||||||
|
expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy()
|
||||||
|
expect(fs.existsSync(path.join(toolPath, 'file.txt'))).toBeTruthy()
|
||||||
|
expect(
|
||||||
|
fs.existsSync(path.join(toolPath, 'folder', 'nested-file.txt'))
|
||||||
|
).toBeTruthy()
|
||||||
|
} finally {
|
||||||
|
await io.rmRF(tempDir)
|
||||||
|
process.env['PATH'] = originalPath
|
||||||
}
|
}
|
||||||
|
|
||||||
const extPath: string = await tc.extractZip(zipFile)
|
|
||||||
await tc.cacheDir(extPath, 'foo', '1.1.0')
|
|
||||||
const toolPath: string = tc.find('foo', '1.1.0')
|
|
||||||
|
|
||||||
expect(fs.existsSync(toolPath)).toBeTruthy()
|
|
||||||
expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy()
|
|
||||||
expect(fs.existsSync(path.join(toolPath, 'file.txt'))).toBeTruthy()
|
|
||||||
expect(
|
|
||||||
fs.existsSync(path.join(toolPath, 'folder', 'nested-file.txt'))
|
|
||||||
).toBeTruthy()
|
|
||||||
} finally {
|
|
||||||
await io.rmRF(tempDir)
|
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
it('installs a zip and extracts it to specified directory', async function() {
|
it('installs a zip and extracts it to specified directory', async function() {
|
||||||
const tempDir = path.join(__dirname, 'test-install-zip')
|
const tempDir = path.join(__dirname, 'test-install-zip')
|
||||||
@@ -597,7 +653,7 @@ describe('@actions/tool-cache', function() {
|
|||||||
// folder/nested-file.txt
|
// folder/nested-file.txt
|
||||||
const stagingDir = path.join(tempDir, 'zip-staging')
|
const stagingDir = path.join(tempDir, 'zip-staging')
|
||||||
await io.mkdirP(path.join(stagingDir, 'folder'))
|
await io.mkdirP(path.join(stagingDir, 'folder'))
|
||||||
fs.writeFileSync(path.join(stagingDir, 'file.txt'), '')
|
fs.writeFileSync(path.join(stagingDir, 'file.txt'), 'originalText')
|
||||||
fs.writeFileSync(path.join(stagingDir, 'folder', 'nested-file.txt'), '')
|
fs.writeFileSync(path.join(stagingDir, 'folder', 'nested-file.txt'), '')
|
||||||
|
|
||||||
// create the zip
|
// create the zip
|
||||||
@@ -629,12 +685,16 @@ describe('@actions/tool-cache', function() {
|
|||||||
await io.rmRF(destDir)
|
await io.rmRF(destDir)
|
||||||
await io.mkdirP(destDir)
|
await io.mkdirP(destDir)
|
||||||
try {
|
try {
|
||||||
|
fs.writeFileSync(path.join(destDir, 'file.txt'), 'overwriteMe')
|
||||||
const extPath: string = await tc.extractZip(zipFile, destDir)
|
const extPath: string = await tc.extractZip(zipFile, destDir)
|
||||||
await tc.cacheDir(extPath, 'foo', '1.1.0')
|
await tc.cacheDir(extPath, 'foo', '1.1.0')
|
||||||
const toolPath: string = tc.find('foo', '1.1.0')
|
const toolPath: string = tc.find('foo', '1.1.0')
|
||||||
expect(fs.existsSync(toolPath)).toBeTruthy()
|
expect(fs.existsSync(toolPath)).toBeTruthy()
|
||||||
expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy()
|
expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy()
|
||||||
expect(fs.existsSync(path.join(toolPath, 'file.txt'))).toBeTruthy()
|
expect(fs.existsSync(path.join(toolPath, 'file.txt'))).toBeTruthy()
|
||||||
|
expect(fs.readFileSync(path.join(toolPath, 'file.txt'), 'utf8')).toBe(
|
||||||
|
'originalText'
|
||||||
|
)
|
||||||
expect(
|
expect(
|
||||||
fs.existsSync(path.join(toolPath, 'folder', 'nested-file.txt'))
|
fs.existsSync(path.join(toolPath, 'folder', 'nested-file.txt'))
|
||||||
).toBeTruthy()
|
).toBeTruthy()
|
||||||
@@ -843,3 +903,12 @@ function setGlobal<T>(key: string, value: T | undefined): void {
|
|||||||
g[key] = value
|
g[key] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removePWSHFromPath(pathEnv: string | undefined): string {
|
||||||
|
return (pathEnv || '')
|
||||||
|
.split(';')
|
||||||
|
.filter(segment => {
|
||||||
|
return !segment.startsWith(`C:\\Program Files\\PowerShell`)
|
||||||
|
})
|
||||||
|
.join(';')
|
||||||
|
}
|
||||||
|
|||||||
Generated
+14
-14
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/tool-cache",
|
"name": "@actions/tool-cache",
|
||||||
"version": "1.6.1",
|
"version": "1.7.1",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -26,9 +26,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@actions/io": {
|
"@actions/io": {
|
||||||
"version": "1.0.2",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.1.tgz",
|
||||||
"integrity": "sha512-J8KuFqVPr3p6U8W93DOXlXW6zFvrQAJANdS+vw0YhusLIq+bszW8zmK2Fh1C2kDPX8FMvwIl1OUcFgvJoXLbAg=="
|
"integrity": "sha512-Qi4JoKXjmE0O67wAOH6y0n26QXhMKMFo7GD/4IXNVcrtLjUlGjGuVys6pQgwF3ArfGTQu0XpqaNr0YhED2RaRA=="
|
||||||
},
|
},
|
||||||
"@types/nock": {
|
"@types/nock": {
|
||||||
"version": "10.0.3",
|
"version": "10.0.3",
|
||||||
@@ -123,24 +123,24 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.19",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "0.0.8",
|
"version": "1.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||||
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
|
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"mkdirp": {
|
"mkdirp": {
|
||||||
"version": "0.5.1",
|
"version": "0.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
|
||||||
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
|
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"minimist": "0.0.8"
|
"minimist": "^1.2.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ms": {
|
"ms": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/tool-cache",
|
"name": "@actions/tool-cache",
|
||||||
"version": "1.6.1",
|
"version": "1.7.1",
|
||||||
"description": "Actions tool-cache lib",
|
"description": "Actions tool-cache lib",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"github",
|
"github",
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
"@actions/core": "^1.2.6",
|
"@actions/core": "^1.2.6",
|
||||||
"@actions/exec": "^1.0.0",
|
"@actions/exec": "^1.0.0",
|
||||||
"@actions/http-client": "^1.0.8",
|
"@actions/http-client": "^1.0.8",
|
||||||
"@actions/io": "^1.0.1",
|
"@actions/io": "^1.1.1",
|
||||||
"semver": "^6.1.0",
|
"semver": "^6.1.0",
|
||||||
"uuid": "^3.3.2"
|
"uuid": "^3.3.2"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -135,8 +135,15 @@ export function _getOsVersion(): string {
|
|||||||
const lines = lsbContents.split('\n')
|
const lines = lsbContents.split('\n')
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const parts = line.split('=')
|
const parts = line.split('=')
|
||||||
if (parts.length === 2 && parts[0].trim() === 'DISTRIB_RELEASE') {
|
if (
|
||||||
version = parts[1].trim()
|
parts.length === 2 &&
|
||||||
|
(parts[0].trim() === 'VERSION_ID' ||
|
||||||
|
parts[0].trim() === 'DISTRIB_RELEASE')
|
||||||
|
) {
|
||||||
|
version = parts[1]
|
||||||
|
.trim()
|
||||||
|
.replace(/^"/, '')
|
||||||
|
.replace(/"$/, '')
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,11 +154,14 @@ export function _getOsVersion(): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function _readLinuxVersionFile(): string {
|
export function _readLinuxVersionFile(): string {
|
||||||
const lsbFile = '/etc/lsb-release'
|
const lsbReleaseFile = '/etc/lsb-release'
|
||||||
|
const osReleaseFile = '/etc/os-release'
|
||||||
let contents = ''
|
let contents = ''
|
||||||
|
|
||||||
if (fs.existsSync(lsbFile)) {
|
if (fs.existsSync(lsbReleaseFile)) {
|
||||||
contents = fs.readFileSync(lsbFile).toString()
|
contents = fs.readFileSync(lsbReleaseFile).toString()
|
||||||
|
} else if (fs.existsSync(osReleaseFile)) {
|
||||||
|
contents = fs.readFileSync(osReleaseFile).toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
return contents
|
return contents
|
||||||
|
|||||||
@@ -272,6 +272,7 @@ export async function extractTar(
|
|||||||
if (isGnuTar) {
|
if (isGnuTar) {
|
||||||
// Suppress warnings when using GNU tar to extract archives created by BSD tar
|
// Suppress warnings when using GNU tar to extract archives created by BSD tar
|
||||||
args.push('--warning=no-unknown-keyword')
|
args.push('--warning=no-unknown-keyword')
|
||||||
|
args.push('--overwrite')
|
||||||
}
|
}
|
||||||
|
|
||||||
args.push('-C', destArg, '-f', fileArg)
|
args.push('-C', destArg, '-f', fileArg)
|
||||||
@@ -344,21 +345,55 @@ async function extractZipWin(file: string, dest: string): Promise<void> {
|
|||||||
// build the powershell command
|
// build the powershell command
|
||||||
const escapedFile = file.replace(/'/g, "''").replace(/"|\n|\r/g, '') // double-up single quotes, remove double quotes and newlines
|
const escapedFile = file.replace(/'/g, "''").replace(/"|\n|\r/g, '') // double-up single quotes, remove double quotes and newlines
|
||||||
const escapedDest = dest.replace(/'/g, "''").replace(/"|\n|\r/g, '')
|
const escapedDest = dest.replace(/'/g, "''").replace(/"|\n|\r/g, '')
|
||||||
const command = `$ErrorActionPreference = 'Stop' ; try { Add-Type -AssemblyName System.IO.Compression.FileSystem } catch { } ; [System.IO.Compression.ZipFile]::ExtractToDirectory('${escapedFile}', '${escapedDest}')`
|
const pwshPath = await io.which('pwsh', false)
|
||||||
|
|
||||||
// run powershell
|
//To match the file overwrite behavior on nix systems, we use the overwrite = true flag for ExtractToDirectory
|
||||||
const powershellPath = await io.which('powershell', true)
|
//and the -Force flag for Expand-Archive as a fallback
|
||||||
const args = [
|
if (pwshPath) {
|
||||||
'-NoLogo',
|
//attempt to use pwsh with ExtractToDirectory, if this fails attempt Expand-Archive
|
||||||
'-Sta',
|
const pwshCommand = [
|
||||||
'-NoProfile',
|
`$ErrorActionPreference = 'Stop' ;`,
|
||||||
'-NonInteractive',
|
`try { Add-Type -AssemblyName System.IO.Compression.ZipFile } catch { } ;`,
|
||||||
'-ExecutionPolicy',
|
`try { [System.IO.Compression.ZipFile]::ExtractToDirectory('${escapedFile}', '${escapedDest}', $true) }`,
|
||||||
'Unrestricted',
|
`catch { if (($_.Exception.GetType().FullName -eq 'System.Management.Automation.MethodException') -or ($_.Exception.GetType().FullName -eq 'System.Management.Automation.RuntimeException') ){ Expand-Archive -LiteralPath '${escapedFile}' -DestinationPath '${escapedDest}' -Force } else { throw $_ } } ;`
|
||||||
'-Command',
|
].join(' ')
|
||||||
command
|
|
||||||
]
|
const args = [
|
||||||
await exec(`"${powershellPath}"`, args)
|
'-NoLogo',
|
||||||
|
'-NoProfile',
|
||||||
|
'-NonInteractive',
|
||||||
|
'-ExecutionPolicy',
|
||||||
|
'Unrestricted',
|
||||||
|
'-Command',
|
||||||
|
pwshCommand
|
||||||
|
]
|
||||||
|
|
||||||
|
core.debug(`Using pwsh at path: ${pwshPath}`)
|
||||||
|
await exec(`"${pwshPath}"`, args)
|
||||||
|
} else {
|
||||||
|
const powershellCommand = [
|
||||||
|
`$ErrorActionPreference = 'Stop' ;`,
|
||||||
|
`try { Add-Type -AssemblyName System.IO.Compression.FileSystem } catch { } ;`,
|
||||||
|
`if ((Get-Command -Name Expand-Archive -Module Microsoft.PowerShell.Archive -ErrorAction Ignore)) { Expand-Archive -LiteralPath '${escapedFile}' -DestinationPath '${escapedDest}' -Force }`,
|
||||||
|
`else {[System.IO.Compression.ZipFile]::ExtractToDirectory('${escapedFile}', '${escapedDest}', $true) }`
|
||||||
|
].join(' ')
|
||||||
|
|
||||||
|
const args = [
|
||||||
|
'-NoLogo',
|
||||||
|
'-Sta',
|
||||||
|
'-NoProfile',
|
||||||
|
'-NonInteractive',
|
||||||
|
'-ExecutionPolicy',
|
||||||
|
'Unrestricted',
|
||||||
|
'-Command',
|
||||||
|
powershellCommand
|
||||||
|
]
|
||||||
|
|
||||||
|
const powershellPath = await io.which('powershell', true)
|
||||||
|
core.debug(`Using powershell at path: ${powershellPath}`)
|
||||||
|
|
||||||
|
await exec(`"${powershellPath}"`, args)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function extractZipNix(file: string, dest: string): Promise<void> {
|
async function extractZipNix(file: string, dest: string): Promise<void> {
|
||||||
@@ -367,6 +402,7 @@ async function extractZipNix(file: string, dest: string): Promise<void> {
|
|||||||
if (!core.isDebug()) {
|
if (!core.isDebug()) {
|
||||||
args.unshift('-q')
|
args.unshift('-q')
|
||||||
}
|
}
|
||||||
|
args.unshift('-o') //overwrite with -o, otherwise a prompt is shown which freezes the run
|
||||||
await exec(`"${unzipPath}"`, args, {cwd: dest})
|
await exec(`"${unzipPath}"`, args, {cwd: dest})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -472,9 +508,9 @@ export function find(
|
|||||||
arch = arch || os.arch()
|
arch = arch || os.arch()
|
||||||
|
|
||||||
// attempt to resolve an explicit version
|
// attempt to resolve an explicit version
|
||||||
if (!_isExplicitVersion(versionSpec)) {
|
if (!isExplicitVersion(versionSpec)) {
|
||||||
const localVersions: string[] = findAllVersions(toolName, arch)
|
const localVersions: string[] = findAllVersions(toolName, arch)
|
||||||
const match = _evaluateVersions(localVersions, versionSpec)
|
const match = evaluateVersions(localVersions, versionSpec)
|
||||||
versionSpec = match
|
versionSpec = match
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -514,7 +550,7 @@ export function findAllVersions(toolName: string, arch?: string): string[] {
|
|||||||
if (fs.existsSync(toolPath)) {
|
if (fs.existsSync(toolPath)) {
|
||||||
const children: string[] = fs.readdirSync(toolPath)
|
const children: string[] = fs.readdirSync(toolPath)
|
||||||
for (const child of children) {
|
for (const child of children) {
|
||||||
if (_isExplicitVersion(child)) {
|
if (isExplicitVersion(child)) {
|
||||||
const fullPath = path.join(toolPath, child, arch || '')
|
const fullPath = path.join(toolPath, child, arch || '')
|
||||||
if (fs.existsSync(fullPath) && fs.existsSync(`${fullPath}.complete`)) {
|
if (fs.existsSync(fullPath) && fs.existsSync(`${fullPath}.complete`)) {
|
||||||
versions.push(child)
|
versions.push(child)
|
||||||
@@ -652,7 +688,12 @@ function _completeToolPath(tool: string, version: string, arch?: string): void {
|
|||||||
core.debug('finished caching tool')
|
core.debug('finished caching tool')
|
||||||
}
|
}
|
||||||
|
|
||||||
function _isExplicitVersion(versionSpec: string): boolean {
|
/**
|
||||||
|
* Check if version string is explicit
|
||||||
|
*
|
||||||
|
* @param versionSpec version string to check
|
||||||
|
*/
|
||||||
|
export function isExplicitVersion(versionSpec: string): boolean {
|
||||||
const c = semver.clean(versionSpec) || ''
|
const c = semver.clean(versionSpec) || ''
|
||||||
core.debug(`isExplicit: ${c}`)
|
core.debug(`isExplicit: ${c}`)
|
||||||
|
|
||||||
@@ -662,7 +703,17 @@ function _isExplicitVersion(versionSpec: string): boolean {
|
|||||||
return valid
|
return valid
|
||||||
}
|
}
|
||||||
|
|
||||||
function _evaluateVersions(versions: string[], versionSpec: string): string {
|
/**
|
||||||
|
* Get the highest satisfiying semantic version in `versions` which satisfies `versionSpec`
|
||||||
|
*
|
||||||
|
* @param versions array of versions to evaluate
|
||||||
|
* @param versionSpec semantic version spec to satisfy
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function evaluateVersions(
|
||||||
|
versions: string[],
|
||||||
|
versionSpec: string
|
||||||
|
): string {
|
||||||
let version = ''
|
let version = ''
|
||||||
core.debug(`evaluating ${versions.length} versions`)
|
core.debug(`evaluating ${versions.length} versions`)
|
||||||
versions = versions.sort((a, b) => {
|
versions = versions.sort((a, b) => {
|
||||||
|
|||||||
Executable
+89
@@ -0,0 +1,89 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/*
|
||||||
|
This script takes the output of npm audit --json from stdin
|
||||||
|
and writes a filtered version to stdout.
|
||||||
|
The filtered version will have the entries listed in `AUDIT_ALLOW_LIST` removed.
|
||||||
|
Specifically, each property of `vulnerabilities` in the input is matched by name in the allow list.
|
||||||
|
|
||||||
|
Sample output of `npm audit --json` (NPM v6):
|
||||||
|
|
||||||
|
{
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"action": "review",
|
||||||
|
"module": "trim-newlines",
|
||||||
|
"resolves": [
|
||||||
|
{
|
||||||
|
"id": 1753,
|
||||||
|
"path": "lerna>@lerna/publish>@lerna/version>@lerna/conventional-commits>conventional-changelog-core>get-pkg-repo>meow>trim-newlines",
|
||||||
|
"dev": true,
|
||||||
|
"optional": false,
|
||||||
|
"bundled": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
// Other properties ...
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
The reason we have this script is that there may be low-severity or unexploitable vulnerabilities
|
||||||
|
that have not yet been fixed in newer versions of the package.
|
||||||
|
|
||||||
|
Note: if we update to NPM v7, we will have to change this script because the `npm audit` output will be different.
|
||||||
|
See commit 935647112d96fa5cf82e61314f7135376d24f291 in https://github.com/actions/toolkit/pull/846.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict'
|
||||||
|
const fs = require('fs')
|
||||||
|
|
||||||
|
const USAGE = "Usage: npm audit --json | scripts/audit-allow-list"
|
||||||
|
|
||||||
|
// To add entires to the allow list:
|
||||||
|
// - Run `npm audit --json`
|
||||||
|
// - Copy `path` from each `actions[k].resolves` you want to allow
|
||||||
|
// - Fill in the `advisoryUrl` and `justification` (these are just for documentation)
|
||||||
|
const AUDIT_ALLOW_LIST = [
|
||||||
|
{
|
||||||
|
path: "lerna>@lerna/publish>@lerna/version>@lerna/conventional-commits>conventional-changelog-core>get-pkg-repo>meow>trim-newlines",
|
||||||
|
advisoryUrl: "https://www.npmjs.com/advisories/1753",
|
||||||
|
justification: "dependency of lerna (dev only); low severity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "lerna>@lerna/version>@lerna/conventional-commits>conventional-changelog-core>get-pkg-repo>meow>trim-newlines",
|
||||||
|
advisoryUrl: "https://www.npmjs.com/advisories/1753",
|
||||||
|
justification: "dependency of lerna (dev only); low severity"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param audits - JavaScript object matching the schema of `npm audit --json`
|
||||||
|
* @param allowedPaths - List of dependency paths to exclude from the audit
|
||||||
|
*/
|
||||||
|
function filterVulnerabilities(audits, allowedPaths) {
|
||||||
|
const vulnerabilities = audits.actions.flatMap(x => x.resolves)
|
||||||
|
return vulnerabilities.filter(x => !allowedPaths.includes(x.path))
|
||||||
|
}
|
||||||
|
|
||||||
|
const input = fs.readFileSync("/dev/stdin", "utf-8")
|
||||||
|
if (input === "") {
|
||||||
|
console.error(USAGE)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const audits = JSON.parse(input)
|
||||||
|
const allowedPaths = AUDIT_ALLOW_LIST.map(x => x.path)
|
||||||
|
// This function assumes `audits` has the right structure.
|
||||||
|
// Just let the error terminate the process if the input doesn't match the schema.
|
||||||
|
const remainingVulnerabilities = filterVulnerabilities(audits, allowedPaths)
|
||||||
|
|
||||||
|
// `npm audit` will return exit code 1 if it finds vulnerabilities.
|
||||||
|
// This script should do the same.
|
||||||
|
const numVulnerabilities = remainingVulnerabilities.length
|
||||||
|
if (numVulnerabilities > 0) {
|
||||||
|
const pluralized = numVulnerabilities === 1 ? "y" : "ies"
|
||||||
|
console.log(`Found ${numVulnerabilities} unrecognized vulnerabilit${pluralized} from \`npm audit\`:`)
|
||||||
|
console.log(JSON.stringify(remainingVulnerabilities, null, 2))
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user