diff --git a/.github/actions/get-next-package-version.js b/.github/actions/get-next-package-version.js index 8715d2b9d5..4b52b35668 100644 --- a/.github/actions/get-next-package-version.js +++ b/.github/actions/get-next-package-version.js @@ -13,7 +13,7 @@ const options = { type: 'string', short: 'p', }, - 'type': { + type: { type: 'string', // nightly, hotfix, monthly short: 't', }, @@ -25,8 +25,10 @@ const { values } = parseArgs({ allowPositionals: true, }); -if (!values["package-json"]) { - console.error('Please specify the path to package.json using --package-json or -p option.'); +if (!values['package-json']) { + console.error( + 'Please specify the path to package.json using --package-json or -p option.', + ); process.exit(1); } @@ -45,14 +47,24 @@ try { // Create date and add 1 month const versionDate = new Date(2000 + versionYear, versionMonth - 1, 1); // month is 0-indexed - const nextVersionMonthDate = new Date(versionDate.getFullYear(), versionDate.getMonth() + 1, 1); + const nextVersionMonthDate = new Date( + versionDate.getFullYear(), + versionDate.getMonth() + 1, + 1, + ); // Format back to YY.M format - const nextVersionYear = nextVersionMonthDate.getFullYear().toString().slice(-2); + const nextVersionYear = nextVersionMonthDate + .getFullYear() + .toString() + .slice(-2); const nextVersionMonth = nextVersionMonthDate.getMonth() + 1; // Convert back to 1-indexed // Get current date string - const currentDate = new Date().toISOString().split('T')[0].replaceAll('-', ''); + const currentDate = new Date() + .toISOString() + .split('T')[0] + .replaceAll('-', ''); switch (values.type) { case 'nightly': { @@ -71,7 +83,9 @@ try { process.exit(); } default: - console.error('Invalid type specified. Use "nightly", "hotfix", or "monthly".'); + console.error( + 'Invalid type specified. Use "nightly", "hotfix", or "monthly".', + ); process.exit(1); } } catch (error) { diff --git a/.github/scripts/count-points.mjs b/.github/scripts/count-points.mjs new file mode 100644 index 0000000000..bb8eb7306d --- /dev/null +++ b/.github/scripts/count-points.mjs @@ -0,0 +1,350 @@ +import { Octokit } from '@octokit/rest'; +import { minimatch } from 'minimatch'; + +/** Repository-specific configuration for points calculation */ +const REPOSITORY_CONFIG = new Map([ + [ + 'actual', + { + POINTS_PER_ISSUE_TRIAGE_ACTION: 1, + POINTS_PER_ISSUE_CLOSING_ACTION: 1, + POINTS_PER_RELEASE_PR: 0, + PR_REVIEW_POINT_TIERS: [ + { minChanges: 1000, points: 6 }, + { minChanges: 100, points: 4 }, + { minChanges: 0, points: 2 }, + ], + EXCLUDED_FILES: [ + 'yarn.lock', + '.yarn/**/*', + 'packages/component-library/src/icons/**/*', + 'release-notes/**/*', + ], + }, + ], + [ + 'docs', + { + POINTS_PER_ISSUE_TRIAGE_ACTION: 1, + POINTS_PER_ISSUE_CLOSING_ACTION: 1, + POINTS_PER_RELEASE_PR: 4, + PR_REVIEW_POINT_TIERS: [ + { minChanges: 1000, points: 6 }, + { minChanges: 100, points: 4 }, + { minChanges: 0, points: 2 }, + ], + EXCLUDED_FILES: ['yarn.lock', '.yarn/**/*'], + }, + ], +]); + +/** + * Get the start and end dates for the last month. + * @returns {Object} An object containing the start and end dates. + */ +function getLastMonthDates() { + // Get data relating to the last month + const now = new Date(); + const firstDayOfLastMonth = new Date( + now.getFullYear(), + now.getMonth() - 1, + 1, + ); + const since = process.env.START_DATE + ? new Date(process.env.START_DATE) + : firstDayOfLastMonth; + + // Calculate the end of the month for the since date + const until = new Date( + since.getFullYear(), + since.getMonth() + 1, + 0, + 23, + 59, + 59, + 999, + ); + + return { since, until }; +} + +/** + * Used for calculating the monthly points each core contributor has earned. + * These are used for payouts depending. + * @param {string} repo - The repository to analyze ('actual' or 'docs') + * @returns {number} The total points earned for the repository + */ +async function countContributorPoints(repo) { + const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN }); + const owner = 'actualbudget'; + const config = REPOSITORY_CONFIG.get(repo); + + const { since, until } = getLastMonthDates(); + + // Get organization members + const { data: orgMembers } = await octokit.orgs.listMembers({ + org: owner, + }); + const orgMemberLogins = new Set(orgMembers.map(member => member.login)); + + // Initialize stats map with all org members + const stats = new Map( + Array.from(orgMemberLogins).map(login => [ + login, + { + reviews: [], // Will store objects with PR number and points + labelRemovals: [], + issueClosings: [], + points: 0, + }, + ]), + ); + + // Helper function to print statistics + const printStats = (title, getValue, formatLine) => { + console.log(`\n${title}:`); + console.log('='.repeat(title.length + 1)); + + const entries = Array.from(stats.entries()) + .map(([user, userStats]) => [user, getValue(userStats)]) + .filter(([, count]) => count > 0) + .sort((a, b) => b[1] - a[1]); + + if (entries.length === 0) { + console.log(`No ${title.toLowerCase()} found in the last month.`); + } else { + entries.forEach(([user, count]) => { + console.log(formatLine(user, count)); + }); + } + }; + + // Get all PRs using search + const searchQuery = `repo:${owner}/${repo} is:pr is:merged merged:${since.toISOString()}..${until.toISOString()}`; + const recentPRs = await octokit.paginate( + octokit.search.issuesAndPullRequests, + { + q: searchQuery, + per_page: 100, + advanced_search: true, + }, + response => response.data, + ); + + // Get reviews and PR details for each PR + for (const pr of recentPRs) { + const { data: reviews } = await octokit.pulls.listReviews({ + owner, + repo, + pull_number: pr.number, + }); + + // Get list of modified files + const { data: modifiedFiles } = await octokit.pulls.listFiles({ + owner, + repo, + pull_number: pr.number, + }); + + // Calculate points based on PR size, excluding specified files + const totalChanges = modifiedFiles + .filter( + file => + !config.EXCLUDED_FILES.some(pattern => + minimatch(file.filename, pattern), + ), + ) + .reduce((sum, file) => sum + file.additions + file.deletions, 0); + + // Check if this is a release PR + const isReleasePR = pr.title.match(/^🔖 \(\d+\.\d+\.\d+\)/); + + // Calculate points for reviewers based on PR size + const prPoints = config.PR_REVIEW_POINT_TIERS.find( + tier => totalChanges > tier.minChanges, + ).points; + + // Add points to the reviewers + const uniqueReviewers = new Set(); + reviews + .filter( + review => + stats.has(review.user?.login) && + review.state === 'APPROVED' && + !uniqueReviewers.has(review.user?.login), + ) + .forEach(({ user: { login: reviewer } }) => { + uniqueReviewers.add(reviewer); + const userStats = stats.get(reviewer); + userStats.reviews.push({ pr: pr.number.toString(), points: prPoints }); + userStats.points += prPoints; + }); + + // Award points to the PR creator if it's a release PR + if (isReleasePR && stats.has(pr.user.login)) { + const creatorStats = stats.get(pr.user.login); + creatorStats.reviews.push({ + pr: pr.number.toString(), + points: config.POINTS_PER_RELEASE_PR, + isReleaseCreator: true, + }); + creatorStats.points += config.POINTS_PER_RELEASE_PR; + } + } + + // Get all issues with label events in the last month + const issues = await octokit.paginate( + octokit.issues.listForRepo, + { + owner, + repo, + state: 'all', + sort: 'updated', + direction: 'desc', + per_page: 100, + since: since.toISOString(), + }, + (response, done) => + response.data.filter(issue => new Date(issue.updated_at) <= until), + ); + + // Get label events for each issue + for (const issue of issues) { + const { data: events } = await octokit.issues.listEventsForTimeline({ + owner, + repo, + issue_number: issue.number, + }); + + // Process events + events + .filter( + event => + new Date(event.created_at) > since && + new Date(event.created_at) <= until && + stats.has(event.actor?.login), + ) + .forEach(event => { + if ( + event.event === 'unlabeled' && + event.label && + event.label.name.toLowerCase() === 'needs triage' + ) { + const remover = event.actor.login; + const userStats = stats.get(remover); + userStats.labelRemovals.push(issue.number.toString()); + userStats.points += config.POINTS_PER_ISSUE_TRIAGE_ACTION; + } + + if (event.event === 'closed') { + const closer = event.actor.login; + const userStats = stats.get(closer); + userStats.issueClosings.push(issue.number.toString()); + userStats.points += config.POINTS_PER_ISSUE_CLOSING_ACTION; + } + }); + } + + // Print all statistics + printStats( + `PR Review Statistics (${repo})`, + stats => stats.reviews.length, + (user, count) => + `${user}: ${count} (PRs: ${stats + .get(user) + .reviews.map(r => { + if (r.isReleaseCreator) { + return `#${r.pr} (${r.points}pts - Release Creator)`; + } + return `#${r.pr} (${r.points}pts)`; + }) + .join(', ')})`, + ); + printStats( + `"Needs Triage" Label Removal Statistics (${repo})`, + stats => stats.labelRemovals.length, + (user, count) => + `${user}: ${count} (Issues: ${stats.get(user).labelRemovals.join(', ')})`, + ); + printStats( + `Issue Closing Statistics (${repo})`, + stats => stats.issueClosings.length, + (user, count) => + `${user}: ${count} (Issues: ${stats.get(user).issueClosings.join(', ')})`, + ); + + // Print points summary + printStats( + `Points Summary (${repo})`, + stats => stats.points, + (user, userPoints) => `${user}: ${userPoints}`, + ); + + // Calculate and print total points + const totalPoints = Array.from(stats.values()).reduce( + (sum, userStats) => sum + userStats.points, + 0, + ); + console.log(`\nTotal points earned for ${repo}: ${totalPoints}`); + + // Return the points + return new Map( + Array.from(stats.entries()).map(([login, userStats]) => [ + login, + userStats.points, + ]), + ); +} + +/** + * Calculate the points for both repositories and print cumulative results + */ +async function calculateCumulativePoints() { + // Get stats for each repository + const repoPointsResults = await Promise.all( + Array.from(REPOSITORY_CONFIG.keys()).map(countContributorPoints), + ); + + // Calculate cumulative stats + const cumulativeStats = new Map(repoPointsResults[0]); + + // Combine stats from all repositories + for (let i = 1; i < repoPointsResults.length; i++) { + for (const [login, points] of repoPointsResults[i].entries()) { + if (!cumulativeStats.has(login)) { + cumulativeStats.set(login, 0); + } + + cumulativeStats.set(login, cumulativeStats.get(login) + points); + } + } + + // Print cumulative statistics + console.log('\n\nCUMULATIVE STATISTICS ACROSS ALL REPOSITORIES'); + console.log('='.repeat(50)); + + console.log('\nCumulative Points Summary:'); + console.log('='.repeat('Cumulative Points Summary'.length + 1)); + + const entries = Array.from(cumulativeStats.entries()) + .filter(([, count]) => count > 0) + .sort((a, b) => b[1] - a[1]); + + if (entries.length === 0) { + console.log('No cumulative points summary found.'); + } else { + entries.forEach(([user, points]) => { + console.log(`${user}: ${points}`); + }); + } + + // Calculate and print total cumulative points + const totalCumulativePoints = Array.from(cumulativeStats.values()).reduce( + (sum, points) => sum + points, + 0, + ); + console.log('\nTotal cumulative points earned: ' + totalCumulativePoints); +} + +// Run the calculations +calculateCumulativePoints().catch(console.error); diff --git a/.github/workflows/count-points.yml b/.github/workflows/count-points.yml new file mode 100644 index 0000000000..61c68308ed --- /dev/null +++ b/.github/workflows/count-points.yml @@ -0,0 +1,26 @@ +name: Count points + +on: + schedule: + # Run at 00:00 on the first day of every month + - cron: '0 0 1 * *' + workflow_dispatch: + inputs: + startDate: + description: 'Start date for point counter (YYYY-MM-DD)' + required: true + type: string + +jobs: + count-points: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Set up environment + uses: ./.github/actions/setup + - name: Count points + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + START_DATE: ${{ inputs.startDate }} + run: node .github/scripts/count-points.mjs diff --git a/.github/workflows/docker-edge.yml b/.github/workflows/docker-edge.yml index 2ddb98b468..3aa8a8d944 100644 --- a/.github/workflows/docker-edge.yml +++ b/.github/workflows/docker-edge.yml @@ -107,5 +107,3 @@ jobs: tags: ${{ steps.meta.outputs.tags }} build-args: | GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} - - diff --git a/.github/workflows/i18n-string-extract-master.yml b/.github/workflows/i18n-string-extract-master.yml index 484f042d21..b9dfda0580 100644 --- a/.github/workflows/i18n-string-extract-master.yml +++ b/.github/workflows/i18n-string-extract-master.yml @@ -3,7 +3,7 @@ name: Extract and upload i18n strings on: schedule: # 4am UTC - - cron: "0 4 * * *" + - cron: '0 4 * * *' workflow_dispatch: jobs: @@ -19,7 +19,7 @@ jobs: uses: ./actual/.github/actions/setup with: working-directory: actual - download-translations: false # As we'll manually clone instead + download-translations: false # As we'll manually clone instead - name: Configure Git config run: | git config --global user.name "github-actions[bot]" @@ -78,7 +78,7 @@ jobs: actualbudget/actual - name: Unlock translations - if: always() # Clean up even on failure + if: always() # Clean up even on failure run: | wlc \ --url https://hosted.weblate.org/api/ \ diff --git a/.github/workflows/netlify-release.yml b/.github/workflows/netlify-release.yml index 649054132c..9bc3ec35c1 100644 --- a/.github/workflows/netlify-release.yml +++ b/.github/workflows/netlify-release.yml @@ -22,15 +22,15 @@ jobs: steps: - name: Repository Checkout uses: actions/checkout@v4 - + - name: Set up environment uses: ./.github/actions/setup - + - name: Install Netlify run: npm install netlify-cli@17.10.1 -g - + - name: Build Actual - run: yarn build:browser + run: yarn build:browser - name: Deploy to Netlify id: netlify_deploy @@ -40,4 +40,4 @@ jobs: --site ${{ secrets.NETLIFY_SITE_ID }} \ --auth ${{ secrets.NETLIFY_API_TOKEN }} \ --filter @actual-app/web \ - --prod \ No newline at end of file + --prod diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 1cb4b48443..0e6fcba0e9 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -2,7 +2,7 @@ name: 'Close stale PRs' on: schedule: - cron: '30 1 * * *' - workflow_dispatch: # Allow manual triggering + workflow_dispatch: # Allow manual triggering jobs: stale: @@ -36,7 +36,7 @@ jobs: days-before-close: 7 close-issue-message: 'This issue has been automatically closed because there have been no comments for 7 days after the "needs info" label was added. If you still need help, please feel free to reopen the issue with the requested information.' remove-stale-when-updated: false - stale-pr-message: '' # Disable PR processing - close-pr-message: '' # Disable PR processing - days-before-pr-stale: -1 # Disable PR processing - days-before-pr-close: -1 # Disable PR processing + stale-pr-message: '' # Disable PR processing + close-pr-message: '' # Disable PR processing + days-before-pr-stale: -1 # Disable PR processing + days-before-pr-close: -1 # Disable PR processing diff --git a/.prettierignore b/.prettierignore index bfdf11b887..c6dad259eb 100644 --- a/.prettierignore +++ b/.prettierignore @@ -26,5 +26,4 @@ packages/loot-core/**/node_modules/* packages/loot-core/**/lib-dist/* packages/loot-core/**/proto/* .yarn/* -.github/* upcoming-release-notes/* diff --git a/package.json b/package.json index a260e47e8d..83123ec320 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "prepare": "husky" }, "devDependencies": { + "@octokit/rest": "^22.0.0", "@types/node": "^22.15.18", "@types/prompts": "^2.4.9", "@typescript-eslint/parser": "^8.32.1", @@ -70,6 +71,7 @@ "html-to-image": "^1.11.13", "husky": "^9.1.7", "lint-staged": "^15.5.2", + "minimatch": "^10.0.3", "node-jq": "^6.0.1", "npm-run-all": "^4.1.5", "prettier": "^3.5.3", @@ -89,7 +91,7 @@ "yarn": "^4.9.1" }, "lint-staged": { - "*.{js,jsx,ts,tsx,md,json,yml}": [ + "*.{js,mjs,jsx,ts,tsx,md,json,yml}": [ "eslint --fix", "prettier --write" ] diff --git a/upcoming-release-notes/5147.md b/upcoming-release-notes/5147.md new file mode 100644 index 0000000000..d4647c0eb1 --- /dev/null +++ b/upcoming-release-notes/5147.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MatissJanis] +--- + +Add GitHub workflow for counting monthly contributor points diff --git a/yarn.lock b/yarn.lock index cdc9d0ef97..5b147987f2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2608,6 +2608,22 @@ __metadata: languageName: node linkType: hard +"@isaacs/balanced-match@npm:^4.0.1": + version: 4.0.1 + resolution: "@isaacs/balanced-match@npm:4.0.1" + checksum: 10/102fbc6d2c0d5edf8f6dbf2b3feb21695a21bc850f11bc47c4f06aa83bd8884fde3fe9d6d797d619901d96865fdcb4569ac2a54c937992c48885c5e3d9967fe8 + languageName: node + linkType: hard + +"@isaacs/brace-expansion@npm:^5.0.0": + version: 5.0.0 + resolution: "@isaacs/brace-expansion@npm:5.0.0" + dependencies: + "@isaacs/balanced-match": "npm:^4.0.1" + checksum: 10/cf3b7f206aff12128214a1df764ac8cdbc517c110db85249b945282407e3dfc5c6e66286383a7c9391a059fc8e6e6a8ca82262fc9d2590bd615376141fbebd2d + languageName: node + linkType: hard + "@isaacs/cliui@npm:^8.0.2": version: 8.0.2 resolution: "@isaacs/cliui@npm:8.0.2" @@ -2837,6 +2853,130 @@ __metadata: languageName: node linkType: hard +"@octokit/auth-token@npm:^6.0.0": + version: 6.0.0 + resolution: "@octokit/auth-token@npm:6.0.0" + checksum: 10/a30f5c4c984964b57193de5b6f67169f74e4779fedbe716157dd3558dd9de3ca5c105cae521b7bd8ce1ae180773a2ef01afe2306ad5a329f4fd291eba2b7c7d1 + languageName: node + linkType: hard + +"@octokit/core@npm:^7.0.2": + version: 7.0.2 + resolution: "@octokit/core@npm:7.0.2" + dependencies: + "@octokit/auth-token": "npm:^6.0.0" + "@octokit/graphql": "npm:^9.0.1" + "@octokit/request": "npm:^10.0.2" + "@octokit/request-error": "npm:^7.0.0" + "@octokit/types": "npm:^14.0.0" + before-after-hook: "npm:^4.0.0" + universal-user-agent: "npm:^7.0.0" + checksum: 10/bef39511f3653b9dec239a7e8e8bdb4f17eb43f95d4f69b14eda44a4e2d22ab0239e2a4b0a445f474afd85169928b60420d0be5b316165505851b8a69b3ab596 + languageName: node + linkType: hard + +"@octokit/endpoint@npm:^11.0.0": + version: 11.0.0 + resolution: "@octokit/endpoint@npm:11.0.0" + dependencies: + "@octokit/types": "npm:^14.0.0" + universal-user-agent: "npm:^7.0.2" + checksum: 10/d7583a44f8560343b0fbd191aa9d2653e563cdd78f550c83cf7440a66edbe47bab6d0d6c52ae271bcbd35703356154ed590b22881aa8ee690f0d8f249ce6bde0 + languageName: node + linkType: hard + +"@octokit/graphql@npm:^9.0.1": + version: 9.0.1 + resolution: "@octokit/graphql@npm:9.0.1" + dependencies: + "@octokit/request": "npm:^10.0.2" + "@octokit/types": "npm:^14.0.0" + universal-user-agent: "npm:^7.0.0" + checksum: 10/02d7ea4e2c17a4d4b7311150d0326318c756aff6cf955d9ba443a4bf26b32784832060379fc74f4537657415b262c10adb7f4a1655e15b143d19c2f099b87f16 + languageName: node + linkType: hard + +"@octokit/openapi-types@npm:^25.1.0": + version: 25.1.0 + resolution: "@octokit/openapi-types@npm:25.1.0" + checksum: 10/91989a4cec12250e6b3226e9aa931c05c27d46a946725d01e6a831af3890f157210a7032f07641a156c608cc6bf6cf55a28f07179910b644966358d6d559dec6 + languageName: node + linkType: hard + +"@octokit/plugin-paginate-rest@npm:^13.0.1": + version: 13.0.1 + resolution: "@octokit/plugin-paginate-rest@npm:13.0.1" + dependencies: + "@octokit/types": "npm:^14.1.0" + peerDependencies: + "@octokit/core": ">=6" + checksum: 10/eb58db6bbe69ccb7ac4f73ddc20f4e491d26cdef820d0676a5682ecfe01c486a3a3059cc5151802dc6efeb2b4766eac84d05eafc9a35ac4855cb4b73b29ce809 + languageName: node + linkType: hard + +"@octokit/plugin-request-log@npm:^6.0.0": + version: 6.0.0 + resolution: "@octokit/plugin-request-log@npm:6.0.0" + peerDependencies: + "@octokit/core": ">=6" + checksum: 10/8a79973b1429bfead9113c4117f418aaef5ff368795daded3415ba14623d97d5fc08d1e822dbd566ecc9f041119e1a48a11853a9c48d9eb1caa62baa79c17f83 + languageName: node + linkType: hard + +"@octokit/plugin-rest-endpoint-methods@npm:^16.0.0": + version: 16.0.0 + resolution: "@octokit/plugin-rest-endpoint-methods@npm:16.0.0" + dependencies: + "@octokit/types": "npm:^14.1.0" + peerDependencies: + "@octokit/core": ">=6" + checksum: 10/17a299d2cda214fbc3a9d741746abb181845375b8094d1086e3810ec3796547754fa5a2d83aee410821d0d67c1f168343b38e6573813552482afdb6ebbb08189 + languageName: node + linkType: hard + +"@octokit/request-error@npm:^7.0.0": + version: 7.0.0 + resolution: "@octokit/request-error@npm:7.0.0" + dependencies: + "@octokit/types": "npm:^14.0.0" + checksum: 10/c4370d2c31f599c1f366c480d5a02bc93442e5a0e151ec5caf0d5a5b0f0f91b50ecedc945aa6ea61b4c9ed1e89153dc7727daf4317680d33e916f829da7d141b + languageName: node + linkType: hard + +"@octokit/request@npm:^10.0.2": + version: 10.0.2 + resolution: "@octokit/request@npm:10.0.2" + dependencies: + "@octokit/endpoint": "npm:^11.0.0" + "@octokit/request-error": "npm:^7.0.0" + "@octokit/types": "npm:^14.0.0" + fast-content-type-parse: "npm:^3.0.0" + universal-user-agent: "npm:^7.0.2" + checksum: 10/eaddfd49787e8caad664a80c7c665d69bd303f90b5e6be822d571b684a4cd42bdfee29119f838fdfaed2946bc09f38219e1d7a0923388436bff0bfdd0202acca + languageName: node + linkType: hard + +"@octokit/rest@npm:^22.0.0": + version: 22.0.0 + resolution: "@octokit/rest@npm:22.0.0" + dependencies: + "@octokit/core": "npm:^7.0.2" + "@octokit/plugin-paginate-rest": "npm:^13.0.1" + "@octokit/plugin-request-log": "npm:^6.0.0" + "@octokit/plugin-rest-endpoint-methods": "npm:^16.0.0" + checksum: 10/d2b80fefd6aed307cb728980cb1d94cb484d48fabf0055198664287a7fb50544d312b005e4fb8dec2a6e97a153ec0ad7654d62f59898e1077a4cfba64e6d5c3e + languageName: node + linkType: hard + +"@octokit/types@npm:^14.0.0, @octokit/types@npm:^14.1.0": + version: 14.1.0 + resolution: "@octokit/types@npm:14.1.0" + dependencies: + "@octokit/openapi-types": "npm:^25.1.0" + checksum: 10/ea5549ca6176bd1184427141a77bca88c68f07d252d3ea1db7f9b58ec16b66391218a75a99927efb1e36a2cb00e8ed37a79b71fdc95a1117a9982516156fd997 + languageName: node + linkType: hard + "@paralleldrive/cuid2@npm:^2.2.2": version: 2.2.2 resolution: "@paralleldrive/cuid2@npm:2.2.2" @@ -7414,6 +7554,7 @@ __metadata: version: 0.0.0-use.local resolution: "actual@workspace:." dependencies: + "@octokit/rest": "npm:^22.0.0" "@types/node": "npm:^22.15.18" "@types/prompts": "npm:^2.4.9" "@typescript-eslint/parser": "npm:^8.32.1" @@ -7431,6 +7572,7 @@ __metadata: html-to-image: "npm:^1.11.13" husky: "npm:^9.1.7" lint-staged: "npm:^15.5.2" + minimatch: "npm:^10.0.3" node-jq: "npm:^6.0.1" npm-run-all: "npm:^4.1.5" prettier: "npm:^3.5.3" @@ -8057,6 +8199,13 @@ __metadata: languageName: node linkType: hard +"before-after-hook@npm:^4.0.0": + version: 4.0.0 + resolution: "before-after-hook@npm:4.0.0" + checksum: 10/9fd52bc0c3cca0fb115e04dacbeeaacff38fa23e1af725d62392254c31ef433b15da60efcba61552e44d64e26f25ea259f72dba005115924389e88d2fd56e19f + languageName: node + linkType: hard + "better-sqlite3@npm:^11.10.0": version: 11.10.0 resolution: "better-sqlite3@npm:11.10.0" @@ -11156,6 +11305,13 @@ __metadata: languageName: node linkType: hard +"fast-content-type-parse@npm:^3.0.0": + version: 3.0.0 + resolution: "fast-content-type-parse@npm:3.0.0" + checksum: 10/8616a8aa6c9b4f8f4f3c90eaa4e7bfc2240cfa6f41f0eef5b5aa2b2c8b38bd9ad435f1488b6d817ffd725c54651e2777b882ae9dd59366e71e7896f1ec11d473 + languageName: node + linkType: hard + "fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": version: 3.1.3 resolution: "fast-deep-equal@npm:3.1.3" @@ -14884,6 +15040,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^10.0.3": + version: 10.0.3 + resolution: "minimatch@npm:10.0.3" + dependencies: + "@isaacs/brace-expansion": "npm:^5.0.0" + checksum: 10/d5b8b2538b367f2cfd4aeef27539fddeee58d1efb692102b848e4a968a09780a302c530eb5aacfa8c57f7299155fb4b4e85219ad82664dcef5c66f657111d9b8 + languageName: node + linkType: hard + "minimatch@npm:^3.0.2, minimatch@npm:^3.0.3, minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" @@ -19751,6 +19916,13 @@ __metadata: languageName: node linkType: hard +"universal-user-agent@npm:^7.0.0, universal-user-agent@npm:^7.0.2": + version: 7.0.3 + resolution: "universal-user-agent@npm:7.0.3" + checksum: 10/c497e85f8b11eb8fa4dce584d7a39cc98710164959f494cafc3c269b51abb20fff269951838efd7424d15f6b3d001507f3cb8b52bb5676fdb642019dfd17e63e + languageName: node + linkType: hard + "universalify@npm:^0.1.0": version: 0.1.2 resolution: "universalify@npm:0.1.2"