Fallback to GNU tar if BSD tar is unavailable
This commit is contained in:
		| @ -1,5 +1,7 @@ | ||||
| import * as exec from "@actions/exec"; | ||||
| import * as io from "@actions/io"; | ||||
| import * as fs from "fs"; | ||||
| import * as path from "path"; | ||||
| import * as tar from "../src/tar"; | ||||
|  | ||||
| jest.mock("@actions/exec"); | ||||
| @ -11,17 +13,19 @@ beforeAll(() => { | ||||
|     }); | ||||
| }); | ||||
|  | ||||
| test("extract tar", async () => { | ||||
| test("extract BSD tar", async () => { | ||||
|     const mkdirMock = jest.spyOn(io, "mkdirP"); | ||||
|     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"; | ||||
|     await tar.extractTar(archivePath, targetDirectory); | ||||
|  | ||||
|     expect(mkdirMock).toHaveBeenCalledWith(targetDirectory); | ||||
|  | ||||
|     const IS_WINDOWS = process.platform === "win32"; | ||||
|     const tarPath = IS_WINDOWS | ||||
|         ? `${process.env["windir"]}\\System32\\tar.exe` | ||||
|         : "tar"; | ||||
| @ -29,13 +33,48 @@ test("extract tar", async () => { | ||||
|     expect(execMock).toHaveBeenCalledWith(`"${tarPath}"`, [ | ||||
|         "-xz", | ||||
|         "-f", | ||||
|         archivePath, | ||||
|         archivePath?.replace(/\\/g, "/"), | ||||
|         "-C", | ||||
|         targetDirectory | ||||
|         targetDirectory?.replace(/\\/g, "/"), | ||||
|     ]); | ||||
| }); | ||||
|  | ||||
| test("create tar", async () => { | ||||
| test("extract GNU tar", async () => { | ||||
|     const IS_WINDOWS = process.platform === "win32"; | ||||
|     if (IS_WINDOWS) { | ||||
|         jest.mock("fs"); | ||||
|  | ||||
|         const execMock = jest.spyOn(exec, "exec"); | ||||
|         const existsSyncMock = jest | ||||
|             .spyOn(fs, "existsSync") | ||||
|             .mockReturnValue(false); | ||||
|         const isGnuTarMock = jest | ||||
|             .spyOn(tar, "isGnuTar") | ||||
|             .mockReturnValue(Promise.resolve(true)); | ||||
|         const archivePath = `${process.env["windir"]}\\fakepath\\cache.tar`; | ||||
|         const targetDirectory = "~/.npm/cache"; | ||||
|  | ||||
|         await tar.extractTar(archivePath, targetDirectory); | ||||
|  | ||||
|         expect(existsSyncMock).toHaveBeenCalledTimes(1); | ||||
|         expect(isGnuTarMock).toHaveBeenCalledTimes(1); | ||||
|         expect(execMock).toHaveBeenCalledTimes(2); | ||||
|         expect(execMock).toHaveBeenLastCalledWith( | ||||
|             "tar", | ||||
|             [ | ||||
|                 "-xz", | ||||
|                 "-f", | ||||
|                 archivePath?.replace(/\\/g, "/"), | ||||
|                 "-C", | ||||
|                 targetDirectory?.replace(/\\/g, "/"), | ||||
|                 "--force-local" | ||||
|             ], | ||||
|             { cwd: undefined } | ||||
|         ); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| test("create BSD tar", async () => { | ||||
|     const execMock = jest.spyOn(exec, "exec"); | ||||
|  | ||||
|     const archivePath = "cache.tar"; | ||||
| @ -50,9 +89,9 @@ test("create tar", async () => { | ||||
|     expect(execMock).toHaveBeenCalledWith(`"${tarPath}"`, [ | ||||
|         "-cz", | ||||
|         "-f", | ||||
|         archivePath, | ||||
|         archivePath?.replace(/\\/g, "/"), | ||||
|         "-C", | ||||
|         sourceDirectory, | ||||
|         sourceDirectory?.replace(/\\/g, "/"), | ||||
|         "." | ||||
|     ]); | ||||
| }); | ||||
|  | ||||
							
								
								
									
										87
									
								
								dist/restore/index.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										87
									
								
								dist/restore/index.js
									
									
									
									
										vendored
									
									
								
							| @ -2928,10 +2928,34 @@ 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); | ||||
| <<<<<<< HEAD | ||||
| function getTarPath() { | ||||
| ======= | ||||
| 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; | ||||
| function getTarPath(args) { | ||||
| >>>>>>> 4fa017f... Fallback to GNU tar if BSD tar is unavailable | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         // Explicitly use BSD Tar on Windows | ||||
|         const IS_WINDOWS = process.platform === "win32"; | ||||
| @ -2940,38 +2964,91 @@ function getTarPath() { | ||||
|             if (fs_1.existsSync(systemTar)) { | ||||
|                 return systemTar; | ||||
|             } | ||||
|             else if (isGnuTar()) { | ||||
|                 args.push("--force-local"); | ||||
|             } | ||||
|         } | ||||
|         return yield io.which("tar", true); | ||||
|     }); | ||||
| } | ||||
| <<<<<<< HEAD | ||||
| function execTar(args) { | ||||
|     var _a, _b; | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         try { | ||||
|             yield exec_1.exec(`"${yield getTarPath()}"`, args); | ||||
| ======= | ||||
| 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 }); | ||||
| >>>>>>> 4fa017f... Fallback to GNU tar if BSD tar is unavailable | ||||
|         } | ||||
|         catch (error) { | ||||
|             const IS_WINDOWS = process.platform === "win32"; | ||||
|             if (IS_WINDOWS) { | ||||
|                 throw new Error(`Tar failed with error: ${(_a = error) === null || _a === void 0 ? void 0 : _a.message}. Ensure BSD tar is installed and on the PATH.`); | ||||
|             } | ||||
|             throw new Error(`Tar failed with error: ${(_b = error) === null || _b === void 0 ? void 0 : _b.message}`); | ||||
|             throw new Error(`Tar failed with error: ${(_a = error) === null || _a === void 0 ? void 0 : _a.message}`); | ||||
|         } | ||||
|     }); | ||||
| } | ||||
| <<<<<<< HEAD | ||||
| function extractTar(archivePath, targetDirectory) { | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         // Create directory to extract tar into | ||||
|         yield io.mkdirP(targetDirectory); | ||||
|         const args = ["-xz", "-f", archivePath, "-C", targetDirectory]; | ||||
| ======= | ||||
| function getWorkingDirectory() { | ||||
|     var _a; | ||||
|     return _a = process.env["GITHUB_WORKSPACE"], (_a !== null && _a !== void 0 ? _a : process.cwd()); | ||||
| } | ||||
| function extractTar(archivePath) { | ||||
|     var _a, _b; | ||||
|     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", | ||||
|             (_a = archivePath) === null || _a === void 0 ? void 0 : _a.replace(/\\/g, "/"), | ||||
|             "-P", | ||||
|             "-C", | ||||
|             (_b = workingDirectory) === null || _b === void 0 ? void 0 : _b.replace(/\\/g, "/") | ||||
|         ]; | ||||
| >>>>>>> 4fa017f... Fallback to GNU tar if BSD tar is unavailable | ||||
|         yield execTar(args); | ||||
|     }); | ||||
| } | ||||
| exports.extractTar = extractTar; | ||||
| <<<<<<< HEAD | ||||
| function createTar(archivePath, sourceDirectory) { | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         const args = ["-cz", "-f", archivePath, "-C", sourceDirectory, "."]; | ||||
|         yield execTar(args); | ||||
| ======= | ||||
| function createTar(archiveFolder, sourceDirectories) { | ||||
|     var _a, _b; | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         // Write source directories to manifest.txt to avoid command length limits | ||||
|         const manifestFilename = "manifest.txt"; | ||||
|         fs_1.writeFileSync(path.join(archiveFolder, manifestFilename), sourceDirectories.join("\n")); | ||||
|         const workingDirectory = getWorkingDirectory(); | ||||
|         const args = [ | ||||
|             "-cz", | ||||
|             "-f", | ||||
| <<<<<<< HEAD | ||||
|             constants_1.CacheFilename, | ||||
|             "-P", | ||||
| ======= | ||||
|             (_a = constants_1.CacheFilename) === null || _a === void 0 ? void 0 : _a.replace(/\\/g, "/"), | ||||
| >>>>>>> Fallback to GNU tar if BSD tar is unavailable | ||||
|             "-C", | ||||
|             (_b = workingDirectory) === null || _b === void 0 ? void 0 : _b.replace(/\\/g, "/"), | ||||
|             "--files-from", | ||||
|             manifestFilename | ||||
|         ]; | ||||
|         yield execTar(args, archiveFolder); | ||||
| >>>>>>> 4fa017f... Fallback to GNU tar if BSD tar is unavailable | ||||
|     }); | ||||
| } | ||||
| exports.createTar = createTar; | ||||
|  | ||||
							
								
								
									
										87
									
								
								dist/save/index.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										87
									
								
								dist/save/index.js
									
									
									
									
										vendored
									
									
								
							| @ -2909,10 +2909,34 @@ 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); | ||||
| <<<<<<< HEAD | ||||
| function getTarPath() { | ||||
| ======= | ||||
| 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; | ||||
| function getTarPath(args) { | ||||
| >>>>>>> 4fa017f... Fallback to GNU tar if BSD tar is unavailable | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         // Explicitly use BSD Tar on Windows | ||||
|         const IS_WINDOWS = process.platform === "win32"; | ||||
| @ -2921,38 +2945,91 @@ function getTarPath() { | ||||
|             if (fs_1.existsSync(systemTar)) { | ||||
|                 return systemTar; | ||||
|             } | ||||
|             else if (isGnuTar()) { | ||||
|                 args.push("--force-local"); | ||||
|             } | ||||
|         } | ||||
|         return yield io.which("tar", true); | ||||
|     }); | ||||
| } | ||||
| <<<<<<< HEAD | ||||
| function execTar(args) { | ||||
|     var _a, _b; | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         try { | ||||
|             yield exec_1.exec(`"${yield getTarPath()}"`, args); | ||||
| ======= | ||||
| 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 }); | ||||
| >>>>>>> 4fa017f... Fallback to GNU tar if BSD tar is unavailable | ||||
|         } | ||||
|         catch (error) { | ||||
|             const IS_WINDOWS = process.platform === "win32"; | ||||
|             if (IS_WINDOWS) { | ||||
|                 throw new Error(`Tar failed with error: ${(_a = error) === null || _a === void 0 ? void 0 : _a.message}. Ensure BSD tar is installed and on the PATH.`); | ||||
|             } | ||||
|             throw new Error(`Tar failed with error: ${(_b = error) === null || _b === void 0 ? void 0 : _b.message}`); | ||||
|             throw new Error(`Tar failed with error: ${(_a = error) === null || _a === void 0 ? void 0 : _a.message}`); | ||||
|         } | ||||
|     }); | ||||
| } | ||||
| <<<<<<< HEAD | ||||
| function extractTar(archivePath, targetDirectory) { | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         // Create directory to extract tar into | ||||
|         yield io.mkdirP(targetDirectory); | ||||
|         const args = ["-xz", "-f", archivePath, "-C", targetDirectory]; | ||||
| ======= | ||||
| function getWorkingDirectory() { | ||||
|     var _a; | ||||
|     return _a = process.env["GITHUB_WORKSPACE"], (_a !== null && _a !== void 0 ? _a : process.cwd()); | ||||
| } | ||||
| function extractTar(archivePath) { | ||||
|     var _a, _b; | ||||
|     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", | ||||
|             (_a = archivePath) === null || _a === void 0 ? void 0 : _a.replace(/\\/g, "/"), | ||||
|             "-P", | ||||
|             "-C", | ||||
|             (_b = workingDirectory) === null || _b === void 0 ? void 0 : _b.replace(/\\/g, "/") | ||||
|         ]; | ||||
| >>>>>>> 4fa017f... Fallback to GNU tar if BSD tar is unavailable | ||||
|         yield execTar(args); | ||||
|     }); | ||||
| } | ||||
| exports.extractTar = extractTar; | ||||
| <<<<<<< HEAD | ||||
| function createTar(archivePath, sourceDirectory) { | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         const args = ["-cz", "-f", archivePath, "-C", sourceDirectory, "."]; | ||||
|         yield execTar(args); | ||||
| ======= | ||||
| function createTar(archiveFolder, sourceDirectories) { | ||||
|     var _a, _b; | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         // Write source directories to manifest.txt to avoid command length limits | ||||
|         const manifestFilename = "manifest.txt"; | ||||
|         fs_1.writeFileSync(path.join(archiveFolder, manifestFilename), sourceDirectories.join("\n")); | ||||
|         const workingDirectory = getWorkingDirectory(); | ||||
|         const args = [ | ||||
|             "-cz", | ||||
|             "-f", | ||||
| <<<<<<< HEAD | ||||
|             constants_1.CacheFilename, | ||||
|             "-P", | ||||
| ======= | ||||
|             (_a = constants_1.CacheFilename) === null || _a === void 0 ? void 0 : _a.replace(/\\/g, "/"), | ||||
| >>>>>>> Fallback to GNU tar if BSD tar is unavailable | ||||
|             "-C", | ||||
|             (_b = workingDirectory) === null || _b === void 0 ? void 0 : _b.replace(/\\/g, "/"), | ||||
|             "--files-from", | ||||
|             manifestFilename | ||||
|         ]; | ||||
|         yield execTar(args, archiveFolder); | ||||
| >>>>>>> 4fa017f... Fallback to GNU tar if BSD tar is unavailable | ||||
|     }); | ||||
| } | ||||
| exports.createTar = createTar; | ||||
|  | ||||
							
								
								
									
										48
									
								
								src/tar.ts
									
									
									
									
									
								
							
							
						
						
									
										48
									
								
								src/tar.ts
									
									
									
									
									
								
							| @ -1,14 +1,35 @@ | ||||
