mirror of
				https://gitea.com/actions/cache.git
				synced 2025-11-02 07:47:08 +00:00 
			
		
		
		
	Compare commits
	
		
			29 Commits
		
	
	
		
			v3.0.7
			...
			releases/v
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					f5ce41475b | ||
| 
						 | 
					68fa0a8d81 | ||
| 
						 | 
					56ec64e417 | ||
| 
						 | 
					efbc4e162b | ||
| 
						 | 
					d9747005de | ||
| 
						 | 
					3f662ca624 | ||
| 
						 | 
					0232e3178d | ||
| 
						 | 
					ee7a57c615 | ||
| 
						 | 
					da9f90cb83 | ||
| 
						 | 
					ec7f7ebd08 | ||
| 
						 | 
					2a973a0f4e | ||
| 
						 | 
					cbbb8b4d4f | ||
| 
						 | 
					5a0add1806 | ||
| 
						 | 
					9fe7ad8b07 | ||
| 
						 | 
					7c7d003bbb | ||
| 
						 | 
					96e5a46c57 | ||
| 
						 | 
					84e606dfac | ||
| 
						 | 
					70655ec832 | ||
| 
						 | 
					fe1055e9d1 | ||
| 
						 | 
					a505c2e7a6 | ||
| 
						 | 
					10a14413e7 | ||
| 
						 | 
					cf4f44db70 | ||
| 
						 | 
					4c4974aff1 | ||
| 
						 | 
					cffae9552b | ||
| 
						 | 
					44543250bd | ||
| 
						 | 
					6491e51b66 | ||
| 
						 | 
					86dff562ab | ||
| 
						 | 
					0f810ad45a | ||
