mirror of
				https://github.com/actions/cache.git
				synced 2025-10-31 18:34:19 +08:00 
			
		
		
		
	Use zstd instead of gzip if available
Add zstd to cache versioning
This commit is contained in:
		
							parent
							
								
									9ceee97d99
								
							
						
					
					
						commit
						97f7baa910
					
				| @ -31,4 +31,4 @@ Here are a few things you can do that will increase the likelihood of your pull | ||||
| 
 | ||||
| - [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) | ||||
| - [Using Pull Requests](https://help.github.com/articles/about-pull-requests/) | ||||
| - [GitHub Help](https://help.github.com) | ||||
| - [GitHub Help](https://help.github.com) | ||||
|  | ||||
| @ -1,12 +1,12 @@ | ||||
| import { getCacheVersion } from "../src/cacheHttpClient"; | ||||
| import { Inputs } from "../src/constants"; | ||||
| import { CompressionMethod, Inputs } from "../src/constants"; | ||||
| import * as testUtils from "../src/utils/testUtils"; | ||||
| 
 | ||||
| afterEach(() => { | ||||
|     testUtils.clearInputs(); | ||||
| }); | ||||
| 
 | ||||
| test("getCacheVersion with path input returns version", async () => { | ||||
| test("getCacheVersion with path input and compression method undefined returns version", async () => { | ||||
|     testUtils.setInput(Inputs.Path, "node_modules"); | ||||
| 
 | ||||
|     const result = getCacheVersion(); | ||||
| @ -16,6 +16,24 @@ test("getCacheVersion with path input returns version", async () => { | ||||
|     ); | ||||
| }); | ||||
| 
 | ||||
| test("getCacheVersion with zstd compression returns version", async () => { | ||||
|     testUtils.setInput(Inputs.Path, "node_modules"); | ||||
|     const result = getCacheVersion(CompressionMethod.Zstd); | ||||
| 
 | ||||
|     expect(result).toEqual( | ||||
|         "273877e14fd65d270b87a198edbfa2db5a43de567c9a548d2a2505b408befe24" | ||||
|     ); | ||||
| }); | ||||
| 
 | ||||
| test("getCacheVersion with gzip compression does not change vesion", async () => { | ||||
|     testUtils.setInput(Inputs.Path, "node_modules"); | ||||
|     const result = getCacheVersion(CompressionMethod.Gzip); | ||||
| 
 | ||||
|     expect(result).toEqual( | ||||
|         "b3e0c6cb5ecf32614eeb2997d905b9c297046d7cbf69062698f25b14b4cb0985" | ||||
|     ); | ||||
| }); | ||||
| 
 | ||||
| test("getCacheVersion with no input throws", async () => { | ||||
|     expect(() => getCacheVersion()).toThrow(); | ||||
| }); | ||||
|  | ||||
| @ -2,7 +2,12 @@ import * as core from "@actions/core"; | ||||
| import * as path from "path"; | ||||
| 
 | ||||
| import * as cacheHttpClient from "../src/cacheHttpClient"; | ||||
| import { Events, Inputs } from "../src/constants"; | ||||
| import { | ||||
|     CacheFilename, | ||||
|     CompressionMethod, | ||||
|     Events, | ||||
|     Inputs | ||||
| } from "../src/constants"; | ||||
| import { ArtifactCacheEntry } from "../src/contracts"; | ||||
| import run from "../src/restore"; | ||||
| import * as tar from "../src/tar"; | ||||
| @ -30,6 +35,11 @@ beforeAll(() => { | ||||
|         const actualUtils = jest.requireActual("../src/utils/actionUtils"); | ||||
|         return actualUtils.getSupportedEvents(); | ||||
|     }); | ||||
| 
 | ||||
|     jest.spyOn(actionUtils, "getCacheFileName").mockImplementation(cm => { | ||||
|         const actualUtils = jest.requireActual("../src/utils/actionUtils"); | ||||
|         return actualUtils.getCacheFileName(cm); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| beforeEach(() => { | ||||
| @ -197,7 +207,7 @@ test("restore with restore keys and no cache found", async () => { | ||||
|     ); | ||||
| }); | ||||
| 
 | ||||
| test("restore with cache found", async () => { | ||||
| test("restore with gzip compressed cache found", async () => { | ||||
|     const key = "node-test"; | ||||
|     testUtils.setInputs({ | ||||
|         path: "node_modules", | ||||
| @ -227,7 +237,7 @@ test("restore with cache found", async () => { | ||||
|         return Promise.resolve(tempPath); | ||||
|     }); | ||||
| 
 | ||||
|     const archivePath = path.join(tempPath, "cache.tgz"); | ||||
|     const archivePath = path.join(tempPath, CacheFilename.Gzip); | ||||
|     const setCacheStateMock = jest.spyOn(actionUtils, "setCacheState"); | ||||
|     const downloadCacheMock = jest.spyOn(cacheHttpClient, "downloadCache"); | ||||
| 
 | ||||
| @ -240,10 +250,17 @@ test("restore with cache found", async () => { | ||||
|     const unlinkFileMock = jest.spyOn(actionUtils, "unlinkFile"); | ||||
|     const setCacheHitOutputMock = jest.spyOn(actionUtils, "setCacheHitOutput"); | ||||
| 
 | ||||
|     const compression = CompressionMethod.Gzip; | ||||
|     const getCompressionMock = jest | ||||
|         .spyOn(actionUtils, "getCompressionMethod") | ||||
|         .mockReturnValue(Promise.resolve(compression)); | ||||
| 
 | ||||
|     await run(); | ||||
| 
 | ||||
|     expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); | ||||
|     expect(getCacheMock).toHaveBeenCalledWith([key]); | ||||
|     expect(getCacheMock).toHaveBeenCalledWith([key], { | ||||
|         compressionMethod: compression | ||||
|     }); | ||||
|     expect(setCacheStateMock).toHaveBeenCalledWith(cacheEntry); | ||||
|     expect(createTempDirectoryMock).toHaveBeenCalledTimes(1); | ||||
|     expect(downloadCacheMock).toHaveBeenCalledWith( | ||||
| @ -253,7 +270,7 @@ test("restore with cache found", async () => { | ||||
|     expect(getArchiveFileSizeMock).toHaveBeenCalledWith(archivePath); | ||||
| 
 | ||||
|     expect(extractTarMock).toHaveBeenCalledTimes(1); | ||||
|     expect(extractTarMock).toHaveBeenCalledWith(archivePath); | ||||
|     expect(extractTarMock).toHaveBeenCalledWith(archivePath, compression); | ||||
| 
 | ||||
|     expect(unlinkFileMock).toHaveBeenCalledTimes(1); | ||||
|     expect(unlinkFileMock).toHaveBeenCalledWith(archivePath); | ||||
| @ -263,9 +280,10 @@ test("restore with cache found", async () => { | ||||
| 
 | ||||
|     expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`); | ||||
|     expect(failedMock).toHaveBeenCalledTimes(0); | ||||
|     expect(getCompressionMock).toHaveBeenCalledTimes(1); | ||||
| }); | ||||
| 
 | ||||
| test("restore with a pull request event and cache found", async () => { | ||||
| test("restore with a pull request event and zstd compressed cache found", async () => { | ||||
|     const key = "node-test"; | ||||
|     testUtils.setInputs({ | ||||
|         path: "node_modules", | ||||
| @ -297,7 +315,7 @@ test("restore with a pull request event and cache found", async () => { | ||||
|         return Promise.resolve(tempPath); | ||||
|     }); | ||||
| 
 | ||||
|     const archivePath = path.join(tempPath, "cache.tgz"); | ||||
|     const archivePath = path.join(tempPath, CacheFilename.Zstd); | ||||
|     const setCacheStateMock = jest.spyOn(actionUtils, "setCacheState"); | ||||
|     const downloadCacheMock = jest.spyOn(cacheHttpClient, "downloadCache"); | ||||
| 
 | ||||
| @ -308,11 +326,17 @@ test("restore with a pull request event and cache found", async () => { | ||||
| 
 | ||||
|     const extractTarMock = jest.spyOn(tar, "extractTar"); | ||||
|     const setCacheHitOutputMock = jest.spyOn(actionUtils, "setCacheHitOutput"); | ||||
|     const compression = CompressionMethod.Zstd; | ||||
|     const getCompressionMock = jest | ||||
|         .spyOn(actionUtils, "getCompressionMethod") | ||||
|         .mockReturnValue(Promise.resolve(compression)); | ||||
| 
 | ||||
|     await run(); | ||||
| 
 | ||||
|     expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); | ||||
|     expect(getCacheMock).toHaveBeenCalledWith([key]); | ||||
|     expect(getCacheMock).toHaveBeenCalledWith([key], { | ||||
|         compressionMethod: compression | ||||
|     }); | ||||
|     expect(setCacheStateMock).toHaveBeenCalledWith(cacheEntry); | ||||
|     expect(createTempDirectoryMock).toHaveBeenCalledTimes(1); | ||||
|     expect(downloadCacheMock).toHaveBeenCalledWith( | ||||
| @ -323,13 +347,14 @@ test("restore with a pull request event and cache found", async () => { | ||||
|     expect(infoMock).toHaveBeenCalledWith(`Cache Size: ~60 MB (62915000 B)`); | ||||
| 
 | ||||
|     expect(extractTarMock).toHaveBeenCalledTimes(1); | ||||
|     expect(extractTarMock).toHaveBeenCalledWith(archivePath); | ||||
|     expect(extractTarMock).toHaveBeenCalledWith(archivePath, compression); | ||||
| 
 | ||||
|     expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1); | ||||
|     expect(setCacheHitOutputMock).toHaveBeenCalledWith(true); | ||||
| 
 | ||||
|     expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`); | ||||
|     expect(failedMock).toHaveBeenCalledTimes(0); | ||||
|     expect(getCompressionMock).toHaveBeenCalledTimes(1); | ||||
| }); | ||||
| 
 | ||||
| test("restore with cache found for restore key", async () => { | ||||
| @ -364,7 +389,7 @@ test("restore with cache found for restore key", async () => { | ||||
|         return Promise.resolve(tempPath); | ||||
|     }); | ||||
| 
 | ||||
|     const archivePath = path.join(tempPath, "cache.tgz"); | ||||
|     const archivePath = path.join(tempPath, CacheFilename.Zstd); | ||||
|     const setCacheStateMock = jest.spyOn(actionUtils, "setCacheState"); | ||||
|     const downloadCacheMock = jest.spyOn(cacheHttpClient, "downloadCache"); | ||||
| 
 | ||||
| @ -375,11 +400,17 @@ test("restore with cache found for restore key", async () => { | ||||
| 
 | ||||
|     const extractTarMock = jest.spyOn(tar, "extractTar"); | ||||
|     const setCacheHitOutputMock = jest.spyOn(actionUtils, "setCacheHitOutput"); | ||||
|     const compression = CompressionMethod.Zstd; | ||||
|     const getCompressionMock = jest | ||||
|         .spyOn(actionUtils, "getCompressionMethod") | ||||
|         .mockReturnValue(Promise.resolve(compression)); | ||||
| 
 | ||||
|     await run(); | ||||
| 
 | ||||
|     expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); | ||||
|     expect(getCacheMock).toHaveBeenCalledWith([key, restoreKey]); | ||||
|     expect(getCacheMock).toHaveBeenCalledWith([key, restoreKey], { | ||||
|         compressionMethod: compression | ||||
|     }); | ||||
|     expect(setCacheStateMock).toHaveBeenCalledWith(cacheEntry); | ||||
|     expect(createTempDirectoryMock).toHaveBeenCalledTimes(1); | ||||
|     expect(downloadCacheMock).toHaveBeenCalledWith( | ||||
| @ -390,7 +421,7 @@ test("restore with cache found for restore key", async () => { | ||||
|     expect(infoMock).toHaveBeenCalledWith(`Cache Size: ~0 MB (142 B)`); | ||||
| 
 | ||||
|     expect(extractTarMock).toHaveBeenCalledTimes(1); | ||||
|     expect(extractTarMock).toHaveBeenCalledWith(archivePath); | ||||
|     expect(extractTarMock).toHaveBeenCalledWith(archivePath, compression); | ||||
| 
 | ||||
|     expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1); | ||||
|     expect(setCacheHitOutputMock).toHaveBeenCalledWith(false); | ||||
| @ -399,4 +430,5 @@ test("restore with cache found for restore key", async () => { | ||||
|         `Cache restored from key: ${restoreKey}` | ||||
|     ); | ||||
|     expect(failedMock).toHaveBeenCalledTimes(0); | ||||
|     expect(getCompressionMock).toHaveBeenCalledTimes(1); | ||||
| }); | ||||
|  | ||||
| @ -2,7 +2,12 @@ import * as core from "@actions/core"; | ||||
| import * as path from "path"; | ||||
| 
 | ||||
| import * as cacheHttpClient from "../src/cacheHttpClient"; | ||||
| import { CacheFilename, Events, Inputs } from "../src/constants"; | ||||
| import { | ||||
|     CacheFilename, | ||||
|     CompressionMethod, | ||||
|     Events, | ||||
|     Inputs | ||||
| } from "../src/constants"; | ||||
| import { ArtifactCacheEntry } from "../src/contracts"; | ||||
| import run from "../src/save"; | ||||
| import * as tar from "../src/tar"; | ||||
| @ -50,6 +55,11 @@ beforeAll(() => { | ||||
|     jest.spyOn(actionUtils, "createTempDirectory").mockImplementation(() => { | ||||
|         return Promise.resolve("/foo/bar"); | ||||
|     }); | ||||
| 
 | ||||
|     jest.spyOn(actionUtils, "getCacheFileName").mockImplementation(cm => { | ||||
|         const actualUtils = jest.requireActual("../src/utils/actionUtils"); | ||||
|         return actualUtils.getCacheFileName(cm); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| beforeEach(() => { | ||||
| @ -201,20 +211,27 @@ test("save with large cache outputs warning", async () => { | ||||
|     jest.spyOn(actionUtils, "getArchiveFileSize").mockImplementationOnce(() => { | ||||
|         return cacheSize; | ||||
|     }); | ||||
|     const compression = CompressionMethod.Gzip; | ||||
|     const getCompressionMock = jest | ||||
|         .spyOn(actionUtils, "getCompressionMethod") | ||||
|         .mockReturnValue(Promise.resolve(compression)); | ||||
| 
 | ||||
|     await run(); | ||||
| 
 | ||||
|     const archiveFolder = "/foo/bar"; | ||||
| 
 | ||||
|     expect(createTarMock).toHaveBeenCalledTimes(1); | ||||
|     expect(createTarMock).toHaveBeenCalledWith(archiveFolder, cachePaths); | ||||
| 
 | ||||
|     expect(createTarMock).toHaveBeenCalledWith( | ||||
|         archiveFolder, | ||||
|         cachePaths, | ||||
|         compression | ||||
|     ); | ||||
|     expect(logWarningMock).toHaveBeenCalledTimes(1); | ||||
|     expect(logWarningMock).toHaveBeenCalledWith( | ||||
|         "Cache size of ~6144 MB (6442450944 B) is over the 5GB limit, not saving cache." | ||||
|     ); | ||||
| 
 | ||||
|     expect(failedMock).toHaveBeenCalledTimes(0); | ||||
|     expect(getCompressionMock).toHaveBeenCalledTimes(1); | ||||
| }); | ||||
| 
 | ||||
| test("save with reserve cache failure outputs warning", async () => { | ||||
| @ -250,13 +267,18 @@ test("save with reserve cache failure outputs warning", async () => { | ||||
|         }); | ||||
| 
 | ||||
|     const createTarMock = jest.spyOn(tar, "createTar"); | ||||
| 
 | ||||
|     const saveCacheMock = jest.spyOn(cacheHttpClient, "saveCache"); | ||||
|     const compression = CompressionMethod.Zstd; | ||||
|     const getCompressionMock = jest | ||||
|         .spyOn(actionUtils, "getCompressionMethod") | ||||
|         .mockReturnValue(Promise.resolve(compression)); | ||||
| 
 | ||||
|     await run(); | ||||
| 
 | ||||
|     expect(reserveCacheMock).toHaveBeenCalledTimes(1); | ||||
|     expect(reserveCacheMock).toHaveBeenCalledWith(primaryKey); | ||||
|     expect(reserveCacheMock).toHaveBeenCalledWith(primaryKey, { | ||||
|         compressionMethod: compression | ||||
|     }); | ||||
| 
 | ||||
|     expect(infoMock).toHaveBeenCalledWith( | ||||
|         `Unable to reserve cache with key ${primaryKey}, another job may be creating this cache.` | ||||
| @ -266,6 +288,7 @@ test("save with reserve cache failure outputs warning", async () => { | ||||
|     expect(saveCacheMock).toHaveBeenCalledTimes(0); | ||||
|     expect(logWarningMock).toHaveBeenCalledTimes(0); | ||||
|     expect(failedMock).toHaveBeenCalledTimes(0); | ||||
|     expect(getCompressionMock).toHaveBeenCalledTimes(1); | ||||
| }); | ||||
| 
 | ||||
| test("save with server error outputs warning", async () => { | ||||
| @ -308,17 +331,27 @@ test("save with server error outputs warning", async () => { | ||||
|         .mockImplementationOnce(() => { | ||||
|             throw new Error("HTTP Error Occurred"); | ||||
|         }); | ||||
|     const compression = CompressionMethod.Zstd; | ||||
|     const getCompressionMock = jest | ||||
|         .spyOn(actionUtils, "getCompressionMethod") | ||||
|         .mockReturnValue(Promise.resolve(compression)); | ||||
| 
 | ||||
|     await run(); | ||||
| 
 | ||||
|     expect(reserveCacheMock).toHaveBeenCalledTimes(1); | ||||
|     expect(reserveCacheMock).toHaveBeenCalledWith(primaryKey); | ||||
|     expect(reserveCacheMock).toHaveBeenCalledWith(primaryKey, { | ||||
|         compressionMethod: compression | ||||
|     }); | ||||
| 
 | ||||
|     const archiveFolder = "/foo/bar"; | ||||
|     const archiveFile = path.join(archiveFolder, CacheFilename); | ||||
|     const archiveFile = path.join(archiveFolder, CacheFilename.Zstd); | ||||
| 
 | ||||
|     expect(createTarMock).toHaveBeenCalledTimes(1); | ||||
|     expect(createTarMock).toHaveBeenCalledWith(archiveFolder, cachePaths); | ||||
|     expect(createTarMock).toHaveBeenCalledWith( | ||||
|         archiveFolder, | ||||
|         cachePaths, | ||||
|         compression | ||||
|     ); | ||||
| 
 | ||||
|     expect(saveCacheMock).toHaveBeenCalledTimes(1); | ||||
|     expect(saveCacheMock).toHaveBeenCalledWith(cacheId, archiveFile); | ||||
| @ -327,6 +360,7 @@ test("save with server error outputs warning", async () => { | ||||
|     expect(logWarningMock).toHaveBeenCalledWith("HTTP Error Occurred"); | ||||
| 
 | ||||
|     expect(failedMock).toHaveBeenCalledTimes(0); | ||||
|     expect(getCompressionMock).toHaveBeenCalledTimes(1); | ||||
| }); | ||||
| 
 | ||||
| test("save with valid inputs uploads a cache", async () => { | ||||
| @ -364,20 +398,31 @@ test("save with valid inputs uploads a cache", async () => { | ||||
|     const createTarMock = jest.spyOn(tar, "createTar"); | ||||
| 
 | ||||
|     const saveCacheMock = jest.spyOn(cacheHttpClient, "saveCache"); | ||||
|     const compression = CompressionMethod.Zstd; | ||||
|     const getCompressionMock = jest | ||||
|         .spyOn(actionUtils, "getCompressionMethod") | ||||
|         .mockReturnValue(Promise.resolve(compression)); | ||||
| 
 | ||||
|     await run(); | ||||
| 
 | ||||
|     expect(reserveCacheMock).toHaveBeenCalledTimes(1); | ||||
|     expect(reserveCacheMock).toHaveBeenCalledWith(primaryKey); | ||||
|     expect(reserveCacheMock).toHaveBeenCalledWith(primaryKey, { | ||||
|         compressionMethod: compression | ||||
|     }); | ||||
| 
 | ||||
|     const archiveFolder = "/foo/bar"; | ||||
|     const archiveFile = path.join(archiveFolder, CacheFilename); | ||||
|     const archiveFile = path.join(archiveFolder, CacheFilename.Zstd); | ||||
| 
 | ||||
|     expect(createTarMock).toHaveBeenCalledTimes(1); | ||||
|     expect(createTarMock).toHaveBeenCalledWith(archiveFolder, cachePaths); | ||||
|     expect(createTarMock).toHaveBeenCalledWith( | ||||
|         archiveFolder, | ||||
|         cachePaths, | ||||
|         compression | ||||
|     ); | ||||
| 
 | ||||
|     expect(saveCacheMock).toHaveBeenCalledTimes(1); | ||||
|     expect(saveCacheMock).toHaveBeenCalledWith(cacheId, archiveFile); | ||||
| 
 | ||||
|     expect(failedMock).toHaveBeenCalledTimes(0); | ||||
|     expect(getCompressionMock).toHaveBeenCalledTimes(1); | ||||
| }); | ||||
|  | ||||
| @ -2,14 +2,17 @@ import * as exec from "@actions/exec"; | ||||
| import * as io from "@actions/io"; | ||||
| import * as path from "path"; | ||||
| 
 | ||||
| import { CacheFilename } from "../src/constants"; | ||||
| import { CacheFilename, CompressionMethod } from "../src/constants"; | ||||
| import * as tar from "../src/tar"; | ||||
| import * as utils from "../src/utils/actionUtils"; | ||||
| 
 | ||||
| import fs = require("fs"); | ||||
| 
 | ||||
| jest.mock("@actions/exec"); | ||||
| jest.mock("@actions/io"); | ||||
| 
 | ||||
| const IS_WINDOWS = process.platform === "win32"; | ||||
| 
 | ||||
| function getTempDir(): string { | ||||
|     return path.join(__dirname, "_temp", "tar"); | ||||
| } | ||||
| @ -28,29 +31,28 @@ afterAll(async () => { | ||||
|     await jest.requireActual("@actions/io").rmRF(getTempDir()); | ||||
| }); | ||||
| 
 | ||||
| test("extract BSD tar", async () => { | ||||
| test("zstd extract tar", async () => { | ||||
|     const mkdirMock = jest.spyOn(io, "mkdirP"); | ||||
|     const execMock = jest.spyOn(exec, "exec"); | ||||
| 
 | ||||
|     const IS_WINDOWS = process.platform === "win32"; | ||||
|     const archivePath = IS_WINDOWS | ||||
|         ? `${process.env["windir"]}\\fakepath\\cache.tar` | ||||
|         : "cache.tar"; | ||||
|     const workspace = process.env["GITHUB_WORKSPACE"]; | ||||
| 
 | ||||
|     await tar.extractTar(archivePath); | ||||
|     await tar.extractTar(archivePath, CompressionMethod.Zstd); | ||||
| 
 | ||||
|     expect(mkdirMock).toHaveBeenCalledWith(workspace); | ||||
| 
 | ||||
|     const tarPath = IS_WINDOWS | ||||
|         ? `${process.env["windir"]}\\System32\\tar.exe` | ||||
|         : "tar"; | ||||
|     expect(execMock).toHaveBeenCalledTimes(1); | ||||
|     expect(execMock).toHaveBeenCalledWith( | ||||
|         `"${tarPath}"`, | ||||
|         `${tarPath}`, | ||||
|         [ | ||||
|             "-xz", | ||||
|             "-f", | ||||
|             "--use-compress-program", | ||||
|             "zstd -d", | ||||
|             "-xf", | ||||
|             IS_WINDOWS ? archivePath.replace(/\\/g, "/") : archivePath, | ||||
|             "-P", | ||||
|             "-C", | ||||
| @ -60,24 +62,55 @@ test("extract BSD tar", async () => { | ||||
|     ); | ||||
| }); | ||||
| 
 | ||||
| test("extract GNU tar", async () => { | ||||
|     const IS_WINDOWS = process.platform === "win32"; | ||||
| test("gzip extract tar", async () => { | ||||
|     const mkdirMock = jest.spyOn(io, "mkdirP"); | ||||
|     const execMock = jest.spyOn(exec, "exec"); | ||||
|     const archivePath = IS_WINDOWS | ||||
|         ? `${process.env["windir"]}\\fakepath\\cache.tar` | ||||
|         : "cache.tar"; | ||||
|     const workspace = process.env["GITHUB_WORKSPACE"]; | ||||
| 
 | ||||
|     await tar.extractTar(archivePath, CompressionMethod.Gzip); | ||||
| 
 | ||||
|     expect(mkdirMock).toHaveBeenCalledWith(workspace); | ||||
|     const tarPath = IS_WINDOWS | ||||
|         ? `${process.env["windir"]}\\System32\\tar.exe` | ||||
|         : "tar"; | ||||
|     expect(execMock).toHaveBeenCalledTimes(1); | ||||
|     expect(execMock).toHaveBeenCalledWith( | ||||
|         `${tarPath}`, | ||||
|         [ | ||||
|             "-z", | ||||
|             "-xf", | ||||
|             IS_WINDOWS ? archivePath.replace(/\\/g, "/") : archivePath, | ||||
|             "-P", | ||||
|             "-C", | ||||
|             IS_WINDOWS ? workspace?.replace(/\\/g, "/") : workspace | ||||
|         ], | ||||
|         { cwd: undefined } | ||||
|     ); | ||||
| }); | ||||
| 
 | ||||
| test("gzip extract GNU tar on windows", async () => { | ||||
|     if (IS_WINDOWS) { | ||||
|         jest.spyOn(fs, "existsSync").mockReturnValueOnce(false); | ||||
|         jest.spyOn(tar, "isGnuTar").mockReturnValue(Promise.resolve(true)); | ||||
| 
 | ||||
|         const isGnuMock = jest | ||||
|             .spyOn(utils, "useGnuTar") | ||||
|             .mockReturnValue(Promise.resolve(true)); | ||||
|         const execMock = jest.spyOn(exec, "exec"); | ||||
|         const archivePath = `${process.env["windir"]}\\fakepath\\cache.tar`; | ||||
|         const workspace = process.env["GITHUB_WORKSPACE"]; | ||||
| 
 | ||||
|         await tar.extractTar(archivePath); | ||||
|         await tar.extractTar(archivePath, CompressionMethod.Gzip); | ||||
| 
 | ||||
|         expect(execMock).toHaveBeenCalledTimes(2); | ||||
|         expect(execMock).toHaveBeenLastCalledWith( | ||||
|             `"tar"`, | ||||
|         expect(isGnuMock).toHaveBeenCalledTimes(1); | ||||
|         expect(execMock).toHaveBeenCalledTimes(1); | ||||
|         expect(execMock).toHaveBeenCalledWith( | ||||
|             `tar`, | ||||
|             [ | ||||
|                 "-xz", | ||||
|                 "-f", | ||||
|                 "-z", | ||||
|                 "-xf", | ||||
|                 archivePath.replace(/\\/g, "/"), | ||||
|                 "-P", | ||||
|                 "-C", | ||||
| @ -89,7 +122,7 @@ test("extract GNU tar", async () => { | ||||
|     } | ||||
| }); | ||||
| 
 | ||||
| test("create BSD tar", async () => { | ||||
| test("zstd create tar", async () => { | ||||
|     const execMock = jest.spyOn(exec, "exec"); | ||||
| 
 | ||||
|     const archiveFolder = getTempDir(); | ||||
| @ -98,20 +131,66 @@ test("create BSD tar", async () => { | ||||
| 
 | ||||
|     await fs.promises.mkdir(archiveFolder, { recursive: true }); | ||||
| 
 | ||||
|     await tar.createTar(archiveFolder, sourceDirectories); | ||||
|     await tar.createTar( | ||||
|         archiveFolder, | ||||
|         sourceDirectories, | ||||
|         CompressionMethod.Zstd | ||||
|     ); | ||||
| 
 | ||||
|     const IS_WINDOWS = process.platform === "win32"; | ||||
|     const tarPath = IS_WINDOWS | ||||
|         ? `${process.env["windir"]}\\System32\\tar.exe` | ||||
|         : "tar"; | ||||
| 
 | ||||
|     expect(execMock).toHaveBeenCalledTimes(1); | ||||
|     expect(execMock).toHaveBeenCalledWith( | ||||
|         `"${tarPath}"`, | ||||
|         `${tarPath}`, | ||||
|         [ | ||||
|             "-cz", | ||||
|             "-f", | ||||
|             IS_WINDOWS ? CacheFilename.replace(/\\/g, "/") : CacheFilename, | ||||
|             "--use-compress-program", | ||||
|             "zstd -T0", | ||||
|             "-cf", | ||||
|             IS_WINDOWS | ||||
|                 ? CacheFilename.Zstd.replace(/\\/g, "/") | ||||
|                 : CacheFilename.Zstd, | ||||
|             "-P", | ||||
|             "-C", | ||||
|             IS_WINDOWS ? workspace?.replace(/\\/g, "/") : workspace, | ||||
|             "--files-from", | ||||
|             "manifest.txt" | ||||
|         ], | ||||
|         { | ||||
|             cwd: archiveFolder | ||||
|         } | ||||
|     ); | ||||
| }); | ||||
| 
 | ||||
| test("gzip create tar", async () => { | ||||
|     const execMock = jest.spyOn(exec, "exec"); | ||||
| 
 | ||||
|     const archiveFolder = getTempDir(); | ||||
|     const workspace = process.env["GITHUB_WORKSPACE"]; | ||||
|     const sourceDirectories = ["~/.npm/cache", `${workspace}/dist`]; | ||||
| 
 | ||||
|     await fs.promises.mkdir(archiveFolder, { recursive: true }); | ||||
| 
 | ||||
|     await tar.createTar( | ||||
|         archiveFolder, | ||||
|         sourceDirectories, | ||||
|         CompressionMethod.Gzip | ||||
|     ); | ||||
| 
 | ||||
|     const tarPath = IS_WINDOWS | ||||
|         ? `${process.env["windir"]}\\System32\\tar.exe` | ||||
|         : "tar"; | ||||
| 
 | ||||
|     expect(execMock).toHaveBeenCalledTimes(1); | ||||
|     expect(execMock).toHaveBeenCalledWith( | ||||
|         `${tarPath}`, | ||||
|         [ | ||||
|             "-z", | ||||
|             "-cf", | ||||
|             IS_WINDOWS | ||||
|                 ? CacheFilename.Gzip.replace(/\\/g, "/") | ||||
|                 : CacheFilename.Gzip, | ||||
|             "-P", | ||||
|             "-C", | ||||
|             IS_WINDOWS ? workspace?.replace(/\\/g, "/") : workspace, | ||||
|  | ||||
							
								
								
									
										140
									
								
								dist/restore/index.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										140
									
								
								dist/restore/index.js
									
									
									
									
										vendored
									
									
								
							| @ -2236,23 +2236,22 @@ function createHttpClient() { | ||||
|     const bearerCredentialHandler = new auth_1.BearerCredentialHandler(token); | ||||
|     return new http_client_1.HttpClient("actions/cache", [bearerCredentialHandler], getRequestOptions()); | ||||
| } | ||||
| function getCacheVersion() { | ||||
| function getCacheVersion(compressionMethod) { | ||||
|     // Add salt to cache version to support breaking changes in cache entry
 | ||||
|     const components = [ | ||||
|         core.getInput(constants_1.Inputs.Path, { required: true }), | ||||
|         versionSalt | ||||
|     ]; | ||||
|     const components = [core.getInput(constants_1.Inputs.Path, { required: true })].concat(compressionMethod == constants_1.CompressionMethod.Zstd | ||||
|         ? [compressionMethod, versionSalt] | ||||
|         : versionSalt); | ||||
|     return crypto | ||||
|         .createHash("sha256") | ||||
|         .update(components.join("|")) | ||||
|         .digest("hex"); | ||||
| } | ||||
| exports.getCacheVersion = getCacheVersion; | ||||
| function getCacheEntry(keys) { | ||||
|     var _a; | ||||
| function getCacheEntry(keys, options) { | ||||
|     var _a, _b; | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         const httpClient = createHttpClient(); | ||||
|         const version = getCacheVersion(); | ||||
|         const version = getCacheVersion((_a = options) === null || _a === void 0 ? void 0 : _a.compressionMethod); | ||||
|         const resource = `cache?keys=${encodeURIComponent(keys.join(","))}&version=${version}`; | ||||
|         const response = yield httpClient.getJson(getCacheApiUrl(resource)); | ||||
|         if (response.statusCode === 204) { | ||||
| @ -2262,7 +2261,7 @@ function getCacheEntry(keys) { | ||||
|             throw new Error(`Cache service responded with ${response.statusCode}`); | ||||
|         } | ||||
|         const cacheResult = response.result; | ||||
|         const cacheDownloadUrl = (_a = cacheResult) === null || _a === void 0 ? void 0 : _a.archiveLocation; | ||||
|         const cacheDownloadUrl = (_b = cacheResult) === null || _b === void 0 ? void 0 : _b.archiveLocation; | ||||
|         if (!cacheDownloadUrl) { | ||||
|             throw new Error("Cache not found."); | ||||
|         } | ||||
| @ -2306,17 +2305,17 @@ function downloadCache(archiveLocation, archivePath) { | ||||
| } | ||||
| exports.downloadCache = downloadCache; | ||||
| // Reserve Cache
 | ||||
| function reserveCache(key) { | ||||
|     var _a, _b, _c; | ||||
| function reserveCache(key, options) { | ||||
|     var _a, _b, _c, _d; | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         const httpClient = createHttpClient(); | ||||
|         const version = getCacheVersion(); | ||||
|         const version = getCacheVersion((_a = options) === null || _a === void 0 ? void 0 : _a.compressionMethod); | ||||
|         const reserveCacheRequest = { | ||||
|             key, | ||||
|             version | ||||
|         }; | ||||
|         const response = yield httpClient.postJson(getCacheApiUrl("caches"), reserveCacheRequest); | ||||
|         return _c = (_b = (_a = response) === null || _a === void 0 ? void 0 : _a.result) === null || _b === void 0 ? void 0 : _b.cacheId, (_c !== null && _c !== void 0 ? _c : -1); | ||||
|         return _d = (_c = (_b = response) === null || _b === void 0 ? void 0 : _b.result) === null || _c === void 0 ? void 0 : _c.cacheId, (_d !== null && _d !== void 0 ? _d : -1); | ||||
|     }); | ||||
| } | ||||
| exports.reserveCache = reserveCache; | ||||
| @ -3201,6 +3200,7 @@ var __importStar = (this && this.__importStar) || function (mod) { | ||||
| }; | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| const core = __importStar(__webpack_require__(470)); | ||||
| const exec = __importStar(__webpack_require__(986)); | ||||
| const glob = __importStar(__webpack_require__(281)); | ||||
| const io = __importStar(__webpack_require__(1)); | ||||
| const fs = __importStar(__webpack_require__(747)); | ||||
| @ -3320,6 +3320,50 @@ function unlinkFile(path) { | ||||
|     return util.promisify(fs.unlink)(path); | ||||
| } | ||||
| exports.unlinkFile = unlinkFile; | ||||
| function checkVersion(app) { | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         core.debug(`Checking ${app} --version`); | ||||
|         let versionOutput = ""; | ||||
|         try { | ||||
|             yield exec.exec(`${app} --version`, [], { | ||||
|                 ignoreReturnCode: true, | ||||
|                 silent: true, | ||||
|                 listeners: { | ||||
|                     stdout: (data) => (versionOutput += data.toString()), | ||||
|                     stderr: (data) => (versionOutput += data.toString()) | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|         catch (err) { | ||||
|             core.debug(err.message); | ||||
|         } | ||||
|         versionOutput = versionOutput.trim(); | ||||
|         core.debug(versionOutput); | ||||
|         return versionOutput; | ||||
|     }); | ||||
| } | ||||
| function getCompressionMethod() { | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         const versionOutput = yield checkVersion("zstd"); | ||||
|         return versionOutput.toLowerCase().includes("zstd command line interface") | ||||
|             ? constants_1.CompressionMethod.Zstd | ||||
|             : constants_1.CompressionMethod.Gzip; | ||||
|     }); | ||||
| } | ||||
| exports.getCompressionMethod = getCompressionMethod; | ||||
| function getCacheFileName(compressionMethod) { | ||||
|     return compressionMethod == constants_1.CompressionMethod.Zstd | ||||
|         ? constants_1.CacheFilename.Zstd | ||||
|         : constants_1.CacheFilename.Gzip; | ||||
| } | ||||
| exports.getCacheFileName = getCacheFileName; | ||||
| function useGnuTar() { | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         const versionOutput = yield checkVersion("tar"); | ||||
|         return versionOutput.toLowerCase().includes("gnu tar"); | ||||
|     }); | ||||
| } | ||||
| exports.useGnuTar = useGnuTar; | ||||
| 
 | ||||
| 
 | ||||
| /***/ }), | ||||
| @ -3599,12 +3643,6 @@ class HttpClientResponse { | ||||
|             this.message.on('data', (chunk) => { | ||||
|                 output = Buffer.concat([output, chunk]); | ||||
|             }); | ||||
|             this.message.on('aborted', () => { | ||||
|                 reject("Request was aborted or closed prematurely"); | ||||
|             }); | ||||
|             this.message.on('timeout', (socket) => { | ||||
|                 reject("Request timed out"); | ||||
|             }); | ||||
|             this.message.on('end', () => { | ||||
|                 resolve(output.toString()); | ||||
|             }); | ||||
| @ -3726,7 +3764,6 @@ class HttpClient { | ||||
|         let response; | ||||
|         while (numTries < maxTries) { | ||||
|             response = await this.requestRaw(info, data); | ||||
| 
 | ||||
|             // Check if it's an authentication challenge
 | ||||
|             if (response && response.message && response.message.statusCode === HttpCodes.Unauthorized) { | ||||
|                 let authenticationHandler; | ||||
| @ -4490,7 +4527,16 @@ var Events; | ||||
|     Events["Push"] = "push"; | ||||
|     Events["PullRequest"] = "pull_request"; | ||||
| })(Events = exports.Events || (exports.Events = {})); | ||||
| exports.CacheFilename = "cache.tgz"; | ||||
| var CacheFilename; | ||||
| (function (CacheFilename) { | ||||
|     CacheFilename["Gzip"] = "cache.tgz"; | ||||
|     CacheFilename["Zstd"] = "cache.tzst"; | ||||
| })(CacheFilename = exports.CacheFilename || (exports.CacheFilename = {})); | ||||
| var CompressionMethod; | ||||
| (function (CompressionMethod) { | ||||
|     CompressionMethod["Gzip"] = "gzip"; | ||||
|     CompressionMethod["Zstd"] = "zstd"; | ||||
| })(CompressionMethod = exports.CompressionMethod || (exports.CompressionMethod = {})); | ||||
| // 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.
 | ||||
| @ -4617,13 +4663,16 @@ function run() { | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
|             const compressionMethod = yield utils.getCompressionMethod(); | ||||
|             try { | ||||
|                 const cacheEntry = yield cacheHttpClient.getCacheEntry(keys); | ||||
|                 const cacheEntry = yield cacheHttpClient.getCacheEntry(keys, { | ||||
|                     compressionMethod: compressionMethod | ||||
|                 }); | ||||
|                 if (!((_a = cacheEntry) === null || _a === void 0 ? void 0 : _a.archiveLocation)) { | ||||
|                     core.info(`Cache not found for input keys: ${keys.join(", ")}`); | ||||
|                     return; | ||||
|                 } | ||||
|                 const archivePath = path.join(yield utils.createTempDirectory(), "cache.tgz"); | ||||
|                 const archivePath = path.join(yield utils.createTempDirectory(), utils.getCacheFileName(compressionMethod)); | ||||
|                 core.debug(`Archive Path: ${archivePath}`); | ||||
|                 // Store the cache result
 | ||||
|                 utils.setCacheState(cacheEntry); | ||||
| @ -4632,7 +4681,7 @@ function run() { | ||||
|                     yield cacheHttpClient.downloadCache(cacheEntry.archiveLocation, archivePath); | ||||
|                     const archiveFileSize = utils.getArchiveFileSize(archivePath); | ||||
|                     core.info(`Cache Size: ~${Math.round(archiveFileSize / (1024 * 1024))} MB (${archiveFileSize} B)`); | ||||
|                     yield tar_1.extractTar(archivePath); | ||||
|                     yield tar_1.extractTar(archivePath, compressionMethod); | ||||
|                 } | ||||
|                 finally { | ||||
|                     // Try to delete the archive to save space
 | ||||
| @ -4993,29 +5042,12 @@ var __importStar = (this && this.__importStar) || function (mod) { | ||||
|     return result; | ||||
| }; | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| const core = __importStar(__webpack_require__(470)); | ||||
| const exec_1 = __webpack_require__(986); | ||||
| const io = __importStar(__webpack_require__(1)); | ||||
| const fs_1 = __webpack_require__(747); | ||||
| const path = __importStar(__webpack_require__(622)); | ||||
| const constants_1 = __webpack_require__(694); | ||||
| function isGnuTar() { | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         core.debug("Checking tar --version"); | ||||
|         let versionOutput = ""; | ||||
|         yield exec_1.exec("tar --version", [], { | ||||
|             ignoreReturnCode: true, | ||||
|             silent: true, | ||||
|             listeners: { | ||||
|                 stdout: (data) => (versionOutput += data.toString()), | ||||
|                 stderr: (data) => (versionOutput += data.toString()) | ||||
|             } | ||||
|         }); | ||||
|         core.debug(versionOutput.trim()); | ||||
|         return versionOutput.toUpperCase().includes("GNU TAR"); | ||||
|     }); | ||||
| } | ||||
| exports.isGnuTar = isGnuTar; | ||||
| const utils = __importStar(__webpack_require__(443)); | ||||
| function getTarPath(args) { | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         // Explicitly use BSD Tar on Windows
 | ||||
| @ -5025,7 +5057,7 @@ function getTarPath(args) { | ||||
|             if (fs_1.existsSync(systemTar)) { | ||||
|                 return systemTar; | ||||
|             } | ||||
|             else if (isGnuTar()) { | ||||
|             else if (yield utils.useGnuTar()) { | ||||
|                 args.push("--force-local"); | ||||
|             } | ||||
|         } | ||||
| @ -5036,7 +5068,7 @@ function execTar(args, cwd) { | ||||
|     var _a; | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         try { | ||||
|             yield exec_1.exec(`"${yield getTarPath(args)}"`, args, { cwd: cwd }); | ||||
|             yield exec_1.exec(`${yield getTarPath(args)}`, args, { cwd: cwd }); | ||||
|         } | ||||
|         catch (error) { | ||||
|             throw new Error(`Tar failed with error: ${(_a = error) === null || _a === void 0 ? void 0 : _a.message}`); | ||||
| @ -5047,14 +5079,16 @@ function getWorkingDirectory() { | ||||
|     var _a; | ||||
|     return _a = process.env["GITHUB_WORKSPACE"], (_a !== null && _a !== void 0 ? _a : process.cwd()); | ||||
| } | ||||
| function extractTar(archivePath) { | ||||
| function extractTar(archivePath, compressionMethod) { | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         // Create directory to extract tar into
 | ||||
|         const workingDirectory = getWorkingDirectory(); | ||||
|         yield io.mkdirP(workingDirectory); | ||||
|         const args = [ | ||||
|             "-xz", | ||||
|             "-f", | ||||
|             ...(compressionMethod == constants_1.CompressionMethod.Zstd | ||||
|                 ? ["--use-compress-program", "zstd -d"] | ||||
|                 : ["-z"]), | ||||
|             "-xf", | ||||
|             archivePath.replace(new RegExp("\\" + path.sep, "g"), "/"), | ||||
|             "-P", | ||||
|             "-C", | ||||
| @ -5064,16 +5098,20 @@ function extractTar(archivePath) { | ||||
|     }); | ||||
| } | ||||
| exports.extractTar = extractTar; | ||||
| function createTar(archiveFolder, sourceDirectories) { | ||||
| function createTar(archiveFolder, sourceDirectories, compressionMethod) { | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         // Write source directories to manifest.txt to avoid command length limits
 | ||||
|         const manifestFilename = "manifest.txt"; | ||||
|         const cacheFileName = utils.getCacheFileName(compressionMethod); | ||||
|         fs_1.writeFileSync(path.join(archiveFolder, manifestFilename), sourceDirectories.join("\n")); | ||||
|         // -T#: Compress using # working thread. If # is 0, attempt to detect and use the number of physical CPU cores.
 | ||||
|         const workingDirectory = getWorkingDirectory(); | ||||
|         const args = [ | ||||
|             "-cz", | ||||
|             "-f", | ||||
|             constants_1.CacheFilename.replace(new RegExp("\\" + path.sep, "g"), "/"), | ||||
|             ...(compressionMethod == constants_1.CompressionMethod.Zstd | ||||
|                 ? ["--use-compress-program", "zstd -T0"] | ||||
|                 : ["-z"]), | ||||
|             "-cf", | ||||
|             cacheFileName.replace(new RegExp("\\" + path.sep, "g"), "/"), | ||||
|             "-P", | ||||
|             "-C", | ||||
|             workingDirectory.replace(new RegExp("\\" + path.sep, "g"), "/"), | ||||
|  | ||||
							
								
								
									
										140
									
								
								dist/save/index.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										140
									
								
								dist/save/index.js
									
									
									
									
										vendored
									
									
								
							| @ -2236,23 +2236,22 @@ function createHttpClient() { | ||||
|     const bearerCredentialHandler = new auth_1.BearerCredentialHandler(token); | ||||
|     return new http_client_1.HttpClient("actions/cache", [bearerCredentialHandler], getRequestOptions()); | ||||
| } | ||||
| function getCacheVersion() { | ||||
| function getCacheVersion(compressionMethod) { | ||||
|     // Add salt to cache version to support breaking changes in cache entry
 | ||||
|     const components = [ | ||||
|         core.getInput(constants_1.Inputs.Path, { required: true }), | ||||
|         versionSalt | ||||
|     ]; | ||||
|     const components = [core.getInput(constants_1.Inputs.Path, { required: true })].concat(compressionMethod == constants_1.CompressionMethod.Zstd | ||||
|         ? [compressionMethod, versionSalt] | ||||
|         : versionSalt); | ||||
|     return crypto | ||||
|         .createHash("sha256") | ||||
|         .update(components.join("|")) | ||||
|         .digest("hex"); | ||||
| } | ||||
| exports.getCacheVersion = getCacheVersion; | ||||
| function getCacheEntry(keys) { | ||||
|     var _a; | ||||
| function getCacheEntry(keys, options) { | ||||
|     var _a, _b; | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         const httpClient = createHttpClient(); | ||||
|         const version = getCacheVersion(); | ||||
|         const version = getCacheVersion((_a = options) === null || _a === void 0 ? void 0 : _a.compressionMethod); | ||||
|         const resource = `cache?keys=${encodeURIComponent(keys.join(","))}&version=${version}`; | ||||
|         const response = yield httpClient.getJson(getCacheApiUrl(resource)); | ||||
|         if (response.statusCode === 204) { | ||||
| @ -2262,7 +2261,7 @@ function getCacheEntry(keys) { | ||||
|             throw new Error(`Cache service responded with ${response.statusCode}`); | ||||
|         } | ||||
|         const cacheResult = response.result; | ||||
|         const cacheDownloadUrl = (_a = cacheResult) === null || _a === void 0 ? void 0 : _a.archiveLocation; | ||||
|         const cacheDownloadUrl = (_b = cacheResult) === null || _b === void 0 ? void 0 : _b.archiveLocation; | ||||
|         if (!cacheDownloadUrl) { | ||||
|             throw new Error("Cache not found."); | ||||
|         } | ||||
| @ -2306,17 +2305,17 @@ function downloadCache(archiveLocation, archivePath) { | ||||
| } | ||||
| exports.downloadCache = downloadCache; | ||||
| // Reserve Cache
 | ||||
| function reserveCache(key) { | ||||
|     var _a, _b, _c; | ||||
| function reserveCache(key, options) { | ||||
|     var _a, _b, _c, _d; | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         const httpClient = createHttpClient(); | ||||
|         const version = getCacheVersion(); | ||||
|         const version = getCacheVersion((_a = options) === null || _a === void 0 ? void 0 : _a.compressionMethod); | ||||
|         const reserveCacheRequest = { | ||||
|             key, | ||||
|             version | ||||
|         }; | ||||
|         const response = yield httpClient.postJson(getCacheApiUrl("caches"), reserveCacheRequest); | ||||
|         return _c = (_b = (_a = response) === null || _a === void 0 ? void 0 : _a.result) === null || _b === void 0 ? void 0 : _b.cacheId, (_c !== null && _c !== void 0 ? _c : -1); | ||||
|         return _d = (_c = (_b = response) === null || _b === void 0 ? void 0 : _b.result) === null || _c === void 0 ? void 0 : _c.cacheId, (_d !== null && _d !== void 0 ? _d : -1); | ||||
|     }); | ||||
| } | ||||
| exports.reserveCache = reserveCache; | ||||
| @ -3201,6 +3200,7 @@ var __importStar = (this && this.__importStar) || function (mod) { | ||||
| }; | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| const core = __importStar(__webpack_require__(470)); | ||||
| const exec = __importStar(__webpack_require__(986)); | ||||
| const glob = __importStar(__webpack_require__(281)); | ||||
| const io = __importStar(__webpack_require__(1)); | ||||
| const fs = __importStar(__webpack_require__(747)); | ||||
| @ -3320,6 +3320,50 @@ function unlinkFile(path) { | ||||
|     return util.promisify(fs.unlink)(path); | ||||
| } | ||||
| exports.unlinkFile = unlinkFile; | ||||
| function checkVersion(app) { | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         core.debug(`Checking ${app} --version`); | ||||
|         let versionOutput = ""; | ||||
|         try { | ||||
|             yield exec.exec(`${app} --version`, [], { | ||||
|                 ignoreReturnCode: true, | ||||
|                 silent: true, | ||||
|                 listeners: { | ||||
|                     stdout: (data) => (versionOutput += data.toString()), | ||||
|                     stderr: (data) => (versionOutput += data.toString()) | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|         catch (err) { | ||||
|             core.debug(err.message); | ||||
|         } | ||||
|         versionOutput = versionOutput.trim(); | ||||
|         core.debug(versionOutput); | ||||
|         return versionOutput; | ||||
|     }); | ||||
| } | ||||
| function getCompressionMethod() { | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         const versionOutput = yield checkVersion("zstd"); | ||||
|         return versionOutput.toLowerCase().includes("zstd command line interface") | ||||
|             ? constants_1.CompressionMethod.Zstd | ||||
|             : constants_1.CompressionMethod.Gzip; | ||||
|     }); | ||||
| } | ||||
| exports.getCompressionMethod = getCompressionMethod; | ||||
| function getCacheFileName(compressionMethod) { | ||||
|     return compressionMethod == constants_1.CompressionMethod.Zstd | ||||
|         ? constants_1.CacheFilename.Zstd | ||||
|         : constants_1.CacheFilename.Gzip; | ||||
| } | ||||
| exports.getCacheFileName = getCacheFileName; | ||||
| function useGnuTar() { | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         const versionOutput = yield checkVersion("tar"); | ||||
|         return versionOutput.toLowerCase().includes("gnu tar"); | ||||
|     }); | ||||
| } | ||||
| exports.useGnuTar = useGnuTar; | ||||
| 
 | ||||
| 
 | ||||
| /***/ }), | ||||
| @ -3599,12 +3643,6 @@ class HttpClientResponse { | ||||
|             this.message.on('data', (chunk) => { | ||||
|                 output = Buffer.concat([output, chunk]); | ||||
|             }); | ||||
|             this.message.on('aborted', () => { | ||||
|                 reject("Request was aborted or closed prematurely"); | ||||
|             }); | ||||
|             this.message.on('timeout', (socket) => { | ||||
|                 reject("Request timed out"); | ||||
|             }); | ||||
|             this.message.on('end', () => { | ||||
|                 resolve(output.toString()); | ||||
|             }); | ||||
| @ -3726,7 +3764,6 @@ class HttpClient { | ||||
|         let response; | ||||
|         while (numTries < maxTries) { | ||||
|             response = await this.requestRaw(info, data); | ||||
| 
 | ||||
|             // Check if it's an authentication challenge
 | ||||
|             if (response && response.message && response.message.statusCode === HttpCodes.Unauthorized) { | ||||
|                 let authenticationHandler; | ||||
| @ -4511,8 +4548,11 @@ function run() { | ||||
|                 core.info(`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`); | ||||
|                 return; | ||||
|             } | ||||
|             const compressionMethod = yield utils.getCompressionMethod(); | ||||
|             core.debug("Reserving Cache"); | ||||
|             const cacheId = yield cacheHttpClient.reserveCache(primaryKey); | ||||
|             const cacheId = yield cacheHttpClient.reserveCache(primaryKey, { | ||||
|                 compressionMethod: compressionMethod | ||||
|             }); | ||||
|             if (cacheId == -1) { | ||||
|                 core.info(`Unable to reserve cache with key ${primaryKey}, another job may be creating this cache.`); | ||||
|                 return; | ||||
| @ -4525,9 +4565,9 @@ function run() { | ||||
|             core.debug("Cache Paths:"); | ||||
|             core.debug(`${JSON.stringify(cachePaths)}`); | ||||
|             const archiveFolder = yield utils.createTempDirectory(); | ||||
|             const archivePath = path.join(archiveFolder, constants_1.CacheFilename); | ||||
|             const archivePath = path.join(archiveFolder, utils.getCacheFileName(compressionMethod)); | ||||
|             core.debug(`Archive Path: ${archivePath}`); | ||||
|             yield tar_1.createTar(archiveFolder, cachePaths); | ||||
|             yield tar_1.createTar(archiveFolder, cachePaths, compressionMethod); | ||||
|             const fileSizeLimit = 5 * 1024 * 1024 * 1024; // 5GB per repo limit
 | ||||
|             const archiveFileSize = utils.getArchiveFileSize(archivePath); | ||||
|             core.debug(`File Size: ${archiveFileSize}`); | ||||
| @ -4576,7 +4616,16 @@ var Events; | ||||
|     Events["Push"] = "push"; | ||||
|     Events["PullRequest"] = "pull_request"; | ||||
| })(Events = exports.Events || (exports.Events = {})); | ||||
| exports.CacheFilename = "cache.tgz"; | ||||
| var CacheFilename; | ||||
| (function (CacheFilename) { | ||||
|     CacheFilename["Gzip"] = "cache.tgz"; | ||||
|     CacheFilename["Zstd"] = "cache.tzst"; | ||||
| })(CacheFilename = exports.CacheFilename || (exports.CacheFilename = {})); | ||||
| var CompressionMethod; | ||||
| (function (CompressionMethod) { | ||||
|     CompressionMethod["Gzip"] = "gzip"; | ||||
|     CompressionMethod["Zstd"] = "zstd"; | ||||
| })(CompressionMethod = exports.CompressionMethod || (exports.CompressionMethod = {})); | ||||
| // 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.
 | ||||
| @ -4970,29 +5019,12 @@ var __importStar = (this && this.__importStar) || function (mod) { | ||||
|     return result; | ||||
| }; | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| const core = __importStar(__webpack_require__(470)); | ||||
| const exec_1 = __webpack_require__(986); | ||||
| const io = __importStar(__webpack_require__(1)); | ||||
| const fs_1 = __webpack_require__(747); | ||||
| const path = __importStar(__webpack_require__(622)); | ||||
| const constants_1 = __webpack_require__(694); | ||||
| function isGnuTar() { | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         core.debug("Checking tar --version"); | ||||
|         let versionOutput = ""; | ||||
|         yield exec_1.exec("tar --version", [], { | ||||
|             ignoreReturnCode: true, | ||||
|             silent: true, | ||||
|             listeners: { | ||||
|                 stdout: (data) => (versionOutput += data.toString()), | ||||
|                 stderr: (data) => (versionOutput += data.toString()) | ||||
|             } | ||||
|         }); | ||||
|         core.debug(versionOutput.trim()); | ||||
|         return versionOutput.toUpperCase().includes("GNU TAR"); | ||||
|     }); | ||||
| } | ||||
| exports.isGnuTar = isGnuTar; | ||||
| const utils = __importStar(__webpack_require__(443)); | ||||
| function getTarPath(args) { | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         // Explicitly use BSD Tar on Windows
 | ||||
| @ -5002,7 +5034,7 @@ function getTarPath(args) { | ||||
|             if (fs_1.existsSync(systemTar)) { | ||||
|                 return systemTar; | ||||
|             } | ||||
|             else if (isGnuTar()) { | ||||
|             else if (yield utils.useGnuTar()) { | ||||
|                 args.push("--force-local"); | ||||
|             } | ||||
|         } | ||||
| @ -5013,7 +5045,7 @@ function execTar(args, cwd) { | ||||
|     var _a; | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         try { | ||||
|             yield exec_1.exec(`"${yield getTarPath(args)}"`, args, { cwd: cwd }); | ||||
|             yield exec_1.exec(`${yield getTarPath(args)}`, args, { cwd: cwd }); | ||||
|         } | ||||
|         catch (error) { | ||||
|             throw new Error(`Tar failed with error: ${(_a = error) === null || _a === void 0 ? void 0 : _a.message}`); | ||||
| @ -5024,14 +5056,16 @@ function getWorkingDirectory() { | ||||
|     var _a; | ||||
|     return _a = process.env["GITHUB_WORKSPACE"], (_a !== null && _a !== void 0 ? _a : process.cwd()); | ||||
| } | ||||
| function extractTar(archivePath) { | ||||
| function extractTar(archivePath, compressionMethod) { | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         // Create directory to extract tar into
 | ||||
|         const workingDirectory = getWorkingDirectory(); | ||||
|         yield io.mkdirP(workingDirectory); | ||||
|         const args = [ | ||||
|             "-xz", | ||||
|             "-f", | ||||
|             ...(compressionMethod == constants_1.CompressionMethod.Zstd | ||||
|                 ? ["--use-compress-program", "zstd -d"] | ||||
|                 : ["-z"]), | ||||
|             "-xf", | ||||
|             archivePath.replace(new RegExp("\\" + path.sep, "g"), "/"), | ||||
|             "-P", | ||||
|             "-C", | ||||
| @ -5041,16 +5075,20 @@ function extractTar(archivePath) { | ||||
|     }); | ||||
| } | ||||
| exports.extractTar = extractTar; | ||||
| function createTar(archiveFolder, sourceDirectories) { | ||||
| function createTar(archiveFolder, sourceDirectories, compressionMethod) { | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         // Write source directories to manifest.txt to avoid command length limits
 | ||||
|         const manifestFilename = "manifest.txt"; | ||||
|         const cacheFileName = utils.getCacheFileName(compressionMethod); | ||||
|         fs_1.writeFileSync(path.join(archiveFolder, manifestFilename), sourceDirectories.join("\n")); | ||||
|         // -T#: Compress using # working thread. If # is 0, attempt to detect and use the number of physical CPU cores.
 | ||||
|         const workingDirectory = getWorkingDirectory(); | ||||
|         const args = [ | ||||
|             "-cz", | ||||
|             "-f", | ||||
|             constants_1.CacheFilename.replace(new RegExp("\\" + path.sep, "g"), "/"), | ||||
|             ...(compressionMethod == constants_1.CompressionMethod.Zstd | ||||
|                 ? ["--use-compress-program", "zstd -T0"] | ||||
|                 : ["-z"]), | ||||
|             "-cf", | ||||
|             cacheFileName.replace(new RegExp("\\" + path.sep, "g"), "/"), | ||||
|             "-P", | ||||
|             "-C", | ||||
|             workingDirectory.replace(new RegExp("\\" + path.sep, "g"), "/"), | ||||
|  | ||||
| @ -11,9 +11,10 @@ import * as fs from "fs"; | ||||
| import * as stream from "stream"; | ||||
| import * as util from "util"; | ||||
| 
 | ||||
| import { Inputs, SocketTimeout } from "./constants"; | ||||
| import { CompressionMethod, Inputs, SocketTimeout } from "./constants"; | ||||
| import { | ||||
|     ArtifactCacheEntry, | ||||
|     CacheOptions, | ||||
|     CommitCacheRequest, | ||||
|     ReserveCacheRequest, | ||||
|     ReserveCacheResponse | ||||
| @ -84,12 +85,13 @@ function createHttpClient(): HttpClient { | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| export function getCacheVersion(): string { | ||||
| export function getCacheVersion(compressionMethod?: CompressionMethod): string { | ||||
|     // Add salt to cache version to support breaking changes in cache entry
 | ||||
|     const components = [ | ||||
|         core.getInput(Inputs.Path, { required: true }), | ||||
|         versionSalt | ||||
|     ]; | ||||
|     const components = [core.getInput(Inputs.Path, { required: true })].concat( | ||||
|         compressionMethod == CompressionMethod.Zstd | ||||
|             ? [compressionMethod, versionSalt] | ||||
|             : versionSalt | ||||
|     ); | ||||
| 
 | ||||
|     return crypto | ||||
|         .createHash("sha256") | ||||
| @ -98,10 +100,11 @@ export function getCacheVersion(): string { | ||||
| } | ||||
| 
 | ||||
| export async function getCacheEntry( | ||||
|     keys: string[] | ||||
|     keys: string[], | ||||
|     options?: CacheOptions | ||||
| ): Promise<ArtifactCacheEntry | null> { | ||||
|     const httpClient = createHttpClient(); | ||||
|     const version = getCacheVersion(); | ||||
|     const version = getCacheVersion(options?.compressionMethod); | ||||
|     const resource = `cache?keys=${encodeURIComponent( | ||||
|         keys.join(",") | ||||
|     )}&version=${version}`;
 | ||||
| @ -173,9 +176,12 @@ export async function downloadCache( | ||||
| } | ||||
| 
 | ||||
| // Reserve Cache
 | ||||
| export async function reserveCache(key: string): Promise<number> { | ||||
| export async function reserveCache( | ||||
|     key: string, | ||||
|     options?: CacheOptions | ||||
| ): Promise<number> { | ||||
|     const httpClient = createHttpClient(); | ||||
|     const version = getCacheVersion(); | ||||
|     const version = getCacheVersion(options?.compressionMethod); | ||||
| 
 | ||||
|     const reserveCacheRequest: ReserveCacheRequest = { | ||||
|         key, | ||||
|  | ||||
| @ -19,7 +19,15 @@ export enum Events { | ||||
|     PullRequest = "pull_request" | ||||
| } | ||||
| 
 | ||||
| export const CacheFilename = "cache.tgz"; | ||||
| export enum CacheFilename { | ||||
|     Gzip = "cache.tgz", | ||||
|     Zstd = "cache.tzst" | ||||
| } | ||||
| 
 | ||||
| export enum CompressionMethod { | ||||
|     Gzip = "gzip", | ||||
|     Zstd = "zstd" | ||||
| } | ||||
| 
 | ||||
| // Socket timeout in milliseconds during download.  If no traffic is received
 | ||||
| // over the socket during this period, the socket is destroyed and the download
 | ||||
|  | ||||
							
								
								
									
										6
									
								
								src/contracts.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								src/contracts.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -1,3 +1,5 @@ | ||||
| import { CompressionMethod } from "./constants"; | ||||
| 
 | ||||
| export interface ArtifactCacheEntry { | ||||
|     cacheKey?: string; | ||||
|     scope?: string; | ||||
| @ -17,3 +19,7 @@ export interface ReserveCacheRequest { | ||||
| export interface ReserveCacheResponse { | ||||
|     cacheId: number; | ||||
| } | ||||
| 
 | ||||
| export interface CacheOptions { | ||||
|     compressionMethod?: CompressionMethod; | ||||
| } | ||||
|  | ||||
| @ -54,8 +54,12 @@ async function run(): Promise<void> { | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         const compressionMethod = await utils.getCompressionMethod(); | ||||
| 
 | ||||
|         try { | ||||
|             const cacheEntry = await cacheHttpClient.getCacheEntry(keys); | ||||
|             const cacheEntry = await cacheHttpClient.getCacheEntry(keys, { | ||||
|                 compressionMethod: compressionMethod | ||||
|             }); | ||||
|             if (!cacheEntry?.archiveLocation) { | ||||
|                 core.info(`Cache not found for input keys: ${keys.join(", ")}`); | ||||
|                 return; | ||||
| @ -63,7 +67,7 @@ async function run(): Promise<void> { | ||||
| 
 | ||||
|             const archivePath = path.join( | ||||
|                 await utils.createTempDirectory(), | ||||
|                 "cache.tgz" | ||||
|                 utils.getCacheFileName(compressionMethod) | ||||
|             ); | ||||
|             core.debug(`Archive Path: ${archivePath}`); | ||||
| 
 | ||||
| @ -84,7 +88,7 @@ async function run(): Promise<void> { | ||||
|                     )} MB (${archiveFileSize} B)` | ||||
|                 ); | ||||
| 
 | ||||
|                 await extractTar(archivePath); | ||||
|                 await extractTar(archivePath, compressionMethod); | ||||
|             } finally { | ||||
|                 // Try to delete the archive to save space
 | ||||
|                 try { | ||||
|  | ||||
							
								
								
									
										16
									
								
								src/save.ts
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								src/save.ts
									
									
									
									
									
								
							| @ -2,7 +2,7 @@ import * as core from "@actions/core"; | ||||
| import * as path from "path"; | ||||
| 
 | ||||
| import * as cacheHttpClient from "./cacheHttpClient"; | ||||
| import { CacheFilename, Events, Inputs, State } from "./constants"; | ||||
| import { Events, Inputs, State } from "./constants"; | ||||
| import { createTar } from "./tar"; | ||||
| import * as utils from "./utils/actionUtils"; | ||||
| 
 | ||||
| @ -35,8 +35,12 @@ async function run(): Promise<void> { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         const compressionMethod = await utils.getCompressionMethod(); | ||||
| 
 | ||||
|         core.debug("Reserving Cache"); | ||||
|         const cacheId = await cacheHttpClient.reserveCache(primaryKey); | ||||
|         const cacheId = await cacheHttpClient.reserveCache(primaryKey, { | ||||
|             compressionMethod: compressionMethod | ||||
|         }); | ||||
|         if (cacheId == -1) { | ||||
|             core.info( | ||||
|                 `Unable to reserve cache with key ${primaryKey}, another job may be creating this cache.` | ||||
| @ -55,10 +59,14 @@ async function run(): Promise<void> { | ||||
|         core.debug(`${JSON.stringify(cachePaths)}`); | ||||
| 
 | ||||
|         const archiveFolder = await utils.createTempDirectory(); | ||||
|         const archivePath = path.join(archiveFolder, CacheFilename); | ||||
|         const archivePath = path.join( | ||||
|             archiveFolder, | ||||
|             utils.getCacheFileName(compressionMethod) | ||||
|         ); | ||||
| 
 | ||||
|         core.debug(`Archive Path: ${archivePath}`); | ||||
| 
 | ||||
|         await createTar(archiveFolder, cachePaths); | ||||
|         await createTar(archiveFolder, cachePaths, compressionMethod); | ||||
| 
 | ||||
|         const fileSizeLimit = 5 * 1024 * 1024 * 1024; // 5GB per repo limit
 | ||||
|         const archiveFileSize = utils.getArchiveFileSize(archivePath); | ||||
|  | ||||
							
								
								
									
										50
									
								
								src/tar.ts
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								src/tar.ts
									
									
									
									
									
								
							| @ -1,27 +1,10 @@ | ||||
| import * as core from "@actions/core"; | ||||
| import { exec } from "@actions/exec"; | ||||
| import * as io from "@actions/io"; | ||||
| import { existsSync, writeFileSync } from "fs"; | ||||
| import * as path from "path"; | ||||
| 
 | ||||
| import { CacheFilename } from "./constants"; | ||||
| 
 | ||||
| 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"); | ||||
| } | ||||
| import { CompressionMethod } from "./constants"; | ||||
| import * as utils from "./utils/actionUtils"; | ||||
| 
 | ||||
| async function getTarPath(args: string[]): Promise<string> { | ||||
|     // Explicitly use BSD Tar on Windows
 | ||||
| @ -30,7 +13,7 @@ async function getTarPath(args: string[]): Promise<string> { | ||||
|         const systemTar = `${process.env["windir"]}\\System32\\tar.exe`; | ||||
|         if (existsSync(systemTar)) { | ||||
|             return systemTar; | ||||
|         } else if (isGnuTar()) { | ||||
|         } else if (await utils.useGnuTar()) { | ||||
|             args.push("--force-local"); | ||||
|         } | ||||
|     } | ||||
| @ -39,7 +22,7 @@ async function getTarPath(args: string[]): Promise<string> { | ||||
| 
 | ||||
| async function execTar(args: string[], cwd?: string): Promise<void> { | ||||
|     try { | ||||
|         await exec(`"${await getTarPath(args)}"`, args, { cwd: cwd }); | ||||
|         await exec(`${await getTarPath(args)}`, args, { cwd: cwd }); | ||||
|     } catch (error) { | ||||
|         throw new Error(`Tar failed with error: ${error?.message}`); | ||||
|     } | ||||
| @ -49,13 +32,18 @@ function getWorkingDirectory(): string { | ||||
|     return process.env["GITHUB_WORKSPACE"] ?? process.cwd(); | ||||
| } | ||||
| 
 | ||||
| export async function extractTar(archivePath: string): Promise<void> { | ||||
| export async function extractTar( | ||||
|     archivePath: string, | ||||
|     compressionMethod: CompressionMethod | ||||
| ): Promise<void> { | ||||
|     // Create directory to extract tar into
 | ||||
|     const workingDirectory = getWorkingDirectory(); | ||||
|     await io.mkdirP(workingDirectory); | ||||
|     const args = [ | ||||
|         "-xz", | ||||
|         "-f", | ||||
|         ...(compressionMethod == CompressionMethod.Zstd | ||||
|             ? ["--use-compress-program", "zstd -d"] | ||||
|             : ["-z"]), | ||||
|         "-xf", | ||||
|         archivePath.replace(new RegExp("\\" + path.sep, "g"), "/"), | ||||
|         "-P", | ||||
|         "-C", | ||||
| @ -66,20 +54,24 @@ export async function extractTar(archivePath: string): Promise<void> { | ||||
| 
 | ||||
| export async function createTar( | ||||
|     archiveFolder: string, | ||||
|     sourceDirectories: string[] | ||||
|     sourceDirectories: string[], | ||||
|     compressionMethod: CompressionMethod | ||||
| ): Promise<void> { | ||||
|     // Write source directories to manifest.txt to avoid command length limits
 | ||||
|     const manifestFilename = "manifest.txt"; | ||||
|     const cacheFileName = utils.getCacheFileName(compressionMethod); | ||||
|     writeFileSync( | ||||
|         path.join(archiveFolder, manifestFilename), | ||||
|         sourceDirectories.join("\n") | ||||
|     ); | ||||
| 
 | ||||
|     // -T#: Compress using # working thread. If # is 0, attempt to detect and use the number of physical CPU cores.
 | ||||
|     const workingDirectory = getWorkingDirectory(); | ||||
|     const args = [ | ||||
|         "-cz", | ||||
|         "-f", | ||||
|         CacheFilename.replace(new RegExp("\\" + path.sep, "g"), "/"), | ||||
|         ...(compressionMethod == CompressionMethod.Zstd | ||||
|             ? ["--use-compress-program", "zstd -T0"] | ||||
|             : ["-z"]), | ||||
|         "-cf", | ||||
|         cacheFileName.replace(new RegExp("\\" + path.sep, "g"), "/"), | ||||
|         "-P", | ||||
|         "-C", | ||||
|         workingDirectory.replace(new RegExp("\\" + path.sep, "g"), "/"), | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| import * as core from "@actions/core"; | ||||
| import * as exec from "@actions/exec"; | ||||
| import * as glob from "@actions/glob"; | ||||
| import * as io from "@actions/io"; | ||||
| import * as fs from "fs"; | ||||
| @ -6,7 +7,13 @@ import * as path from "path"; | ||||
| import * as util from "util"; | ||||
| import * as uuidV4 from "uuid/v4"; | ||||
| 
 | ||||
| import { Events, Outputs, State } from "../constants"; | ||||
| import { | ||||
|     CacheFilename, | ||||
|     CompressionMethod, | ||||
|     Events, | ||||
|     Outputs, | ||||
|     State | ||||
| } from "../constants"; | ||||
| import { ArtifactCacheEntry } from "../contracts"; | ||||
| 
 | ||||
| // From https://github.com/actions/toolkit/blob/master/packages/tool-cache/src/tool-cache.ts#L23
 | ||||
| @ -116,3 +123,44 @@ export function isValidEvent(): boolean { | ||||
| export function unlinkFile(path: fs.PathLike): Promise<void> { | ||||
|     return util.promisify(fs.unlink)(path); | ||||
| } | ||||
| 
 | ||||
| async function checkVersion(app: string): Promise<string> { | ||||
|     core.debug(`Checking ${app} --version`); | ||||
|     let versionOutput = ""; | ||||
|     try { | ||||
|         await exec.exec(`${app} --version`, [], { | ||||
|             ignoreReturnCode: true, | ||||
|             silent: true, | ||||
|             listeners: { | ||||
|                 stdout: (data: Buffer): string => | ||||
|                     (versionOutput += data.toString()), | ||||
|                 stderr: (data: Buffer): string => | ||||
|                     (versionOutput += data.toString()) | ||||
|             } | ||||
|         }); | ||||
|     } catch (err) { | ||||
|         core.debug(err.message); | ||||
|     } | ||||
| 
 | ||||
|     versionOutput = versionOutput.trim(); | ||||
|     core.debug(versionOutput); | ||||
|     return versionOutput; | ||||
| } | ||||
| 
 | ||||
| export async function getCompressionMethod(): Promise<CompressionMethod> { | ||||
|     const versionOutput = await checkVersion("zstd"); | ||||
|     return versionOutput.toLowerCase().includes("zstd command line interface") | ||||
|         ? CompressionMethod.Zstd | ||||
|         : CompressionMethod.Gzip; | ||||
| } | ||||
| 
 | ||||
| export function getCacheFileName(compressionMethod: CompressionMethod): string { | ||||
|     return compressionMethod == CompressionMethod.Zstd | ||||
|         ? CacheFilename.Zstd | ||||
|         : CacheFilename.Gzip; | ||||
| } | ||||
| 
 | ||||
| export async function useGnuTar(): Promise<boolean> { | ||||
|     const versionOutput = await checkVersion("tar"); | ||||
|     return versionOutput.toLowerCase().includes("gnu tar"); | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Aiqiao Yan
						Aiqiao Yan