| import * as core from "@actions/core"; | ||||
| import { exec } from "@actions/exec"; | ||||
| import * as io from "@actions/io"; | ||||
| import { existsSync } from "fs"; | ||||
| import * as path from "path"; | ||||
|  | ||||
| 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 | ||||
|     const IS_WINDOWS = process.platform === "win32"; | ||||
|     if (IS_WINDOWS) { | ||||
|         const systemTar = `${process.env["windir"]}\\System32\\tar.exe`; | ||||
|         if (existsSync(systemTar)) { | ||||
|             return systemTar; | ||||
|         } else if (isGnuTar()) { | ||||
|             args.push("--force-local"); | ||||
|         } | ||||
|     } | ||||
|     return await io.which("tar", true); | ||||
| @ -16,14 +37,8 @@ async function getTarPath(): Promise<string> { | ||||
|  | ||||
| async function execTar(args: string[]): Promise<void> { | ||||
|     try { | ||||
|         await exec(`"${await getTarPath()}"`, args); | ||||
|         await exec(`"${await getTarPath(args)}"`, args); | ||||
|     } 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}`); | ||||
|     } | ||||
| } | ||||
| @ -34,7 +49,13 @@ export async function extractTar( | ||||
| ): Promise<void> { | ||||
|     // Create directory to extract tar into | ||||
|     await io.mkdirP(targetDirectory); | ||||
|     const args = ["-xz", "-f", archivePath, "-C", targetDirectory]; | ||||
|     const args = [ | ||||
|         "-xz", | ||||
|         "-f", | ||||
|         archivePath?.replace(/\\/g, "/"), | ||||
|         "-C", | ||||
|         targetDirectory?.replace(/\\/g, "/") | ||||
|     ]; | ||||
|     await execTar(args); | ||||
| } | ||||
|  | ||||
| @ -42,6 +63,13 @@ export async function createTar( | ||||
|     archivePath: string, | ||||
|     sourceDirectory: string | ||||
| ): Promise<void> { | ||||
|     const args = ["-cz", "-f", archivePath, "-C", sourceDirectory, "."]; | ||||
|     const args = [ | ||||
|         "-cz", | ||||
|         "-f", | ||||
|         archivePath?.replace(/\\/g, "/"), | ||||
|         "-C", | ||||
|         sourceDirectory?.replace(/\\/g, "/"), | ||||
|         "." | ||||
|     ]; | ||||
|     await execTar(args); | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Aiqiao Yan
					Aiqiao Yan