| 
						 | 
					9d8c7b4041 | 
							
								
								
									
										111
									
								
								.github/workflows/workflow.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										111
									
								
								.github/workflows/workflow.yml
									
									
									
									
										vendored
									
									
								
							@@ -4,51 +4,130 @@ on:
 | 
				
			|||||||
  pull_request:
 | 
					  pull_request:
 | 
				
			||||||
    branches:
 | 
					    branches:
 | 
				
			||||||
      - master
 | 
					      - master
 | 
				
			||||||
 | 
					      - releases/**
 | 
				
			||||||
    paths-ignore:
 | 
					    paths-ignore:
 | 
				
			||||||
      - '**.md'
 | 
					      - '**.md'
 | 
				
			||||||
  push:
 | 
					  push:
 | 
				
			||||||
    branches:
 | 
					    branches:
 | 
				
			||||||
      - master
 | 
					      - master
 | 
				
			||||||
 | 
					      - releases/**
 | 
				
			||||||
    paths-ignore:
 | 
					    paths-ignore:
 | 
				
			||||||
      - '**.md'
 | 
					      - '**.md'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
  test:
 | 
					  # Build and unit test
 | 
				
			||||||
    name: Test on ${{ matrix.os }}
 | 
					  build:
 | 
				
			||||||
 | 
					 | 
				
			||||||
    strategy:
 | 
					    strategy:
 | 
				
			||||||
      matrix:
 | 
					      matrix:
 | 
				
			||||||
        os: [ubuntu-latest, windows-latest, macOS-latest]
 | 
					        os: [ubuntu-latest, windows-latest, macOS-latest]
 | 
				
			||||||
      fail-fast: false
 | 
					      fail-fast: false
 | 
				
			||||||
 | 
					 | 
				
			||||||
    runs-on: ${{ matrix.os }}
 | 
					    runs-on: ${{ matrix.os }}
 | 
				
			||||||
 | 
					 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
    - uses: actions/checkout@v1
 | 
					    - name: Checkout
 | 
				
			||||||
 | 
					      uses: actions/checkout@v2
 | 
				
			||||||
    - uses: actions/setup-node@v1
 | 
					    - name: Setup Node.js
 | 
				
			||||||
 | 
					      uses: actions/setup-node@v1
 | 
				
			||||||
      with:
 | 
					      with:
 | 
				
			||||||
        node-version: '12.x'
 | 
					        node-version: '12.x'
 | 
				
			||||||
 | 
					    - name: Determine npm cache directory
 | 
				
			||||||
    - name: Get npm cache directory
 | 
					 | 
				
			||||||
      id: npm-cache
 | 
					      id: npm-cache
 | 
				
			||||||
      run: |
 | 
					      run: |
 | 
				
			||||||
        echo "::set-output name=dir::$(npm config get cache)"
 | 
					        echo "::set-output name=dir::$(npm config get cache)"
 | 
				
			||||||
 | 
					    - name: Restore npm cache
 | 
				
			||||||
    - uses: actions/cache@v1
 | 
					      uses: actions/cache@v1
 | 
				
			||||||
      with:
 | 
					      with:
 | 
				
			||||||
        path: ${{ steps.npm-cache.outputs.dir }}
 | 
					        path: ${{ steps.npm-cache.outputs.dir }}
 | 
				
			||||||
        key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
 | 
					        key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
 | 
				
			||||||
        restore-keys: |
 | 
					        restore-keys: |
 | 
				
			||||||
          ${{ runner.os }}-node-
 | 
					          ${{ runner.os }}-node-
 | 
				
			||||||
 | 
					 | 
				
			||||||
    - run: npm ci
 | 
					    - run: npm ci
 | 
				
			||||||
 | 
					 | 
				
			||||||
    - name: Prettier Format Check
 | 
					    - name: Prettier Format Check
 | 
				
			||||||
      run: npm run format-check
 | 
					      run: npm run format-check
 | 
				
			||||||
 | 
					 | 
				
			||||||
    - name: ESLint Check
 | 
					    - name: ESLint Check
 | 
				
			||||||
      run: npm run lint
 | 
					      run: npm run lint
 | 
				
			||||||
 | 
					 | 
				
			||||||
    - name: Build & Test
 | 
					    - name: Build & Test
 | 
				
			||||||
      run: npm run test
 | 
					      run: npm run test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # End to end save and restore
 | 
				
			||||||
 | 
					  test-save:
 | 
				
			||||||
 | 
					    strategy:
 | 
				
			||||||
 | 
					      matrix:
 | 
				
			||||||
 | 
					        os: [ubuntu-latest, windows-latest, macOS-latest]
 | 
				
			||||||
 | 
					      fail-fast: false
 | 
				
			||||||
 | 
					    runs-on: ${{ matrix.os }}
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					    - name: Checkout
 | 
				
			||||||
 | 
					      uses: actions/checkout@v2
 | 
				
			||||||
 | 
					    - name: Generate files
 | 
				
			||||||
 | 
					      shell: bash
 | 
				
			||||||
 | 
					      run: __tests__/create-cache-files.sh ${{ runner.os }}
 | 
				
			||||||
 | 
					    - name: Save cache
 | 
				
			||||||
 | 
					      uses: ./
 | 
				
			||||||
 | 
					      with:
 | 
				
			||||||
 | 
					        key: test-${{ runner.os }}-${{ github.run_id }}
 | 
				
			||||||
 | 
					        path: test-cache
 | 
				
			||||||
 | 
					  test-restore:
 | 
				
			||||||
 | 
					    needs: test-save
 | 
				
			||||||
 | 
					    strategy:
 | 
				
			||||||
 | 
					      matrix:
 | 
				
			||||||
 | 
					        os: [ubuntu-latest, windows-latest, macOS-latest]
 | 
				
			||||||
 | 
					      fail-fast: false
 | 
				
			||||||
 | 
					    runs-on: ${{ matrix.os }}
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					    - name: Checkout
 | 
				
			||||||
 | 
					      uses: actions/checkout@v2
 | 
				
			||||||
 | 
					    - name: Restore cache
 | 
				
			||||||
 | 
					      uses: ./
 | 
				
			||||||
 | 
					      with:
 | 
				
			||||||
 | 
					        key: test-${{ runner.os }}-${{ github.run_id }}
 | 
				
			||||||
 | 
					        path: test-cache
 | 
				
			||||||
 | 
					    - name: Verify cache
 | 
				
			||||||
 | 
					      shell: bash
 | 
				
			||||||
 | 
					      run: __tests__/verify-cache-files.sh ${{ runner.os }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # End to end with proxy
 | 
				
			||||||
 | 
					  test-proxy-save:
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    container:
 | 
				
			||||||
 | 
					      image: ubuntu:latest
 | 
				
			||||||
 | 
					      options: --dns 127.0.0.1
 | 
				
			||||||
 | 
					    services:
 | 
				
			||||||
 | 
					      squid-proxy:
 | 
				
			||||||
 | 
					        image: ubuntu/squid:latest
 | 
				
			||||||
 | 
					        ports:
 | 
				
			||||||
 | 
					          - 3128:3128
 | 
				
			||||||
 | 
					    env:
 | 
				
			||||||
 | 
					      https_proxy: http://squid-proxy:3128
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					    - name: Checkout
 | 
				
			||||||
 | 
					      uses: actions/checkout@v2
 | 
				
			||||||
 | 
					    - name: Generate files
 | 
				
			||||||
 | 
					      run: __tests__/create-cache-files.sh proxy
 | 
				
			||||||
 | 
					    - name: Save cache
 | 
				
			||||||
 | 
					      uses: ./
 | 
				
			||||||
 | 
					      with:
 | 
				
			||||||
 | 
					        key: test-proxy-${{ github.run_id }}
 | 
				
			||||||
 | 
					        path: test-cache
 | 
				
			||||||
 | 
					  test-proxy-restore:
 | 
				
			||||||
 | 
					    needs: test-proxy-save
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    container:
 | 
				
			||||||
 | 
					      image: ubuntu:latest
 | 
				
			||||||
 | 
					      options: --dns 127.0.0.1
 | 
				
			||||||
 | 
					    services:
 | 
				
			||||||
 | 
					      squid-proxy:
 | 
				
			||||||
 | 
					        image: ubuntu/squid:latest
 | 
				
			||||||
 | 
					        ports:
 | 
				
			||||||
 | 
					          - 3128:3128
 | 
				
			||||||
 | 
					    env:
 | 
				
			||||||
 | 
					      https_proxy: http://squid-proxy:3128
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					    - name: Checkout
 | 
				
			||||||
 | 
					      uses: actions/checkout@v2
 | 
				
			||||||
 | 
					    - name: Restore cache
 | 
				
			||||||
 | 
					      uses: ./
 | 
				
			||||||
 | 
					      with:
 | 
				
			||||||
 | 
					        key: test-proxy-${{ github.run_id }}
 | 
				
			||||||
 | 
					        path: test-cache
 | 
				
			||||||
 | 
					    - name: Verify cache
 | 
				
			||||||
 | 
					      run: __tests__/verify-cache-files.sh proxy
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										144
									
								
								__tests__/cacheHttpsClient.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								__tests__/cacheHttpsClient.test.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,144 @@
 | 
				
			|||||||
 | 
					import { retry } from "../src/cacheHttpClient";
 | 
				
			||||||
 | 
					import * as testUtils from "../src/utils/testUtils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					afterEach(() => {
 | 
				
			||||||
 | 
					    testUtils.clearInputs();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface TestResponse {
 | 
				
			||||||
 | 
					    statusCode: number;
 | 
				
			||||||
 | 
					    result: string | null;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function handleResponse(
 | 
				
			||||||
 | 
					    response: TestResponse | undefined
 | 
				
			||||||
 | 
					): Promise<TestResponse> {
 | 
				
			||||||
 | 
					    if (!response) {
 | 
				
			||||||
 | 
					        fail("Retry method called too many times");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (response.statusCode === 999) {
 | 
				
			||||||
 | 
					        throw Error("Test Error");
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        return Promise.resolve(response);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function testRetryExpectingResult(
 | 
				
			||||||
 | 
					    responses: Array<TestResponse>,
 | 
				
			||||||
 | 
					    expectedResult: string | null
 | 
				
			||||||
 | 
					): Promise<void> {
 | 
				
			||||||
 | 
					    responses = responses.reverse(); // Reverse responses since we pop from end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const actualResult = await retry(
 | 
				
			||||||
 | 
					        "test",
 | 
				
			||||||
 | 
					        () => handleResponse(responses.pop()),
 | 
				
			||||||
 | 
					        (response: TestResponse) => response.statusCode
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(actualResult.result).toEqual(expectedResult);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function testRetryExpectingError(
 | 
				
			||||||
 | 
					    responses: Array<TestResponse>
 | 
				
			||||||
 | 
					): Promise<void> {
 | 
				
			||||||
 | 
					    responses = responses.reverse(); // Reverse responses since we pop from end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(
 | 
				
			||||||
 | 
					        retry(
 | 
				
			||||||
 | 
					            "test",
 | 
				
			||||||
 | 
					            () => handleResponse(responses.pop()),
 | 
				
			||||||
 | 
					            (response: TestResponse) => response.statusCode
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    ).rejects.toBeInstanceOf(Error);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test("retry works on successful response", async () => {
 | 
				
			||||||
 | 
					    await testRetryExpectingResult(
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                statusCode: 200,
 | 
				
			||||||
 | 
					                result: "Ok"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "Ok"
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test("retry works after retryable status code", async () => {
 | 
				
			||||||
 | 
					    await testRetryExpectingResult(
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                statusCode: 503,
 | 
				
			||||||
 | 
					                result: null
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                statusCode: 200,
 | 
				
			||||||
 | 
					                result: "Ok"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "Ok"
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test("retry fails after exhausting retries", async () => {
 | 
				
			||||||
 | 
					    await testRetryExpectingError([
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            statusCode: 503,
 | 
				
			||||||
 | 
					            result: null
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            statusCode: 503,
 | 
				
			||||||
 | 
					            result: null
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            statusCode: 200,
 | 
				
			||||||
 | 
					            result: "Ok"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test("retry fails after non-retryable status code", async () => {
 | 
				
			||||||
 | 
					    await testRetryExpectingError([
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            statusCode: 500,
 | 
				
			||||||
 | 
					            result: null
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            statusCode: 200,
 | 
				
			||||||
 | 
					            result: "Ok"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test("retry works after error", async () => {
 | 
				
			||||||
 | 
					    await testRetryExpectingResult(
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                statusCode: 999,
 | 
				
			||||||
 | 
					                result: null
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                statusCode: 200,
 | 
				
			||||||
 | 
					                result: "Ok"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "Ok"
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test("retry returns after client error", async () => {
 | 
				
			||||||
 | 
					    await testRetryExpectingResult(
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                statusCode: 400,
 | 
				
			||||||
 | 
					                result: null
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                statusCode: 200,
 | 
				
			||||||
 | 
					                result: "Ok"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        null
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										11
									
								
								__tests__/create-cache-files.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										11
									
								
								__tests__/create-cache-files.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					#!/bin/sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Validate args
 | 
				
			||||||
 | 
					prefix="$1"
 | 
				
			||||||
 | 
					if [ -z "$prefix" ]; then
 | 
				
			||||||
 | 
					  echo "Must supply prefix argument"
 | 
				
			||||||
 | 
					  exit 1
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mkdir test-cache
 | 
				
			||||||
 | 
					echo "$prefix $GITHUB_RUN_ID" > test-cache/test-file.txt
 | 
				
			||||||
@@ -2,6 +2,8 @@ import * as exec from "@actions/exec";
 | 
				
			|||||||
import * as io from "@actions/io";
 | 
					import * as io from "@actions/io";
 | 
				
			||||||
import * as tar from "../src/tar";
 | 
					import * as tar from "../src/tar";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import fs = require("fs");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
jest.mock("@actions/exec");
 | 
					jest.mock("@actions/exec");
 | 
				
			||||||
jest.mock("@actions/io");
 | 
					jest.mock("@actions/io");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -11,17 +13,19 @@ beforeAll(() => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test("extract tar", async () => {
 | 
					test("extract BSD tar", async () => {
 | 
				
			||||||
    const mkdirMock = jest.spyOn(io, "mkdirP");
 | 
					    const mkdirMock = jest.spyOn(io, "mkdirP");
 | 
				
			||||||
    const execMock = jest.spyOn(exec, "exec");
 | 
					    const execMock = jest.spyOn(exec, "exec");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const archivePath = "cache.tar";
 | 
					    const IS_WINDOWS = process.platform === "win32";
 | 
				
			||||||
 | 
					    const archivePath = IS_WINDOWS
 | 
				
			||||||
 | 
					        ? `${process.env["windir"]}\\fakepath\\cache.tar`
 | 
				
			||||||
 | 
					        : "cache.tar";
 | 
				
			||||||
    const targetDirectory = "~/.npm/cache";
 | 
					    const targetDirectory = "~/.npm/cache";
 | 
				
			||||||
    await tar.extractTar(archivePath, targetDirectory);
 | 
					    await tar.extractTar(archivePath, targetDirectory);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    expect(mkdirMock).toHaveBeenCalledWith(targetDirectory);
 | 
					    expect(mkdirMock).toHaveBeenCalledWith(targetDirectory);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const IS_WINDOWS = process.platform === "win32";
 | 
					 | 
				
			||||||
    const tarPath = IS_WINDOWS
 | 
					    const tarPath = IS_WINDOWS
 | 
				
			||||||
        ? `${process.env["windir"]}\\System32\\tar.exe`
 | 
					        ? `${process.env["windir"]}\\System32\\tar.exe`
 | 
				
			||||||
        : "tar";
 | 
					        : "tar";
 | 
				
			||||||
@@ -29,13 +33,37 @@ test("extract tar", async () => {
 | 
				
			|||||||
    expect(execMock).toHaveBeenCalledWith(`"${tarPath}"`, [
 | 
					    expect(execMock).toHaveBeenCalledWith(`"${tarPath}"`, [
 | 
				
			||||||
        "-xz",
 | 
					        "-xz",
 | 
				
			||||||
        "-f",
 | 
					        "-f",
 | 
				
			||||||
        archivePath,
 | 
					        IS_WINDOWS ? archivePath.replace(/\\/g, "/") : archivePath,
 | 
				
			||||||
        "-C",
 | 
					        "-C",
 | 
				
			||||||
        targetDirectory
 | 
					        IS_WINDOWS ? targetDirectory?.replace(/\\/g, "/") : targetDirectory
 | 
				
			||||||
    ]);
 | 
					    ]);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test("create tar", async () => {
 | 
					test("extract GNU tar", async () => {
 | 
				
			||||||
 | 
					    const IS_WINDOWS = process.platform === "win32";
 | 
				
			||||||
 | 
					    if (IS_WINDOWS) {
 | 
				
			||||||
 | 
					        jest.spyOn(fs, "existsSync").mockReturnValueOnce(false);
 | 
				
			||||||
 | 
					        jest.spyOn(tar, "isGnuTar").mockReturnValue(Promise.resolve(true));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const execMock = jest.spyOn(exec, "exec");
 | 
				
			||||||
 | 
					        const archivePath = `${process.env["windir"]}\\fakepath\\cache.tar`;
 | 
				
			||||||
 | 
					        const targetDirectory = "~/.npm/cache";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await tar.extractTar(archivePath, targetDirectory);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(execMock).toHaveBeenCalledTimes(1);
 | 
				
			||||||
 | 
					        expect(execMock).toHaveBeenLastCalledWith(`"tar"`, [
 | 
				
			||||||
 | 
					            "-xz",
 | 
				
			||||||
 | 
					            "-f",
 | 
				
			||||||
 | 
					            archivePath.replace(/\\/g, "/"),
 | 
				
			||||||
 | 
					            "-C",
 | 
				
			||||||
 | 
					            targetDirectory?.replace(/\\/g, "/"),
 | 
				
			||||||
 | 
					            "--force-local"
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test("create BSD tar", async () => {
 | 
				
			||||||
    const execMock = jest.spyOn(exec, "exec");
 | 
					    const execMock = jest.spyOn(exec, "exec");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const archivePath = "cache.tar";
 | 
					    const archivePath = "cache.tar";
 | 
				
			||||||
@@ -50,9 +78,9 @@ test("create tar", async () => {
 | 
				
			|||||||
    expect(execMock).toHaveBeenCalledWith(`"${tarPath}"`, [
 | 
					    expect(execMock).toHaveBeenCalledWith(`"${tarPath}"`, [
 | 
				
			||||||
        "-cz",
 | 
					        "-cz",
 | 
				
			||||||
        "-f",
 | 
					        "-f",
 | 
				
			||||||
        archivePath,
 | 
					        IS_WINDOWS ? archivePath.replace(/\\/g, "/") : archivePath,
 | 
				
			||||||
        "-C",
 | 
					        "-C",
 | 
				
			||||||
        sourceDirectory,
 | 
					        IS_WINDOWS ? sourceDirectory?.replace(/\\/g, "/") : sourceDirectory,
 | 
				
			||||||
        "."
 | 
					        "."
 | 
				
			||||||
    ]);
 | 
					    ]);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										30
									
								
								__tests__/verify-cache-files.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										30
									
								
								__tests__/verify-cache-files.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					#!/bin/sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Validate args
 | 
				
			||||||
 | 
					prefix="$1"
 | 
				
			||||||
 | 
					if [ -z "$prefix" ]; then
 | 
				
			||||||
 | 
					  echo "Must supply prefix argument"
 | 
				
			||||||
 | 
					  exit 1
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Sanity check GITHUB_RUN_ID defined
 | 
				
			||||||
 | 
					if [ -z "$GITHUB_RUN_ID" ]; then
 | 
				
			||||||
 | 
					  echo "GITHUB_RUN_ID not defined"
 | 
				
			||||||
 | 
					  exit 1
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Verify file exists
 | 
				
			||||||
 | 
					file="test-cache/test-file.txt"
 | 
				
			||||||
 | 
					echo "Checking for $file"
 | 
				
			||||||
 | 
					if [ ! -e $file ]; then
 | 
				
			||||||
 | 
					  echo "File does not exist"
 | 
				
			||||||
 | 
					  exit 1
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Verify file content
 | 
				
			||||||
 | 
					content="$(cat $file)"
 | 
				
			||||||
 | 
					echo "File content:\n$content"
 | 
				
			||||||
 | 
					if [ -z "$(echo $content | grep --fixed-strings "$prefix $GITHUB_RUN_ID")" ]; then
 | 
				
			||||||
 | 
					  echo "Unexpected file content"
 | 
				
			||||||
 | 
					  exit 1
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
							
								
								
									
										5337
									
								
								dist/restore/index.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5337
									
								
								dist/restore/index.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										5318
									
								
								dist/save/index.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5318
									
								
								dist/save/index.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										27
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										27
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -1,13 +1,32 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "cache",
 | 
					  "name": "cache",
 | 
				
			||||||
  "version": "1.1.2",
 | 
					  "version": "1.2.0",
 | 
				
			||||||
  "lockfileVersion": 1,
 | 
					  "lockfileVersion": 1,
 | 
				
			||||||
  "requires": true,
 | 
					  "requires": true,
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@actions/core": {
 | 
					    "@actions/core": {
 | 
				
			||||||
      "version": "1.2.0",
 | 
					      "version": "1.10.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-ZKdyhlSlyz38S6YFfPnyNgCDZuAF2T0Qv5eHflNWytPS8Qjvz39bZFMry9Bb/dpSnqWcNeav5yM2CTYpJeY+Dw=="
 | 
					      "integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
 | 
				
			||||||
 | 
					      "requires": {
 | 
				
			||||||
 | 
					        "@actions/http-client": "^2.0.1",
 | 
				
			||||||
 | 
					        "uuid": "^8.3.2"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "@actions/http-client": {
 | 
				
			||||||
 | 
					          "version": "2.1.0",
 | 
				
			||||||
 | 
					          "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.1.0.tgz",
 | 
				
			||||||
 | 
					          "integrity": "sha512-BonhODnXr3amchh4qkmjPMUO8mFi/zLaaCeCAJZqch8iQqyDnVIkySjB38VHAC8IJ+bnlgfOqlhpyCUZHlQsqw==",
 | 
				
			||||||
 | 
					          "requires": {
 | 
				
			||||||
 | 
					            "tunnel": "^0.0.6"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "uuid": {
 | 
				
			||||||
 | 
					          "version": "8.3.2",
 | 
				
			||||||
 | 
					          "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
 | 
				
			||||||
 | 
					          "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "@actions/exec": {
 | 
					    "@actions/exec": {
 | 
				
			||||||
      "version": "1.0.1",
 | 
					      "version": "1.0.1",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "cache",
 | 
					  "name": "cache",
 | 
				
			||||||
  "version": "1.1.2",
 | 
					  "version": "1.2.0",
 | 
				
			||||||
  "private": true,
 | 
					  "private": true,
 | 
				
			||||||
  "description": "Cache dependencies and build outputs",
 | 
					  "description": "Cache dependencies and build outputs",
 | 
				
			||||||
  "main": "dist/restore/index.js",
 | 
					  "main": "dist/restore/index.js",
 | 
				
			||||||
@@ -24,7 +24,7 @@
 | 
				
			|||||||
  "author": "GitHub",
 | 
					  "author": "GitHub",
 | 
				
			||||||
  "license": "MIT",
 | 
					  "license": "MIT",
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@actions/core": "^1.2.0",
 | 
					    "@actions/core": "^1.10.0",
 | 
				
			||||||
    "@actions/exec": "^1.0.1",
 | 
					    "@actions/exec": "^1.0.1",
 | 
				
			||||||
    "@actions/http-client": "^1.0.6",
 | 
					    "@actions/http-client": "^1.0.6",
 | 
				
			||||||
    "@actions/io": "^1.0.1",
 | 
					    "@actions/io": "^1.0.1",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,16 @@
 | 
				
			|||||||
import * as core from "@actions/core";
 | 
					import * as core from "@actions/core";
 | 
				
			||||||
import * as fs from "fs";
 | 
					 | 
				
			||||||
import { BearerCredentialHandler } from "@actions/http-client/auth";
 | 
					 | 
				
			||||||
import { HttpClient, HttpCodes } from "@actions/http-client";
 | 
					import { HttpClient, HttpCodes } from "@actions/http-client";
 | 
				
			||||||
 | 
					import { BearerCredentialHandler } from "@actions/http-client/auth";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    IHttpClientResponse,
 | 
					    IHttpClientResponse,
 | 
				
			||||||
    IRequestOptions,
 | 
					    IRequestOptions,
 | 
				
			||||||
    ITypedResponse
 | 
					    ITypedResponse
 | 
				
			||||||
} from "@actions/http-client/interfaces";
 | 
					} from "@actions/http-client/interfaces";
 | 
				
			||||||
 | 
					import * as fs from "fs";
 | 
				
			||||||
 | 
					import * as stream from "stream";
 | 
				
			||||||
 | 
					import * as util from "util";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { SocketTimeout } from "./constants";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    ArtifactCacheEntry,
 | 
					    ArtifactCacheEntry,
 | 
				
			||||||
    CommitCacheRequest,
 | 
					    CommitCacheRequest,
 | 
				
			||||||
@@ -22,6 +26,13 @@ function isSuccessStatusCode(statusCode?: number): boolean {
 | 
				
			|||||||
    return statusCode >= 200 && statusCode < 300;
 | 
					    return statusCode >= 200 && statusCode < 300;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function isServerErrorStatusCode(statusCode?: number): boolean {
 | 
				
			||||||
 | 
					    if (!statusCode) {
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return statusCode >= 500;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function isRetryableStatusCode(statusCode?: number): boolean {
 | 
					function isRetryableStatusCode(statusCode?: number): boolean {
 | 
				
			||||||
    if (!statusCode) {
 | 
					    if (!statusCode) {
 | 
				
			||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
@@ -77,14 +88,83 @@ function createHttpClient(): HttpClient {
 | 
				
			|||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function retry<T>(
 | 
				
			||||||
 | 
					    name: string,
 | 
				
			||||||
 | 
					    method: () => Promise<T>,
 | 
				
			||||||
 | 
					    getStatusCode: (T) => number | undefined,
 | 
				
			||||||
 | 
					    maxAttempts = 2
 | 
				
			||||||
 | 
					): Promise<T> {
 | 
				
			||||||
 | 
					    let response: T | undefined = undefined;
 | 
				
			||||||
 | 
					    let statusCode: number | undefined = undefined;
 | 
				
			||||||
 | 
					    let isRetryable = false;
 | 
				
			||||||
 | 
					    let errorMessage = "";
 | 
				
			||||||
 | 
					    let attempt = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while (attempt <= maxAttempts) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            response = await method();
 | 
				
			||||||
 | 
					            statusCode = getStatusCode(response);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!isServerErrorStatusCode(statusCode)) {
 | 
				
			||||||
 | 
					                return response;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            isRetryable = isRetryableStatusCode(statusCode);
 | 
				
			||||||
 | 
					            errorMessage = `Cache service responded with ${statusCode}`;
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					            isRetryable = true;
 | 
				
			||||||
 | 
					            errorMessage = error.message;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        core.debug(
 | 
				
			||||||
 | 
					            `${name} - Attempt ${attempt} of ${maxAttempts} failed with error: ${errorMessage}`
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!isRetryable) {
 | 
				
			||||||
 | 
					            core.debug(`${name} - Error is not retryable`);
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        attempt++;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    throw Error(`${name} failed: ${errorMessage}`);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function retryTypedResponse<T>(
 | 
				
			||||||
 | 
					    name: string,
 | 
				
			||||||
 | 
					    method: () => Promise<ITypedResponse<T>>,
 | 
				
			||||||
 | 
					    maxAttempts = 2
 | 
				
			||||||
 | 
					): Promise<ITypedResponse<T>> {
 | 
				
			||||||
 | 
					    return await retry(
 | 
				
			||||||
 | 
					        name,
 | 
				
			||||||
 | 
					        method,
 | 
				
			||||||
 | 
					        (response: ITypedResponse<T>) => response.statusCode,
 | 
				
			||||||
 | 
					        maxAttempts
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function retryHttpClientResponse<T>(
 | 
				
			||||||
 | 
					    name: string,
 | 
				
			||||||
 | 
					    method: () => Promise<IHttpClientResponse>,
 | 
				
			||||||
 | 
					    maxAttempts = 2
 | 
				
			||||||
 | 
					): Promise<IHttpClientResponse> {
 | 
				
			||||||
 | 
					    return await retry(
 | 
				
			||||||
 | 
					        name,
 | 
				
			||||||
 | 
					        method,
 | 
				
			||||||
 | 
					        (response: IHttpClientResponse) => response.message.statusCode,
 | 
				
			||||||
 | 
					        maxAttempts
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function getCacheEntry(
 | 
					export async function getCacheEntry(
 | 
				
			||||||
    keys: string[]
 | 
					    keys: string[]
 | 
				
			||||||
): Promise<ArtifactCacheEntry | null> {
 | 
					): Promise<ArtifactCacheEntry | null> {
 | 
				
			||||||
    const httpClient = createHttpClient();
 | 
					    const httpClient = createHttpClient();
 | 
				
			||||||
    const resource = `cache?keys=${encodeURIComponent(keys.join(","))}`;
 | 
					    const resource = `cache?keys=${encodeURIComponent(keys.join(","))}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const response = await httpClient.getJson<ArtifactCacheEntry>(
 | 
					    const response = await retryTypedResponse("getCacheEntry", () =>
 | 
				
			||||||
        getCacheApiUrl(resource)
 | 
					        httpClient.getJson<ArtifactCacheEntry>(getCacheApiUrl(resource))
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    if (response.statusCode === 204) {
 | 
					    if (response.statusCode === 204) {
 | 
				
			||||||
        return null;
 | 
					        return null;
 | 
				
			||||||
@@ -107,13 +187,10 @@ export async function getCacheEntry(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
async function pipeResponseToStream(
 | 
					async function pipeResponseToStream(
 | 
				
			||||||
    response: IHttpClientResponse,
 | 
					    response: IHttpClientResponse,
 | 
				
			||||||
    stream: NodeJS.WritableStream
 | 
					    output: NodeJS.WritableStream
 | 
				
			||||||
): Promise<void> {
 | 
					): Promise<void> {
 | 
				
			||||||
    return new Promise(resolve => {
 | 
					    const pipeline = util.promisify(stream.pipeline);
 | 
				
			||||||
        response.message.pipe(stream).on("close", () => {
 | 
					    await pipeline(response.message, output);
 | 
				
			||||||
            resolve();
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function downloadCache(
 | 
					export async function downloadCache(
 | 
				
			||||||
@@ -122,8 +199,37 @@ export async function downloadCache(
 | 
				
			|||||||
): Promise<void> {
 | 
					): Promise<void> {
 | 
				
			||||||
    const stream = fs.createWriteStream(archivePath);
 | 
					    const stream = fs.createWriteStream(archivePath);
 | 
				
			||||||
    const httpClient = new HttpClient("actions/cache");
 | 
					    const httpClient = new HttpClient("actions/cache");
 | 
				
			||||||
    const downloadResponse = await httpClient.get(archiveLocation);
 | 
					    const downloadResponse = await retryHttpClientResponse(
 | 
				
			||||||
 | 
					        "downloadCache",
 | 
				
			||||||
 | 
					        () => httpClient.get(archiveLocation)
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Abort download if no traffic received over the socket.
 | 
				
			||||||
 | 
					    downloadResponse.message.socket.setTimeout(SocketTimeout, () => {
 | 
				
			||||||
 | 
					        downloadResponse.message.destroy();
 | 
				
			||||||
 | 
					        core.debug(
 | 
				
			||||||
 | 
					            `Aborting download, socket timed out after ${SocketTimeout} ms`
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await pipeResponseToStream(downloadResponse, stream);
 | 
					    await pipeResponseToStream(downloadResponse, stream);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Validate download size.
 | 
				
			||||||
 | 
					    const contentLengthHeader =
 | 
				
			||||||
 | 
					        downloadResponse.message.headers["content-length"];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (contentLengthHeader) {
 | 
				
			||||||
 | 
					        const expectedLength = parseInt(contentLengthHeader);
 | 
				
			||||||
 | 
					        const actualLength = utils.getArchiveFileSize(archivePath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (actualLength != expectedLength) {
 | 
				
			||||||
 | 
					            throw new Error(
 | 
				
			||||||
 | 
					                `Incomplete download. Expected file size: ${expectedLength}, actual file size: ${actualLength}`
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        core.debug("Unable to validate download, no Content-Length header");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Reserve Cache
 | 
					// Reserve Cache
 | 
				
			||||||
@@ -133,9 +239,11 @@ export async function reserveCache(key: string): Promise<number> {
 | 
				
			|||||||
    const reserveCacheRequest: ReserveCacheRequest = {
 | 
					    const reserveCacheRequest: ReserveCacheRequest = {
 | 
				
			||||||
        key
 | 
					        key
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    const response = await httpClient.postJson<ReserveCacheResponse>(
 | 
					    const response = await retryTypedResponse("reserveCache", () =>
 | 
				
			||||||
        getCacheApiUrl("caches"),
 | 
					        httpClient.postJson<ReserveCacheResponse>(
 | 
				
			||||||
        reserveCacheRequest
 | 
					            getCacheApiUrl("caches"),
 | 
				
			||||||
 | 
					            reserveCacheRequest
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    return response?.result?.cacheId ?? -1;
 | 
					    return response?.result?.cacheId ?? -1;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -152,7 +260,7 @@ function getContentRange(start: number, end: number): string {
 | 
				
			|||||||
async function uploadChunk(
 | 
					async function uploadChunk(
 | 
				
			||||||
    httpClient: HttpClient,
 | 
					    httpClient: HttpClient,
 | 
				
			||||||
    resourceUrl: string,
 | 
					    resourceUrl: string,
 | 
				
			||||||
    data: NodeJS.ReadableStream,
 | 
					    openStream: () => NodeJS.ReadableStream,
 | 
				
			||||||
    start: number,
 | 
					    start: number,
 | 
				
			||||||
    end: number
 | 
					    end: number
 | 
				
			||||||
): Promise<void> {
 | 
					): Promise<void> {
 | 
				
			||||||
@@ -169,32 +277,15 @@ async function uploadChunk(
 | 
				
			|||||||
        "Content-Range": getContentRange(start, end)
 | 
					        "Content-Range": getContentRange(start, end)
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const uploadChunkRequest = async (): Promise<IHttpClientResponse> => {
 | 
					    await retryHttpClientResponse(
 | 
				
			||||||
        return await httpClient.sendStream(
 | 
					        `uploadChunk (start: ${start}, end: ${end})`,
 | 
				
			||||||
            "PATCH",
 | 
					        () =>
 | 
				
			||||||
            resourceUrl,
 | 
					            httpClient.sendStream(
 | 
				
			||||||
            data,
 | 
					                "PATCH",
 | 
				
			||||||
            additionalHeaders
 | 
					                resourceUrl,
 | 
				
			||||||
        );
 | 
					                openStream(),
 | 
				
			||||||
    };
 | 
					                additionalHeaders
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
    const response = await uploadChunkRequest();
 | 
					 | 
				
			||||||
    if (isSuccessStatusCode(response.message.statusCode)) {
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (isRetryableStatusCode(response.message.statusCode)) {
 | 
					 | 
				
			||||||
        core.debug(
 | 
					 | 
				
			||||||
            `Received ${response.message.statusCode}, retrying chunk at offset ${start}.`
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        const retryResponse = await uploadChunkRequest();
 | 
					 | 
				
			||||||
        if (isSuccessStatusCode(retryResponse.message.statusCode)) {
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    throw new Error(
 | 
					 | 
				
			||||||
        `Cache service responded with ${response.message.statusCode} during chunk upload.`
 | 
					 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -236,17 +327,23 @@ async function uploadFile(
 | 
				
			|||||||
                    const start = offset;
 | 
					                    const start = offset;
 | 
				
			||||||
                    const end = offset + chunkSize - 1;
 | 
					                    const end = offset + chunkSize - 1;
 | 
				
			||||||
                    offset += MAX_CHUNK_SIZE;
 | 
					                    offset += MAX_CHUNK_SIZE;
 | 
				
			||||||
                    const chunk = fs.createReadStream(archivePath, {
 | 
					 | 
				
			||||||
                        fd,
 | 
					 | 
				
			||||||
                        start,
 | 
					 | 
				
			||||||
                        end,
 | 
					 | 
				
			||||||
                        autoClose: false
 | 
					 | 
				
			||||||
                    });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    await uploadChunk(
 | 
					                    await uploadChunk(
 | 
				
			||||||
                        httpClient,
 | 
					                        httpClient,
 | 
				
			||||||
                        resourceUrl,
 | 
					                        resourceUrl,
 | 
				
			||||||
                        chunk,
 | 
					                        () =>
 | 
				
			||||||
 | 
					                            fs
 | 
				
			||||||
 | 
					                                .createReadStream(archivePath, {
 | 
				
			||||||
 | 
					                                    fd,
 | 
				
			||||||
 | 
					                                    start,
 | 
				
			||||||
 | 
					                                    end,
 | 
				
			||||||
 | 
					                                    autoClose: false
 | 
				
			||||||
 | 
					                                })
 | 
				
			||||||
 | 
					                                .on("error", error => {
 | 
				
			||||||
 | 
					                                    throw new Error(
 | 
				
			||||||
 | 
					                                        `Cache upload failed because file read failed with ${error.Message}`
 | 
				
			||||||
 | 
					                                    );
 | 
				
			||||||
 | 
					                                }),
 | 
				
			||||||
                        start,
 | 
					                        start,
 | 
				
			||||||
                        end
 | 
					                        end
 | 
				
			||||||
                    );
 | 
					                    );
 | 
				
			||||||
@@ -265,9 +362,11 @@ async function commitCache(
 | 
				
			|||||||
    filesize: number
 | 
					    filesize: number
 | 
				
			||||||
): Promise<ITypedResponse<null>> {
 | 
					): Promise<ITypedResponse<null>> {
 | 
				
			||||||
    const commitCacheRequest: CommitCacheRequest = { size: filesize };
 | 
					    const commitCacheRequest: CommitCacheRequest = { size: filesize };
 | 
				
			||||||
    return await httpClient.postJson<null>(
 | 
					    return await retryTypedResponse("commitCache", () =>
 | 
				
			||||||
        getCacheApiUrl(`caches/${cacheId.toString()}`),
 | 
					        httpClient.postJson<null>(
 | 
				
			||||||
        commitCacheRequest
 | 
					            getCacheApiUrl(`caches/${cacheId.toString()}`),
 | 
				
			||||||
 | 
					            commitCacheRequest
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,3 +18,8 @@ export enum Events {
 | 
				
			|||||||
    Push = "push",
 | 
					    Push = "push",
 | 
				
			||||||
    PullRequest = "pull_request"
 | 
					    PullRequest = "pull_request"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Socket timeout in milliseconds during download.  If no traffic is received
 | 
				
			||||||
 | 
					// over the socket during this period, the socket is destroyed and the download
 | 
				
			||||||
 | 
					// is aborted.
 | 
				
			||||||
 | 
					export const SocketTimeout = 5000;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										49
									
								
								src/tar.ts
									
									
									
									
									
								
							
							
						
						
									
										49
									
								
								src/tar.ts
									
									
									
									
									
								
							@@ -1,14 +1,36 @@
 | 
				
			|||||||
 | 
					import * as core from "@actions/core";
 | 
				
			||||||
import { exec } from "@actions/exec";
 | 
					import { exec } from "@actions/exec";
 | 
				
			||||||
import * as io from "@actions/io";
 | 
					import * as io from "@actions/io";
 | 
				
			||||||
import { existsSync } from "fs";
 | 
					import { existsSync } from "fs";
 | 
				
			||||||
 | 
					import * as path from "path";
 | 
				
			||||||
 | 
					import * as tar from "./tar";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function getTarPath(): Promise<string> {
 | 
					export async function isGnuTar(): Promise<boolean> {
 | 
				
			||||||
 | 
					    core.debug("Checking tar --version");
 | 
				
			||||||
 | 
					    let versionOutput = "";
 | 
				
			||||||
 | 
					    await exec("tar --version", [], {
 | 
				
			||||||
 | 
					        ignoreReturnCode: true,
 | 
				
			||||||
 | 
					        silent: true,
 | 
				
			||||||
 | 
					        listeners: {
 | 
				
			||||||
 | 
					            stdout: (data: Buffer): string =>
 | 
				
			||||||
 | 
					                (versionOutput += data.toString()),
 | 
				
			||||||
 | 
					            stderr: (data: Buffer): string => (versionOutput += data.toString())
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    core.debug(versionOutput.trim());
 | 
				
			||||||
 | 
					    return versionOutput.toUpperCase().includes("GNU TAR");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function getTarPath(args: string[]): Promise<string> {
 | 
				
			||||||
    // Explicitly use BSD Tar on Windows
 | 
					    // Explicitly use BSD Tar on Windows
 | 
				
			||||||
    const IS_WINDOWS = process.platform === "win32";
 | 
					    const IS_WINDOWS = process.platform === "win32";
 | 
				
			||||||
    if (IS_WINDOWS) {
 | 
					    if (IS_WINDOWS) {
 | 
				
			||||||
        const systemTar = `${process.env["windir"]}\\System32\\tar.exe`;
 | 
					        const systemTar = `${process.env["windir"]}\\System32\\tar.exe`;
 | 
				
			||||||
        if (existsSync(systemTar)) {
 | 
					        if (existsSync(systemTar)) {
 | 
				
			||||||
            return systemTar;
 | 
					            return systemTar;
 | 
				
			||||||
 | 
					        } else if (await tar.isGnuTar()) {
 | 
				
			||||||
 | 
					            args.push("--force-local");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return await io.which("tar", true);
 | 
					    return await io.which("tar", true);
 | 
				
			||||||
@@ -16,14 +38,8 @@ async function getTarPath(): Promise<string> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
async function execTar(args: string[]): Promise<void> {
 | 
					async function execTar(args: string[]): Promise<void> {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
        await exec(`"${await getTarPath()}"`, args);
 | 
					        await exec(`"${await getTarPath(args)}"`, args);
 | 
				
			||||||
    } catch (error) {
 | 
					    } catch (error) {
 | 
				
			||||||
        const IS_WINDOWS = process.platform === "win32";
 | 
					 | 
				
			||||||
        if (IS_WINDOWS) {
 | 
					 | 
				
			||||||
            throw new Error(
 | 
					 | 
				
			||||||
                `Tar failed with error: ${error?.message}. Ensure BSD tar is installed and on the PATH.`
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        throw new Error(`Tar failed with error: ${error?.message}`);
 | 
					        throw new Error(`Tar failed with error: ${error?.message}`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -34,7 +50,13 @@ export async function extractTar(
 | 
				
			|||||||
): Promise<void> {
 | 
					): Promise<void> {
 | 
				
			||||||
    // Create directory to extract tar into
 | 
					    // Create directory to extract tar into
 | 
				
			||||||
    await io.mkdirP(targetDirectory);
 | 
					    await io.mkdirP(targetDirectory);
 | 
				
			||||||
    const args = ["-xz", "-f", archivePath, "-C", targetDirectory];
 | 
					    const args = [
 | 
				
			||||||
 | 
					        "-xz",
 | 
				
			||||||
 | 
					        "-f",
 | 
				
			||||||
 | 
					        archivePath.replace(new RegExp("\\" + path.sep, "g"), "/"),
 | 
				
			||||||
 | 
					        "-C",
 | 
				
			||||||
 | 
					        targetDirectory.replace(new RegExp("\\" + path.sep, "g"), "/")
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
    await execTar(args);
 | 
					    await execTar(args);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -42,6 +64,13 @@ export async function createTar(
 | 
				
			|||||||
    archivePath: string,
 | 
					    archivePath: string,
 | 
				
			||||||
    sourceDirectory: string
 | 
					    sourceDirectory: string
 | 
				
			||||||
): Promise<void> {
 | 
					): Promise<void> {
 | 
				
			||||||
    const args = ["-cz", "-f", archivePath, "-C", sourceDirectory, "."];
 | 
					    const args = [
 | 
				
			||||||
 | 
					        "-cz",
 | 
				
			||||||
 | 
					        "-f",
 | 
				
			||||||
 | 
					        archivePath.replace(new RegExp("\\" + path.sep, "g"), "/"),
 | 
				
			||||||
 | 
					        "-C",
 | 
				
			||||||
 | 
					        sourceDirectory.replace(new RegExp("\\" + path.sep, "g"), "/"),
 | 
				
			||||||
 | 
					        "."
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
    await execTar(args);
 | 
					    await execTar(args);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user