diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4f7501c..0467317 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,5 +1,5 @@ name: "build-test" -on: # rebuild any PRs and main branch changes +on: pull_request: types: - opened @@ -8,14 +8,15 @@ on: # rebuild any PRs and main branch changes - master jobs: - build: # make sure build/ci work properly + build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - run: | npm install npm run all - test: # make sure the action works without building + + test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 diff --git a/README.md b/README.md index faf286b..0883c5b 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,10 @@ Output variables can be later used in the `if` clause to conditionally run speci - `'false'` - if **none** of changed files matches any of rule patterns +### Notes +- minimatch [dot](https://www.npmjs.com/package/minimatch#dot) option is set to true - therefore + globbing will match also paths where file or folder name starts with a dot. + ### Sample workflow ```yaml ... @@ -94,4 +98,4 @@ jobs: - [Changed File Filter](https://github.com/tony84727/changed-file-filter) - allows change detection between any refs or commits - fetches whole history of your git repository - - might have negative performance impact on big repositories (github by default fetches only single commit) + - might have negative performance impact on big repositories (github by default fetches only single commit) \ No newline at end of file diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts index 9e0d18a..052ca51 100644 --- a/__tests__/main.test.ts +++ b/__tests__/main.test.ts @@ -80,4 +80,14 @@ describe('matching tests', () => { const match = filter.match(['test/test.js']) expect(match.any).toBeTruthy() }) + + test('globbing matches path where file or folder name starts with dot', () => { + const yaml = ` + dot: + - "**/*.js" + ` + const filter = new Filter(yaml) + const match = filter.match(['.test/.test.js']) + expect(match.dot).toBeTruthy() + }) }) diff --git a/dist/index.js b/dist/index.js index 97b6790..4a273a0 100644 --- a/dist/index.js +++ b/dist/index.js @@ -19,7 +19,13 @@ module.exports = /******/ }; /******/ /******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ var threw = true; +/******/ try { +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ threw = false; +/******/ } finally { +/******/ if(threw) delete installedModules[moduleId]; +/******/ } /******/ /******/ // Flag the module as loaded /******/ module.l = true; @@ -296,9 +302,9 @@ module.exports = require("tls"); /***/ }), /***/ 18: -/***/ (function() { +/***/ (function(module) { -eval("require")("encoding"); +module.exports = eval("require")("encoding"); /***/ }), @@ -4090,74 +4096,74 @@ function checkMode (stat, options) { /***/ (function(__unusedmodule, exports, __webpack_require__) { "use strict"; - -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -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; -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const core = __importStar(__webpack_require__(470)); -const github = __importStar(__webpack_require__(469)); -const filter_1 = __importDefault(__webpack_require__(235)); -function run() { - return __awaiter(this, void 0, void 0, function* () { - try { - const token = core.getInput('githubToken', { required: true }); - const filterYaml = core.getInput('filters', { required: true }); - const client = new github.GitHub(token); - if (github.context.eventName !== 'pull_request') { - core.setFailed('This action can be triggered only by pull_request event'); - return; - } - const pr = github.context.payload.pull_request; - const filter = new filter_1.default(filterYaml); - const files = yield getChangedFiles(client, pr); - const result = filter.match(files); - for (const key in result) { - core.setOutput(key, String(result[key])); - } - } - catch (error) { - core.setFailed(error.message); - } - }); -} -// Uses github REST api to get list of files changed in PR -function getChangedFiles(client, pullRequest) { - return __awaiter(this, void 0, void 0, function* () { - const pageSize = 100; - const files = []; - for (let page = 0; page * pageSize < pullRequest.changed_files; page++) { - const response = yield client.pulls.listFiles({ - owner: github.context.repo.owner, - repo: github.context.repo.repo, - pull_number: pullRequest.number, - page, - per_page: pageSize - }); - for (const row of response.data) { - files.push(row.filename); - } - } - return files; - }); -} -run(); + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +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; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const core = __importStar(__webpack_require__(470)); +const github = __importStar(__webpack_require__(469)); +const filter_1 = __importDefault(__webpack_require__(235)); +function run() { + return __awaiter(this, void 0, void 0, function* () { + try { + const token = core.getInput('githubToken', { required: true }); + const filterYaml = core.getInput('filters', { required: true }); + const client = new github.GitHub(token); + if (github.context.eventName !== 'pull_request') { + core.setFailed('This action can be triggered only by pull_request event'); + return; + } + const pr = github.context.payload.pull_request; + const filter = new filter_1.default(filterYaml); + const files = yield getChangedFiles(client, pr); + const result = filter.match(files); + for (const key in result) { + core.setOutput(key, String(result[key])); + } + } + catch (error) { + core.setFailed(error.message); + } + }); +} +// Uses github REST api to get list of files changed in PR +function getChangedFiles(client, pullRequest) { + return __awaiter(this, void 0, void 0, function* () { + const pageSize = 100; + const files = []; + for (let page = 0; page * pageSize < pullRequest.changed_files; page++) { + const response = yield client.pulls.listFiles({ + owner: github.context.repo.owner, + repo: github.context.repo.repo, + pull_number: pullRequest.number, + page, + per_page: pageSize + }); + for (const row of response.data) { + files.push(row.filename); + } + } + return files; + }); +} +run(); /***/ }), @@ -4223,49 +4229,52 @@ module.exports = new Type('tag:yaml.org,2002:bool', { /***/ (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 jsyaml = __importStar(__webpack_require__(414)); -const minimatch = __importStar(__webpack_require__(595)); -class Filter { - constructor(yaml) { - this.rules = {}; - const doc = jsyaml.safeLoad(yaml); - if (typeof doc !== 'object') { - this.throwInvalidFormatError(); - } - for (const name of Object.keys(doc)) { - const patterns = doc[name]; - if (!Array.isArray(patterns)) { - this.throwInvalidFormatError(); - } - if (!patterns.every(x => typeof x === 'string')) { - this.throwInvalidFormatError(); - } - this.rules[name] = patterns.map(x => new minimatch.Minimatch(x)); - } - } - // Returns dictionary with match result per rules group - match(paths) { - const result = {}; - for (const [key, patterns] of Object.entries(this.rules)) { - const match = paths.some(fileName => patterns.some(rule => rule.match(fileName))); - result[key] = match; - } - return result; - } - throwInvalidFormatError() { - throw new Error('Invalid filter YAML format: Expected dictionary of string arrays'); - } -} -exports.default = Filter; + +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 jsyaml = __importStar(__webpack_require__(414)); +const minimatch = __importStar(__webpack_require__(595)); +class Filter { + constructor(yaml) { + this.rules = {}; + const doc = jsyaml.safeLoad(yaml); + if (typeof doc !== 'object') { + this.throwInvalidFormatError(); + } + const opts = { + dot: true + }; + for (const name of Object.keys(doc)) { + const patterns = doc[name]; + if (!Array.isArray(patterns)) { + this.throwInvalidFormatError(); + } + if (!patterns.every(x => typeof x === 'string')) { + this.throwInvalidFormatError(); + } + this.rules[name] = patterns.map(x => new minimatch.Minimatch(x, opts)); + } + } + // Returns dictionary with match result per rules group + match(paths) { + const result = {}; + for (const [key, patterns] of Object.entries(this.rules)) { + const match = paths.some(fileName => patterns.some(rule => rule.match(fileName))); + result[key] = match; + } + return result; + } + throwInvalidFormatError() { + throw new Error('Invalid filter YAML format: Expected dictionary of string arrays'); + } +} +exports.default = Filter; /***/ }), diff --git a/package-lock.json b/package-lock.json index 9ac11c8..6826ee4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { - "name": "typescript-action", - "version": "0.0.0", + "name": "pr-changed-files-filter", + "version": "1.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -878,9 +878,9 @@ } }, "@zeit/ncc": { - "version": "0.20.5", - "resolved": "https://registry.npmjs.org/@zeit/ncc/-/ncc-0.20.5.tgz", - "integrity": "sha512-XU6uzwvv95DqxciQx+aOLhbyBx/13ky+RK1y88Age9Du3BlA4mMPCy13BGjayOrrumOzlq1XV3SD/BWiZENXlw==", + "version": "0.22.2", + "resolved": "https://registry.npmjs.org/@zeit/ncc/-/ncc-0.22.2.tgz", + "integrity": "sha512-LGW5RPIwulh+fyKiMTgbIEbdc/hdf/4+U5B1WbT/We0jweHoywDbk9hi+3hNjSzQNShGumP72zgc6PHEUG5syg==", "dev": true }, "abab": { diff --git a/package.json b/package.json index 18da2fe..8b421da 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "typescript-action", - "version": "0.0.0", + "name": "pr-changed-files-filter", + "version": "1.0.0", "private": true, - "description": "TypeScript template action", + "description": "Enables conditional execution of workflow job steps considering which files are modified by a pull request.", "main": "lib/main.js", "scripts": { "build": "tsc", @@ -36,7 +36,7 @@ "@types/minimatch": "^3.0.3", "@types/node": "^12.7.12", "@typescript-eslint/parser": "^2.8.0", - "@zeit/ncc": "^0.20.5", + "@zeit/ncc": "^0.22.2", "eslint": "^5.16.0", "eslint-plugin-github": "^2.0.0", "eslint-plugin-jest": "^22.21.0", @@ -49,5 +49,5 @@ }, "jest": { "testEnvironment": "node" - } + } } diff --git a/src/filter.ts b/src/filter.ts index 7cf30ab..6815002 100644 --- a/src/filter.ts +++ b/src/filter.ts @@ -10,6 +10,10 @@ export default class Filter { this.throwInvalidFormatError() } + const opts: minimatch.IOptions = { + dot: true + } + for (const name of Object.keys(doc)) { const patterns = doc[name] as string[] if (!Array.isArray(patterns)) { @@ -18,7 +22,7 @@ export default class Filter { if (!patterns.every(x => typeof x === 'string')) { this.throwInvalidFormatError() } - this.rules[name] = patterns.map(x => new minimatch.Minimatch(x)) + this.rules[name] = patterns.map(x => new minimatch.Minimatch(x, opts)) } }