diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 44057f39da11..2c06ec94b2ad 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -253,6 +253,34 @@ jobs: # Only run comparison against develop if this is a PR comparison_branch: ${{ (github.event_name == 'pull_request' && github.base_ref) || ''}} + job_node_overhead_check: + name: Node Overhead + needs: [job_get_metadata, job_build] + timeout-minutes: 15 + runs-on: ubuntu-24.04 + if: + (needs.job_build.outputs.changed_node == 'true' && github.event_name == 'pull_request') || + needs.job_get_metadata.outputs.is_base_branch == 'true' || needs.job_get_metadata.outputs.is_release == 'true' + steps: + - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: 'package.json' + - name: Restore caches + uses: ./.github/actions/restore-cache + with: + dependency_cache_key: ${{ needs.job_build.outputs.dependency_cache_key }} + - name: Check node overhead + uses: ./dev-packages/node-overhead-gh-action + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + # Only run comparison against develop if this is a PR + comparison_branch: ${{ (github.event_name == 'pull_request' && github.base_ref) || ''}} + job_lint: name: Lint # Even though the linter only checks source code, not built code, it needs the built code in order check that all diff --git a/dev-packages/node-overhead-gh-action/.eslintrc.js b/dev-packages/node-overhead-gh-action/.eslintrc.js new file mode 100644 index 000000000000..7966cc8ca48c --- /dev/null +++ b/dev-packages/node-overhead-gh-action/.eslintrc.js @@ -0,0 +1,15 @@ +module.exports = { + env: { + node: true, + }, + extends: ['../../.eslintrc.js'], + overrides: [ + { + files: ['src/**/*.mjs', 'lib/**/*.mjs'], + parserOptions: { + project: ['tsconfig.json'], + sourceType: 'module', + }, + }, + ], +}; diff --git a/dev-packages/node-overhead-gh-action/README.md b/dev-packages/node-overhead-gh-action/README.md new file mode 100644 index 000000000000..1759ab7bd7c3 --- /dev/null +++ b/dev-packages/node-overhead-gh-action/README.md @@ -0,0 +1,3 @@ +# node-overhead-gh-action + +Capture the overhead of Sentry in a node app. diff --git a/dev-packages/node-overhead-gh-action/action.yml b/dev-packages/node-overhead-gh-action/action.yml new file mode 100644 index 000000000000..e90aef2e4342 --- /dev/null +++ b/dev-packages/node-overhead-gh-action/action.yml @@ -0,0 +1,17 @@ +name: 'node-overhead-gh-action' +description: 'Run node overhead comparison' +inputs: + github_token: + required: true + description: 'a github access token' + comparison_branch: + required: false + default: '' + description: 'If set, compare the current branch with this branch' + threshold: + required: false + default: '3' + description: 'The percentage threshold for size changes before posting a comment' +runs: + using: 'node24' + main: 'index.mjs' diff --git a/dev-packages/node-overhead-gh-action/index.mjs b/dev-packages/node-overhead-gh-action/index.mjs new file mode 100644 index 000000000000..de7d5dc910dd --- /dev/null +++ b/dev-packages/node-overhead-gh-action/index.mjs @@ -0,0 +1,226 @@ +/* eslint-disable complexity */ +import { promises as fs } from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { DefaultArtifactClient } from '@actions/artifact'; +import * as core from '@actions/core'; +import { exec } from '@actions/exec'; +import { context, getOctokit } from '@actions/github'; +import * as glob from '@actions/glob'; +import * as io from '@actions/io'; +import { markdownTable } from 'markdown-table'; +import { getOverheadMeasurements } from './lib/getOverheadMeasurements.mjs'; +import { Formatter } from './lib/Formatter.mjs'; + +const NODE_OVERHEAD_HEADING = '## node-overhead report 📦 '; +const ARTIFACT_NAME = 'node-overhead-action'; +const RESULTS_FILE = 'node-overhead-results.json'; + +function getResultsFilePath() { + const __dirname = path.dirname(fileURLToPath(import.meta.url)); + return path.resolve(__dirname, RESULTS_FILE); +} + +const { getInput, setFailed } = core; + +async function fetchPreviousComment(octokit, repo, pr) { + const { data: commentList } = await octokit.rest.issues.listComments({ + ...repo, + issue_number: pr.number, + }); + + const sizeLimitComment = commentList.find(comment => comment.body.startsWith(NODE_OVERHEAD_HEADING)); + return !sizeLimitComment ? null : sizeLimitComment; +} + +async function run() { + const __dirname = path.dirname(fileURLToPath(import.meta.url)); + + try { + const { payload, repo } = context; + const pr = payload.pull_request; + + const comparisonBranch = getInput('comparison_branch'); + const githubToken = getInput('github_token'); + const threshold = getInput('threshold'); + + if (comparisonBranch && !pr) { + throw new Error('No PR found. Only pull_request workflows are supported.'); + } + + const octokit = getOctokit(githubToken); + const resultsFilePath = getResultsFilePath(); + const formatter = new Formatter(); + + // If we have no comparison branch, we just run size limit & store the result as artifact + if (!comparisonBranch) { + return runNodeOverheadOnComparisonBranch(); + } + + // Else, we run size limit for the current branch, AND fetch it for the comparison branch + let base; + let current; + let baseIsNotLatest = false; + let baseWorkflowRun; + + try { + const workflowName = `${process.env.GITHUB_WORKFLOW || ''}`; + core.startGroup(`getArtifactsForBranchAndWorkflow - workflow:"${workflowName}", branch:"${comparisonBranch}"`); + const artifacts = await getArtifactsForBranchAndWorkflow(octokit, { + ...repo, + artifactName: ARTIFACT_NAME, + branch: comparisonBranch, + workflowName, + }); + core.endGroup(); + + if (!artifacts) { + throw new Error('No artifacts found'); + } + + baseWorkflowRun = artifacts.workflowRun; + + await downloadOtherWorkflowArtifact(octokit, { + ...repo, + artifactName: ARTIFACT_NAME, + artifactId: artifacts.artifact.id, + downloadPath: __dirname, + }); + + base = JSON.parse(await fs.readFile(resultsFilePath, { encoding: 'utf8' })); + + if (!artifacts.isLatest) { + baseIsNotLatest = true; + core.info('Base artifact is not the latest one. This may lead to incorrect results.'); + } + } catch (error) { + core.startGroup('Warning, unable to find base results'); + core.error(error); + core.endGroup(); + } + + current = await getOverheadMeasurements(); + const thresholdNumber = Number(threshold); + + const nodeOverheadComment = await fetchPreviousComment(octokit, repo, pr); + + if (nodeOverheadComment) { + core.debug('Found existing node overhead comment, updating it instead of creating a new one...'); + } + + const shouldComment = + isNaN(thresholdNumber) || formatter.hasSizeChanges(base, current, thresholdNumber) || nodeOverheadComment; + + if (shouldComment) { + const bodyParts = [NODE_OVERHEAD_HEADING]; + + if (baseIsNotLatest) { + bodyParts.push( + '⚠️ **Warning:** Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.', + ); + } + try { + bodyParts.push(markdownTable(formatter.formatResults(base, current))); + } catch (error) { + core.error('Error generating markdown table'); + core.error(error); + } + + if (baseWorkflowRun) { + bodyParts.push(''); + bodyParts.push(`[View base workflow run](${baseWorkflowRun.html_url})`); + } + + const body = bodyParts.join('\r\n'); + + try { + if (!nodeOverheadComment) { + await octokit.rest.issues.createComment({ + ...repo, + issue_number: pr.number, + body, + }); + } else { + await octokit.rest.issues.updateComment({ + ...repo, + comment_id: nodeOverheadComment.id, + body, + }); + } + } catch (error) { + core.error( + "Error updating comment. This can happen for PR's originating from a fork without write permissions.", + ); + } + } else { + core.debug('Skipping comment because there are no changes.'); + } + } catch (error) { + core.error(error); + setFailed(error.message); + } +} + +async function runNodeOverheadOnComparisonBranch() { + const __dirname = path.dirname(fileURLToPath(import.meta.url)); + const resultsFilePath = getResultsFilePath(); + + const artifactClient = new DefaultArtifactClient(); + + const result = await getOverheadMeasurements(); + + try { + await fs.writeFile(resultsFilePath, JSON.stringify(result), 'utf8'); + } catch (error) { + core.error('Error parsing node overhead output. The output should be a json.'); + throw error; + } + + const globber = await glob.create(resultsFilePath, { + followSymbolicLinks: false, + }); + const files = await globber.glob(); + + await artifactClient.uploadArtifact(ARTIFACT_NAME, files, __dirname); +} + +run(); + +/** + * Use GitHub API to fetch artifact download url, then + * download and extract artifact to `downloadPath` + */ +async function downloadOtherWorkflowArtifact(octokit, { owner, repo, artifactId, artifactName, downloadPath }) { + const artifact = await octokit.rest.actions.downloadArtifact({ + owner, + repo, + artifact_id: artifactId, + archive_format: 'zip', + }); + + // Make sure output path exists + try { + await io.mkdirP(downloadPath); + } catch { + // ignore errors + } + + const downloadFile = path.resolve(downloadPath, `${artifactName}.zip`); + + await exec('wget', [ + '-nv', + '--retry-connrefused', + '--waitretry=1', + '--read-timeout=20', + '--timeout=15', + '-t', + '0', + '-O', + downloadFile, + artifact.url, + ]); + + await exec('unzip', ['-q', '-d', downloadPath, downloadFile], { + silent: true, + }); +} diff --git a/dev-packages/node-overhead-gh-action/lib/Formatter.mjs b/dev-packages/node-overhead-gh-action/lib/Formatter.mjs new file mode 100644 index 000000000000..b049029b9a57 --- /dev/null +++ b/dev-packages/node-overhead-gh-action/lib/Formatter.mjs @@ -0,0 +1,61 @@ +import * as core from '@actions/core'; +import bytes from 'bytes-iec'; + +const NODE_OVERHEAD_RESULTS_HEADER = ['Scenario', 'Prev Requests/s', 'New Requests/s', 'Change %']; + +export class Formatter { + formatPercentageChange(base = 0, current = 0) { + const value = ((current - base) / base) * 100; + const formatted = (Math.sign(value) * Math.ceil(Math.abs(value) * 100)) / 100; + + if (value > 0) { + return `+${formatted}%`; + } + + if (value === 0) { + return '-'; + } + + return `${formatted}%`; + } + + formatSizeResult(name, base, current) { + if (!current.passed) { + core.debug( + `Size limit exceeded for ${name} - ${this.formatBytes(current.size)} > ${this.formatBytes(current.sizeLimit)}`, + ); + } + + return [name, base, current, this.formatPercentageChange(base, current)]; + } + + hasSizeChanges(base, current, threshold = 0) { + if (!base || !current) { + return true; + } + + const names = ['baseline', 'withInstrument', 'withInstrumentErrorOnly']; + + return names.some(name => { + const baseResult = base[name]; + const currentResult = current[name]; + + if (!baseResult || !currentResult) { + return true; + } + + return Math.abs((currentResult.size - baseResult.size) / baseResult.size) * 100 > threshold; + }); + } + + formatResults(base, current) { + const headers = NODE_OVERHEAD_RESULTS_HEADER; + + return [ + headers, + this.formatSizeResult('Baseline', base.baseline, current.baseline), + this.formatSizeResult('With Sentry', base.withInstrument, current.withInstrument), + this.formatSizeResult('With Sentry (error only)', base.withInstrumentErrorOnly, current.withInstrumentErrorOnly), + ]; + } +} diff --git a/dev-packages/node-overhead-gh-action/lib/getOverheadMeasurements.mjs b/dev-packages/node-overhead-gh-action/lib/getOverheadMeasurements.mjs new file mode 100644 index 000000000000..ba69110ac89a --- /dev/null +++ b/dev-packages/node-overhead-gh-action/lib/getOverheadMeasurements.mjs @@ -0,0 +1,78 @@ +import { spawn } from 'child_process'; + +const DEBUG = !!process.env.DEBUG; + +async function getMeasurements(instrumentFile) { + const args = ['./src/app.mjs']; + + if (instrumentFile) { + args.unshift('--import', instrumentFile); + } + + log(`Getting arguments for instrumentFile=${instrumentFile}`); + + const appProcess = spawn('node', args, { shell: 'bash' }); + + await new Promise(resolve => { + appProcess.stdout.on('data', data => { + if (data.includes('Example app listening on port')) { + resolve(); + } + }); + }); + + const autocannon = spawn('yarn test', { + shell: 'bash', + }); + + let lastJson = undefined; + autocannon.stdout.on('data', data => { + log(`autocannon: ${data}`); + try { + lastJson = JSON.parse(data); + } catch { + // do nothing + } + }); + + autocannon.stderr.on('data', data => { + log(`autocannon stderr: ${data}`); + }); + + return new Promise(resolve => { + autocannon.on('close', code => { + log(`autocannon closed with code ${code}`); + + log(`Average requests: ${lastJson?.requests.average}`); + + appProcess.kill(); + + resolve(lastJson?.requests.average); + }); + }); +} + +export async function getOverheadMeasurements() { + const baseline = await getMeasurements(); + const withInstrument = await getMeasurements('./src/instrument.mjs'); + const withInstrumentErrorOnly = await getMeasurements('./src/instrument-error-only.mjs'); + + const withInstrumentPercentage = ((baseline - withInstrument) / baseline) * 100; + const withInstrumentErrorOnlyPercentage = ((baseline - withInstrumentErrorOnly) / baseline) * 100; + + return { + baseline, + withInstrument, + withInstrumentErrorOnly, + withInstrumentPercentage, + withInstrumentErrorOnlyPercentage, + }; +} + + +function log(message) { + if (DEBUG) { + // eslint-disable-next-line no-console + console.log(message); + } +} diff --git a/dev-packages/node-overhead-gh-action/package.json b/dev-packages/node-overhead-gh-action/package.json new file mode 100644 index 000000000000..1be24943f557 --- /dev/null +++ b/dev-packages/node-overhead-gh-action/package.json @@ -0,0 +1,40 @@ +{ + "name": "@sentry-internal/node-overhead-gh-action", + "version": "10.5.0", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "private": true, + "type": "module", + "main": "index.mjs", + "scripts": { + "start": "node ./src/app.mjs", + "start:sentry": "node --import ./src/instrument.mjs ./src/app.mjs", + "start:sentry-error-only": "node --import ./src/instrument-error-only.mjs ./src/app.mjs", + "test": "autocannon --json -c 100 -p 10 -d 10 -W [ -c 100 -d 5] http://localhost:3030/test-success", + "clean": "rimraf -g **/node_modules", + "lint": "eslint . --format stylish", + "fix": "eslint . --format stylish --fix" + }, + "dependencies": { + "@sentry/node": "10.5.0", + "express": "^4.21.1" + }, + "devDependencies": { + "autocannon": "^8.0.0", + "@actions/artifact": "2.1.11", + "@actions/core": "1.10.1", + "@actions/exec": "1.1.1", + "@actions/github": "^5.0.0", + "@actions/glob": "0.4.0", + "@actions/io": "1.1.3", + "bytes-iec": "3.1.1", + "markdown-table": "3.0.3" + }, + "volta": { + "extends": "../../package.json" + } +} + + diff --git a/dev-packages/node-overhead-gh-action/src/app.mjs b/dev-packages/node-overhead-gh-action/src/app.mjs new file mode 100644 index 000000000000..f7dd8901083b --- /dev/null +++ b/dev-packages/node-overhead-gh-action/src/app.mjs @@ -0,0 +1,18 @@ +import * as Sentry from '@sentry/node'; +import express from 'express'; + +const app = express(); +const port = 3030; + +app.use(express.json()); + +app.get('/test-success', function (req, res) { + res.send({ version: 'v1' }); +}); + +Sentry.setupExpressErrorHandler(app); + +app.listen(port, () => { + // eslint-disable-next-line no-console + console.log(`Example app listening on port ${port}`); +}); diff --git a/dev-packages/node-overhead-gh-action/src/instrument-error-only.mjs b/dev-packages/node-overhead-gh-action/src/instrument-error-only.mjs new file mode 100644 index 000000000000..6476a071226a --- /dev/null +++ b/dev-packages/node-overhead-gh-action/src/instrument-error-only.mjs @@ -0,0 +1,5 @@ +import * as Sentry from '@sentry/node'; + +Sentry.init({ + dsn: process.env.E2E_TEST_DSN || 'https://1234567890@sentry.io/1234567890', +}); diff --git a/dev-packages/node-overhead-gh-action/src/instrument.mjs b/dev-packages/node-overhead-gh-action/src/instrument.mjs new file mode 100644 index 000000000000..8a49ebb67a7e --- /dev/null +++ b/dev-packages/node-overhead-gh-action/src/instrument.mjs @@ -0,0 +1,6 @@ +import * as Sentry from '@sentry/node'; + +Sentry.init({ + dsn: process.env.E2E_TEST_DSN || 'https://1234567890@sentry.io/1234567890', + tracesSampleRate: 1, +}); diff --git a/dev-packages/node-overhead-gh-action/tsconfig.json b/dev-packages/node-overhead-gh-action/tsconfig.json new file mode 100644 index 000000000000..aa2f0763d61b --- /dev/null +++ b/dev-packages/node-overhead-gh-action/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.json", + + "include": ["src/**/*.mjs"], + + "compilerOptions": { + "lib": ["ES2018"], + // package-specific options + "esModuleInterop": true, + "types": ["node"] + } +} diff --git a/package.json b/package.json index 330343d570ab..8125283f46f0 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,8 @@ "dev-packages/size-limit-gh-action", "dev-packages/clear-cache-gh-action", "dev-packages/external-contributor-gh-action", - "dev-packages/rollup-utils" + "dev-packages/rollup-utils", + "dev-packages/node-overhead-gh-action" ], "devDependencies": { "@rollup/plugin-commonjs": "^25.0.7", diff --git a/yarn.lock b/yarn.lock index 4175e91b712c..16cb239b1585 100644 --- a/yarn.lock +++ b/yarn.lock @@ -416,6 +416,11 @@ resolved "https://registry.yarnpkg.com/@assemblyscript/loader/-/loader-0.10.1.tgz#70e45678f06c72fa2e350e8553ec4a4d72b92e06" integrity sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg== +"@assemblyscript/loader@^0.19.21": + version "0.19.23" + resolved "https://registry.yarnpkg.com/@assemblyscript/loader/-/loader-0.19.23.tgz#7fccae28d0a2692869f1d1219d36093bc24d5e72" + integrity sha512-ulkCYfFbYj01ie1MDOyxv2F6SpRN1TOj7fQxbP07D6HmeR+gr2JLSmINKjga2emB+b1L2KGrFKBTc+e00p54nw== + "@astrojs/compiler@^2.3.0": version "2.3.0" resolved "https://registry.yarnpkg.com/@astrojs/compiler/-/compiler-2.3.0.tgz#c56bc982f9640e0a9e2bcb8756dd362baee022bb" @@ -2707,6 +2712,11 @@ exec-sh "^0.3.2" minimist "^1.2.0" +"@colors/colors@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" + integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== + "@colors/colors@1.6.0", "@colors/colors@^1.6.0": version "1.6.0" resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.6.0.tgz#ec6cd237440700bc23ca23087f513c75508958b0" @@ -4902,6 +4912,13 @@ semver "^7.5.3" tar "^7.4.0" +"@minimistjs/subarg@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@minimistjs/subarg/-/subarg-1.0.0.tgz#484fdfebda9dc32087d7c7999ec6350684fb42d2" + integrity sha512-Q/ONBiM2zNeYUy0mVSO44mWWKYM3UHuEK43PKIOzJCbvUnPoMH1K+gk3cf1kgnCVJFlWmddahQQCmrmBGlk9jQ== + dependencies: + minimist "^1.1.0" + "@mjackson/node-fetch-server@^0.2.0": version "0.2.0" resolved "https://registry.yarnpkg.com/@mjackson/node-fetch-server/-/node-fetch-server-0.2.0.tgz#577c0c25d8aae9f69a97738b7b0d03d1471cdc49" @@ -6924,7 +6941,7 @@ mitt "^3.0.0" "@sentry-internal/test-utils@link:dev-packages/test-utils": - version "10.4.0" + version "10.5.0" dependencies: express "^4.21.1" @@ -10838,6 +10855,35 @@ atomic-sleep@^1.0.0: resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== +autocannon@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/autocannon/-/autocannon-8.0.0.tgz#72b3ade6ec63dca0dc3be157c873d0a27e3f3745" + integrity sha512-fMMcWc2JPFcUaqHeR6+PbmEpTxCrPZyBUM95oG4w3ngJ8NfBNas/ZXA+pTHXLqJ0UlFVTcy05GC25WxKx/M20A== + dependencies: + "@minimistjs/subarg" "^1.0.0" + chalk "^4.1.0" + char-spinner "^1.0.1" + cli-table3 "^0.6.0" + color-support "^1.1.1" + cross-argv "^2.0.0" + form-data "^4.0.0" + has-async-hooks "^1.0.0" + hdr-histogram-js "^3.0.0" + hdr-histogram-percentiles-obj "^3.0.0" + http-parser-js "^0.5.2" + hyperid "^3.0.0" + lodash.chunk "^4.2.0" + lodash.clonedeep "^4.5.0" + lodash.flatten "^4.4.0" + manage-path "^2.0.0" + on-net-listen "^1.1.1" + pretty-bytes "^5.4.1" + progress "^2.0.3" + reinterval "^1.1.0" + retimer "^3.0.0" + semver "^7.3.2" + timestring "^6.0.0" + autoprefixer@^10.4.13, autoprefixer@^10.4.19, autoprefixer@^10.4.20, autoprefixer@^10.4.8: version "10.4.20" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.20.tgz#5caec14d43976ef42e32dcb4bd62878e96be5b3b" @@ -11978,7 +12024,7 @@ buffer-more-ints@~1.0.0: resolved "https://registry.yarnpkg.com/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz#ef4f8e2dddbad429ed3828a9c55d44f05c611422" integrity sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg== -buffer@^5.5.0: +buffer@^5.2.1, buffer@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== @@ -12395,6 +12441,11 @@ chalk@^5.0.0, chalk@^5.2.0, chalk@^5.3.0: resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== +char-spinner@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/char-spinner/-/char-spinner-1.0.1.tgz#e6ea67bd247e107112983b7ab0479ed362800081" + integrity sha512-acv43vqJ0+N0rD+Uw3pDHSxP30FHrywu2NO6/wBaHChJIizpDeBUd6NjqhNhy9LGaEAhZAXn46QzmlAvIWd16g== + character-entities-html4@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz#1f1adb940c971a4b22ba39ddca6b618dc6e56b2b" @@ -12593,6 +12644,15 @@ cli-spinners@^2.0.0, cli-spinners@^2.5.0, cli-spinners@^2.9.0: resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.1.tgz#9c0b9dad69a6d47cbb4333c14319b060ed395a35" integrity sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ== +cli-table3@^0.6.0: + version "0.6.5" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.5.tgz#013b91351762739c16a9567c21a04632e449bf2f" + integrity sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ== + dependencies: + string-width "^4.2.0" + optionalDependencies: + "@colors/colors" "1.5.0" + cli-table@^0.3.1: version "0.3.6" resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.6.tgz#e9d6aa859c7fe636981fd3787378c2a20bce92fc" @@ -12734,7 +12794,7 @@ color-string@^1.6.0, color-string@^1.9.0: color-name "^1.0.0" simple-swizzle "^0.2.2" -color-support@^1.1.3: +color-support@^1.1.1, color-support@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== @@ -13371,6 +13431,11 @@ cronstrue@^2.50.0: resolved "https://registry.yarnpkg.com/cronstrue/-/cronstrue-2.50.0.tgz#eabba0f915f186765258b707b7a3950c663b5573" integrity sha512-ULYhWIonJzlScCCQrPUG5uMXzXxSixty4djud9SS37DoNxDdkeRocxzHuAo4ImRBUK+mAuU5X9TSwEDccnnuPg== +cross-argv@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/cross-argv/-/cross-argv-2.0.0.tgz#2e7907ba3246f82c967623a3e8525925bbd6c0ad" + integrity sha512-YIaY9TR5Nxeb8SMdtrU8asWVM4jqJDNDYlKV21LxtYcfNJhp1kEsgSa6qXwXgzN0WQWGODps0+TlGp2xQSHwOg== + cross-spawn@^6.0.0: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -18016,6 +18081,11 @@ has-ansi@^3.0.0: dependencies: ansi-regex "^3.0.0" +has-async-hooks@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-async-hooks/-/has-async-hooks-1.0.0.tgz#3df965ade8cd2d9dbfdacfbca3e0a5152baaf204" + integrity sha512-YF0VPGjkxr7AyyQQNykX8zK4PvtEDsUJAPqwu06UFz1lb6EvI53sPh5H1kWxg8NXI5LsfRCZ8uX9NkYDZBb/mw== + has-bigints@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" @@ -18302,6 +18372,15 @@ hdr-histogram-js@^2.0.1: base64-js "^1.2.0" pako "^1.0.3" +hdr-histogram-js@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/hdr-histogram-js/-/hdr-histogram-js-3.0.1.tgz#b281e90d6ca80ee656bc378dafa39d7239b90855" + integrity sha512-l3GSdZL1Jr1C0kyb461tUjEdrRPZr8Qry7jByltf5JGrA0xvqOSrxRBfcrJqqV/AMEtqqhHhC6w8HW0gn76tRQ== + dependencies: + "@assemblyscript/loader" "^0.19.21" + base64-js "^1.2.0" + pako "^1.0.3" + hdr-histogram-percentiles-obj@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/hdr-histogram-percentiles-obj/-/hdr-histogram-percentiles-obj-3.0.0.tgz#9409f4de0c2dda78e61de2d9d78b1e9f3cba283c" @@ -18579,6 +18658,11 @@ http-parser-js@>=0.5.1: resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.3.tgz#01d2709c79d41698bb01d4decc5e9da4e4a033d9" integrity sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg== +http-parser-js@^0.5.2: + version "0.5.10" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.10.tgz#b3277bd6d7ed5588e20ea73bf724fcbe44609075" + integrity sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA== + http-proxy-agent@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" @@ -18693,6 +18777,15 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" +hyperid@^3.0.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/hyperid/-/hyperid-3.3.0.tgz#2042bb296b7f1d5ba0797a5705469af0899c8556" + integrity sha512-7qhCVT4MJIoEsNcbhglhdmBKb09QtcmJNiIQGq7js/Khf5FtQQ9bzcAuloeqBeee7XD7JqDeve9KNlQya5tSGQ== + dependencies: + buffer "^5.2.1" + uuid "^8.3.2" + uuid-parse "^1.1.0" + iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -20706,6 +20799,11 @@ lodash.camelcase@^4.1.1: resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= +lodash.chunk@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.chunk/-/lodash.chunk-4.2.0.tgz#66e5ce1f76ed27b4303d8c6512e8d1216e8106bc" + integrity sha512-ZzydJKfUHJwHa+hF5X66zLFCBrWn5GeF28OHEr4WVWtNDXlQ/IjWKPBiikqKo2ne0+v6JgCgJ0GzJp8k8bHC7w== + lodash.clonedeep@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" @@ -20741,6 +20839,11 @@ lodash.flatten@^3.0.2: lodash._baseflatten "^3.0.0" lodash._isiterateecall "^3.0.0" +lodash.flatten@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g== + lodash.foreach@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53" @@ -21187,6 +21290,11 @@ makeerror@1.0.x: dependencies: tmpl "1.0.x" +manage-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/manage-path/-/manage-path-2.0.0.tgz#f4cf8457b926eeee2a83b173501414bc76eb9597" + integrity sha512-NJhyB+PJYTpxhxZJ3lecIGgh4kwIY2RAh44XvAz9UlqthlQwtPBf62uBVR8XaD8CRuSjQ6TnZH2lNJkbLPZM2A== + map-age-cleaner@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" @@ -22056,7 +22164,7 @@ minimist@^0.2.1: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.2.4.tgz#0085d5501e29033748a2f2a4da0180142697a475" integrity sha512-Pkrrm8NjyQ8yVt8Am9M+yUt74zE3iokhzbG1bFVNjLB92vwM71hf40RkEsryg98BujhVOncKm/C1xROxZ030LQ== -minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6: +minimist@^1.1.0, minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -23647,6 +23755,11 @@ on-headers@~1.0.2: resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== +on-net-listen@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/on-net-listen/-/on-net-listen-1.1.2.tgz#671e55a81c910fa7e5b1e4d506545e9ea0f2e11c" + integrity sha512-y1HRYy8s/RlcBvDUwKXSmkODMdx4KSuIvloCnQYJ2LdBBC1asY4HtfhXwe3UWknLakATZDnbzht2Ijw3M1EqFg== + once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -25386,7 +25499,7 @@ prettier@^3.1.1: resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.1.1.tgz#6ba9f23165d690b6cbdaa88cb0807278f7019848" integrity sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw== -pretty-bytes@^5.3.0: +pretty-bytes@^5.3.0, pretty-bytes@^5.4.1: version "5.6.0" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== @@ -26373,6 +26486,11 @@ rehype@^12.0.1: rehype-stringify "^9.0.0" unified "^10.0.0" +reinterval@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/reinterval/-/reinterval-1.1.0.tgz#3361ecfa3ca6c18283380dd0bb9546f390f5ece7" + integrity sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ== + relateurl@^0.2.7: version "0.2.7" resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" @@ -26720,6 +26838,11 @@ retext@^8.1.0: retext-stringify "^3.0.0" unified "^10.0.0" +retimer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/retimer/-/retimer-3.0.0.tgz#98b751b1feaf1af13eb0228f8ea68b8f9da530df" + integrity sha512-WKE0j11Pa0ZJI5YIk0nflGI7SQsfl2ljihVy7ogh7DeQSeYAUi0ubZ/yEueGtDfUPk6GH5LRw1hBdLq4IwUBWA== + retry-request@^4.1.1: version "4.1.3" resolved "https://registry.yarnpkg.com/retry-request/-/retry-request-4.1.3.tgz#d5f74daf261372cff58d08b0a1979b4d7cab0fde" @@ -28548,7 +28671,6 @@ stylus@0.59.0, stylus@^0.59.0: sucrase@^3.27.0, sucrase@^3.35.0, sucrase@getsentry/sucrase#es2020-polyfills: version "3.36.0" - uid fd682f6129e507c00bb4e6319cc5d6b767e36061 resolved "https://codeload.github.com/getsentry/sucrase/tar.gz/fd682f6129e507c00bb4e6319cc5d6b767e36061" dependencies: "@jridgewell/gen-mapping" "^0.3.2" @@ -29035,6 +29157,11 @@ tildify@2.0.0: resolved "https://registry.yarnpkg.com/tildify/-/tildify-2.0.0.tgz#f205f3674d677ce698b7067a99e949ce03b4754a" integrity sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw== +timestring@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/timestring/-/timestring-6.0.0.tgz#b0c7c331981ecf2066ce88bcfb8ee3ae32e7a0f6" + integrity sha512-wMctrWD2HZZLuIlchlkE2dfXJh7J2KDI9Dwl+2abPYg0mswQHfOAyQW3jJg1pY5VfttSINZuKcXoB3FGypVklA== + tiny-glob@0.2.9, tiny-glob@^0.2.9: version "0.2.9" resolved "https://registry.yarnpkg.com/tiny-glob/-/tiny-glob-0.2.9.tgz#2212d441ac17928033b110f8b3640683129d31e2" @@ -30317,6 +30444,11 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= +uuid-parse@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/uuid-parse/-/uuid-parse-1.1.0.tgz#7061c5a1384ae0e1f943c538094597e1b5f3a65b" + integrity sha512-OdmXxA8rDsQ7YpNVbKSJkNzTw2I+S5WsbMDnCtIWSQaosNAcWtFuI/YK1TjzUI6nbkgiqEyh8gWngfcv8Asd9A== + uuid-v4@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/uuid-v4/-/uuid-v4-0.1.0.tgz#62d7b310406f6cecfea1528c69f1e8e0bcec5a3a"