diff --git a/.github/ISSUE_TEMPLATE/meeting.md b/.github/ISSUE_TEMPLATE/meeting.md index a921bfa..3b0669d 100644 --- a/.github/ISSUE_TEMPLATE/meeting.md +++ b/.github/ISSUE_TEMPLATE/meeting.md @@ -34,7 +34,7 @@ Or in your local time: -* https://www.timeanddate.com/worldclock/?iso=<%= date.toZonedDateTimeISO('UTC').toPlainDateTime().toString() %> +* https://www.timeanddate.com/worldclock/fixedtime.html?msg=<%= encodeURIComponent(title) %>&iso=<%= date.toZonedDateTimeISO('UTC').toPlainDateTime().toString().slice(0, 16).replace(/[-:]/g, '') %>&p1=1440&ah=1 ## Agenda diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 24388c5..4d4064f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,9 +23,12 @@ jobs: node-version: ${{ matrix.node-version }} - run: npm install - run: npm test + - run: npm run test:integration-issues testint: runs-on: ubuntu-latest + # only run if not a fork PR targeting main repo due to permissions + if: ${{ !(github.head_repo || github.event.pull_request.head.repo) || github.repository == (github.head_repo.full_name || github.event.pull_request.head.repo.full_name) }} steps: - uses: actions/checkout@v4 - name: Use Node.js 24 @@ -35,9 +38,13 @@ jobs: - run: npm install - run: npm run test:integration env: - GITHUB_TOKEN: ${{ secrets.INT_TEST_TOKEN }} + GITHUB_TOKEN: ${{ secrets.INT_TEST_TOKEN != '' && secrets.INT_TEST_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_HEAD_REPO: ${{ github.head_repo && github.head_repo.full_name || github.event.pull_request.head.repo.full_name || '' }} action-in-action: runs-on: ubuntu-latest + # only run if not a fork PR targeting main repo due to permissions + if: ${{ !(github.head_repo || github.event.pull_request.head.repo) || github.repository == (github.head_repo.full_name || github.event.pull_request.head.repo.full_name) }} steps: - uses: actions/checkout@v4 - name: Use Node.js 24 @@ -47,6 +54,9 @@ jobs: - run: npm install - uses: ./ id: maker + env: + TEST_REPOS: ${{ github.repository != 'pkgjs/meet' && github.repository || 'pkgjs/meet,pkgjs/meet' }} + TEST_ORGS: ${{ github.repository != 'pkgjs/meet' && '' || 'pkgjs' }} with: token: ${{ secrets.GITHUB_TOKEN }} schedules: 2020-04-02T17:00:00.0Z/P1D @@ -56,7 +66,7 @@ jobs: agendaLabel: meeting-agenda-test meetingLink: https://github.com/pkgjs/meet createNotes: true - repos: pkgjs/meet,pkgjs/meet - orgs: pkgjs + repos: ${{ env.TEST_REPOS }} + orgs: ${{ env.TEST_ORGS }} - name: clean up issue run: node ./test/_close-issue.js ${{ secrets.GITHUB_TOKEN }} ${{ steps.maker.outputs.issueNumber }} diff --git a/README.md b/README.md index 42d3c6f..a285aa2 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,7 @@ When using EJS templates for your meeting issues, the following data properties Or in your local time: -* https://www.timeanddate.com/worldclock/?iso=<%= date.toZonedDateTimeISO('UTC').toPlainDateTime().toString() %> +* https://www.timeanddate.com/worldclock/fixedtime.html?msg=<%= encodeURIComponent(title) %>&iso=<%= date.toZonedDateTimeISO('UTC').toPlainDateTime().toString().slice(0, 16).replace(/[-:]/g, '') %>&p1=1440&ah=1 ## Agenda diff --git a/lib/agenda.js b/lib/agenda.js new file mode 100644 index 0000000..2bbb9ba --- /dev/null +++ b/lib/agenda.js @@ -0,0 +1,57 @@ +'use strict' + +/** + * get agenda issues and PRs from repositories + * @param {Object} client - GitHub client + * @param {Array} repos - array of objects with owner and repo properties + * @param {string} agendaLabel - label to filter issues and PRs by + * @returns {Promise} array of unique issues and PRs + */ +async function fetchAgendaItems (client, repos, agendaLabel) { + const agendaIssues = [] + + // deduplicate repos + const uniqueRepos = [...new Map( + repos.map(repo => [`${repo.owner}/${repo.repo}`, repo]) + ).values()] + + for (const r of uniqueRepos) { + const _agendaIssues = await client.paginate('GET /repos/{owner}/{repo}/issues', { + owner: r.owner, + repo: r.repo, + state: 'open', + labels: agendaLabel, + per_page: 100 + }) + + console.log(`Fetching issues for ${r.owner}/${r.repo}: Found ${_agendaIssues.length}`) + + for (const i of _agendaIssues) { + console.log(`Adding Issue: ${i.url}`) + agendaIssues.push(i) + } + + const _agendaPrs = (await client.paginate('GET /repos/{owner}/{repo}/pulls', { + owner: r.owner, + repo: r.repo, + state: 'open', + labels: agendaLabel, + per_page: 100 + })).filter(pr => pr.labels.find(label => label.name === agendaLabel) && + !(agendaIssues.find((i) => i.url === pr.url))) // workaround for flaky GH API/SDK behavior where sometimes the issue endpoint loads PRs + + console.log(`Fetching PRs for ${r.owner}/${r.repo}: Found ${_agendaPrs.length}`) + + for (const pr of _agendaPrs) { + console.log(`Adding PR: ${pr.url}`) + agendaIssues.push(pr) + } + } + + console.log(`Found ${agendaIssues.length} total issues for agenda`) + return agendaIssues +} + +module.exports = { + fetchAgendaItems +} diff --git a/lib/default-template.js b/lib/default-template.js index d2afe41..bd18fec 100644 --- a/lib/default-template.js +++ b/lib/default-template.js @@ -1,6 +1,6 @@ 'use strict' -module.exports = ({ date, agendaIssues, agendaLabel, meetingNotes, owner, repo, meetingLink }) => { +module.exports = ({ date, agendaIssues, agendaLabel, meetingNotes, owner, repo, meetingLink, title }) => { const timezones = [ 'America/Los_Angeles', 'America/Denver', @@ -37,7 +37,8 @@ ${timezones.map((zone) => { }).join('\n')} Or in your local time: -* https://www.timeanddate.com/worldclock/?iso=${date.toZonedDateTimeISO('UTC').toPlainDateTime().toString()} + +* https://www.timeanddate.com/worldclock/fixedtime.html?msg=${encodeURIComponent(title)}&iso=${date.toZonedDateTimeISO('UTC').toPlainDateTime().toString().slice(0, 16).replace(/[-:]/g, '')}&p1=1440&ah=1 ## Agenda diff --git a/package-lock.json b/package-lock.json index 92ee5e4..8d1eca2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "safe-parse-list": "^0.1.1" }, "devDependencies": { + "@octokit/rest": "^22.0.0", "@vercel/ncc": "^0.38.3", "dotenv": "^17.2.1", "mocha": "^11.7.1", @@ -130,18 +131,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/@eslint/js": { "version": "8.57.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", @@ -184,18 +173,6 @@ "node": ">=10.10.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -514,6 +491,186 @@ "node": ">= 18" } }, + "node_modules/@octokit/rest": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-22.0.0.tgz", + "integrity": "sha512-z6tmTu9BTnw51jYGulxrlernpsQYXpui1RK21vmXn8yF5bp6iX16yfTtJYGK5Mh1qDkvDOmp2n8sRMcQmR8jiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/core": "^7.0.2", + "@octokit/plugin-paginate-rest": "^13.0.1", + "@octokit/plugin-request-log": "^6.0.0", + "@octokit/plugin-rest-endpoint-methods": "^16.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/rest/node_modules/@octokit/auth-token": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", + "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/rest/node_modules/@octokit/core": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.3.tgz", + "integrity": "sha512-oNXsh2ywth5aowwIa7RKtawnkdH6LgU1ztfP9AIUCQCvzysB+WeU8o2kyyosDPwBZutPpjZDKPQGIzzrfTWweQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^6.0.0", + "@octokit/graphql": "^9.0.1", + "@octokit/request": "^10.0.2", + "@octokit/request-error": "^7.0.0", + "@octokit/types": "^14.0.0", + "before-after-hook": "^4.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/rest/node_modules/@octokit/endpoint": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.0.tgz", + "integrity": "sha512-hoYicJZaqISMAI3JfaDr1qMNi48OctWuOih1m80bkYow/ayPw6Jj52tqWJ6GEoFTk1gBqfanSoI1iY99Z5+ekQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/rest/node_modules/@octokit/graphql": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.1.tgz", + "integrity": "sha512-j1nQNU1ZxNFx2ZtKmL4sMrs4egy5h65OMDmSbVyuCzjOcwsHq6EaYjOTGXPQxgfiN8dJ4CriYHk6zF050WEULg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request": "^10.0.2", + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/rest/node_modules/@octokit/openapi-types": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", + "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/rest/node_modules/@octokit/plugin-paginate-rest": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-13.1.1.tgz", + "integrity": "sha512-q9iQGlZlxAVNRN2jDNskJW/Cafy7/XE52wjZ5TTvyhyOD904Cvx//DNyoO3J/MXJ0ve3rPoNWKEg5iZrisQSuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.1.0" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/rest/node_modules/@octokit/plugin-request-log": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-6.0.0.tgz", + "integrity": "sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/rest/node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-16.0.0.tgz", + "integrity": "sha512-kJVUQk6/dx/gRNLWUnAWKFs1kVPn5O5CYZyssyEoNYaFedqZxsfYs7DwI3d67hGz4qOwaJ1dpm07hOAD1BXx6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.1.0" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/rest/node_modules/@octokit/request": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.3.tgz", + "integrity": "sha512-V6jhKokg35vk098iBqp2FBKunk3kMTXlmq+PtbV9Gl3TfskWlebSofU9uunVKhUN7xl+0+i5vt0TGTG8/p/7HA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^11.0.0", + "@octokit/request-error": "^7.0.0", + "@octokit/types": "^14.0.0", + "fast-content-type-parse": "^3.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/rest/node_modules/@octokit/request-error": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.0.0.tgz", + "integrity": "sha512-KRA7VTGdVyJlh0cP5Tf94hTiYVVqmt2f3I6mnimmaVz4UG3gQV/k4mDJlJv3X67iX6rmN7gSHCF8ssqeMnmhZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/rest/node_modules/@octokit/types": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", + "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^25.1.0" + } + }, + "node_modules/@octokit/rest/node_modules/before-after-hook": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", + "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@octokit/rest/node_modules/universal-user-agent": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", + "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", + "dev": true, + "license": "ISC" + }, "node_modules/@octokit/types": { "version": "13.10.0", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", @@ -860,9 +1017,9 @@ "license": "Apache-2.0" }, "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==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1736,18 +1893,6 @@ "node": ">=0.10.0" } }, - "node_modules/eslint-plugin-import/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/eslint-plugin-import/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -1782,18 +1927,6 @@ "eslint": ">=7.0.0" } }, - "node_modules/eslint-plugin-n/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/eslint-plugin-promise": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", @@ -1851,18 +1984,6 @@ "node": ">=0.10.0" } }, - "node_modules/eslint-plugin-react/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/eslint-plugin-react/node_modules/resolve": { "version": "2.0.0-next.5", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", @@ -1987,18 +2108,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/eslint/node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -2097,6 +2206,23 @@ "node": ">=0.10.0" } }, + "node_modules/fast-content-type-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", + "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2145,9 +2271,9 @@ } }, "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dependencies": { "balanced-match": "^1.0.0" } @@ -3326,9 +3452,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" }, diff --git a/package.json b/package.json index dc0ee4d..1878da1 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "test": "standard && mocha test/index.js", "test:integration": "standard && mocha test/integration.js", "test:integration:debug": "GITHUB_TOKEN=$(gh auth token) mocha --inspect --inspect-brk test/integration.js", + "test:integration-issues": "standard && mocha test/integration-issues.js", "lint:fix": "standard --fix", "preversion": "npm t", "postpublish": "git push origin && git push origin --tags", @@ -24,11 +25,12 @@ "@actions/core": "^1.11.1", "@actions/github": "^6.0.1", "@hackmd/api": "^2.5.0", - "ejs": "^3.1.10", "@js-temporal/polyfill": "^0.5.1", + "ejs": "^3.1.10", "safe-parse-list": "^0.1.1" }, "devDependencies": { + "@octokit/rest": "^22.0.0", "@vercel/ncc": "^0.38.3", "dotenv": "^17.2.1", "mocha": "^11.7.1", diff --git a/run.js b/run.js index a574fd8..5c57fa7 100644 --- a/run.js +++ b/run.js @@ -9,6 +9,7 @@ const notes = require('./lib/notes') const defaultTemplate = require('./lib/default-template') const defaultNotesTemplate = require('./lib/default-notes-template') const conversions = require('./lib/conversions') +const agenda = require('./lib/agenda') const pkg = require('./package.json') ;(async function run () { @@ -95,23 +96,7 @@ const pkg = require('./package.json') } } - const agendaIssues = [] - for (const r of repos) { - const _agendaIssues = await client.paginate('GET /repos/{owner}/{repo}/issues', { - owner: r.owner, - repo: r.repo, - state: 'open', - labels: agendaLabel - }) - console.log(`Fetching issues for ${r.owner}/${r.repo}: Found ${_agendaIssues.length}`) - for (const i of _agendaIssues) { - console.log(`Adding Issue: ${i.url}`) - if (!agendaIssues.find((ii) => ii.url === i.url)) { - agendaIssues.push(i) - } - } - } - console.log(`Found ${agendaIssues.length} total issues for agenda`) + const agendaIssues = await agenda.fetchAgendaItems(client, repos, agendaLabel) const opts = { ...repo, diff --git a/test/_close-issue.js b/test/_close-issue.js index 6931923..1b70ea4 100644 --- a/test/_close-issue.js +++ b/test/_close-issue.js @@ -6,10 +6,13 @@ const issues = require('../lib/issues') if (!issueNumber) { return } - console.log(`Closing test issue ${issueNumber}`) const client = getOctokit(token) + const repo = context.repo + + console.log(`Closing test issue ${issueNumber} in ${repo.owner}/${repo.repo}`) + await issues.closeIssue(client, issueNumber, { - ...context.repo + ...repo }) })(process.argv) diff --git a/test/integration-issues.js b/test/integration-issues.js new file mode 100644 index 0000000..27e73cb --- /dev/null +++ b/test/integration-issues.js @@ -0,0 +1,51 @@ +'use strict' + +const { suite, test, before } = require('mocha') +const assert = require('assert') +const agenda = require('../lib/agenda') + +suite('agenda/issues integration', () => { + let client + + before(async () => { + const { Octokit } = await import('@octokit/rest') + client = new Octokit() + }) + + test('should fetch issues and PRs from wesleytodd/meeting-maker repo without duplicates', async () => { + const agendaLabel = 'meeting-agenda-test' + const repos = [ + { owner: 'wesleytodd', repo: 'meeting-maker' }, + { owner: 'wesleytodd', repo: 'meeting-maker' } // test for when a repo is duplicated + ] + + const agendaIssues = await agenda.fetchAgendaItems(client, repos, agendaLabel) + + assert(Array.isArray(agendaIssues), 'agendaIssues should be an array') + + const urls = agendaIssues.map(item => item.url) + const uniqueUrls = [...new Set(urls)] + assert.strictEqual(urls.length, uniqueUrls.length, 'should have no duplicate URLs') + + for (const item of agendaIssues) { + assert(item.url.includes('wesleytodd/meeting-maker'), + `all issues should be from wesleytodd/meeting-maker, found: ${item.url}`) + } + + for (const item of agendaIssues) { + const hasAgendaLabel = item.labels && item.labels.some(label => label.name === agendaLabel) + assert(hasAgendaLabel, `all issues should have the '${agendaLabel}' label`) + } + + for (const item of agendaIssues) { + assert.strictEqual(item.state, 'open', 'all issues should be open') + } + + const issueCount = agendaIssues.filter(i => !i.pull_request).length + const prCount = agendaIssues.filter(i => i.pull_request).length + + // relaxed assertions here so the test isn't too brittle + assert.ok(issueCount >= 2, 'should have at least 2 issues') + assert.ok(prCount >= 1, 'should have at least 1 PR') + }) +}) diff --git a/test/integration.js b/test/integration.js index 347cc79..33d786b 100644 --- a/test/integration.js +++ b/test/integration.js @@ -11,15 +11,47 @@ const { getOctokit } = require('@actions/github') const pkg = require('../package.json') const meetings = require('../lib/meetings') +const mainRepo = 'pkgjs/meet' + +function getTestRepo () { + let testRepo = { owner: 'wesleytodd', repo: 'meeting-maker' } // ✨ Wes, the meeting maker ✨ + + if (process.env.GITHUB_REPOSITORY) { + // we appear to be in a GH action + if (process.env.GITHUB_REPOSITORY !== mainRepo) { + // action running in a fork + const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/') + testRepo = { owner, repo } + } else if (process.env.GITHUB_HEAD_REPO && + process.env.GITHUB_HEAD_REPO !== mainRepo) { + // action running in a fork PR targeting main repo + // skip tests - GH token doesn't have write permissions for either repo + throw new Error('skipping integration tests: fork PR targeting main repo (no permissions)') + } + } + + console.log(`using repository ${testRepo.owner}/${testRepo.repo}`) + return testRepo +} + suite(`${pkg.name} integration`, () => { let client + let testRepo + before(() => { - client = getOctokit(process.env.GITHUB_TOKEN) + const token = process.env.GITHUB_TOKEN + if (!token) { + throw new Error('GITHUB_TOKEN environment variable is required for integration tests') + } + + client = getOctokit(token) + testRepo = getTestRepo() }) + test('should create next meeting issue', async () => { const issue = await meetings.shouldCreateNextMeetingIssue(client, { - owner: 'wesleytodd', - repo: 'meeting-maker', + owner: testRepo.owner, + repo: testRepo.repo, issueTitle: ({ date }) => `Test Meeting ${date.toZonedDateTimeISO('UTC').toPlainDate().toString()}`, createWithin: 'P7D', agendaLabel: 'meeting-agenda', @@ -30,8 +62,8 @@ suite(`${pkg.name} integration`, () => { now: Temporal.Instant.from('2020-04-13T13:00:00.0Z'), meetingLabels: ['testMeeting', 'test'] }) - assert.deepStrictEqual(issue.owner, 'wesleytodd') - assert.deepStrictEqual(issue.repo, 'meeting-maker') + assert.deepStrictEqual(issue.owner, testRepo.owner) + assert.deepStrictEqual(issue.repo, testRepo.repo) assert.deepStrictEqual(issue.title, `Test Meeting ${Temporal.Instant.from('2020-04-16T13:00:00.0Z').toZonedDateTimeISO('UTC').toPlainDate().toString()}`) assert.deepStrictEqual(issue.agendaLabel, 'meeting-agenda') assert.deepStrictEqual(issue.labels, ['testMeeting', 'test']) @@ -41,8 +73,8 @@ suite(`${pkg.name} integration`, () => { test('create next meeting issue', async () => { const issue = await meetings.createNextMeeting(client, { - owner: 'wesleytodd', - repo: 'meeting-maker', + owner: testRepo.owner, + repo: testRepo.repo, createWithin: 'P7D', schedules: [ // 5pm GMT April 2 repeating every 28 days @@ -60,8 +92,8 @@ suite(`${pkg.name} integration`, () => { assert.deepStrictEqual(issue.data.state, 'open') await client.rest.issues.update({ - owner: 'wesleytodd', - repo: 'meeting-maker', + owner: testRepo.owner, + repo: testRepo.repo, issue_number: issue.data.number, state: 'closed' })