diff --git a/.github/workflows/basic-validation.yml b/.github/workflows/basic-validation.yml new file mode 100644 index 0000000..5e5029a --- /dev/null +++ b/.github/workflows/basic-validation.yml @@ -0,0 +1,17 @@ +name: Basic validation + +on: + pull_request: + paths-ignore: + - '**.md' + push: + branches: + - main + - releases/* + paths-ignore: + - '**.md' + +jobs: + call-basic-validation: + name: Basic validation + uses: actions/reusable-workflows/.github/workflows/basic-validation.yml@main \ No newline at end of file diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml deleted file mode 100644 index 27858e4..0000000 --- a/.github/workflows/build_test.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Build & Test - -on: - pull_request: - paths-ignore: - - '**.md' - push: - branches: - - main - - releases/* - paths-ignore: - - '**.md' - -jobs: - build: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - steps: - - uses: actions/checkout@v3 - - name: Setup node 16 - uses: actions/setup-node@v3 - with: - node-version: 16.x - - run: npm install - - run: npm run build - - run: npm test diff --git a/.github/workflows/check-dist.yml b/.github/workflows/check-dist.yml index 1019cac..1251c11 100644 --- a/.github/workflows/check-dist.yml +++ b/.github/workflows/check-dist.yml @@ -1,8 +1,3 @@ -# `dist/index.js` is a special file in Actions. -# When you reference an action with `uses:` in a workflow, -# `index.js` is the code that will run. -# For our project, we generate this file through a build process from other source files. -# We need to make sure the checked-in `index.js` actually matches what we expect it to be. name: Check dist/ on: @@ -17,36 +12,6 @@ on: workflow_dispatch: jobs: - check-dist: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - - name: Set Node.js 16.x - uses: actions/setup-node@v3 - with: - node-version: 16.x - cache: npm - - - name: Install dependencies - run: npm ci - - - name: Rebuild the dist/ directory - run: npm run build - - - name: Compare the expected and actual dist/ directories - run: | - if [ "$(git diff --ignore-space-at-eol dist/ | wc -l)" -gt "0" ]; then - echo "Detected uncommitted changes after build. See status below:" - git diff - exit 1 - fi - id: diff - - # If index.js was different than expected, upload the expected version as an artifact - - uses: actions/upload-artifact@v3 - if: ${{ failure() && steps.diff.conclusion == 'failure' }} - with: - name: dist - path: dist/ + call-check-dist: + name: Check dist/ + uses: actions/reusable-workflows/.github/workflows/check-dist.yml@main \ No newline at end of file diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..f1f430a --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,14 @@ +name: CodeQL analysis + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + schedule: + - cron: '0 3 * * 0' + +jobs: + call-codeQL-analysis: + name: CodeQL analysis + uses: actions/reusable-workflows/.github/workflows/codeql-analysis.yml@main \ No newline at end of file diff --git a/.github/workflows/licensed.yml b/.github/workflows/licensed.yml new file mode 100644 index 0000000..42084b2 --- /dev/null +++ b/.github/workflows/licensed.yml @@ -0,0 +1,15 @@ +name: Licensed + +on: + push: + branches: + - main + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + call-licensed: + name: Licensed + uses: actions/reusable-workflows/.github/workflows/licensed.yml@main \ No newline at end of file diff --git a/.github/workflows/release-new-action-version.yml b/.github/workflows/release-new-action-version.yml index b8076d4..a187d1a 100644 --- a/.github/workflows/release-new-action-version.yml +++ b/.github/workflows/release-new-action-version.yml @@ -1,4 +1,5 @@ name: Release new action version + on: release: types: [released] diff --git a/.github/workflows/update_and_check_licenses.yml b/.github/workflows/update_and_check_licenses.yml deleted file mode 100644 index 24d0131..0000000 --- a/.github/workflows/update_and_check_licenses.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Licenses - -on: - push: - branches: [main] - paths: [package.json, package-lock.json] - pull_request: - branches: [main] - paths: [package.json, package-lock.json] - workflow_dispatch: - -jobs: - # Updates our cache of license files in response to changes to our dependencies - # declared in package-lock.json. Automatically commits the changes and pushes - # them to your branch. NB `check_license_status` should always run *after* this - # - # see https://github.com/actions/labeler/pull/155 for more context - update_licenses: - runs-on: ubuntu-latest - name: Update Licenses - steps: - - uses: actions/checkout@v1 - - uses: jonabc/setup-licensed@v1 - with: - version: '3.x' - github_token: ${{ secrets.GITHUB_TOKEN }} - - run: npm install --production - - uses: jonabc/licensed-ci@v1 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - - # Fails if any of our dependencies have licenses that our incompatible with our - # requirements (see .licensed.yml) OR if any of our dependencies have been - # upgraded to a new version without us having updated their corresponding - # license metadata file in .licenses/ - # - # see https://github.com/actions/labeler/pull/91 for more context - check_licenses: - needs: update_licenses - if: always() # always run after we update the license cache. if it failed, we will probably just fail as well - runs-on: ubuntu-latest - name: Check Licenses - steps: - - uses: actions/checkout@v2 - - uses: jonabc/setup-licensed@v1.0.2 - with: - version: '3.x' - github_token: ${{ secrets.GITHUB_TOKEN }} - - run: npm install - - run: licensed status diff --git a/.licenses/npm/minimatch.dep.yml b/.licenses/npm/minimatch.dep.yml index 89db6e0..a4ff623 100644 --- a/.licenses/npm/minimatch.dep.yml +++ b/.licenses/npm/minimatch.dep.yml @@ -1,6 +1,6 @@ --- name: minimatch -version: 3.0.4 +version: 3.1.2 type: npm summary: a glob matcher in javascript homepage: diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..ca09262 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,12 @@ +{ + "printWidth": 80, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": true, + "trailingComma": "none", + "bracketSpacing": false, + "arrowParens": "avoid", + "parser": "typescript", + "endOfLine": "auto" + } \ No newline at end of file diff --git a/__mocks__/@actions/github.ts b/__mocks__/@actions/github.ts index d423a29..ea8319f 100644 --- a/__mocks__/@actions/github.ts +++ b/__mocks__/@actions/github.ts @@ -1,34 +1,34 @@ export const context = { payload: { pull_request: { - number: 123, - }, + number: 123 + } }, repo: { - owner: "monalisa", - repo: "helloworld", - }, + owner: 'monalisa', + repo: 'helloworld' + } }; const mockApi = { rest: { issues: { addLabels: jest.fn(), - removeLabel: jest.fn(), + removeLabel: jest.fn() }, pulls: { get: jest.fn().mockResolvedValue({}), listFiles: { endpoint: { - merge: jest.fn().mockReturnValue({}), - }, - }, + merge: jest.fn().mockReturnValue({}) + } + } }, repos: { - getContent: jest.fn(), - }, + getContent: jest.fn() + } }, - paginate: jest.fn(), + paginate: jest.fn() }; export const getOctokit = jest.fn().mockImplementation(() => mockApi); diff --git a/__tests__/labeler.test.ts b/__tests__/labeler.test.ts index 082ed67..d684d28 100644 --- a/__tests__/labeler.test.ts +++ b/__tests__/labeler.test.ts @@ -1,27 +1,27 @@ -import { checkGlobs } from "../src/labeler"; +import {checkGlobs} from '../src/labeler'; -import * as core from "@actions/core"; +import * as core from '@actions/core'; -jest.mock("@actions/core"); +jest.mock('@actions/core'); beforeAll(() => { - jest.spyOn(core, "getInput").mockImplementation((name, options) => { - return jest.requireActual("@actions/core").getInput(name, options); + jest.spyOn(core, 'getInput').mockImplementation((name, options) => { + return jest.requireActual('@actions/core').getInput(name, options); }); }); -const matchConfig = [{ any: ["*.txt"] }]; +const matchConfig = [{any: ['*.txt']}]; -describe("checkGlobs", () => { - it("returns true when our pattern does match changed files", () => { - const changedFiles = ["foo.txt", "bar.txt"]; +describe('checkGlobs', () => { + it('returns true when our pattern does match changed files', () => { + const changedFiles = ['foo.txt', 'bar.txt']; const result = checkGlobs(changedFiles, matchConfig); expect(result).toBeTruthy(); }); - it("returns false when our pattern does not match changed files", () => { - const changedFiles = ["foo.docx"]; + it('returns false when our pattern does not match changed files', () => { + const changedFiles = ['foo.docx']; const result = checkGlobs(changedFiles, matchConfig); expect(result).toBeFalsy(); diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts index de14559..e886a02 100644 --- a/__tests__/main.test.ts +++ b/__tests__/main.test.ts @@ -1,45 +1,45 @@ -import { run } from "../src/labeler"; -import * as github from "@actions/github"; -import * as core from "@actions/core"; +import {run} from '../src/labeler'; +import * as github from '@actions/github'; +import * as core from '@actions/core'; -const fs = jest.requireActual("fs"); +const fs = jest.requireActual('fs'); -jest.mock("@actions/core"); -jest.mock("@actions/github"); +jest.mock('@actions/core'); +jest.mock('@actions/github'); -const gh = github.getOctokit("_"); -const addLabelsMock = jest.spyOn(gh.rest.issues, "addLabels"); -const removeLabelMock = jest.spyOn(gh.rest.issues, "removeLabel"); -const reposMock = jest.spyOn(gh.rest.repos, "getContent"); -const paginateMock = jest.spyOn(gh, "paginate"); -const getPullMock = jest.spyOn(gh.rest.pulls, "get"); +const gh = github.getOctokit('_'); +const addLabelsMock = jest.spyOn(gh.rest.issues, 'addLabels'); +const removeLabelMock = jest.spyOn(gh.rest.issues, 'removeLabel'); +const reposMock = jest.spyOn(gh.rest.repos, 'getContent'); +const paginateMock = jest.spyOn(gh, 'paginate'); +const getPullMock = jest.spyOn(gh.rest.pulls, 'get'); const yamlFixtures = { - "only_pdfs.yml": fs.readFileSync("__tests__/fixtures/only_pdfs.yml"), + 'only_pdfs.yml': fs.readFileSync('__tests__/fixtures/only_pdfs.yml') }; afterAll(() => jest.restoreAllMocks()); -describe("run", () => { - it("adds labels to PRs that match our glob patterns", async () => { - usingLabelerConfigYaml("only_pdfs.yml"); - mockGitHubResponseChangedFiles("foo.pdf"); +describe('run', () => { + it('adds labels to PRs that match our glob patterns', async () => { + usingLabelerConfigYaml('only_pdfs.yml'); + mockGitHubResponseChangedFiles('foo.pdf'); await run(); expect(removeLabelMock).toHaveBeenCalledTimes(0); expect(addLabelsMock).toHaveBeenCalledTimes(1); expect(addLabelsMock).toHaveBeenCalledWith({ - owner: "monalisa", - repo: "helloworld", + owner: 'monalisa', + repo: 'helloworld', issue_number: 123, - labels: ["touched-a-pdf-file"], + labels: ['touched-a-pdf-file'] }); }); - it("does not add labels to PRs that do not match our glob patterns", async () => { - usingLabelerConfigYaml("only_pdfs.yml"); - mockGitHubResponseChangedFiles("foo.txt"); + it('does not add labels to PRs that do not match our glob patterns', async () => { + usingLabelerConfigYaml('only_pdfs.yml'); + mockGitHubResponseChangedFiles('foo.txt'); await run(); @@ -47,23 +47,23 @@ describe("run", () => { expect(addLabelsMock).toHaveBeenCalledTimes(0); }); - it("(with sync-labels: true) it deletes preexisting PR labels that no longer match the glob pattern", async () => { + it('(with sync-labels: true) it deletes preexisting PR labels that no longer match the glob pattern', async () => { let mockInput = { - "repo-token": "foo", - "configuration-path": "bar", - "sync-labels": true, + 'repo-token': 'foo', + 'configuration-path': 'bar', + 'sync-labels': true }; jest - .spyOn(core, "getInput") + .spyOn(core, 'getInput') .mockImplementation((name: string, ...opts) => mockInput[name]); - usingLabelerConfigYaml("only_pdfs.yml"); - mockGitHubResponseChangedFiles("foo.txt"); + usingLabelerConfigYaml('only_pdfs.yml'); + mockGitHubResponseChangedFiles('foo.txt'); getPullMock.mockResolvedValue({ data: { - labels: [{ name: "touched-a-pdf-file" }], - }, + labels: [{name: 'touched-a-pdf-file'}] + } }); await run(); @@ -71,30 +71,30 @@ describe("run", () => { expect(addLabelsMock).toHaveBeenCalledTimes(0); expect(removeLabelMock).toHaveBeenCalledTimes(1); expect(removeLabelMock).toHaveBeenCalledWith({ - owner: "monalisa", - repo: "helloworld", + owner: 'monalisa', + repo: 'helloworld', issue_number: 123, - name: "touched-a-pdf-file", + name: 'touched-a-pdf-file' }); }); - it("(with sync-labels: false) it issues no delete calls even when there are preexisting PR labels that no longer match the glob pattern", async () => { + it('(with sync-labels: false) it issues no delete calls even when there are preexisting PR labels that no longer match the glob pattern', async () => { let mockInput = { - "repo-token": "foo", - "configuration-path": "bar", - "sync-labels": false, + 'repo-token': 'foo', + 'configuration-path': 'bar', + 'sync-labels': false }; jest - .spyOn(core, "getInput") + .spyOn(core, 'getInput') .mockImplementation((name: string, ...opts) => mockInput[name]); - usingLabelerConfigYaml("only_pdfs.yml"); - mockGitHubResponseChangedFiles("foo.txt"); + usingLabelerConfigYaml('only_pdfs.yml'); + mockGitHubResponseChangedFiles('foo.txt'); getPullMock.mockResolvedValue({ data: { - labels: [{ name: "touched-a-pdf-file" }], - }, + labels: [{name: 'touched-a-pdf-file'}] + } }); await run(); @@ -106,11 +106,11 @@ describe("run", () => { function usingLabelerConfigYaml(fixtureName: keyof typeof yamlFixtures): void { reposMock.mockResolvedValue({ - data: { content: yamlFixtures[fixtureName], encoding: "utf8" }, + data: {content: yamlFixtures[fixtureName], encoding: 'utf8'} }); } function mockGitHubResponseChangedFiles(...files: string[]): void { - const returnValue = files.map((f) => ({ filename: f })); + const returnValue = files.map(f => ({filename: f})); paginateMock.mockReturnValue(returnValue); } diff --git a/dist/index.js b/dist/index.js index 6bf3651..11bbe36 100644 --- a/dist/index.js +++ b/dist/index.js @@ -47,19 +47,19 @@ const minimatch_1 = __nccwpck_require__(3973); function run() { return __awaiter(this, void 0, void 0, function* () { try { - const token = core.getInput("repo-token", { required: true }); - const configPath = core.getInput("configuration-path", { required: true }); - const syncLabels = !!core.getInput("sync-labels", { required: false }); + const token = core.getInput('repo-token', { required: true }); + const configPath = core.getInput('configuration-path', { required: true }); + const syncLabels = !!core.getInput('sync-labels', { required: false }); const prNumber = getPrNumber(); if (!prNumber) { - console.log("Could not get pull request number from context, exiting"); + console.log('Could not get pull request number from context, exiting'); return; } const client = github.getOctokit(token); const { data: pullRequest } = yield client.rest.pulls.get({ owner: github.context.repo.owner, repo: github.context.repo.repo, - pull_number: prNumber, + pull_number: prNumber }); core.debug(`fetching changed files for pr #${prNumber}`); const changedFiles = yield getChangedFiles(client, prNumber); @@ -71,7 +71,7 @@ function run() { if (checkGlobs(changedFiles, globs)) { labels.push(label); } - else if (pullRequest.labels.find((l) => l.name === label)) { + else if (pullRequest.labels.find(l => l.name === label)) { labelsToRemove.push(label); } } @@ -101,13 +101,13 @@ function getChangedFiles(client, prNumber) { const listFilesOptions = client.rest.pulls.listFiles.endpoint.merge({ owner: github.context.repo.owner, repo: github.context.repo.repo, - pull_number: prNumber, + pull_number: prNumber }); const listFilesResponse = yield client.paginate(listFilesOptions); const changedFiles = listFilesResponse.map((f) => f.filename); - core.debug("found changed files:"); + core.debug('found changed files:'); for (const file of changedFiles) { - core.debug(" " + file); + core.debug(' ' + file); } return changedFiles; }); @@ -127,7 +127,7 @@ function fetchContent(client, repoPath) { owner: github.context.repo.owner, repo: github.context.repo.repo, path: repoPath, - ref: github.context.sha, + ref: github.context.sha }); return Buffer.from(response.data.content, response.data.encoding).toString(); }); @@ -135,7 +135,7 @@ function fetchContent(client, repoPath) { function getLabelGlobMapFromObject(configObject) { const labelGlobs = new Map(); for (const label in configObject) { - if (typeof configObject[label] === "string") { + if (typeof configObject[label] === 'string') { labelGlobs.set(label, [configObject[label]]); } else if (configObject[label] instanceof Array) { @@ -148,15 +148,15 @@ function getLabelGlobMapFromObject(configObject) { return labelGlobs; } function toMatchConfig(config) { - if (typeof config === "string") { + if (typeof config === 'string') { return { - any: [config], + any: [config] }; } return config; } function printPattern(matcher) { - return (matcher.negate ? "!" : "") + matcher.pattern; + return (matcher.negate ? '!' : '') + matcher.pattern; } function checkGlobs(changedFiles, globs) { for (const glob of globs) { @@ -183,7 +183,7 @@ function isMatch(changedFile, matchers) { } // equivalent to "Array.some()" but expanded for debugging and clarity function checkAny(changedFiles, globs) { - const matchers = globs.map((g) => new minimatch_1.Minimatch(g)); + const matchers = globs.map(g => new minimatch_1.Minimatch(g)); core.debug(` checking "any" patterns`); for (const changedFile of changedFiles) { if (isMatch(changedFile, matchers)) { @@ -196,7 +196,7 @@ function checkAny(changedFiles, globs) { } // equivalent to "Array.every()" but expanded for debugging and clarity function checkAll(changedFiles, globs) { - const matchers = globs.map((g) => new minimatch_1.Minimatch(g)); + const matchers = globs.map(g => new minimatch_1.Minimatch(g)); core.debug(` checking "all" patterns`); for (const changedFile of changedFiles) { if (!isMatch(changedFile, matchers)) { @@ -226,17 +226,17 @@ function addLabels(client, prNumber, labels) { owner: github.context.repo.owner, repo: github.context.repo.repo, issue_number: prNumber, - labels: labels, + labels: labels }); }); } function removeLabels(client, prNumber, labels) { return __awaiter(this, void 0, void 0, function* () { - yield Promise.all(labels.map((label) => client.rest.issues.removeLabel({ + yield Promise.all(labels.map(label => client.rest.issues.removeLabel({ owner: github.context.repo.owner, repo: github.context.repo.repo, issue_number: prNumber, - name: label, + name: label }))); }); } @@ -10010,10 +10010,10 @@ module.exports = new Type('tag:yaml.org,2002:timestamp', { module.exports = minimatch minimatch.Minimatch = Minimatch -var path = { sep: '/' } -try { - path = __nccwpck_require__(1017) -} catch (er) {} +var path = (function () { try { return __nccwpck_require__(1017) } catch (e) {}}()) || { + sep: '/' +} +minimatch.sep = path.sep var GLOBSTAR = minimatch.GLOBSTAR = Minimatch.GLOBSTAR = {} var expand = __nccwpck_require__(3717) @@ -10065,43 +10065,64 @@ function filter (pattern, options) { } function ext (a, b) { - a = a || {} b = b || {} var t = {} - Object.keys(b).forEach(function (k) { - t[k] = b[k] - }) Object.keys(a).forEach(function (k) { t[k] = a[k] }) + Object.keys(b).forEach(function (k) { + t[k] = b[k] + }) return t } minimatch.defaults = function (def) { - if (!def || !Object.keys(def).length) return minimatch + if (!def || typeof def !== 'object' || !Object.keys(def).length) { + return minimatch + } var orig = minimatch var m = function minimatch (p, pattern, options) { - return orig.minimatch(p, pattern, ext(def, options)) + return orig(p, pattern, ext(def, options)) } m.Minimatch = function Minimatch (pattern, options) { return new orig.Minimatch(pattern, ext(def, options)) } + m.Minimatch.defaults = function defaults (options) { + return orig.defaults(ext(def, options)).Minimatch + } + + m.filter = function filter (pattern, options) { + return orig.filter(pattern, ext(def, options)) + } + + m.defaults = function defaults (options) { + return orig.defaults(ext(def, options)) + } + + m.makeRe = function makeRe (pattern, options) { + return orig.makeRe(pattern, ext(def, options)) + } + + m.braceExpand = function braceExpand (pattern, options) { + return orig.braceExpand(pattern, ext(def, options)) + } + + m.match = function (list, pattern, options) { + return orig.match(list, pattern, ext(def, options)) + } return m } Minimatch.defaults = function (def) { - if (!def || !Object.keys(def).length) return Minimatch return minimatch.defaults(def).Minimatch } function minimatch (p, pattern, options) { - if (typeof pattern !== 'string') { - throw new TypeError('glob pattern string required') - } + assertValidPattern(pattern) if (!options) options = {} @@ -10110,9 +10131,6 @@ function minimatch (p, pattern, options) { return false } - // "" only matches "" - if (pattern.trim() === '') return p === '' - return new Minimatch(pattern, options).match(p) } @@ -10121,15 +10139,14 @@ function Minimatch (pattern, options) { return new Minimatch(pattern, options) } - if (typeof pattern !== 'string') { - throw new TypeError('glob pattern string required') - } + assertValidPattern(pattern) if (!options) options = {} + pattern = pattern.trim() // windows support: need to use /, not \ - if (path.sep !== '/') { + if (!options.allowWindowsEscape && path.sep !== '/') { pattern = pattern.split(path.sep).join('/') } @@ -10140,6 +10157,7 @@ function Minimatch (pattern, options) { this.negate = false this.comment = false this.empty = false + this.partial = !!options.partial // make the set of regexps etc. this.make() @@ -10149,9 +10167,6 @@ Minimatch.prototype.debug = function () {} Minimatch.prototype.make = make function make () { - // don't do it more than once. - if (this._made) return - var pattern = this.pattern var options = this.options @@ -10171,7 +10186,7 @@ function make () { // step 2: expand braces var set = this.globSet = this.braceExpand() - if (options.debug) this.debug = console.error + if (options.debug) this.debug = function debug() { console.error.apply(console, arguments) } this.debug(this.pattern, set) @@ -10251,12 +10266,11 @@ function braceExpand (pattern, options) { pattern = typeof pattern === 'undefined' ? this.pattern : pattern - if (typeof pattern === 'undefined') { - throw new TypeError('undefined pattern') - } + assertValidPattern(pattern) - if (options.nobrace || - !pattern.match(/\{.*\}/)) { + // Thanks to Yeting Li for + // improving this regexp to avoid a ReDOS vulnerability. + if (options.nobrace || !/\{(?:(?!\{).)*\}/.test(pattern)) { // shortcut. no need to expand. return [pattern] } @@ -10264,6 +10278,17 @@ function braceExpand (pattern, options) { return expand(pattern) } +var MAX_PATTERN_LENGTH = 1024 * 64 +var assertValidPattern = function (pattern) { + if (typeof pattern !== 'string') { + throw new TypeError('invalid pattern') + } + + if (pattern.length > MAX_PATTERN_LENGTH) { + throw new TypeError('pattern is too long') + } +} + // parse a component of the expanded set. // At this point, no pattern may contain "/" in it // so we're going to return a 2d array, where each entry is the full @@ -10278,14 +10303,17 @@ function braceExpand (pattern, options) { Minimatch.prototype.parse = parse var SUBPARSE = {} function parse (pattern, isSub) { - if (pattern.length > 1024 * 64) { - throw new TypeError('pattern is too long') - } + assertValidPattern(pattern) var options = this.options // shortcuts - if (!options.noglobstar && pattern === '**') return GLOBSTAR + if (pattern === '**') { + if (!options.noglobstar) + return GLOBSTAR + else + pattern = '*' + } if (pattern === '') return '' var re = '' @@ -10341,10 +10369,12 @@ function parse (pattern, isSub) { } switch (c) { - case '/': + /* istanbul ignore next */ + case '/': { // completely not allowed, even escaped. // Should already be path-split by now. return false + } case '\\': clearStateChar() @@ -10463,25 +10493,23 @@ function parse (pattern, isSub) { // handle the case where we left a class open. // "[z-a]" is valid, equivalent to "\[z-a\]" - if (inClass) { - // split where the last [ was, make sure we don't have - // an invalid re. if so, re-walk the contents of the - // would-be class to re-translate any characters that - // were passed through as-is - // TODO: It would probably be faster to determine this - // without a try/catch and a new RegExp, but it's tricky - // to do safely. For now, this is safe and works. - var cs = pattern.substring(classStart + 1, i) - try { - RegExp('[' + cs + ']') - } catch (er) { - // not a valid class! - var sp = this.parse(cs, SUBPARSE) - re = re.substr(0, reClassStart) + '\\[' + sp[0] + '\\]' - hasMagic = hasMagic || sp[1] - inClass = false - continue - } + // split where the last [ was, make sure we don't have + // an invalid re. if so, re-walk the contents of the + // would-be class to re-translate any characters that + // were passed through as-is + // TODO: It would probably be faster to determine this + // without a try/catch and a new RegExp, but it's tricky + // to do safely. For now, this is safe and works. + var cs = pattern.substring(classStart + 1, i) + try { + RegExp('[' + cs + ']') + } catch (er) { + // not a valid class! + var sp = this.parse(cs, SUBPARSE) + re = re.substr(0, reClassStart) + '\\[' + sp[0] + '\\]' + hasMagic = hasMagic || sp[1] + inClass = false + continue } // finish up the class. @@ -10565,9 +10593,7 @@ function parse (pattern, isSub) { // something that could conceivably capture a dot var addPatternStart = false switch (re.charAt(0)) { - case '.': - case '[': - case '(': addPatternStart = true + case '[': case '.': case '(': addPatternStart = true } // Hack to work around lack of negative lookbehind in JS @@ -10629,7 +10655,7 @@ function parse (pattern, isSub) { var flags = options.nocase ? 'i' : '' try { var regExp = new RegExp('^' + re + '$', flags) - } catch (er) { + } catch (er) /* istanbul ignore next - should be impossible */ { // If it was an invalid regular expression, then it can't match // anything. This trick looks for a character after the end of // the string, which is of course impossible, except in multi-line @@ -10687,7 +10713,7 @@ function makeRe () { try { this.regexp = new RegExp(re, flags) - } catch (ex) { + } catch (ex) /* istanbul ignore next - should be impossible */ { this.regexp = false } return this.regexp @@ -10705,8 +10731,8 @@ minimatch.match = function (list, pattern, options) { return list } -Minimatch.prototype.match = match -function match (f, partial) { +Minimatch.prototype.match = function match (f, partial) { + if (typeof partial === 'undefined') partial = this.partial this.debug('match', f, this.pattern) // short-circuit in the case of busted things. // comments, etc. @@ -10788,6 +10814,7 @@ Minimatch.prototype.matchOne = function (file, pattern, partial) { // should be impossible. // some invalid regexp stuff in the set. + /* istanbul ignore if */ if (p === false) return false if (p === GLOBSTAR) { @@ -10861,6 +10888,7 @@ Minimatch.prototype.matchOne = function (file, pattern, partial) { // no match was found. // However, in partial mode, we can't say this is necessarily over. // If there's more *pattern* left, then + /* istanbul ignore if */ if (partial) { // ran out of file this.debug('\n>>> no match, partial?', file, fr, pattern, pr) @@ -10874,11 +10902,7 @@ Minimatch.prototype.matchOne = function (file, pattern, partial) { // patterns with magic have been turned into regexps. var hit if (typeof p === 'string') { - if (options.nocase) { - hit = f.toLowerCase() === p.toLowerCase() - } else { - hit = f === p - } + hit = f === p this.debug('string match', p, f, hit) } else { hit = f.match(p) @@ -10909,16 +10933,16 @@ Minimatch.prototype.matchOne = function (file, pattern, partial) { // this is ok if we're doing the match as part of // a glob fs traversal. return partial - } else if (pi === pl) { + } else /* istanbul ignore else */ if (pi === pl) { // ran out of pattern, still have file left. // this is only acceptable if we're on the very last // empty segment of a file with a trailing slash. // a/* should match a/b/ - var emptyFileEnd = (fi === fl - 1) && (file[fi] === '') - return emptyFileEnd + return (fi === fl - 1) && (file[fi] === '') } // should be unreachable. + /* istanbul ignore next */ throw new Error('wtf?') } diff --git a/package-lock.json b/package-lock.json index 71b5637..fdfa0bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3306,9 +3306,9 @@ } }, "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -6994,9 +6994,9 @@ "dev": true }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "requires": { "brace-expansion": "^1.1.7" } diff --git a/package.json b/package.json index 4f9b711..cc14027 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,9 @@ "main": "lib/main.js", "scripts": { "build": "tsc && ncc build lib/main.js", - "format": "prettier --write \"**/*.ts\"", - "format-check": "prettier --check \"**/*.ts\"", + "format": "prettier --write **/*.ts", + "format-check": "prettier --check **/*.ts", + "lint": "echo \"Fake command that does nothing. It is used in reusable workflows\"", "test": "jest" }, "repository": { diff --git a/src/labeler.ts b/src/labeler.ts index 59cf23f..b33073a 100644 --- a/src/labeler.ts +++ b/src/labeler.ts @@ -1,7 +1,7 @@ -import * as core from "@actions/core"; -import * as github from "@actions/github"; -import * as yaml from "js-yaml"; -import { Minimatch, IMinimatch } from "minimatch"; +import * as core from '@actions/core'; +import * as github from '@actions/github'; +import * as yaml from 'js-yaml'; +import {Minimatch, IMinimatch} from 'minimatch'; interface MatchConfig { all?: string[]; @@ -13,22 +13,22 @@ type ClientType = ReturnType; export async function run() { try { - const token = core.getInput("repo-token", { required: true }); - const configPath = core.getInput("configuration-path", { required: true }); - const syncLabels = !!core.getInput("sync-labels", { required: false }); + const token = core.getInput('repo-token', {required: true}); + const configPath = core.getInput('configuration-path', {required: true}); + const syncLabels = !!core.getInput('sync-labels', {required: false}); const prNumber = getPrNumber(); if (!prNumber) { - console.log("Could not get pull request number from context, exiting"); + console.log('Could not get pull request number from context, exiting'); return; } const client: ClientType = github.getOctokit(token); - const { data: pullRequest } = await client.rest.pulls.get({ + const {data: pullRequest} = await client.rest.pulls.get({ owner: github.context.repo.owner, repo: github.context.repo.repo, - pull_number: prNumber, + pull_number: prNumber }); core.debug(`fetching changed files for pr #${prNumber}`); @@ -44,7 +44,7 @@ export async function run() { core.debug(`processing ${label}`); if (checkGlobs(changedFiles, globs)) { labels.push(label); - } else if (pullRequest.labels.find((l) => l.name === label)) { + } else if (pullRequest.labels.find(l => l.name === label)) { labelsToRemove.push(label); } } @@ -78,15 +78,15 @@ async function getChangedFiles( const listFilesOptions = client.rest.pulls.listFiles.endpoint.merge({ owner: github.context.repo.owner, repo: github.context.repo.repo, - pull_number: prNumber, + pull_number: prNumber }); const listFilesResponse = await client.paginate(listFilesOptions); const changedFiles = listFilesResponse.map((f: any) => f.filename); - core.debug("found changed files:"); + core.debug('found changed files:'); for (const file of changedFiles) { - core.debug(" " + file); + core.debug(' ' + file); } return changedFiles; @@ -116,7 +116,7 @@ async function fetchContent( owner: github.context.repo.owner, repo: github.context.repo.repo, path: repoPath, - ref: github.context.sha, + ref: github.context.sha }); return Buffer.from(response.data.content, response.data.encoding).toString(); @@ -127,7 +127,7 @@ function getLabelGlobMapFromObject( ): Map { const labelGlobs: Map = new Map(); for (const label in configObject) { - if (typeof configObject[label] === "string") { + if (typeof configObject[label] === 'string') { labelGlobs.set(label, [configObject[label]]); } else if (configObject[label] instanceof Array) { labelGlobs.set(label, configObject[label]); @@ -142,9 +142,9 @@ function getLabelGlobMapFromObject( } function toMatchConfig(config: StringOrMatchConfig): MatchConfig { - if (typeof config === "string") { + if (typeof config === 'string') { return { - any: [config], + any: [config] }; } @@ -152,7 +152,7 @@ function toMatchConfig(config: StringOrMatchConfig): MatchConfig { } function printPattern(matcher: IMinimatch): string { - return (matcher.negate ? "!" : "") + matcher.pattern; + return (matcher.negate ? '!' : '') + matcher.pattern; } export function checkGlobs( @@ -185,7 +185,7 @@ function isMatch(changedFile: string, matchers: IMinimatch[]): boolean { // equivalent to "Array.some()" but expanded for debugging and clarity function checkAny(changedFiles: string[], globs: string[]): boolean { - const matchers = globs.map((g) => new Minimatch(g)); + const matchers = globs.map(g => new Minimatch(g)); core.debug(` checking "any" patterns`); for (const changedFile of changedFiles) { if (isMatch(changedFile, matchers)) { @@ -200,7 +200,7 @@ function checkAny(changedFiles: string[], globs: string[]): boolean { // equivalent to "Array.every()" but expanded for debugging and clarity function checkAll(changedFiles: string[], globs: string[]): boolean { - const matchers = globs.map((g) => new Minimatch(g)); + const matchers = globs.map(g => new Minimatch(g)); core.debug(` checking "all" patterns`); for (const changedFile of changedFiles) { if (!isMatch(changedFile, matchers)) { @@ -238,7 +238,7 @@ async function addLabels( owner: github.context.repo.owner, repo: github.context.repo.repo, issue_number: prNumber, - labels: labels, + labels: labels }); } @@ -248,12 +248,12 @@ async function removeLabels( labels: string[] ) { await Promise.all( - labels.map((label) => + labels.map(label => client.rest.issues.removeLabel({ owner: github.context.repo.owner, repo: github.context.repo.repo, issue_number: prNumber, - name: label, + name: label }) ) ); diff --git a/src/main.ts b/src/main.ts index d9d6212..60377ad 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,3 +1,3 @@ -import { run } from "./labeler"; +import {run} from './labeler'; run();