From faad9ff688eb34a9c0c8b53b60a6b5250678a0e2 Mon Sep 17 00:00:00 2001 From: usernameseb Date: Fri, 16 Apr 2021 19:24:08 -0600 Subject: [PATCH] Added options to customize the comment header/footer, include hidden files/folders with minimatch and show/hide matched paths. --- README.md | 27 +++++++ action.yml | 16 ++++ dist/index.js | 214 ++++++++++++++++++++++++++++++++++++++++---------- index.ts | 32 +++++--- package.json | 2 +- 5 files changed, 237 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 746c581..6661f20 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,35 @@ jobs: uses: wyozi/contextual-qa-checklist-action@master with: gh-token: ${{ secrets.GITHUB_TOKEN }} + # See options documentation below ``` +#### Options + +##### `comment-header` + +Overrides the default header text in the PR comment. + +##### `comment-footer` + +Overrides the default footer text in the PR comment. + +##### `include-hidden-files` + +Includes files and folders starting with `.` when matching. Defaults to `false`. + +##### `input-file` + +The path to the checklist definition file. Default to `CHECKLIST.yml` in the project root. + +##### `gh-token` + +The Github token for you project. + +##### `show-paths` + +Shows the matched file path in the PR comment. Defaults to `true`. + #### Result When matching files are updated in a PR, the action will automatically post a checklist containing items under that path's key. diff --git a/action.yml b/action.yml index 9ad0f54..332c426 100644 --- a/action.yml +++ b/action.yml @@ -1,6 +1,18 @@ name: "Contextual QA Checklists" description: "Submit a comment to PRs with a QA checklist based on context" inputs: + comment-header: + description: "Header text for the Github comment" + required: true + default: "Great PR! Please pay attention to the following items before merging:" + comment-footer: + description: "Footer text for the Github comment" + required: true + default: "This is an automatically generated QA checklist based on modified files" + include-hidden-files: + description: "Includes files and folders starting with '.' in file pattern matching" + required: true + default: false input-file: description: "Path to file containing checklist definitions" required: true @@ -8,6 +20,10 @@ inputs: gh-token: description: "GH Token" required: true + show-paths: + description: "Shows the matched file paths in the Github comment" + required: true + default: true runs: using: "node12" main: "dist/index.js" diff --git a/dist/index.js b/dist/index.js index 647beaa..cff6df0 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1083,6 +1083,32 @@ exports.default = _default; /***/ }), +/***/ 82: +/***/ (function(__unusedmodule, exports) { + +"use strict"; + +// We use any as a valid input type +/* eslint-disable @typescript-eslint/no-explicit-any */ +Object.defineProperty(exports, "__esModule", { value: true }); +/** + * Sanitizes an input into a string so it can be passed into issueCommand safely + * @param input input to sanitize into a string + */ +function toCommandValue(input) { + if (input === null || input === undefined) { + return ''; + } + else if (typeof input === 'string' || input instanceof String) { + return input; + } + return JSON.stringify(input); +} +exports.toCommandValue = toCommandValue; +//# sourceMappingURL=utils.js.map + +/***/ }), + /***/ 87: /***/ (function(module) { @@ -2018,6 +2044,42 @@ function regExpEscape (s) { } +/***/ }), + +/***/ 102: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + +// For internal use, subject to change. +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 }); +// We use any as a valid input type +/* eslint-disable @typescript-eslint/no-explicit-any */ +const fs = __importStar(__webpack_require__(747)); +const os = __importStar(__webpack_require__(87)); +const utils_1 = __webpack_require__(82); +function issueCommand(command, message) { + const filePath = process.env[`GITHUB_${command}`]; + if (!filePath) { + throw new Error(`Unable to find environment variable for file command ${command}`); + } + if (!fs.existsSync(filePath)) { + throw new Error(`Missing file at path: ${filePath}`); + } + fs.appendFileSync(filePath, `${utils_1.toCommandValue(message)}${os.EOL}`, { + encoding: 'utf8' + }); +} +exports.issueCommand = issueCommand; +//# sourceMappingURL=file-command.js.map + /***/ }), /***/ 118: @@ -8783,17 +8845,25 @@ function octokitValidate(octokit) { "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 = __webpack_require__(87); +const os = __importStar(__webpack_require__(87)); +const utils_1 = __webpack_require__(82); /** * Commands * * Command Format: - * ##[name key=value;key=value]message + * ::name key=value,key=value::message * * Examples: - * ##[warning]This is the user warning message - * ##[set-secret name=mypassword]definitelyNotAPassword! + * ::warning::This is the message + * ::set-env name=MY_VAR::some value */ function issueCommand(command, properties, message) { const cmd = new Command(command, properties, message); @@ -8818,34 +8888,39 @@ class Command { let cmdStr = CMD_STRING + this.command; if (this.properties && Object.keys(this.properties).length > 0) { cmdStr += ' '; + let first = true; for (const key in this.properties) { if (this.properties.hasOwnProperty(key)) { const val = this.properties[key]; if (val) { - // safely append the val - avoid blowing up when attempting to - // call .replace() if message is not a string for some reason - cmdStr += `${key}=${escape(`${val || ''}`)},`; + if (first) { + first = false; + } + else { + cmdStr += ','; + } + cmdStr += `${key}=${escapeProperty(val)}`; } } } } - cmdStr += CMD_STRING; - // safely append the message - avoid blowing up when attempting to - // call .replace() if message is not a string for some reason - const message = `${this.message || ''}`; - cmdStr += escapeData(message); + cmdStr += `${CMD_STRING}${escapeData(this.message)}`; return cmdStr; } } function escapeData(s) { - return s.replace(/\r/g, '%0D').replace(/\n/g, '%0A'); + return utils_1.toCommandValue(s) + .replace(/%/g, '%25') + .replace(/\r/g, '%0D') + .replace(/\n/g, '%0A'); } -function escape(s) { - return s +function escapeProperty(s) { + return utils_1.toCommandValue(s) + .replace(/%/g, '%25') .replace(/\r/g, '%0D') .replace(/\n/g, '%0A') - .replace(/]/g, '%5D') - .replace(/;/g, '%3B'); + .replace(/:/g, '%3A') + .replace(/,/g, '%2C'); } //# sourceMappingURL=command.js.map @@ -9411,6 +9486,12 @@ function convertBody(buffer, headers) { // html4 if (!res && str) { res = / `- [ ] ${item}\n`), + "\n", + ].join("") + : [...items.map((item) => `- [ ] ${item}\n`)].join(""); +} function run() { return __awaiter(this, void 0, void 0, function* () { const issue = github.context.issue; @@ -14082,7 +14220,7 @@ function run() { })).data.map(file => file.filename); const applicableChecklistPaths = Object.entries(checklistPaths).filter(([key, _]) => { for (const modifiedPath of modifiedPaths) { - if (minimatch(modifiedPath, key)) { + if (minimatch(modifiedPath, key, minimatchOptions)) { return true; } } @@ -14096,13 +14234,7 @@ function run() { if (applicableChecklistPaths.length > 0) { const body = [ `${header}\n\n`, - ...applicableChecklistPaths.map(([path, items]) => { - return [ - `__Files matching \`${path}\`:__\n`, - ...items.map(item => `- [ ] ${item}\n`), - "\n" - ].join(""); - }), + ...applicableChecklistPaths.map(formatItemsForPath), `\n${footer}` ].join(""); if (existingComment) { diff --git a/index.ts b/index.ts index 88ee918..066166d 100644 --- a/index.ts +++ b/index.ts @@ -4,10 +4,12 @@ const YAML = require("yaml"); const minimatch = require("minimatch"); const { readFileSync } = require("fs"); -const header = - "Great PR! Please pay attention to the following items before merging:"; -const footer = - "This is an automatically generated QA checklist based on modified files"; +const header = core.getInput("comment-header"); +const footer = core.getInput("comment-footer") + +const minimatchOptions = { + dot: core.getInput('include-hidden-files') === 'true' +}; function getChecklistPaths(): Record { const inputFile = core.getInput("input-file"); @@ -15,6 +17,18 @@ function getChecklistPaths(): Record { return parsedFile.paths; } +function formatItemsForPath([path, items]): string { + const showPaths = core.getInput("show-paths") === 'true'; + + return showPaths + ? [ + `__Files matching \`${path}\`:__\n`, + ...items.map((item) => `- [ ] ${item}\n`), + "\n", + ].join("") + : [...items.map((item) => `- [ ] ${item}\n`)].join(""); +} + async function run() { const issue: { owner: string; repo: string; number: number } = github.context.issue; @@ -34,7 +48,7 @@ async function run() { const applicableChecklistPaths = Object.entries(checklistPaths).filter( ([key, _]) => { for (const modifiedPath of modifiedPaths) { - if (minimatch(modifiedPath, key)) { + if (minimatch(modifiedPath, key, minimatchOptions)) { return true; } } @@ -53,13 +67,7 @@ async function run() { if (applicableChecklistPaths.length > 0) { const body = [ `${header}\n\n`, - ...applicableChecklistPaths.map(([path, items]) => { - return [ - `__Files matching \`${path}\`:__\n`, - ...items.map(item => `- [ ] ${item}\n`), - "\n" - ].join(""); - }), + ...applicableChecklistPaths.map(formatItemsForPath), `\n${footer}` ].join(""); diff --git a/package.json b/package.json index 3e844e5..f8dc711 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "contextual-qa-checklist-action", - "version": "1.0.0", + "version": "1.1.0", "main": "index.js", "repository": { "url": "git@github.com:wyozi/contextual-qa-checklist-action.git",