Skip to content

Commit

Permalink
Fix globbing, update metadata, update ncc (#4)
Browse files Browse the repository at this point in the history
* Enable minimatch dot option
It's not a default globbing behavior, however for our use-case is much more convenient to match those files.

* Update README and package.json
  • Loading branch information
dorny authored May 21, 2020
1 parent f9b6173 commit 29d498d
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 129 deletions.
7 changes: 4 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: "build-test"
on: # rebuild any PRs and main branch changes
on:
pull_request:
types:
- opened
Expand All @@ -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
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
...
Expand Down Expand Up @@ -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)
10 changes: 10 additions & 0 deletions __tests__/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
})
})
237 changes: 123 additions & 114 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -296,9 +302,9 @@ module.exports = require("tls");
/***/ }),

/***/ 18:
/***/ (function() {
/***/ (function(module) {

eval("require")("encoding");
module.exports = eval("require")("encoding");


/***/ }),
Expand Down Expand Up @@ -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();


/***/ }),
Expand Down Expand Up @@ -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;


/***/ }),
Expand Down
10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -49,5 +49,5 @@
},
"jest": {
"testEnvironment": "node"
}
}
}
6 changes: 5 additions & 1 deletion src/filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand All @@ -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))
}
}

Expand Down

0 comments on commit 29d498d

Please sign in to comment.