diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 1b0cc0e299..e13f7f254e 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -5,5 +5,3 @@ Lints the player, and builds and tests each combination of OS and browser. - 'update_issues.yaml': Updates GitHub issues on a timer. - - 'test_update_issues.yaml': - Runs tests on the update-issues tool when it changes. diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml index 28eebcfa85..8acae463fa 100644 --- a/.github/workflows/build_and_test.yaml +++ b/.github/workflows/build_and_test.yaml @@ -3,8 +3,6 @@ name: Build and Test PR on: pull_request: # Trigger for pull requests. types: [opened, synchronize, reopened] - paths-ignore: - .github/workflows/tools/update-issues/** workflow_dispatch: # Allows for manual triggering. inputs: ref: diff --git a/.github/workflows/test_update_issues.yaml b/.github/workflows/test_update_issues.yaml deleted file mode 100644 index 404d000d26..0000000000 --- a/.github/workflows/test_update_issues.yaml +++ /dev/null @@ -1,28 +0,0 @@ -name: Test Update Issues Tool - -on: - pull_request: # Trigger for pull requests. - types: [opened, synchronize, reopened] - paths: - .github/workflows/tools/update-issues/** - workflow_dispatch: # Allows for manual triggering. - inputs: - ref: - description: "The ref to build and test." - required: False - -jobs: - test: - name: Test Update Issues Tool - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v2 - with: - ref: ${{ github.event.inputs.ref || github.ref }} - - - name: Test - run: | - cd .github/workflows/tools/update-issues - npm ci - npm test diff --git a/.github/workflows/tools/update-issues/issues.js b/.github/workflows/tools/update-issues/issues.js deleted file mode 100644 index a8fa83c04b..0000000000 --- a/.github/workflows/tools/update-issues/issues.js +++ /dev/null @@ -1,535 +0,0 @@ -/*! @license - * Shaka Player - * Copyright 2016 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview A set of classes to represent GitHub issues & comments. - */ - -const github = require('@actions/github'); -const core = require('@actions/core'); - -const octokit = github.getOctokit(process.env.GITHUB_TOKEN); -const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/'); - -// Values of "author_association" that indicate a team member: -const TEAM_ASSOCIATIONS = [ - 'OWNER', - 'MEMBER', - 'COLLABORATOR', -]; - -const ACTIONS_BOT = 'github-actions[bot]'; - -/** - * Parse a version string into an array of numbers with an optional string tag - * at the end. A string tag will be preceded by a negative one (-1) so that - * any tagged version (like -beta or -rc1) will be sorted before the final - * release version. - * - * @param {string} versionString - * @return {Array} - */ -function parseVersion(versionString) { - const matches = /^v?([0-9]+(?:\.[0-9]+)*)(?:-(.*))?$/.exec(versionString); - if (!matches) { - return null; - } - - // If there is a tag, append it as a string after a negative one. This will - // ensure that versions like "-beta" sort above their production - // counterparts. - const version = matches[1].split('.').map(x => parseInt(x)); - if (matches[2]) { - version.push(-1); - version.push(matches[2]); - } - - return version; -} - -/** - * Compare two version arrays. Can be used as a callback to - * Array.prototype.sort to sort by version numbers (ascending). - * - * The last item in a version array may be a string (a tag like "beta"), but - * the rest are numbers. See notes in parseVersion above for details on tags. - * - * @param {Array} a - * @param {Array} b - * @return {number} - */ -function compareVersions(a, b) { - // If a milestone's version can't be parsed, it will be null. Push those to - // the end of any sorted list. - if (!a && !b) { - return 0; - } else if (!a) { - return 1; - } else if (!b) { - return -1; - } - - for (let i = 0; i < Math.min(a.length, b.length); ++i) { - if (a[i] < b[i]) { - return -1; - } else if (a[i] > b[i]) { - return 1; - } - // If equal, keep going through the array. - } - - // If one has a tag that the other does not, the one with the tag (the longer - // one) comes first. - if (a.length > b.length) { - return -1; - } else if (a.length < b.length) { - return 1; - } else { - return 0; - } -} - -/** - * Compare two Numbers. Can be used as a callback to Array.prototype.sort to - * sort by number (ascending). - * - * @param {Number} a - * @param {Number} b - * @return {number} - */ -function compareNumbers(a, b) { - // Sort NaNs to the end. - if (isNaN(a) && isNaN(b)) { - return 0; - } else if (isNaN(a)) { - return 1; - } else if (isNaN(b)) { - return -1; - } - - if (a < b) { - return -1; - } else if (a > b) { - return 1; - } else { - return 0; - } -} - -/** - * Convert a Date to an age in days (by comparing with the current time). - * - * @param {!Date} d - * @return {number} Time passed since d, in days. - */ -function dateToAgeInDays(d) { - // getTime() and now() both return milliseconds, which we diff and then - // convert to days. - return (Date.now() - d.getTime()) / (86400 * 1000); -} - -/** - * A base class for objects returned by the GitHub API. - */ -class GitHubObject { - /** @param {!Object} obj */ - constructor(obj) { - /** @type {number} */ - this.id = obj.id; - /** @type {number} */ - this.number = obj.number; - /** @type {number} */ - this.ageInDays = NaN; - /** @type {number} */ - this.closedDays = NaN; - - if (obj.created_at != null) { - this.ageInDays = dateToAgeInDays(new Date(obj.created_at)); - } - - if (obj.closed_at != null) { - this.closedDays = dateToAgeInDays(new Date(obj.closed_at)); - } - } - - /** @return {string} */ - toString() { - return JSON.stringify(this, null, ' '); - } - - /** - * @param {Function} listMethod A method from the octokit API, which will be - * passed to octokit.paginate. - * @param {function(new:T, !Object)} SubClass - * @param {!Object} parameters - * @return {!Promise>} - * @template T - */ - static async getAll(listMethod, SubClass, parameters) { - const query = { owner, repo, ...parameters }; - return (await octokit.paginate(listMethod, query)) - .map(obj => new SubClass(obj)); - } -} - -class Milestone extends GitHubObject { - /** @param {!Object} obj */ - constructor(obj) { - super(obj); - /** @type {string} */ - this.title = obj.title; - /** @type {Array} */ - this.version = parseVersion(obj.title); - /** @type {boolean} */ - this.closed = obj.state == 'closed'; - } - - /** @return {boolean} */ - isBacklog() { - return this.title.toLowerCase() == 'backlog'; - } - - /** @return {!Promise>} */ - static async getAll() { - return GitHubObject.getAll( - octokit.rest.issues.listMilestones, Milestone, {}); - } - - /** - * Compare two Milestones. Can be used as a callback to Array.prototype.sort - * to sort by version numbers (ascending). - * - * @param {!Milestone} a - * @param {!Milestone} b - * @return {number} - */ - static compare(a, b) { - return compareVersions(a.version, b.version); - } -} - -class Comment extends GitHubObject { - /** @param {!Object} obj */ - constructor(obj) { - super(obj); - /** @type {string} */ - this.author = obj.user.login; - /** @type {string} */ - this.body = obj.body; - /** @type {string} */ - this.authorAssociation = obj.author_association; - /** @type {boolean} */ - this.fromTeam = - TEAM_ASSOCIATIONS.includes(obj.author_association) || - this.author == ACTIONS_BOT; - } - - /** - * @param {number} issueNumber - * @return {!Promise>} - */ - static async getAll(issueNumber) { - return GitHubObject.getAll(octokit.rest.issues.listComments, Comment, { - issue_number: issueNumber, - }); - } - - /** - * Compare two Comments. Can be used as a callback to Array.prototype.sort - * to sort by creation time (descending, newest comments first). - * - * @param {!Comment} a - * @param {!Comment} b - * @return {number} - */ - static compare(a, b) { - // Put most recent comments first. - return compareNumbers(a.ageInDays, b.ageInDays); - } -} - -class Event extends GitHubObject { - /** @param {!Object} obj */ - constructor(obj) { - super(obj); - - /** @type {string} */ - this.event = obj.event; - - if (obj.event == 'labeled') { - /** @type {string} */ - this.label = obj.label.name; - } - } - - /** - * @param {number} issueNumber - * @return {!Promise>} - */ - static async getAll(issueNumber) { - return GitHubObject.getAll(octokit.rest.issues.listEvents, Event, { - issue_number: issueNumber, - }); - } - - /** - * Compare two Events. Can be used as a callback to Array.prototype.sort - * to sort by creation time (descending, newest events first). - * - * @param {!Event} a - * @param {!Event} b - * @return {number} - */ - static compare(a, b) { - // Put most recent events first. - return compareNumbers(a.ageInDays, b.ageInDays); - } -} - -class Issue extends GitHubObject { - /** @param {!Object} obj */ - constructor(obj) { - super(obj); - /** @type {string} */ - this.author = obj.user.login; - /** @type {!Array} */ - this.labels = obj.labels.map(l => l.name); - /** @type {boolean} */ - this.closed = obj.state == 'closed'; - /** @type {boolean} */ - this.locked = obj.locked; - /** @type {Milestone} */ - this.milestone = obj.milestone ? new Milestone(obj.milestone) : null; - /** @type {boolean} */ - this.isPR = !!obj.pull_request; - } - - /** - * @param {string} name - * @return {boolean} - */ - hasLabel(name) { - return this.labels.includes(name); - } - - /** - * @param {!Array} names - * @return {boolean} - */ - hasAnyLabel(names) { - return this.labels.some(l => names.includes(l)); - } - - /** - * @param {string} name - * @return {!Promise l != name); - } - - /** @return {!Promise} */ - async lock() { - if (this.locked) { - return; - } - - core.notice(`Locking issue #${this.number}`); - await octokit.rest.issues.lock({ - owner, - repo, - issue_number: this.number, - lock_reason: 'resolved', - }); - this.locked = true; - } - - /** @return {!Promise} */ - async unlock() { - if (!this.locked) { - return; - } - - core.notice(`Unlocking issue #${this.number}`); - await octokit.rest.issues.unlock({ - owner, - repo, - issue_number: this.number, - }); - this.locked = false; - } - - /** @return {!Promise} */ - async close() { - if (this.closed) { - return; - } - - core.notice(`Closing issue #${this.number}`); - await octokit.rest.issues.update({ - owner, - repo, - issue_number: this.number, - state: 'closed', - }); - this.closed = true; - } - - /** @return {!Promise} */ - async reopen() { - if (!this.closed) { - return; - } - - core.notice(`Reopening issue #${this.number}`); - await octokit.rest.issues.update({ - owner, - repo, - issue_number: this.number, - state: 'open', - }); - this.closed = false; - } - - /** - * @param {!Milestone} milestone - * @return {!Promise} - */ - async setMilestone(milestone) { - if (this.milestone && this.milestone.number == milestone.number) { - return; - } - - core.notice( - `Adding issue #${this.number} to milestone ${milestone.title}`); - await octokit.rest.issues.update({ - owner, - repo, - issue_number: this.number, - milestone: milestone.number, - }); - this.milestone = milestone; - } - - /** @return {!Promise} */ - async removeMilestone() { - if (!this.milestone) { - return; - } - - core.notice( - `Removing issue #${this.number} ` + - `from milestone ${this.milestone.title}`); - await octokit.rest.issues.update({ - owner, - repo, - issue_number: this.number, - milestone: null, - }); - this.milestone = null; - } - - /** - * @param {string} body - * @return {!Promise} - */ - async postComment(body) { - core.notice(`Posting to issue #${this.number}: "${body}"`); - await octokit.rest.issues.createComment({ - owner, - repo, - issue_number: this.number, - body, - }); - - if (this.comments) { - this.comments.push(new Comment({ - created_at: (new Date()).toJSON(), - user: {login: 'shaka-bot'}, - body, - })); - } - } - - /** - * Important: Don't load comments by default! Only some issues need - * comments checked, and we don't want to exceed our query quota by loading - * all comments for all issues. - * - * @return {!Promise} - */ - async loadComments() { - if (this.comments) { - return; - } - - this.comments = await Comment.getAll(this.number); - // Puts most recent comments first. - this.comments.sort(Comment.compare); - } - - /** @return {!Promise>} */ - static async getAll() { - const all = await GitHubObject.getAll( - octokit.rest.issues.listForRepo, Issue, { - state: 'all', - }); - return all.filter(issue => !issue.isPR); - } -} - -module.exports = { - Issue, - Milestone, -}; diff --git a/.github/workflows/tools/update-issues/main.js b/.github/workflows/tools/update-issues/main.js deleted file mode 100644 index e2ff1bcb3b..0000000000 --- a/.github/workflows/tools/update-issues/main.js +++ /dev/null @@ -1,279 +0,0 @@ -/*! @license - * Shaka Player - * Copyright 2016 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview A workflow tool to maintain GitHub issues. - */ - -const core = require('@actions/core'); -const { Issue, Milestone } = require('./issues.js'); - -const TYPE_ACCESSIBILITY = 'type: accessibility'; -const TYPE_ANNOUNCEMENT = 'type: announcement'; -const TYPE_BUG = 'type: bug'; -const TYPE_CI = 'type: CI'; -const TYPE_CODE_HEALTH = 'type: code health'; -const TYPE_DOCS = 'type: docs'; -const TYPE_ENHANCEMENT = 'type: enhancement'; -const TYPE_PERFORMANCE = 'type: performance'; -const TYPE_PROCESS = 'type: process'; -const TYPE_QUESTION = 'type: question'; - -const PRIORITY_P0 = 'priority: P0'; -const PRIORITY_P1 = 'priority: P1'; -const PRIORITY_P2 = 'priority: P2'; -const PRIORITY_P3 = 'priority: P3'; -const PRIORITY_P4 = 'priority: P4'; - -const STATUS_ARCHIVED = 'status: archived'; -const STATUS_WAITING = 'status: waiting on response'; - -const FLAG_IGNORE = 'flag: bot ignore'; - -// Issues of these types default to the next milestone. See also -// BACKLOG_PRIORITIES below, which can override the type. -const LABELS_FOR_NEXT_MILESTONE = [ - TYPE_ACCESSIBILITY, - TYPE_BUG, - TYPE_DOCS, -]; - -// Issues of these types default to the backlog. -const LABELS_FOR_BACKLOG = [ - TYPE_CI, - TYPE_CODE_HEALTH, - TYPE_ENHANCEMENT, - TYPE_PERFORMANCE, -]; - -// An issue with one of these priorities will default to the backlog, even if -// it has one of the types in LABELS_FOR_NEXT_MILESTONE. -const BACKLOG_PRIORITIES = [ - PRIORITY_P3, - PRIORITY_P4, -]; - -const PING_QUESTION_TEXT = - 'Does this answer all your questions? ' + - 'If so, would you please close the issue?'; - -const CLOSE_STALE_TEXT = - 'Closing due to inactivity. If this is still an issue for you or if you ' + - 'have further questions, the OP can ask shaka-bot to reopen it by ' + - 'including `@shaka-bot reopen` in a comment.'; - -const PING_INACTIVE_QUESTION_DAYS = 4; -const CLOSE_AFTER_WAITING_DAYS = 7; -const ARCHIVE_AFTER_CLOSED_DAYS = 60; - - -async function archiveOldIssues(issue) { - // If the issue has been closed for a while, archive it. - // Exclude locked issues, so that this doesn't conflict with unarchiveIssues - // below. - if (!issue.locked && issue.closed && - issue.closedDays >= ARCHIVE_AFTER_CLOSED_DAYS) { - await issue.addLabel(STATUS_ARCHIVED); - await issue.lock(); - } -} - -async function unarchiveIssues(issue) { - // If the archive label is removed from an archived issue, unarchive it. - if (issue.locked && !issue.hasLabel(STATUS_ARCHIVED)) { - await issue.unlock(); - await issue.reopen(); - } -} - -async function reopenIssues(issue) { - // If the original author wants an issue reopened, reopen it. - if (issue.closed && !issue.hasLabel(STATUS_ARCHIVED)) { - // Important: only load comments if prior filters pass! - // If we loaded them on every issue, we could exceed our query quota! - await issue.loadComments(); - - for (const comment of issue.comments) { - body = comment.body.toLowerCase(); - if (comment.author == issue.author && - comment.ageInDays <= issue.closedDays && - body.includes('@shaka-bot') && - (body.includes('reopen') || body.includes('re-open'))) { - core.notice(`Found reopen request for issue #${issue.number}`); - await issue.reopen(); - break; - } - } - } -} - -async function manageWaitingIssues(issue) { - // Filter for waiting issues. - if (!issue.closed && issue.hasLabel(STATUS_WAITING)) { - const labelAgeInDays = await issue.getLabelAgeInDays(STATUS_WAITING); - - // If an issue has been replied to, remove the waiting tag. - // Important: only load comments if prior filters pass! - // If we loaded them on every issue, we could exceed our query quota! - await issue.loadComments(); - - const latestNonTeamComment = issue.comments.find(c => !c.fromTeam); - if (latestNonTeamComment && - latestNonTeamComment.ageInDays < labelAgeInDays) { - await issue.removeLabel(STATUS_WAITING); - return; - } - - // If an issue has been in a waiting state for too long, close it as stale. - if (labelAgeInDays >= CLOSE_AFTER_WAITING_DAYS) { - await issue.postComment(CLOSE_STALE_TEXT); - await issue.close(); - } - } -} - -async function cleanUpIssueTags(issue) { - // If an issue with the waiting tag was closed, remove the tag. - if (issue.closed && issue.hasLabel(STATUS_WAITING)) { - await issue.removeLabel(STATUS_WAITING); - } -} - -async function pingQuestions(issue) { - // If a question hasn't been responded to recently, ping it. - if (!issue.closed && - issue.hasLabel(TYPE_QUESTION) && - !issue.hasLabel(STATUS_WAITING)) { - // Important: only load comments if prior filters pass! - // If we loaded them on every issue, we could exceed our query quota! - await issue.loadComments(); - - // Most recent ones are first. - const lastComment = issue.comments[0]; - if (lastComment && - lastComment.fromTeam && - // If the last comment was from the team, but not from the OP (in case - // the OP was a member of the team). - lastComment.author != issue.author && - lastComment.ageInDays >= PING_INACTIVE_QUESTION_DAYS) { - await issue.postComment(`@${issue.author} ${PING_QUESTION_TEXT}`); - await issue.addLabel(STATUS_WAITING); - } - } -} - -async function maintainMilestones(issue, nextMilestone, backlog) { - // Set or remove milestones based on type labels. - if (!issue.closed) { - if (issue.hasAnyLabel(LABELS_FOR_NEXT_MILESTONE)) { - if (!issue.milestone) { - // Some (low) priority flags will indicate that an issue should go to - // the backlog, in spite of its type. - if (issue.hasAnyLabel(BACKLOG_PRIORITIES)) { - await issue.setMilestone(backlog); - } else { - await issue.setMilestone(nextMilestone); - } - } - } else if (issue.hasAnyLabel(LABELS_FOR_BACKLOG)) { - if (!issue.milestone) { - await issue.setMilestone(backlog); - } - } else { - if (issue.milestone) { - await issue.removeMilestone(); - } - } - } -} - - -const ALL_ISSUE_TASKS = [ - reopenIssues, - archiveOldIssues, - unarchiveIssues, - manageWaitingIssues, - cleanUpIssueTags, - pingQuestions, - maintainMilestones, -]; - -async function processIssues(issues, nextMilestone, backlog) { - let success = true; - - for (const issue of issues) { - if (issue.hasLabel(FLAG_IGNORE)) { - core.info(`Ignoring issue #${issue.number}`); - continue; - } - - core.info(`Processing issue #${issue.number}`); - - for (const task of ALL_ISSUE_TASKS) { - try { - await task(issue, nextMilestone, backlog); - } catch (error) { - // Make this show up in the Actions UI without needing to search the - // logs. - core.error( - `Failed to process issue #${issue.number} in task ${task.name}: ` + - `${error}\n${error.stack}`); - success = false; - } - } - } - - return success; -} - -async function main() { - const milestones = await Milestone.getAll(); - const issues = await Issue.getAll(); - - const backlog = milestones.find(m => m.isBacklog()); - if (!backlog) { - core.error('No backlog milestone found!'); - process.exit(1); - } - - milestones.sort(Milestone.compare); - const nextMilestone = milestones[0]; - if (nextMilestone.version == null) { - core.error('No version milestone found!'); - process.exit(1); - } - - const success = await processIssues(issues, nextMilestone, backlog); - if (!success) { - process.exit(1); - } -} - -// If this file is the entrypoint, run main. Otherwise, export certain pieces -// to the tests. -if (require.main == module) { - main(); -} else { - module.exports = { - processIssues, - TYPE_ACCESSIBILITY, - TYPE_ANNOUNCEMENT, - TYPE_BUG, - TYPE_CODE_HEALTH, - TYPE_DOCS, - TYPE_ENHANCEMENT, - TYPE_PROCESS, - TYPE_QUESTION, - PRIORITY_P0, - PRIORITY_P1, - PRIORITY_P2, - PRIORITY_P3, - PRIORITY_P4, - STATUS_ARCHIVED, - STATUS_WAITING, - FLAG_IGNORE, - }; -} diff --git a/.github/workflows/tools/update-issues/mocks.js b/.github/workflows/tools/update-issues/mocks.js deleted file mode 100644 index e3d1e984a2..0000000000 --- a/.github/workflows/tools/update-issues/mocks.js +++ /dev/null @@ -1,129 +0,0 @@ -/*! @license - * Shaka Player - * Copyright 2016 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview Mocks for the classes in issues.js - */ - -function randomInt() { - return Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); -} -let nextIssueNumber = 1; - -class MockGitHubObject { - constructor(subclassDefaults, params) { - const defaults = { - id: randomInt(), - ageInDays: 0, - closedDays: 0, - - ...subclassDefaults, - }; - - const mergedParams = { - ...defaults, - ...params, - }; - - for (const k in mergedParams) { - this[k] = mergedParams[k]; - } - } - - toString() { - return JSON.stringify(this, null, ' '); - } -} - -class MockMilestone extends MockGitHubObject { - constructor(params) { - const defaults = { - title: 'MockMilestone', - version: null, - closed: false, - isBacklog: () => false, - }; - - super(defaults, params); - } -} - -class MockComment extends MockGitHubObject { - constructor(params) { - const defaults = { - author: 'SomeUser', - body: 'Howdy!', - authorAssociation: 'NONE', - fromTeam: false, - }; - - super(defaults, params); - } -} - -class MockIssue extends MockGitHubObject { - constructor(params) { - const defaults = { - number: nextIssueNumber++, - author: 'SomeUser', - labels: [], - closed: false, - locked: false, - milestone: null, - comments: [], - }; - - super(defaults, params); - - this.getLabelAgeInDays = - jasmine.createSpy('getLabelAgeInDays') - .and.returnValue(params.labelAgeInDays || 0); - this.addLabel = jasmine.createSpy('addLabel').and.callFake((name) => { - console.log(`Adding label ${name}`); - }); - this.removeLabel = jasmine.createSpy('removeLabel').and.callFake((name) => { - console.log(`Removing label ${name}`); - }); - this.lock = jasmine.createSpy('lock').and.callFake(() => { - console.log('Locking'); - }); - this.unlock = jasmine.createSpy('unlock').and.callFake(() => { - console.log('Unlocking'); - }); - this.close = jasmine.createSpy('close').and.callFake(() => { - console.log('Closing'); - }); - this.reopen = jasmine.createSpy('reopen').and.callFake(() => { - console.log('Reopening'); - }); - this.setMilestone = - jasmine.createSpy('setMilestone').and.callFake((milestone) => { - console.log(`Setting milestone to "${milestone.title}"`); - }); - this.removeMilestone = - jasmine.createSpy('removeMilestone').and.callFake(() => { - console.log('Removing milestone.'); - }); - this.postComment = jasmine.createSpy('postComment').and.callFake((body) => { - console.log(`Posting comment: ${body}`); - }); - this.loadComments = jasmine.createSpy('loadComments'); - } - - hasLabel(name) { - return this.labels.includes(name); - } - - hasAnyLabel(names) { - return this.labels.some(l => names.includes(l)); - } -} - -module.exports = { - MockMilestone, - MockComment, - MockIssue, -}; diff --git a/.github/workflows/tools/update-issues/package-lock.json b/.github/workflows/tools/update-issues/package-lock.json deleted file mode 100644 index 8e74a8e9fe..0000000000 --- a/.github/workflows/tools/update-issues/package-lock.json +++ /dev/null @@ -1,654 +0,0 @@ -{ - "name": "update-issues", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "devDependencies": { - "@actions/core": "^1.6.0", - "@actions/github": "^5.0.0", - "jasmine": "^4.0.2" - } - }, - "node_modules/@actions/core": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.6.0.tgz", - "integrity": "sha512-NB1UAZomZlCV/LmJqkLhNTqtKfFXJZAUPcfl/zqG7EfsQdeUJtaWO98SGbuQ3pydJ3fHl2CvI/51OKYlCYYcaw==", - "dev": true, - "dependencies": { - "@actions/http-client": "^1.0.11" - } - }, - "node_modules/@actions/github": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@actions/github/-/github-5.0.0.tgz", - "integrity": "sha512-QvE9eAAfEsS+yOOk0cylLBIO/d6WyWIOvsxxzdrPFaud39G6BOkUwScXZn1iBzQzHyu9SBkkLSWlohDWdsasAQ==", - "dev": true, - "dependencies": { - "@actions/http-client": "^1.0.11", - "@octokit/core": "^3.4.0", - "@octokit/plugin-paginate-rest": "^2.13.3", - "@octokit/plugin-rest-endpoint-methods": "^5.1.1" - } - }, - "node_modules/@actions/http-client": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz", - "integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==", - "dev": true, - "dependencies": { - "tunnel": "0.0.6" - } - }, - "node_modules/@octokit/auth-token": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", - "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", - "dev": true, - "dependencies": { - "@octokit/types": "^6.0.3" - } - }, - "node_modules/@octokit/core": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.5.1.tgz", - "integrity": "sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw==", - "dev": true, - "dependencies": { - "@octokit/auth-token": "^2.4.4", - "@octokit/graphql": "^4.5.8", - "@octokit/request": "^5.6.0", - "@octokit/request-error": "^2.0.5", - "@octokit/types": "^6.0.3", - "before-after-hook": "^2.2.0", - "universal-user-agent": "^6.0.0" - } - }, - "node_modules/@octokit/endpoint": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", - "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", - "dev": true, - "dependencies": { - "@octokit/types": "^6.0.3", - "is-plain-object": "^5.0.0", - "universal-user-agent": "^6.0.0" - } - }, - "node_modules/@octokit/graphql": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", - "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", - "dev": true, - "dependencies": { - "@octokit/request": "^5.6.0", - "@octokit/types": "^6.0.3", - "universal-user-agent": "^6.0.0" - } - }, - "node_modules/@octokit/openapi-types": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz", - "integrity": "sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA==", - "dev": true - }, - "node_modules/@octokit/plugin-paginate-rest": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.17.0.tgz", - "integrity": "sha512-tzMbrbnam2Mt4AhuyCHvpRkS0oZ5MvwwcQPYGtMv4tUa5kkzG58SVB0fcsLulOZQeRnOgdkZWkRUiyBlh0Bkyw==", - "dev": true, - "dependencies": { - "@octokit/types": "^6.34.0" - }, - "peerDependencies": { - "@octokit/core": ">=2" - } - }, - "node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "5.13.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.13.0.tgz", - "integrity": "sha512-uJjMTkN1KaOIgNtUPMtIXDOjx6dGYysdIFhgA52x4xSadQCz3b/zJexvITDVpANnfKPW/+E0xkOvLntqMYpviA==", - "dev": true, - "dependencies": { - "@octokit/types": "^6.34.0", - "deprecation": "^2.3.1" - }, - "peerDependencies": { - "@octokit/core": ">=3" - } - }, - "node_modules/@octokit/request": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.2.tgz", - "integrity": "sha512-je66CvSEVf0jCpRISxkUcCa0UkxmFs6eGDRSbfJtAVwbLH5ceqF+YEyC8lj8ystKyZTy8adWr0qmkY52EfOeLA==", - "dev": true, - "dependencies": { - "@octokit/endpoint": "^6.0.1", - "@octokit/request-error": "^2.1.0", - "@octokit/types": "^6.16.1", - "is-plain-object": "^5.0.0", - "node-fetch": "^2.6.1", - "universal-user-agent": "^6.0.0" - } - }, - "node_modules/@octokit/request-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", - "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", - "dev": true, - "dependencies": { - "@octokit/types": "^6.0.3", - "deprecation": "^2.0.0", - "once": "^1.4.0" - } - }, - "node_modules/@octokit/types": { - "version": "6.34.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz", - "integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==", - "dev": true, - "dependencies": { - "@octokit/openapi-types": "^11.2.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/before-after-hook": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz", - "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/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 - }, - "node_modules/deprecation": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", - "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", - "dev": true - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jasmine": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-4.0.2.tgz", - "integrity": "sha512-YsrgxJQEggxzByYe4j68eQLOiQeSrPDYGv4sHhGBp3c6HHdq+uPXeAQ73kOAQpdLZ3/0zN7x/TZTloqeE1/qIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.6", - "jasmine-core": "^4.0.0" - }, - "bin": { - "jasmine": "bin/jasmine.js" - } - }, - "node_modules/jasmine-core": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.0.0.tgz", - "integrity": "sha512-tq24OCqHElgU9KDpb/8O21r1IfotgjIzalfW9eCmRR40LZpvwXT68iariIyayMwi0m98RDt16aljdbwK0sBMmQ==", - "dev": true - }, - "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", - "dev": true - }, - "node_modules/tunnel": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", - "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", - "dev": true, - "engines": { - "node": ">=0.6.11 <=0.7.0 || >=0.7.3" - } - }, - "node_modules/universal-user-agent": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", - "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==", - "dev": true - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", - "dev": true - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "dev": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - } - }, - "dependencies": { - "@actions/core": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.6.0.tgz", - "integrity": "sha512-NB1UAZomZlCV/LmJqkLhNTqtKfFXJZAUPcfl/zqG7EfsQdeUJtaWO98SGbuQ3pydJ3fHl2CvI/51OKYlCYYcaw==", - "dev": true, - "requires": { - "@actions/http-client": "^1.0.11" - } - }, - "@actions/github": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@actions/github/-/github-5.0.0.tgz", - "integrity": "sha512-QvE9eAAfEsS+yOOk0cylLBIO/d6WyWIOvsxxzdrPFaud39G6BOkUwScXZn1iBzQzHyu9SBkkLSWlohDWdsasAQ==", - "dev": true, - "requires": { - "@actions/http-client": "^1.0.11", - "@octokit/core": "^3.4.0", - "@octokit/plugin-paginate-rest": "^2.13.3", - "@octokit/plugin-rest-endpoint-methods": "^5.1.1" - } - }, - "@actions/http-client": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz", - "integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==", - "dev": true, - "requires": { - "tunnel": "0.0.6" - } - }, - "@octokit/auth-token": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", - "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", - "dev": true, - "requires": { - "@octokit/types": "^6.0.3" - } - }, - "@octokit/core": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.5.1.tgz", - "integrity": "sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw==", - "dev": true, - "requires": { - "@octokit/auth-token": "^2.4.4", - "@octokit/graphql": "^4.5.8", - "@octokit/request": "^5.6.0", - "@octokit/request-error": "^2.0.5", - "@octokit/types": "^6.0.3", - "before-after-hook": "^2.2.0", - "universal-user-agent": "^6.0.0" - } - }, - "@octokit/endpoint": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", - "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", - "dev": true, - "requires": { - "@octokit/types": "^6.0.3", - "is-plain-object": "^5.0.0", - "universal-user-agent": "^6.0.0" - } - }, - "@octokit/graphql": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", - "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", - "dev": true, - "requires": { - "@octokit/request": "^5.6.0", - "@octokit/types": "^6.0.3", - "universal-user-agent": "^6.0.0" - } - }, - "@octokit/openapi-types": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz", - "integrity": "sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA==", - "dev": true - }, - "@octokit/plugin-paginate-rest": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.17.0.tgz", - "integrity": "sha512-tzMbrbnam2Mt4AhuyCHvpRkS0oZ5MvwwcQPYGtMv4tUa5kkzG58SVB0fcsLulOZQeRnOgdkZWkRUiyBlh0Bkyw==", - "dev": true, - "requires": { - "@octokit/types": "^6.34.0" - } - }, - "@octokit/plugin-rest-endpoint-methods": { - "version": "5.13.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.13.0.tgz", - "integrity": "sha512-uJjMTkN1KaOIgNtUPMtIXDOjx6dGYysdIFhgA52x4xSadQCz3b/zJexvITDVpANnfKPW/+E0xkOvLntqMYpviA==", - "dev": true, - "requires": { - "@octokit/types": "^6.34.0", - "deprecation": "^2.3.1" - } - }, - "@octokit/request": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.2.tgz", - "integrity": "sha512-je66CvSEVf0jCpRISxkUcCa0UkxmFs6eGDRSbfJtAVwbLH5ceqF+YEyC8lj8ystKyZTy8adWr0qmkY52EfOeLA==", - "dev": true, - "requires": { - "@octokit/endpoint": "^6.0.1", - "@octokit/request-error": "^2.1.0", - "@octokit/types": "^6.16.1", - "is-plain-object": "^5.0.0", - "node-fetch": "^2.6.1", - "universal-user-agent": "^6.0.0" - } - }, - "@octokit/request-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", - "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", - "dev": true, - "requires": { - "@octokit/types": "^6.0.3", - "deprecation": "^2.0.0", - "once": "^1.4.0" - } - }, - "@octokit/types": { - "version": "6.34.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz", - "integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==", - "dev": true, - "requires": { - "@octokit/openapi-types": "^11.2.0" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "before-after-hook": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz", - "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==", - "dev": true - }, - "brace-expansion": { - "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" - } - }, - "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 - }, - "deprecation": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", - "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "dev": true - }, - "jasmine": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-4.0.2.tgz", - "integrity": "sha512-YsrgxJQEggxzByYe4j68eQLOiQeSrPDYGv4sHhGBp3c6HHdq+uPXeAQ73kOAQpdLZ3/0zN7x/TZTloqeE1/qIA==", - "dev": true, - "requires": { - "glob": "^7.1.6", - "jasmine-core": "^4.0.0" - } - }, - "jasmine-core": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.0.0.tgz", - "integrity": "sha512-tq24OCqHElgU9KDpb/8O21r1IfotgjIzalfW9eCmRR40LZpvwXT68iariIyayMwi0m98RDt16aljdbwK0sBMmQ==", - "dev": true - }, - "minimatch": { - "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" - } - }, - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", - "dev": true - }, - "tunnel": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", - "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", - "dev": true - }, - "universal-user-agent": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", - "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==", - "dev": true - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", - "dev": true - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "dev": true, - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - } - } -} diff --git a/.github/workflows/tools/update-issues/package.json b/.github/workflows/tools/update-issues/package.json deleted file mode 100644 index 8b4b5f8c6f..0000000000 --- a/.github/workflows/tools/update-issues/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "devDependencies": { - "@actions/core": "^1.6.0", - "@actions/github": "^5.0.0", - "jasmine": "^4.0.2" - }, - "scripts": { - "test": "jasmine tests.js" - } -} diff --git a/.github/workflows/tools/update-issues/reporter.js b/.github/workflows/tools/update-issues/reporter.js deleted file mode 100644 index 9820116bc2..0000000000 --- a/.github/workflows/tools/update-issues/reporter.js +++ /dev/null @@ -1,55 +0,0 @@ -/*! @license - * Shaka Player - * Copyright 2016 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview A custom Jasmine reporter for GitHub Actions. - */ - -const core = require('@actions/core'); - -class GitHubActionsReporter { - constructor() { - // In the Actions environment, log through the Actions toolkit. - if (process.env.GITHUB_ACTIONS) { - this.logger = core; - } else { - this.logger = console; - } - - // Escape sequence for ANSI blue. - this.blue = '\u001b[36m'; - // Escape sequence for ANSI red. - this.red = '\u001b[31m'; - // Escape sequence for ANSI color reset. Not needed in GitHub Actions - // environment, but useful for local testing to reset the terminal to - // defaults. - this.reset = '\u001b[0m'; - } - - specStarted(result) { - // Escape sequence is for ANSI bright blue on a black background. - this.logger.info(`\n${this.blue} -- ${result.fullName} --${this.reset}`); - } - - specDone(result) { - for (const failure of result.failedExpectations) { - // The text in error() is bubbled up in GitHub Actions to the top level, - // but at that level, the color escape sequences are not understood. So - // those are done before and after in info() calls. - this.logger.info(this.red); - this.logger.error(`${result.fullName} FAILED`); - this.logger.info(this.reset); - - // This only appears in the logs of the job. - const indentedMessage = failure.message.replaceAll('\n', '\n '); - this.logger.info(` ${indentedMessage}`); - } - } -} - -module.exports = { - GitHubActionsReporter, -}; diff --git a/.github/workflows/tools/update-issues/tests.js b/.github/workflows/tools/update-issues/tests.js deleted file mode 100644 index ec3f7aa3d1..0000000000 --- a/.github/workflows/tools/update-issues/tests.js +++ /dev/null @@ -1,500 +0,0 @@ -/*! @license - * Shaka Player - * Copyright 2016 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview Test cases for the update-issues tool. - */ - -// Bogus values to satisfy load-time calls in issues.js. -process.env.GITHUB_TOKEN = 'asdf'; -process.env.GITHUB_REPOSITORY = 'foo/someproject'; - -// Always execute tests in a consistent order. -jasmine.getEnv().configure({ - random: false, -}); - -// Report results through GitHub Actions when possible. -const {GitHubActionsReporter} = require('./reporter.js'); -jasmine.getEnv().clearReporters(); -jasmine.getEnv().addReporter(new GitHubActionsReporter()); - -const { - MockMilestone, - MockComment, - MockIssue, -} = require('./mocks.js'); - -const { - Milestone, -} = require('./issues.js'); - -const { - processIssues, - TYPE_ACCESSIBILITY, - TYPE_ANNOUNCEMENT, - TYPE_BUG, - TYPE_CODE_HEALTH, - TYPE_DOCS, - TYPE_ENHANCEMENT, - TYPE_PROCESS, - TYPE_QUESTION, - PRIORITY_P0, - PRIORITY_P1, - PRIORITY_P2, - PRIORITY_P3, - PRIORITY_P4, - STATUS_ARCHIVED, - STATUS_WAITING, - FLAG_IGNORE, -} = require('./main.js'); - -describe('update-issues tool', () => { - const nextMilestone = new MockMilestone({ - title: 'v5.1', - version: [5, 1], - }); - - const backlog = new MockMilestone({ - title: 'Backlog', - isBacklog: () => true, - }); - - const teamCommentOld = new MockComment({ - author: 'SomeTeamMember', - fromTeam: true, - ageInDays: 100, - }); - - const teamCommentNew = new MockComment({ - author: 'SomeTeamMember', - fromTeam: true, - ageInDays: 0, - }); - - const externalCommentOld = new MockComment({ - author: 'SomeUser', - fromTeam: false, - ageInDays: 1000, - }); - - const externalCommentNew = new MockComment({ - author: 'SomeUser', - fromTeam: false, - ageInDays: 0, - }); - - it('archives old issues', async () => { - const matchingIssues = [ - new MockIssue({ - closed: true, - closedDays: 60, - }), - new MockIssue({ - closed: true, - closedDays: 100, - }), - // This has the "archived" label, but is not locked. It should still get - // locked. - new MockIssue({ - closed: true, - closedDays: 100, - labels: [STATUS_ARCHIVED], - }), - ]; - - const nonMatchingIssues = [ - new MockIssue({ - closed: false, - }), - new MockIssue({ - closed: true, - closedDays: 1, - }), - // This is already locked, but doesn't have the "archived" label. - // The unarchive task will unlock this one, but the archive task won't - // label it. - new MockIssue({ - closed: true, - closedDays: 100, - locked: true, - }), - ]; - - const issues = matchingIssues.concat(nonMatchingIssues); - - await processIssues(issues, nextMilestone, backlog); - - for (const issue of matchingIssues) { - expect(issue.addLabel).toHaveBeenCalledWith(STATUS_ARCHIVED); - expect(issue.lock).toHaveBeenCalled(); - // Show that there is no conflict with the task to unarchive issues. - expect(issue.unlock).not.toHaveBeenCalled(); - expect(issue.reopen).not.toHaveBeenCalled(); - } - for (const issue of nonMatchingIssues) { - expect(issue.addLabel).not.toHaveBeenCalled(); - expect(issue.lock).not.toHaveBeenCalled(); - } - }); - - it('unarchives issues', async () => { - const matchingIssues = [ - // Closed and locked, but the "archived" label has been removed. - new MockIssue({ - closed: true, - locked: true, - }), - ]; - - const nonMatchingIssues = [ - // Closed and locked, and with the "archived" label still in place. - new MockIssue({ - closed: true, - locked: true, - labels: [STATUS_ARCHIVED], - }), - ]; - - const issues = matchingIssues.concat(nonMatchingIssues); - - await processIssues(issues, nextMilestone, backlog); - - for (const issue of matchingIssues) { - expect(issue.unlock).toHaveBeenCalled(); - expect(issue.reopen).toHaveBeenCalled(); - } - for (const issue of nonMatchingIssues) { - expect(issue.unlock).not.toHaveBeenCalled(); - expect(issue.reopen).not.toHaveBeenCalled(); - // Show that there is no conflict with the task to archive issues. - expect(issue.lock).not.toHaveBeenCalled(); - expect(issue.addLabel).not.toHaveBeenCalled(); - } - }); - - it('removes "waiting" label', async () => { - const matchingIssues = [ - new MockIssue({ - labels: [STATUS_WAITING], - labelAgeInDays: 1, - comments: [externalCommentNew], - }), - new MockIssue({ - labels: [STATUS_WAITING], - labelAgeInDays: 1, - // Most recent comments go first. - comments: [externalCommentNew, teamCommentOld], - }), - new MockIssue({ - labels: [TYPE_BUG, STATUS_WAITING], - labelAgeInDays: 1, - // Most recent comments go first. - comments: [externalCommentNew, teamCommentOld], - }), - ]; - - const nonMatchingIssues = [ - new MockIssue({ - labels: [STATUS_WAITING], - labelAgeInDays: 1, - comments: [teamCommentOld], - }), - new MockIssue({ - labels: [STATUS_WAITING], - labelAgeInDays: 1, - // Most recent comments go first. - comments: [teamCommentNew, externalCommentOld], - }), - ]; - - const issues = matchingIssues.concat(nonMatchingIssues); - - await processIssues(issues, nextMilestone, backlog); - - for (const issue of matchingIssues) { - expect(issue.removeLabel).toHaveBeenCalledWith(STATUS_WAITING); - } - for (const issue of nonMatchingIssues) { - expect(issue.removeLabel).not.toHaveBeenCalled(); - } - }); - - it('closes stale issues', async () => { - const matchingIssues = [ - new MockIssue({ - labels: [STATUS_WAITING], - labelAgeInDays: 100, - }), - new MockIssue({ - labels: [STATUS_WAITING], - labelAgeInDays: 100, - comments: [teamCommentOld], - }), - ]; - - const nonMatchingIssues = [ - new MockIssue({ - labels: [STATUS_WAITING], - labelAgeInDays: 1, - }), - new MockIssue({ - labels: [STATUS_WAITING], - labelAgeInDays: 100, - closed: true, - }), - ]; - - const issues = matchingIssues.concat(nonMatchingIssues); - - await processIssues(issues, nextMilestone, backlog); - - for (const issue of matchingIssues) { - expect(issue.postComment).toHaveBeenCalled(); - expect(issue.close).toHaveBeenCalled(); - } - for (const issue of nonMatchingIssues) { - expect(issue.postComment).not.toHaveBeenCalled(); - expect(issue.close).not.toHaveBeenCalled(); - } - }); - - it('cleans up labels on closed issues', async () => { - const matchingIssues = [ - new MockIssue({ - closed: true, - labels: [STATUS_WAITING], - }), - ]; - - const nonMatchingIssues = [ - new MockIssue({ - labels: [STATUS_WAITING], - }), - ]; - - const issues = matchingIssues.concat(nonMatchingIssues); - - await processIssues(issues, nextMilestone, backlog); - - for (const issue of matchingIssues) { - expect(issue.removeLabel).toHaveBeenCalledWith(STATUS_WAITING); - } - for (const issue of nonMatchingIssues) { - expect(issue.removeLabel).not.toHaveBeenCalled(); - } - }); - - it('pings questions waiting for a response', async () => { - const matchingIssues = [ - new MockIssue({ - labels: [TYPE_QUESTION], - comments: [teamCommentOld], - }), - new MockIssue({ - labels: [TYPE_QUESTION], - // Most recent comments go first. - comments: [teamCommentOld, externalCommentOld], - }), - ]; - - const nonMatchingIssues = [ - // Won't be touched because it's closed. - new MockIssue({ - closed: true, - labels: [TYPE_QUESTION], - comments: [teamCommentOld], - }), - // Won't be touched because it's not a "question" type. - new MockIssue({ - labels: [TYPE_BUG], - comments: [teamCommentOld], - }), - // Won't be touched because the team comment is too new. - new MockIssue({ - labels: [TYPE_QUESTION], - comments: [teamCommentNew], - }), - // Won't be touched because the most recent comment was external. - new MockIssue({ - labels: [TYPE_QUESTION], - // Most recent comments go first. - comments: [externalCommentOld, teamCommentOld], - }), - ]; - - const issues = matchingIssues.concat(nonMatchingIssues); - - await processIssues(issues, nextMilestone, backlog); - - for (const issue of matchingIssues) { - expect(issue.postComment).toHaveBeenCalled(); - expect(issue.addLabel).toHaveBeenCalledWith(STATUS_WAITING); - } - for (const issue of nonMatchingIssues) { - expect(issue.postComment).not.toHaveBeenCalled(); - expect(issue.addLabel).not.toHaveBeenCalled(); - } - }); - - it('sets an appropriate milestone', async () => { - const nextMilestoneIssues = [ - // Bugs go to the next milestone. (If the priority is P0-P2 or unset.) - new MockIssue({ - labels: [TYPE_BUG], - }), - new MockIssue({ - labels: [TYPE_BUG, PRIORITY_P0], - }), - new MockIssue({ - labels: [TYPE_BUG, PRIORITY_P1], - }), - new MockIssue({ - labels: [TYPE_BUG, PRIORITY_P2], - }), - // Docs issues also go to the next milestone. (Same priority rules.) - new MockIssue({ - labels: [TYPE_DOCS], - }), - new MockIssue({ - labels: [TYPE_DOCS, PRIORITY_P2], - }), - // A11y issues also go to the next milestone. (Same priority rules.) - new MockIssue({ - labels: [TYPE_ACCESSIBILITY], - }), - new MockIssue({ - labels: [TYPE_ACCESSIBILITY, PRIORITY_P2], - }), - ]; - - const backlogIssues = [ - // Low priority bugs/docs/a11y issues go the backlog. - new MockIssue({ - labels: [TYPE_BUG, PRIORITY_P3], - }), - new MockIssue({ - labels: [TYPE_BUG, PRIORITY_P4], - }), - new MockIssue({ - labels: [TYPE_DOCS, PRIORITY_P4], - }), - new MockIssue({ - labels: [TYPE_ACCESSIBILITY, PRIORITY_P4], - }), - // Enhancements go to the backlog, regardless of priority. - new MockIssue({ - labels: [TYPE_ENHANCEMENT], - }), - new MockIssue({ - labels: [TYPE_ENHANCEMENT, PRIORITY_P1], - }), - // Code health issues also go to the backlog. - new MockIssue({ - labels: [TYPE_CODE_HEALTH], - }), - ]; - - const clearMilestoneIssues = [ - // Some issue types are always removed from milestones. - new MockIssue({ - labels: [TYPE_QUESTION], - milestone: backlog, - }), - new MockIssue({ - labels: [TYPE_PROCESS], - milestone: nextMilestone, - }), - ]; - - const nonMatchingIssues = [ - // Issue types that _can_ have milestones should always keep their - // milestones once assigned manually, even if they are not the default - // for that type. - new MockIssue({ - labels: [TYPE_BUG], - milestone: backlog, - }), - new MockIssue({ - labels: [TYPE_DOCS], - milestone: backlog, - }), - new MockIssue({ - labels: [TYPE_CODE_HEALTH], - milestone: nextMilestone, - }), - new MockIssue({ - labels: [TYPE_ENHANCEMENT], - milestone: nextMilestone, - }), - // Once closed, issues are never assigned to a milestone regardless of - // type. - new MockIssue({ - labels: [TYPE_BUG], - closed: true, - }), - new MockIssue({ - labels: [TYPE_ENHANCEMENT], - closed: true, - }), - ]; - - const issues = nextMilestoneIssues - .concat(backlogIssues) - .concat(clearMilestoneIssues) - .concat(nonMatchingIssues); - - await processIssues(issues, nextMilestone, backlog); - - for (const issue of nextMilestoneIssues) { - expect(issue.setMilestone).toHaveBeenCalledWith(nextMilestone); - } - for (const issue of backlogIssues) { - expect(issue.setMilestone).toHaveBeenCalledWith(backlog); - } - for (const issue of clearMilestoneIssues) { - expect(issue.removeMilestone).toHaveBeenCalled(); - } - for (const issue of nonMatchingIssues) { - expect(issue.setMilestone).not.toHaveBeenCalled(); - expect(issue.removeMilestone).not.toHaveBeenCalled(); - } - }); - - it('parses and sorts milestone versions', () => { - const milestones = [ - new Milestone({title: 'v1.0'}), - new Milestone({title: 'Backlog'}), - new Milestone({title: 'v1.1'}), - new Milestone({title: 'v11.0'}), - new Milestone({title: 'v11.0-beta'}), - new Milestone({title: 'v10.0'}), - new Milestone({title: 'v2.0'}), - ]; - - expect(milestones.map(m => m.version)).toEqual([ - [1, 0], - null, - [1, 1], - [11, 0], - [11, 0, -1, 'beta'], - [10, 0], - [2, 0], - ]); - - milestones.sort(Milestone.compare); - - expect(milestones.map(m => m.title)).toEqual([ - 'v1.0', - 'v1.1', - 'v2.0', - 'v10.0', - 'v11.0-beta', - 'v11.0', - 'Backlog', - ]); - }); -}); diff --git a/.github/workflows/update_issues.yaml b/.github/workflows/update_issues.yaml index 3ba008185f..0b8238d3f7 100644 --- a/.github/workflows/update_issues.yaml +++ b/.github/workflows/update_issues.yaml @@ -1,3 +1,4 @@ +# Install this in .github/workflows/ to automate issue maintenance. name: Update Issues on: @@ -16,11 +17,13 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v2 + with: + repository: joeyparrish/shaka-github-tools - name: Update Issues env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cd .github/workflows/tools/update-issues + cd update-issues npm ci node main.js