diff --git a/.github/workflows/close-stale-release-prs.yml b/.github/workflows/close-stale-release-prs.yml new file mode 100644 index 00000000000..024e0757a6b --- /dev/null +++ b/.github/workflows/close-stale-release-prs.yml @@ -0,0 +1,36 @@ +name: Close Stale Release PRs + +# Release PRs on `release/*` branches are expected to merge quickly. Abandoned +# ones block other engineers from starting a new release. This workflow closes +# inactive release PRs, leaves a comment, and deletes the branch. +on: + schedule: + # Check twice an hour so the 3h window is reasonably precise. + - cron: '*/30 * * * *' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: close-stale-release-prs + cancel-in-progress: false + +jobs: + close-stale-release-prs: + name: Close stale release PRs + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Checkout and setup environment + uses: MetaMask/action-checkout-and-setup@v3 + with: + is-high-risk-environment: false + persist-credentials: false + cache-node-modules: true + - name: Close inactive release PRs + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: yarn tsx scripts/close-stale-release-prs.mts diff --git a/docs/processes/releasing.md b/docs/processes/releasing.md index e472f9db616..0009ec21569 100644 --- a/docs/processes/releasing.md +++ b/docs/processes/releasing.md @@ -4,6 +4,7 @@ Have changes that you need to release? There are a few things to understand: - The responsibility of maintenance is not the only thing shared among multiple teams at MetaMask; releases are as well. That means **if you work on a team that has codeownership over a package, you are free to create a new release without needing the Wallet Framework team to do so.** - Unlike clients, releases are not issued on a schedule; **anyone may create a release at any time**. Because of this, you may wish to review the Pull Requests tab on GitHub and ensure that no one else has a release candidate already in progress. If not, then you are free to start the process. +- Release PRs on `release/*` branches that sit inactive for **3 hours** are automatically closed, with the branch deleted, so abandoned releases do not block others. Add the `release:keep-open` label if you need a longer-lived release PR in exceptional cases. - The release process is a work in progress. Further improvements to simplify the process are planned, but in the meantime, if you encounter any issues, please reach out to the Wallet Framework team. - Breaking changes take special consideration. [Read the guide](./breaking-changes.md) on how to prepare and handle them effectively. diff --git a/package.json b/package.json index 64853c73443..c5f1d0eebb8 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,8 @@ "workspaces:list-versions": "./scripts/list-workspace-versions.sh" }, "devDependencies": { + "@actions/core": "^3.0.1", + "@actions/github": "^9.1.1", "@lavamoat/allow-scripts": "^3.0.4", "@lavamoat/preinstall-always-fail": "^2.1.0", "@metamask/create-release-branch": "^4.2.1", diff --git a/scripts/close-stale-release-prs.mts b/scripts/close-stale-release-prs.mts new file mode 100755 index 00000000000..97f1bf42dd8 --- /dev/null +++ b/scripts/close-stale-release-prs.mts @@ -0,0 +1,487 @@ +/** + * Close inactive same-repo `release/*` PRs, comment with the outcome, and + * delete the branch when the tip is unchanged. + * + * Usage (from GitHub Actions): + * GITHUB_TOKEN=... yarn tsx scripts/close-stale-release-prs.mts + */ + +import * as core from '@actions/core'; +import { context, getOctokit } from '@actions/github'; +import { Duration, getErrorMessage, inMilliseconds } from '@metamask/utils'; + +/** + * The label users can use to prevent stale release PRs from being auto-closed. + */ +const SKIP_LABEL = 'release:keep-open'; + +/** + * How long inactive release PRs stay open before auto-close. + */ +const STALE_DURATION_HOURS = 3; + +/** + * How long inactive release PRs stay open before auto-close (milliseconds). + */ +const STALE_DURATION_MS = inMilliseconds(STALE_DURATION_HOURS, Duration.Hour); + +const PULL_REQUEST_SNAPSHOT_QUERY = ` + query ($owner: String!, $repo: String!, $number: Int!) { + repository(owner: $owner, name: $repo) { + pullRequest(number: $number) { + number + state + updatedAt + isInMergeQueue + autoMergeRequest { + enabledAt + } + labels(first: 100) { + nodes { + name + } + } + headRefName + headRefOid + headRepository { + isFork + } + } + } + } +`; + +type Octokit = ReturnType; + +type PullRequestSnapshot = { + number: number; + state: string; + updatedAt: string; + isInMergeQueue: boolean; + autoMergeRequest: { enabledAt: string } | null; + labels: { nodes: { name: string }[] }; + headRefName: string; + headRefOid: string; + headRepository: { isFork: boolean } | null; +}; + +type ListedPullRequest = Awaited< + ReturnType +>['data'][number]; + +type BranchDeleteOutcome = { + outcome: + | 'deleted' + | 'kept-refresh-failed' + | 'kept-head-moved' + | 'kept-delete-failed'; + detail: string; +}; + +type StaleEligibility = { eligible: true; ageMs: number } | { eligible: false }; + +/** + * Label names from a GraphQL PR snapshot. + * + * @param pullRequest - GraphQL pull request snapshot. + * @returns Label name list. + */ +function pullRequestLabelNames(pullRequest: PullRequestSnapshot): string[] { + return pullRequest.labels.nodes.map((label) => label.name); +} + +/** + * Fetch PR state, labels, head tip, and merge-queue info in one GraphQL query. + * + * @param octokit - Authenticated Octokit client. + * @param pullNumber - Pull request number. + * @returns Pull request snapshot. + */ +async function getPullRequestSnapshot( + octokit: Octokit, + pullNumber: number, +): Promise { + const { owner, repo } = context.repo; + const response = await octokit.graphql<{ + repository: { pullRequest: PullRequestSnapshot | null }; + }>(PULL_REQUEST_SNAPSHOT_QUERY, { + owner, + repo, + number: pullNumber, + }); + + const { pullRequest } = response.repository; + if (!pullRequest) { + throw new Error(`Pull request #${pullNumber} was not found`); + } + + return pullRequest; +} + +/** + * Whether a listed PR head is a same-repo `release/*` branch that is not skipped. + * + * @param pullRequest - Pull request from `pulls.list`. + * @returns True when the PR is a candidate for stale close. + */ +function isReleasePrCandidate(pullRequest: ListedPullRequest): boolean { + if (!pullRequest.head.ref.startsWith('release/')) { + return false; + } + + // Only manage same-repo release branches (never forks). + if (!pullRequest.head.repo || pullRequest.head.repo.fork) { + return false; + } + + if (pullRequest.labels.some((label) => label.name === SKIP_LABEL)) { + core.info( + `Skipping #${pullRequest.number} (${pullRequest.head.ref}): skip label "${SKIP_LABEL}"`, + ); + return false; + } + + return true; +} + +/** + * Whether merge-queue or auto-merge is active for the PR. + * + * @param pullRequest - GraphQL pull request snapshot. + * @returns True when a merge is already in progress. + */ +function isMergeInProgress(pullRequest: PullRequestSnapshot): boolean { + return pullRequest.isInMergeQueue || pullRequest.autoMergeRequest !== null; +} + +/** + * Evaluate whether a PR snapshot is eligible to close as stale. + * + * Skip-label is re-checked so a label added after the initial list filter still + * prevents auto-close. + * + * @param options - Evaluation inputs. + * @param options.pullRequest - Fresh GraphQL pull request snapshot. + * @param options.staleBefore - Epoch ms; PRs updated at/after this are kept. + * @returns Eligibility result. + */ +function evaluateStaleEligibility({ + pullRequest, + staleBefore, +}: { + pullRequest: PullRequestSnapshot; + staleBefore: number; +}): StaleEligibility { + const ref = pullRequest.headRefName; + + if (pullRequest.state !== 'OPEN') { + core.info(`Skipping #${pullRequest.number} (${ref}): no longer open`); + return { eligible: false }; + } + + if (pullRequestLabelNames(pullRequest).includes(SKIP_LABEL)) { + core.info( + `Skipping #${pullRequest.number} (${ref}): skip label "${SKIP_LABEL}"`, + ); + return { eligible: false }; + } + + if (pullRequest.headRepository?.isFork) { + core.info(`Skipping #${pullRequest.number} (${ref}): fork head`); + return { eligible: false }; + } + + const updatedAtMs = Date.parse(pullRequest.updatedAt); + if (updatedAtMs >= staleBefore) { + core.info( + `Skipping #${pullRequest.number} (${ref}): has not reached stale age of ${STALE_DURATION_HOURS}h yet (updated ${Math.round((Date.now() - updatedAtMs) / Duration.Minute)}m ago)`, + ); + return { eligible: false }; + } + + if (isMergeInProgress(pullRequest)) { + core.info(`Skipping #${pullRequest.number} (${ref}): merge in progress`); + return { eligible: false }; + } + + return { eligible: true, ageMs: Date.now() - updatedAtMs }; +} + +/** + * Close the pull request. + * + * @param octokit - Authenticated Octokit client. + * @param pullNumber - Pull request number. + * @param headRef - Head branch name for logging. + * @returns True when the close succeeded. + */ +async function closePullRequest( + octokit: Octokit, + pullNumber: number, + headRef: string, +): Promise { + const { owner, repo } = context.repo; + try { + await octokit.rest.pulls.update({ + owner, + repo, + pull_number: pullNumber, + state: 'closed', + }); + return true; + } catch (error) { + core.warning( + `Failed to close #${pullNumber} (${headRef}): ${getErrorMessage(error)}`, + ); + return false; + } +} + +/** + * Delete the release branch when its tip still matches the expected SHA. + * + * @param options - Delete inputs. + * @param options.octokit - Authenticated Octokit client. + * @param options.pullNumber - Closed pull request number. + * @param options.expectedHeadRef - Branch name to delete. + * @param options.expectedHeadSha - SHA that must still be the tip. + * @returns Branch deletion outcome. + */ +async function deleteBranchIfUnchanged({ + octokit, + pullNumber, + expectedHeadRef, + expectedHeadSha, +}: { + octokit: Octokit; + pullNumber: number; + expectedHeadRef: string; + expectedHeadSha: string; +}): Promise { + const { owner, repo } = context.repo; + let branchSha: string; + + try { + const { data: branchRef } = await octokit.rest.git.getRef({ + owner, + repo, + ref: `heads/${expectedHeadRef}`, + }); + branchSha = branchRef.object.sha; + } catch (error) { + const message = getErrorMessage(error); + core.warning( + `Closed #${pullNumber} but failed to refresh ${expectedHeadRef} before delete: ${message}`, + ); + return { outcome: 'kept-refresh-failed', detail: message }; + } + + if (branchSha !== expectedHeadSha) { + const detail = `head moved from ${expectedHeadSha} to ${branchSha}`; + core.warning( + `Closed #${pullNumber} but skipped deleting ${expectedHeadRef}: ${detail}`, + ); + return { outcome: 'kept-head-moved', detail }; + } + + try { + await octokit.rest.git.deleteRef({ + owner, + repo, + ref: `heads/${expectedHeadRef}`, + }); + core.info(`Closed #${pullNumber} and deleted branch ${expectedHeadRef}`); + return { outcome: 'deleted', detail: '' }; + } catch (error) { + const message = getErrorMessage(error); + core.warning( + `Closed #${pullNumber} but failed to delete ${expectedHeadRef}: ${message}`, + ); + return { outcome: 'kept-delete-failed', detail: message }; + } +} + +/** + * Build a link to the current workflow run. + * + * @returns Workflow run URL. + */ +function getWorkflowRunUrl(): string { + return `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; +} + +/** + * Build the PR comment body for a completed stale close. + * + * @param options - Comment inputs. + * @param options.inactiveHours - Formatted inactivity duration. + * @param options.outcome - Branch deletion outcome key. + * @returns Markdown comment body. + */ +function buildCloseComment({ + inactiveHours, + outcome, +}: { + inactiveHours: string; + outcome: BranchDeleteOutcome['outcome']; +}): string { + const lines = [ + '## This pull request has been closed', + '', + `This release PR was automatically closed because it had no activity for ${STALE_DURATION_HOURS} hours (last updated ${inactiveHours}h ago).`, + '', + 'Open release PRs are expected to merge promptly so they do not block others from starting a new release.', + '', + `To keep a release PR open longer in exceptional cases, add the \`${SKIP_LABEL}\` label.`, + ]; + + // GitHub already surfaces successful branch deletion on the closed PR. + // Call out real failures and intentional tip move skips separately. + if (outcome === 'kept-head-moved') { + lines.push( + '', + `> (Branch was left in place because its tip changed after close. See more details here: ${getWorkflowRunUrl()})`, + ); + } else if (outcome !== 'deleted') { + lines.push( + '', + `> (A failed attempt was made to delete this branch. See more details here: ${getWorkflowRunUrl()})`, + ); + } + + lines.push('', ''); + return lines.join('\n'); +} + +/** + * Post the stale-close comment on the pull request. + * + * @param octokit - Authenticated Octokit client. + * @param pullNumber - Pull request number. + * @param body - Markdown comment body. + */ +async function commentOnPullRequest( + octokit: Octokit, + pullNumber: number, + body: string, +): Promise { + const { owner, repo } = context.repo; + try { + await octokit.rest.issues.createComment({ + owner, + repo, + issue_number: pullNumber, + body, + }); + } catch (error) { + core.warning( + `Closed #${pullNumber} but failed to comment: ${getErrorMessage(error)}`, + ); + } +} + +/** + * Process a single stale release PR candidate. + * + * @param options - Processing inputs. + * @param options.octokit - Authenticated Octokit client. + * @param options.candidate - Candidate from the initial open-PR list. + * @param options.staleBefore - Epoch ms; PRs updated at/after this are kept. + */ +async function processReleasePr({ + octokit, + candidate, + staleBefore, +}: { + octokit: Octokit; + candidate: ListedPullRequest; + staleBefore: number; +}): Promise { + let pullRequest: PullRequestSnapshot; + try { + pullRequest = await getPullRequestSnapshot(octokit, candidate.number); + } catch (error) { + core.warning( + `Failed to refresh #${candidate.number} (${candidate.head.ref}): ${getErrorMessage(error)}`, + ); + return; + } + + const eligibility = evaluateStaleEligibility({ pullRequest, staleBefore }); + if (!eligibility.eligible) { + return; + } + + const inactiveHours = (eligibility.ageMs / Duration.Hour).toFixed(1); + + // Close before commenting so a failed close does not bump updatedAt. + const closed = await closePullRequest( + octokit, + pullRequest.number, + pullRequest.headRefName, + ); + if (!closed) { + return; + } + + const branchResult = await deleteBranchIfUnchanged({ + octokit, + pullNumber: pullRequest.number, + expectedHeadRef: pullRequest.headRefName, + expectedHeadSha: pullRequest.headRefOid, + }); + + const body = buildCloseComment({ + inactiveHours, + outcome: branchResult.outcome, + }); + + await commentOnPullRequest(octokit, pullRequest.number, body); +} + +/** + * Close inactive same-repo `release/*` PRs. + */ +async function main(): Promise { + // GitHub Actions provides the token via the environment for this workflow. + // eslint-disable-next-line n/no-process-env + const token = process.env.GITHUB_TOKEN; + if (!token) { + core.setFailed('GITHUB_TOKEN is required'); + return; + } + + const octokit = getOctokit(token); + const staleBefore = Date.now() - STALE_DURATION_MS; + const { owner, repo } = context.repo; + + const pullRequests: ListedPullRequest[] = await octokit.paginate( + octokit.rest.pulls.list, + { + owner, + repo, + state: 'open', + per_page: 100, + }, + ); + + const releasePrs = pullRequests.filter(isReleasePrCandidate); + + if (releasePrs.length === 0) { + core.info('No open release PRs to evaluate.'); + return; + } + + for (const candidate of releasePrs) { + await processReleasePr({ + octokit, + candidate, + staleBefore, + }); + } +} + +main().catch((error: unknown) => { + core.setFailed(getErrorMessage(error)); + process.exitCode = 1; +}); diff --git a/yarn.lock b/yarn.lock index ced16eb9d50..eb0ffac27da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,6 +5,67 @@ __metadata: version: 10 cacheKey: 10 +"@actions/core@npm:^3.0.1": + version: 3.0.1 + resolution: "@actions/core@npm:3.0.1" + dependencies: + "@actions/exec": "npm:^3.0.0" + "@actions/http-client": "npm:^4.0.0" + checksum: 10/e1295f6b81299cc5655ea571e7b3eea02889fdc479e71c783ad9ca48432c613f52a1fd01fecc973a64488b053083ea925a0d23ac7af0bcd8462afc4f4371918b + languageName: node + linkType: hard + +"@actions/exec@npm:^3.0.0": + version: 3.0.0 + resolution: "@actions/exec@npm:3.0.0" + dependencies: + "@actions/io": "npm:^3.0.2" + checksum: 10/c1904163e326cbe27f887514b4837e357d46e7a6c5eeda66c0e2efffd2772cb34d8ef0a2a48c65eb0e3b6ec72beb9b049eaba343c9f55978d3f45b09d09d2c54 + languageName: node + linkType: hard + +"@actions/github@npm:^9.1.1": + version: 9.1.1 + resolution: "@actions/github@npm:9.1.1" + dependencies: + "@actions/http-client": "npm:^3.0.2" + "@octokit/core": "npm:^7.0.6" + "@octokit/plugin-paginate-rest": "npm:^14.0.0" + "@octokit/plugin-rest-endpoint-methods": "npm:^17.0.0" + "@octokit/request": "npm:^10.0.7" + "@octokit/request-error": "npm:^7.1.0" + undici: "npm:^6.23.0" + checksum: 10/eb77846e506df107208ee6a57aa38c80ce6cdd9ab499ec3518a8e3000334def8f93fcf2b43c8b512fede9b093a1ca39d184551a9c50f37cb8fc17704d09c7e70 + languageName: node + linkType: hard + +"@actions/http-client@npm:^3.0.2": + version: 3.0.2 + resolution: "@actions/http-client@npm:3.0.2" + dependencies: + tunnel: "npm:^0.0.6" + undici: "npm:^6.23.0" + checksum: 10/36431245545cd54e2e2b25b333732801a904170a426cdcb6611423b9da70daeba2742d7258e7fb5a370e216082d3a416d04f47ea810d5e9d6cda8e6928466079 + languageName: node + linkType: hard + +"@actions/http-client@npm:^4.0.0": + version: 4.0.1 + resolution: "@actions/http-client@npm:4.0.1" + dependencies: + tunnel: "npm:^0.0.6" + undici: "npm:^6.23.0" + checksum: 10/4fab65bf488e15143db87ce200a9d1f6f81832adfb1cbdadc380bbe2a95c86b1f5daa0d89c029533ccea4cd2b811a84ce984dfd0d6530479b82bc9860e8be704 + languageName: node + linkType: hard + +"@actions/io@npm:^3.0.2": + version: 3.0.2 + resolution: "@actions/io@npm:3.0.2" + checksum: 10/ef17cb4ec0a2b640d5f4851446ad1c12bf4b2b1cf83741c5eecee4e8f50b3ca3ac9ae4084027dcaa1bf0c016d653dbc0e5fe20daedd39ee5fb6edb671f6e45b5 + languageName: node + linkType: hard + "@adraffy/ens-normalize@npm:1.10.1": version: 1.10.1 resolution: "@adraffy/ens-normalize@npm:1.10.1" @@ -6602,6 +6663,8 @@ __metadata: version: 0.0.0-use.local resolution: "@metamask/core-monorepo@workspace:." dependencies: + "@actions/core": "npm:^3.0.1" + "@actions/github": "npm:^9.1.1" "@lavamoat/allow-scripts": "npm:^3.0.4" "@lavamoat/preinstall-always-fail": "npm:^2.1.0" "@metamask/create-release-branch": "npm:^4.2.1" @@ -9932,6 +9995,13 @@ __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:^5.0.2": version: 5.2.2 resolution: "@octokit/core@npm:5.2.2" @@ -9947,6 +10017,31 @@ __metadata: languageName: node linkType: hard +"@octokit/core@npm:^7.0.6": + version: 7.0.6 + resolution: "@octokit/core@npm:7.0.6" + dependencies: + "@octokit/auth-token": "npm:^6.0.0" + "@octokit/graphql": "npm:^9.0.3" + "@octokit/request": "npm:^10.0.6" + "@octokit/request-error": "npm:^7.0.2" + "@octokit/types": "npm:^16.0.0" + before-after-hook: "npm:^4.0.0" + universal-user-agent: "npm:^7.0.0" + checksum: 10/852d41fc3150d2a891156427dd0575c77889f1c7a109894ee541594e3fd47c0d4e0a93fee22966c507dfd6158b522e42846c2ac46b9d896078194c95fa81f4ae + languageName: node + linkType: hard + +"@octokit/endpoint@npm:^11.0.3": + version: 11.0.3 + resolution: "@octokit/endpoint@npm:11.0.3" + dependencies: + "@octokit/types": "npm:^16.0.0" + universal-user-agent: "npm:^7.0.2" + checksum: 10/21b67d76fb1ea28bd87ca467c12dfab648af55522b936760316d70f8ccdd638f170d636ee72606857f0cd8f343f40c8a4e8f55993d6b1f5b9ecf102e072044c5 + languageName: node + linkType: hard + "@octokit/endpoint@npm:^9.0.6": version: 9.0.6 resolution: "@octokit/endpoint@npm:9.0.6" @@ -9968,6 +10063,17 @@ __metadata: languageName: node linkType: hard +"@octokit/graphql@npm:^9.0.3": + version: 9.0.3 + resolution: "@octokit/graphql@npm:9.0.3" + dependencies: + "@octokit/request": "npm:^10.0.6" + "@octokit/types": "npm:^16.0.0" + universal-user-agent: "npm:^7.0.0" + checksum: 10/7b16f281f8571dce55280b3986fbb8d15465a7236164a5f6497ded7597ff9ee95d5796924555b979903fe8c6706fe6be1b3e140d807297f85ac8edeadc28f9fe + languageName: node + linkType: hard + "@octokit/openapi-types@npm:^24.2.0": version: 24.2.0 resolution: "@octokit/openapi-types@npm:24.2.0" @@ -9975,6 +10081,13 @@ __metadata: languageName: node linkType: hard +"@octokit/openapi-types@npm:^27.0.0": + version: 27.0.0 + resolution: "@octokit/openapi-types@npm:27.0.0" + checksum: 10/5cd2cdf4e41fdf522e15e3d53f3ece8380d98dda9173a6fc905828fb2c33e8733d5f5d2a757ae3a572525f4749748e66cb40e7939372132988d8eb4ba978d54f + languageName: node + linkType: hard + "@octokit/plugin-paginate-rest@npm:11.4.4-cjs.2": version: 11.4.4-cjs.2 resolution: "@octokit/plugin-paginate-rest@npm:11.4.4-cjs.2" @@ -9986,6 +10099,17 @@ __metadata: languageName: node linkType: hard +"@octokit/plugin-paginate-rest@npm:^14.0.0": + version: 14.0.0 + resolution: "@octokit/plugin-paginate-rest@npm:14.0.0" + dependencies: + "@octokit/types": "npm:^16.0.0" + peerDependencies: + "@octokit/core": ">=6" + checksum: 10/57ddd857528dad9c02431bc6254c2374c06057872cf9656a4a88b162ebe1c2bc9f34fbec360f2ccff72c940f29b120758ce14e8135bd027223d381eb1b8b6579 + languageName: node + linkType: hard + "@octokit/plugin-request-log@npm:^4.0.0": version: 4.0.1 resolution: "@octokit/plugin-request-log@npm:4.0.1" @@ -10006,6 +10130,17 @@ __metadata: languageName: node linkType: hard +"@octokit/plugin-rest-endpoint-methods@npm:^17.0.0": + version: 17.0.0 + resolution: "@octokit/plugin-rest-endpoint-methods@npm:17.0.0" + dependencies: + "@octokit/types": "npm:^16.0.0" + peerDependencies: + "@octokit/core": ">=6" + checksum: 10/e9d9ad4d9755cc7fb82fdcbfa870ddea8a432180f0f76c8469095557fd1e26f8caea8cae58401209be17c4f3d8cc48c0e16a3643e37e48f4d23c39e058bf2c55 + languageName: node + linkType: hard + "@octokit/request-error@npm:^5.1.1": version: 5.1.1 resolution: "@octokit/request-error@npm:5.1.1" @@ -10017,6 +10152,29 @@ __metadata: languageName: node linkType: hard +"@octokit/request-error@npm:^7.0.2, @octokit/request-error@npm:^7.1.0": + version: 7.1.0 + resolution: "@octokit/request-error@npm:7.1.0" + dependencies: + "@octokit/types": "npm:^16.0.0" + checksum: 10/c1d447ff7482382c69f7a4b2eaa44c672906dd111d8a9196a5d07f2adc4ae0f0e12ec4ce0063f14f9b2fb5f0cef4451c95ec961a7a711bd900e5d6441d546570 + languageName: node + linkType: hard + +"@octokit/request@npm:^10.0.6, @octokit/request@npm:^10.0.7": + version: 10.0.11 + resolution: "@octokit/request@npm:10.0.11" + dependencies: + "@octokit/endpoint": "npm:^11.0.3" + "@octokit/request-error": "npm:^7.0.2" + "@octokit/types": "npm:^16.0.0" + content-type: "npm:^2.0.0" + json-with-bigint: "npm:^3.5.3" + universal-user-agent: "npm:^7.0.2" + checksum: 10/036640f49e4ab63470130dccafb828822a18cf9ce060b8170e56ba9ce7173029db92b3c37ad7474acf2c097180d5127754b69d3ffbe60162489aed4324b3cb8f + languageName: node + linkType: hard + "@octokit/request@npm:^8.4.1": version: 8.4.1 resolution: "@octokit/request@npm:8.4.1" @@ -10050,6 +10208,15 @@ __metadata: languageName: node linkType: hard +"@octokit/types@npm:^16.0.0": + version: 16.0.0 + resolution: "@octokit/types@npm:16.0.0" + dependencies: + "@octokit/openapi-types": "npm:^27.0.0" + checksum: 10/03d5cfc29556a9b53eae8beb1bf15c0b704dc722db2c51b53f093f3c3ee6c1d8e20b682be8117a3a17034b458be7746d1b22aaefb959ceb5152ad7589b39e2c9 + languageName: node + linkType: hard + "@open-rpc/meta-schema@npm:^1.14.6, @open-rpc/meta-schema@npm:^1.14.9": version: 1.14.9 resolution: "@open-rpc/meta-schema@npm:1.14.9" @@ -13990,6 +14157,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:^12.9.0": version: 12.10.0 resolution: "better-sqlite3@npm:12.10.0" @@ -15145,6 +15319,13 @@ __metadata: languageName: node linkType: hard +"content-type@npm:^2.0.0": + version: 2.0.0 + resolution: "content-type@npm:2.0.0" + checksum: 10/0bbb276b790ba7e86c479c7d69fae1861b2e908ff3ce2cb01975b516f93eede2216d242902ed6c5b15cd554014611ce9dfdcf51cd35b16569e45a979e50d0cef + languageName: node + linkType: hard + "content-type@npm:~1.0.4, content-type@npm:~1.0.5": version: 1.0.5 resolution: "content-type@npm:1.0.5" @@ -20125,6 +20306,13 @@ __metadata: languageName: node linkType: hard +"json-with-bigint@npm:^3.5.3": + version: 3.5.10 + resolution: "json-with-bigint@npm:3.5.10" + checksum: 10/d9d5b41f30e7ac648296947162cb18bdc7030e46a264231e19561a93e8ee6fb127efe447c86ef6d571106ac32e4656ddeb10d8220a88b2a5edf5b7f263465003 + languageName: node + linkType: hard + "json5@npm:^2.1.2, json5@npm:^2.2.3": version: 2.2.3 resolution: "json5@npm:2.2.3" @@ -26545,6 +26733,13 @@ __metadata: languageName: node linkType: hard +"tunnel@npm:^0.0.6": + version: 0.0.6 + resolution: "tunnel@npm:0.0.6" + checksum: 10/cf1ffed5e67159b901a924dbf94c989f20b2b3b65649cfbbe4b6abb35955ce2cf7433b23498bdb2c5530ab185b82190fce531597b3b4a649f06a907fc8702405 + languageName: node + linkType: hard + "tweetnacl@npm:^1.0.3": version: 1.0.3 resolution: "tweetnacl@npm:1.0.3" @@ -26742,6 +26937,13 @@ __metadata: languageName: node linkType: hard +"undici@npm:^6.23.0": + version: 6.28.0 + resolution: "undici@npm:6.28.0" + checksum: 10/672a7a53bd1f6c80edb73b357ab36e98ef9e474024c442aab2b8ea2bc32f1103cab05525c78661ae724f3e1340e23d03efb9730fba28e76218ab0ec52e15d75a + languageName: node + linkType: hard + "undici@npm:^7.19.0": version: 7.26.0 resolution: "undici@npm:7.26.0" @@ -26911,6 +27113,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:^2.0.0": version: 2.0.1 resolution: "universalify@npm:2.0.1"