From 1e233443e889774bdaf132f7aa74314c16866606 Mon Sep 17 00:00:00 2001 From: Ethan Dennis Date: Thu, 5 Mar 2020 09:15:35 -0800 Subject: [PATCH] Add initial minimatch support --- __tests__/actionUtils.test.ts | 37 --------------- __tests__/pathUtils.test.ts | 42 +++++++++++++++++ __tests__/restore.test.ts | 8 ++-- __tests__/save.test.ts | 6 ++- __tests__/tar.test.ts | 2 +- dist/restore/index.js | 20 +-------- dist/save/index.js | 85 +++++++++++++++++++++++++++-------- package-lock.json | 14 +++--- package.json | 2 + src/restore.ts | 5 --- src/save.ts | 12 ++--- src/tar.ts | 1 + src/utils/actionUtils.ts | 13 ------ src/utils/pathUtils.ts | 63 ++++++++++++++++++++++++++ 14 files changed, 202 insertions(+), 108 deletions(-) create mode 100644 __tests__/pathUtils.test.ts create mode 100644 src/utils/pathUtils.ts diff --git a/__tests__/actionUtils.test.ts b/__tests__/actionUtils.test.ts index f46d65d..01ccff3 100644 --- a/__tests__/actionUtils.test.ts +++ b/__tests__/actionUtils.test.ts @@ -1,5 +1,4 @@ import * as core from "@actions/core"; -import * as os from "os"; import * as path from "path"; import { Events, Outputs, State } from "../src/constants"; @@ -181,42 +180,6 @@ test("isValidEvent returns false for unknown event", () => { expect(isValidEvent).toBe(false); }); -test("resolvePath with no ~ in path", () => { - const filePath = ".cache/yarn"; - - const resolvedPath = actionUtils.resolvePath(filePath); - - const expectedPath = path.resolve(filePath); - expect(resolvedPath).toBe(expectedPath); -}); - -test("resolvePath with ~ in path", () => { - const filePath = "~/.cache/yarn"; - - const homedir = jest.requireActual("os").homedir(); - const homedirMock = jest.spyOn(os, "homedir"); - homedirMock.mockImplementation(() => { - return homedir; - }); - - const resolvedPath = actionUtils.resolvePath(filePath); - - const expectedPath = path.join(homedir, ".cache/yarn"); - expect(resolvedPath).toBe(expectedPath); -}); - -test("resolvePath with home not found", () => { - const filePath = "~/.cache/yarn"; - const homedirMock = jest.spyOn(os, "homedir"); - homedirMock.mockImplementation(() => { - return ""; - }); - - expect(() => actionUtils.resolvePath(filePath)).toThrow( - "Unable to resolve `~` to HOME" - ); -}); - test("isValidEvent returns true for push event", () => { const event = Events.Push; process.env[Events.Key] = event; diff --git a/__tests__/pathUtils.test.ts b/__tests__/pathUtils.test.ts new file mode 100644 index 0000000..59f6ec7 --- /dev/null +++ b/__tests__/pathUtils.test.ts @@ -0,0 +1,42 @@ +import * as path from "path"; +import * as os from "os"; +import * as pathUtils from "../src/utils/pathUtils"; + +jest.mock("@actions/core"); +jest.mock("os"); + +test("expandPaths with no ~ in path", () => { + const filePath = ".cache/yarn"; + + const resolvedPath = pathUtils.expandPaths([filePath]); + + const expectedPath = [path.resolve(filePath)]; + expect(resolvedPath).toStrictEqual(expectedPath); +}); + +test("expandPaths with ~ in path", () => { + const filePath = "~/.cache/yarn"; + + const homedir = jest.requireActual("os").homedir(); + const homedirMock = jest.spyOn(os, "homedir"); + homedirMock.mockImplementation(() => { + return homedir; + }); + + const resolvedPath = pathUtils.expandPaths([filePath]); + + const expectedPath = [path.join(homedir, ".cache/yarn")]; + expect(resolvedPath).toStrictEqual(expectedPath); +}); + +test("expandPaths with home not found", () => { + const filePath = "~/.cache/yarn"; + const homedirMock = jest.spyOn(os, "homedir"); + homedirMock.mockImplementation(() => { + return ""; + }); + + expect(() => pathUtils.expandPaths([filePath])).toThrow( + "Unable to resolve `~` to HOME" + ); +}); diff --git a/__tests__/restore.test.ts b/__tests__/restore.test.ts index 9323a87..23f03cc 100644 --- a/__tests__/restore.test.ts +++ b/__tests__/restore.test.ts @@ -7,15 +7,17 @@ import run from "../src/restore"; import * as tar from "../src/tar"; import * as actionUtils from "../src/utils/actionUtils"; import * as testUtils from "../src/utils/testUtils"; +// import * as pathUtils from "../src/utils/pathUtils"; jest.mock("../src/cacheHttpClient"); jest.mock("../src/tar"); jest.mock("../src/utils/actionUtils"); +// jest.mock("../src/utils/pathUtils"); beforeAll(() => { - jest.spyOn(actionUtils, "resolvePath").mockImplementation(filePath => { - return path.resolve(filePath); - }); + // jest.spyOn(pathUtils, "expandPaths").mockImplementation(filePaths => { + // return filePaths.map(x => path.resolve(x)); + // }); jest.spyOn(actionUtils, "isExactKeyMatch").mockImplementation( (key, cacheResult) => { diff --git a/__tests__/save.test.ts b/__tests__/save.test.ts index 729f423..476b1ad 100644 --- a/__tests__/save.test.ts +++ b/__tests__/save.test.ts @@ -7,11 +7,13 @@ import run from "../src/save"; import * as tar from "../src/tar"; import * as actionUtils from "../src/utils/actionUtils"; import * as testUtils from "../src/utils/testUtils"; +import * as pathUtils from "../src/utils/pathUtils"; jest.mock("@actions/core"); jest.mock("../src/cacheHttpClient"); jest.mock("../src/tar"); jest.mock("../src/utils/actionUtils"); +jest.mock("../src/utils/pathUtils"); beforeAll(() => { jest.spyOn(core, "getInput").mockImplementation((name, options) => { @@ -40,8 +42,8 @@ beforeAll(() => { return actualUtils.getSupportedEvents(); }); - jest.spyOn(actionUtils, "resolvePath").mockImplementation(filePath => { - return path.resolve(filePath); + jest.spyOn(pathUtils, "expandPaths").mockImplementation(filePaths => { + return filePaths.map(x => path.resolve(x)); }); jest.spyOn(actionUtils, "createTempDirectory").mockImplementation(() => { diff --git a/__tests__/tar.test.ts b/__tests__/tar.test.ts index 1284cc4..570f66e 100644 --- a/__tests__/tar.test.ts +++ b/__tests__/tar.test.ts @@ -14,7 +14,7 @@ beforeAll(() => { }); afterAll(() => { - process.env["GITHUB_WORKSPACE"] = undefined; + delete process.env["GITHUB_WORKSPACE"]; }); test("extract tar", async () => { diff --git a/dist/restore/index.js b/dist/restore/index.js index 199109e..08316c7 100644 --- a/dist/restore/index.js +++ b/dist/restore/index.js @@ -1649,7 +1649,6 @@ Object.defineProperty(exports, "__esModule", { value: true }); const core = __importStar(__webpack_require__(470)); const io = __importStar(__webpack_require__(1)); const fs = __importStar(__webpack_require__(747)); -const os = __importStar(__webpack_require__(87)); const path = __importStar(__webpack_require__(622)); const uuidV4 = __importStar(__webpack_require__(826)); const constants_1 = __webpack_require__(694); @@ -1720,17 +1719,6 @@ function logWarning(message) { core.info(`${warningPrefix}${message}`); } exports.logWarning = logWarning; -function resolvePath(filePath) { - if (filePath[0] === "~") { - const home = os.homedir(); - if (!home) { - throw new Error("Unable to resolve `~` to HOME"); - } - return path.join(home, filePath.slice(1)); - } - return path.resolve(filePath); -} -exports.resolvePath = resolvePath; function getSupportedEvents() { return [constants_1.Events.Push, constants_1.Events.PullRequest]; } @@ -2704,7 +2692,6 @@ var Inputs; (function (Inputs) { Inputs["Key"] = "key"; Inputs["Path"] = "path"; - Inputs["Paths"] = "paths"; Inputs["RestoreKeys"] = "restore-keys"; })(Inputs = exports.Inputs || (exports.Inputs = {})); var Outputs; @@ -2803,10 +2790,6 @@ function run() { .join(", ")} events are supported at this time.`); return; } - // const cachePath = utils.resolvePath( - // core.getInput(Inputs.Path, { required: true }) - // ); - // core.debug(`Cache Path: ${cachePath}`); const primaryKey = core.getInput(constants_1.Inputs.Key, { required: true }); core.saveState(constants_1.State.CacheKey, primaryKey); const restoreKeys = core @@ -2964,7 +2947,7 @@ function execTar(args) { } function getWorkingDirectory() { var _a; - return _a = process.env.GITHUB_WORKSPACE, (_a !== null && _a !== void 0 ? _a : process.cwd()); + return _a = process.env["GITHUB_WORKSPACE"], (_a !== null && _a !== void 0 ? _a : process.cwd()); } function extractTar(archivePath) { return __awaiter(this, void 0, void 0, function* () { @@ -2978,6 +2961,7 @@ function extractTar(archivePath) { exports.extractTar = extractTar; function createTar(archivePath, sourceDirectories) { return __awaiter(this, void 0, void 0, function* () { + // TODO: will want to stream sourceDirectories into tar const workingDirectory = getWorkingDirectory(); const args = [ "-cz", diff --git a/dist/save/index.js b/dist/save/index.js index aa8104f..1c83810 100644 --- a/dist/save/index.js +++ b/dist/save/index.js @@ -1649,7 +1649,6 @@ Object.defineProperty(exports, "__esModule", { value: true }); const core = __importStar(__webpack_require__(470)); const io = __importStar(__webpack_require__(1)); const fs = __importStar(__webpack_require__(747)); -const os = __importStar(__webpack_require__(87)); const path = __importStar(__webpack_require__(622)); const uuidV4 = __importStar(__webpack_require__(826)); const constants_1 = __webpack_require__(694); @@ -1720,17 +1719,6 @@ function logWarning(message) { core.info(`${warningPrefix}${message}`); } exports.logWarning = logWarning; -function resolvePath(filePath) { - if (filePath[0] === "~") { - const home = os.homedir(); - if (!home) { - throw new Error("Unable to resolve `~` to HOME"); - } - return path.join(home, filePath.slice(1)); - } - return path.resolve(filePath); -} -exports.resolvePath = resolvePath; function getSupportedEvents() { return [constants_1.Events.Push, constants_1.Events.PullRequest]; } @@ -2722,6 +2710,7 @@ const cacheHttpClient = __importStar(__webpack_require__(154)); const constants_1 = __webpack_require__(694); const tar_1 = __webpack_require__(943); const utils = __importStar(__webpack_require__(443)); +const pathUtils = __importStar(__webpack_require__(914)); function run() { return __awaiter(this, void 0, void 0, function* () { try { @@ -2749,11 +2738,10 @@ function run() { return; } core.debug(`Cache ID: ${cacheId}`); - const cachePaths = core - .getInput(constants_1.Inputs.Path, { required: false }) + const cachePaths = pathUtils.expandPaths(core + .getInput(constants_1.Inputs.Path) .split("\n") - .filter(x => x !== "") - .map(x => utils.resolvePath(x)); + .filter(x => x !== "")); core.debug("Cache Paths:"); core.debug(`${JSON.stringify(cachePaths)}`); const archivePath = path.join(yield utils.createTempDirectory(), "cache.tgz"); @@ -2790,7 +2778,6 @@ var Inputs; (function (Inputs) { Inputs["Key"] = "key"; Inputs["Path"] = "path"; - Inputs["Paths"] = "paths"; Inputs["RestoreKeys"] = "restore-keys"; })(Inputs = exports.Inputs || (exports.Inputs = {})); var Outputs; @@ -2891,6 +2878,67 @@ module.exports = v4; module.exports = require("url"); +/***/ }), + +/***/ 914: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const os = __importStar(__webpack_require__(87)); +const path = __importStar(__webpack_require__(622)); +const globCharacters = ["*", "?", "[", "]"]; +function resolvePath(filePath) { + if (filePath[0] === "~") { + const home = os.homedir(); + if (!home) { + throw new Error("Unable to resolve `~` to HOME"); + } + return path.join(home, filePath.slice(1)); + } + return path.resolve(filePath); +} +exports.resolvePath = resolvePath; +function isMinimatchPattern(pattern) { + if (globCharacters.some(x => pattern.includes(x))) { + return true; + } + return false; +} +exports.isMinimatchPattern = isMinimatchPattern; +function matchDirectories(pattern, workspace) { + const directories = path; +} +exports.matchDirectories = matchDirectories; +function expandPaths(filePaths) { + const paths = []; + const workspace = process.env["GITHUB_WORKSPACE"]; + for (const filePath of filePaths) { + if (isMinimatchPattern(filePath)) { + paths.push(filePath); + } + else if (filePath[0] === "~") { + const home = os.homedir(); + if (!home) { + throw new Error("Unable to resolve `~` to HOME"); + } + paths.push(path.join(home, filePath.slice(1))); + } + paths.push(path.resolve(filePath)); + } + return paths; +} +exports.expandPaths = expandPaths; + + /***/ }), /***/ 943: @@ -2948,7 +2996,7 @@ function execTar(args) { } function getWorkingDirectory() { var _a; - return _a = process.env.GITHUB_WORKSPACE, (_a !== null && _a !== void 0 ? _a : process.cwd()); + return _a = process.env["GITHUB_WORKSPACE"], (_a !== null && _a !== void 0 ? _a : process.cwd()); } function extractTar(archivePath) { return __awaiter(this, void 0, void 0, function* () { @@ -2962,6 +3010,7 @@ function extractTar(archivePath) { exports.extractTar = extractTar; function createTar(archivePath, sourceDirectories) { return __awaiter(this, void 0, void 0, function* () { + // TODO: will want to stream sourceDirectories into tar const workingDirectory = getWorkingDirectory(); const args = [ "-cz", diff --git a/package-lock.json b/package-lock.json index c15b316..002f504 100644 --- a/package-lock.json +++ b/package-lock.json @@ -537,6 +537,12 @@ "integrity": "sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==", "dev": true }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, "@types/nock": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/@types/nock/-/nock-11.1.0.tgz", @@ -900,8 +906,7 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "base": { "version": "0.11.2", @@ -977,7 +982,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1294,8 +1298,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "contains-path": { "version": "0.1.0", @@ -4238,7 +4241,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } diff --git a/package.json b/package.json index 66952be..2401f05 100644 --- a/package.json +++ b/package.json @@ -27,10 +27,12 @@ "@actions/exec": "^1.0.1", "@actions/http-client": "^1.0.6", "@actions/io": "^1.0.1", + "minimatch": "^3.0.4", "uuid": "^3.3.3" }, "devDependencies": { "@types/jest": "^24.0.13", + "@types/minimatch": "^3.0.3", "@types/nock": "^11.1.0", "@types/node": "^12.0.4", "@types/uuid": "^3.4.5", diff --git a/src/restore.ts b/src/restore.ts index 20e8151..f70c17b 100644 --- a/src/restore.ts +++ b/src/restore.ts @@ -19,11 +19,6 @@ async function run(): Promise { return; } - // const cachePath = utils.resolvePath( - // core.getInput(Inputs.Path, { required: true }) - // ); - // core.debug(`Cache Path: ${cachePath}`); - const primaryKey = core.getInput(Inputs.Key, { required: true }); core.saveState(State.CacheKey, primaryKey); diff --git a/src/save.ts b/src/save.ts index 7c32a8e..4facb1e 100644 --- a/src/save.ts +++ b/src/save.ts @@ -4,6 +4,7 @@ import * as cacheHttpClient from "./cacheHttpClient"; import { Events, Inputs, State } from "./constants"; import { createTar } from "./tar"; import * as utils from "./utils/actionUtils"; +import * as pathUtils from "./utils/pathUtils"; async function run(): Promise { try { @@ -43,11 +44,12 @@ async function run(): Promise { return; } core.debug(`Cache ID: ${cacheId}`); - const cachePaths = core - .getInput(Inputs.Path) - .split("\n") - .filter(x => x !== "") - .map(x => utils.resolvePath(x)); + const cachePaths = pathUtils.expandPaths( + core + .getInput(Inputs.Path) + .split("\n") + .filter(x => x !== "") + ); core.debug("Cache Paths:"); core.debug(`${JSON.stringify(cachePaths)}`); diff --git a/src/tar.ts b/src/tar.ts index c55e06e..0d91bfc 100644 --- a/src/tar.ts +++ b/src/tar.ts @@ -44,6 +44,7 @@ export async function createTar( archivePath: string, sourceDirectories: string[] ): Promise { + // TODO: will want to stream sourceDirectories into tar const workingDirectory = getWorkingDirectory(); const args = [ "-cz", diff --git a/src/utils/actionUtils.ts b/src/utils/actionUtils.ts index f6369fb..a3960c7 100644 --- a/src/utils/actionUtils.ts +++ b/src/utils/actionUtils.ts @@ -1,7 +1,6 @@ import * as core from "@actions/core"; import * as io from "@actions/io"; import * as fs from "fs"; -import * as os from "os"; import * as path from "path"; import * as uuidV4 from "uuid/v4"; @@ -82,18 +81,6 @@ export function logWarning(message: string): void { core.info(`${warningPrefix}${message}`); } -export function resolvePath(filePath: string): string { - if (filePath[0] === "~") { - const home = os.homedir(); - if (!home) { - throw new Error("Unable to resolve `~` to HOME"); - } - return path.join(home, filePath.slice(1)); - } - - return path.resolve(filePath); -} - export function getSupportedEvents(): string[] { return [Events.Push, Events.PullRequest]; } diff --git a/src/utils/pathUtils.ts b/src/utils/pathUtils.ts new file mode 100644 index 0000000..43c2b76 --- /dev/null +++ b/src/utils/pathUtils.ts @@ -0,0 +1,63 @@ +import * as os from "os"; +import * as path from "path"; +import { readdirSync } from "fs"; +import { IOptions, Minimatch } from "minimatch"; + +const globCharacters: string[] = ["*", "?", "[", "]"]; +const options: IOptions = { + nocase: true, + dot: true, + nobrace: true +}; + +// export function resolvePath(filePath: string): string { +// if (filePath[0] === "~") { +// const home = os.homedir(); +// if (!home) { +// throw new Error("Unable to resolve `~` to HOME"); +// } +// return path.join(home, filePath.slice(1)); +// } + +// return path.resolve(filePath); +// } + +export function isMinimatchPattern(pattern: string): boolean { + if (globCharacters.some(x => pattern.includes(x))) { + return true; + } + + return false; +} + +export function matchDirectories(pattern: string, workspace: string): string[] { + const directories = readdirSync(workspace, { withFileTypes: true }) + .filter(dirent => dirent.isDirectory()) + .map(dirent => dirent.name); + + const minimatch = new Minimatch(pattern, options); + const matches = directories.filter(x => minimatch.match(x)); + + return matches; +} + +export function expandPaths(filePaths: string[]): string[] { + const paths: string[] = []; + const workspace = process.env["GITHUB_WORKSPACE"] ?? process.cwd(); + + for (const filePath of filePaths) { + if (isMinimatchPattern(filePath)) { + paths.push(...matchDirectories(filePath, workspace)); + } else if (filePath[0] === "~") { + const home = os.homedir(); + if (!home) { + throw new Error("Unable to resolve `~` to HOME"); + } + paths.push(path.join(home, filePath.slice(1))); + } else { + paths.push(path.resolve(filePath)); + } + } + + return paths; +}