From 6cdfa3864bcaa8f74e8c8568aa0fef08609f81a1 Mon Sep 17 00:00:00 2001 From: ckohen Date: Fri, 25 Jul 2025 02:56:02 -0700 Subject: [PATCH] ci: better release workflow (#10325) * ci: better release workflow * ci: simplify + use changelog * ci(release): better parsing and exclusions * ci(release): remove tree log * ci(release): improve logs * ci(release): properly check inputs * ci(release): better promise handling Co-authored-by: Aura * ci: refactor release to use bun * ci(release): whitespace Co-authored-by: Vlad Frangu * ci(release): add dev release handling * ci(release): fixes from testing * ci(release): make the promise run * ci(release): when specifying package, skip exclusions * ci(dev): create-discord-bot dev release * ci(release): improve changelog detection * ci: fix typo and allow releasing branches * ci(release): set make_latest for gh releases * ci(release): add ssh_key so pushed tags run workflow --------- Co-authored-by: Aura Co-authored-by: Vlad Frangu Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .github/workflows/publish-dev.yml | 32 +-- .github/workflows/release.yml | 75 ++++++ package.json | 3 +- packages/actions/package.json | 4 + .../actions/src/releasePackages/action.yml | 24 ++ .../releasePackages/generateReleaseTree.ts | 230 ++++++++++++++++++ packages/actions/src/releasePackages/index.ts | 47 ++++ .../src/releasePackages/releasePackage.ts | 87 +++++++ packages/actions/tsconfig.json | 4 + packages/actions/tsup.config.ts | 2 + packages/ui/package.json | 1 + pnpm-lock.yaml | 159 ++++++++++++ 12 files changed, 646 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 packages/actions/src/releasePackages/action.yml create mode 100644 packages/actions/src/releasePackages/generateReleaseTree.ts create mode 100644 packages/actions/src/releasePackages/index.ts create mode 100644 packages/actions/src/releasePackages/releasePackage.ts diff --git a/.github/workflows/publish-dev.yml b/.github/workflows/publish-dev.yml index 6742d1412..ba797f288 100644 --- a/.github/workflows/publish-dev.yml +++ b/.github/workflows/publish-dev.yml @@ -3,6 +3,10 @@ on: schedule: - cron: '0 */12 * * *' workflow_dispatch: + inputs: + dry_run: + type: boolean + default: false jobs: npm-publish: name: npm publish @@ -55,32 +59,18 @@ jobs: node-version: 22 registry-url: https://registry.npmjs.org/ - - name: Check the current development version - id: release-check - run: | - if [[ $(npm view ${{ matrix.package }}@dev version | grep -e "$(git rev-parse --short HEAD)") ]]; \ - then echo "RELEASE=0" >> "$GITHUB_OUTPUT"; \ - else echo "RELEASE=1" >> "$GITHUB_OUTPUT"; \ - fi - - name: Install dependencies - if: steps.release-check.outputs.release == '1' uses: ./packages/actions/src/pnpmCache - name: Build dependencies - if: steps.release-check.outputs.release == '1' run: pnpm run build - - name: Publish package - if: steps.release-check.outputs.release == '1' - run: | - pnpm --filter=${{ matrix.package }} run release --preid "dev.$(date +%s)-$(git rev-parse --short HEAD)" --skip-changelog - pnpm --filter=${{ matrix.package }} publish --provenance --no-git-checks --tag dev || true - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} - - - name: Deprecate prior development releases - if: steps.release-check.outputs.release == '1' - run: pnpm exec npm-deprecate --name "*dev*" --message "This version is deprecated. Please use a newer version." --package ${{ matrix.package }} + - name: Publish packages + uses: ./packages/actions/src/releasePackages + with: + exclude: '@discordjs/docgen' + dry: ${{ inputs.dry_run }} + dev: true env: NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..55f5134c1 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,75 @@ +name: Release +on: + workflow_dispatch: + inputs: + ref: + description: 'The branch, tag or SHA to checkout' + required: true + default: 'main' + package: + description: 'The published name of a single package to release' + type: choice + required: false + options: + - all + - discord.js + - '@discordjs/brokers' + - '@discordjs/builders' + - '@discordjs/collection' + - '@discordjs/core' + - 'create-discord-bot' + - '@discordjs/docgen' + - '@discordjs/formatters' + - '@discordjs/next' + - '@discordjs/proxy' + - '@discordjs/rest' + - '@discordjs/util' + - '@discordjs/voice' + - '@discordjs/ws' + exclude: + description: 'Comma separated list of packages to exclude from release (if not depended upon)' + required: false + type: string + default: '@discordjs/docgen,@discordjs/next' + dry_run: + type: boolean + default: false +jobs: + npm-publish: + name: npm publish + runs-on: ubuntu-latest + permissions: + id-token: write + contents: write + env: + TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} + TURBO_TEAM: ${{ secrets.TURBO_TEAM }} + if: github.repository_owner == 'discordjs' + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref || '' }} + ssh_key: ${{ secrets.DEPLOY_KEY_CI_RELEASE_TAGS }} + + - name: Install Node.js v20 + uses: actions/setup-node@v4 + with: + node-version: 20 + registry-url: https://registry.npmjs.org/ + + - name: Install dependencies + uses: ./packages/actions/src/pnpmCache + + - name: Build dependencies + run: pnpm run build + + - name: Release packages + uses: ./packages/actions/src/releasePackages + with: + package: ${{ inputs.package }} + exclude: ${{ inputs.exclude }} + dry: ${{ inputs.dry_run }} + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/package.json b/package.json index c696577b1..54305827e 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "prepare": "is-ci || husky", "update": "pnpm --recursive update --interactive", "update:latest": "pnpm --recursive update --interactive --latest", - "create-package": "turbo gen create-package --args" + "create-package": "turbo gen create-package --args", + "release": "bun ./packages/actions/src/releasePackages/index.ts" }, "type": "module", "contributors": [ diff --git a/packages/actions/package.json b/packages/actions/package.json index 81d6a22b2..5f7b68d46 100644 --- a/packages/actions/package.json +++ b/packages/actions/package.json @@ -42,12 +42,14 @@ "funding": "https://github.com/discordjs/discord.js?sponsor", "dependencies": { "@actions/core": "^1.11.1", + "@actions/github": "^6.0.1", "@actions/glob": "^0.5.0", "@aws-sdk/client-s3": "^3.844.0", "@discordjs/scripts": "workspace:^", "@vercel/blob": "^1.1.1", "@vercel/postgres": "^0.10.0", "cloudflare": "^4.4.1", + "commander": "^14.0.0", "meilisearch": "^0.38.0", "p-limit": "^6.2.0", "p-queue": "^8.1.0", @@ -55,6 +57,8 @@ "undici": "7.11.0" }, "devDependencies": { + "@npm/types": "^2.1.0", + "@types/bun": "^1.2.19", "@types/node": "^22.16.3", "@vitest/coverage-v8": "^3.2.4", "cross-env": "^7.0.3", diff --git a/packages/actions/src/releasePackages/action.yml b/packages/actions/src/releasePackages/action.yml new file mode 100644 index 000000000..d25bd849b --- /dev/null +++ b/packages/actions/src/releasePackages/action.yml @@ -0,0 +1,24 @@ +name: 'Release Packages' +description: 'Tags and releases any unreleased packages' +inputs: + dev: + description: 'Releases development versions of packages (skips tagging and github releases)' + default: false + dry: + descrption: 'Perform a dry run that skips publishing and outputs logs indicating what would have happened' + default: false + package: + description: 'The published name of a single package to release' + exclude: + description: 'Comma separated list of packages to exclude from release (if not depended upon)' +runs: + using: composite + steps: + - uses: oven-sh/setup-bun@v1 + - run: bun packages/actions/src/releasePackages/index.ts + shell: bash + env: + INPUT_DEV: ${{ inputs.dev }} + INPUT_DRY: ${{ inputs.dry }} + INPUT_PACKAGE: ${{ inputs.package }} + INPUT_EXCLUDE: ${{ inputs.exclude }} diff --git a/packages/actions/src/releasePackages/generateReleaseTree.ts b/packages/actions/src/releasePackages/generateReleaseTree.ts new file mode 100644 index 000000000..4e52bdf17 --- /dev/null +++ b/packages/actions/src/releasePackages/generateReleaseTree.ts @@ -0,0 +1,230 @@ +import { info, warning } from '@actions/core'; +import type { PackageJSON, PackumentVersion } from '@npm/types'; +import { $, file, write } from 'bun'; + +const nonNodePackages = new Set(['@discordjs/proxy-container']); + +interface pnpmTreeDependency { + from: string; + path: string; + version: string; +} + +interface pnpmTree { + dependencies?: Record; + name?: string; + path: string; + private?: boolean; + unsavedDependencies?: Record; + version?: string; +} + +export interface ReleaseEntry { + changelog?: string; + dependsOn?: string[]; + name: string; + version: string; +} + +async function fetchDevVersion(pkg: string) { + try { + const res = await fetch(`https://registry.npmjs.org/${pkg}/dev`); + if (!res.ok) return null; + const packument = (await res.json()) as PackumentVersion; + return packument.version; + } catch { + return null; + } +} + +async function getReleaseEntries(dev: boolean, dry: boolean) { + const releaseEntries: ReleaseEntry[] = []; + const packageList: pnpmTree[] = + await $`pnpm list --recursive --only-projects --filter {packages/\*} --prod --json`.json(); + + const commitHash = (await $`git rev-parse --short HEAD`.text()).trim(); + + for (const pkg of packageList) { + // Don't release private packages ever (npm will error anyways) + if (pkg.private) continue; + // Just in case + if (!pkg.version || !pkg.name) continue; + if (nonNodePackages.has(pkg.name)) continue; + + const release: ReleaseEntry = { + name: pkg.name, + version: pkg.version, + }; + + if (dev) { + const devVersion = await fetchDevVersion(pkg.name); + if (devVersion?.endsWith(commitHash)) { + // Write the currently released dev version so when pnpm publish runs on dependents they depend on the dev versions + if (dry) { + info(`[DRY] ${pkg.name}@${devVersion} already released. Editing package.json version.`); + } else { + const pkgJson = (await file(`${pkg.path}/package.json`).json()) as PackageJSON; + pkgJson.version = devVersion; + await write(`${pkg.path}/package.json`, JSON.stringify(pkgJson, null, '\t')); + } + + release.version = devVersion; + } else if (dry) { + info(`[DRY] Bumping ${pkg.name} via git-cliff.`); + release.version = `${pkg.version}.DRY-dev.${Math.round(Date.now() / 1_000)}-${commitHash}`; + } else { + await $`pnpm --filter=${pkg.name} run release --preid "dev.${Math.round(Date.now() / 1_000)}-${commitHash} --skip-changelog"`; + // Read again instead of parsing the output to be sure we're matching when checking against npm + const pkgJson = (await file(`${pkg.path}/package.json`).json()) as PackageJSON; + release.version = pkgJson.version; + } + } + // Only need changelog for releases published to github + else { + try { + // Find and parse changelog to post in github release + const changelogFile = await file(`${pkg.path}/CHANGELOG.md`).text(); + + let changelogLines: string[] = []; + let foundChangelog = false; + + for (const line of changelogFile.split('\n')) { + if (line.startsWith('# [')) { + if (foundChangelog) { + if (changelogLines.at(-1) === '') { + changelogLines = changelogLines.slice(2, -1); + } + + break; + } + + // Check changelog release version and assume no changelog if version does not match + if (!line.startsWith(`# [${release.name === 'discord.js' ? `` : `${release.name}@`}${release.version}]`)) { + break; + } + + foundChangelog = true; + } + + if (foundChangelog) { + changelogLines.push(line); + } + } + + release.changelog = changelogLines.join('\n'); + } catch (error) { + // Probably just no changelog file but log just in case + warning(`Error parsing changelog for ${pkg.name}, will use auto generated: ${error}`); + } + } + + if (pkg.dependencies) { + release.dependsOn = Object.keys(pkg.dependencies); + } + + releaseEntries.push(release); + } + + return releaseEntries; +} + +export async function generateReleaseTree(dev: boolean, dry: boolean, packageName?: string, exclude?: string[]) { + let releaseEntries = await getReleaseEntries(dev, dry); + // Try to early return if the package doesn't have deps + if (packageName && packageName !== 'all') { + const releaseEntry = releaseEntries.find((entry) => entry.name === packageName); + if (!releaseEntry) { + throw new Error(`Package ${packageName} not releaseable`); + } + + if (!releaseEntry.dependsOn) { + return [[releaseEntry]]; + } + } + + // Generate the whole tree first, then prune if specified + const releaseTree: ReleaseEntry[][] = []; + const didRelease = new Set(); + + while (releaseEntries.length) { + const nextBranch: ReleaseEntry[] = []; + const unreleased: ReleaseEntry[] = []; + for (const entry of releaseEntries) { + if (!entry.dependsOn) { + nextBranch.push(entry); + continue; + } + + const allDepsReleased = entry.dependsOn.every((dep) => didRelease.has(dep)); + if (allDepsReleased) { + nextBranch.push(entry); + } else { + unreleased.push(entry); + } + } + + // Update didRelease in a second loop to avoid loop order issues + for (const release of nextBranch) { + didRelease.add(release.name); + } + + if (releaseEntries.length === unreleased.length) { + throw new Error( + `One or more packages have dependents that can't be released: ${unreleased.map((entry) => entry.name).join(',')}`, + ); + } + + releaseTree.push(nextBranch); + releaseEntries = unreleased; + } + + // Prune exclusions + if ((!packageName || packageName === 'all') && Array.isArray(exclude) && exclude.length) { + const neededPackages = new Set(); + const excludedReleaseTree: ReleaseEntry[][] = []; + + for (const releaseBranch of releaseTree.reverse()) { + const newThisBranch: ReleaseEntry[] = []; + + for (const entry of releaseBranch) { + if (exclude.includes(entry.name) && !neededPackages.has(entry.name)) { + continue; + } + + newThisBranch.push(entry); + for (const dep of entry.dependsOn ?? []) { + neededPackages.add(dep); + } + } + + if (newThisBranch.length) excludedReleaseTree.unshift(newThisBranch); + } + + return excludedReleaseTree; + } + + if (!packageName || packageName === 'all') { + return releaseTree; + } + + // Prune the tree for the specified package + const neededPackages = new Set([packageName]); + const packageReleaseTree: ReleaseEntry[][] = []; + + for (const releaseBranch of releaseTree.reverse()) { + const newThisBranch: ReleaseEntry[] = []; + + for (const entry of releaseBranch) { + if (neededPackages.has(entry.name)) { + newThisBranch.push(entry); + for (const dep of entry.dependsOn ?? []) { + neededPackages.add(dep); + } + } + } + + if (newThisBranch.length) packageReleaseTree.unshift(newThisBranch); + } + + return packageReleaseTree; +} diff --git a/packages/actions/src/releasePackages/index.ts b/packages/actions/src/releasePackages/index.ts new file mode 100644 index 000000000..b16c81272 --- /dev/null +++ b/packages/actions/src/releasePackages/index.ts @@ -0,0 +1,47 @@ +import { getInput, startGroup, endGroup, getBooleanInput, info } from '@actions/core'; +import { program } from 'commander'; +import { generateReleaseTree } from './generateReleaseTree.js'; +import { releasePackage } from './releasePackage.js'; + +const excludeInput = getInput('exclude'); +let dryInput = false; +let devInput = false; + +try { + devInput = getBooleanInput('dev'); +} catch { + // We're not running in actions +} + +try { + dryInput = getBooleanInput('dry'); +} catch { + // We're not running in actions or the input isn't set (cron) +} + +program + .name('release packages') + .description('releases monorepo packages with proper sequencing') + .argument('[package]', "release a specific package (and it's dependencies)", getInput('package')) + .option( + '-e, --exclude ', + 'exclude specific packages from releasing (will still release if necessary for another package)', + excludeInput ? excludeInput.split(',') : [], + ) + .option('--dry', 'skips actual publishing and outputs logs instead', dryInput) + .option('--dev', 'publishes development versions and skips tagging / github releases', devInput) + .parse(); + +const { exclude, dry, dev } = program.opts<{ dev: boolean; dry: boolean; exclude: string[] }>(); +const packageName = program.args[0]!; + +const tree = await generateReleaseTree(dev, dry, packageName, exclude); +for (const branch of tree) { + startGroup(`Releasing ${branch.map((entry) => `${entry.name}@${entry.version}`).join(', ')}`); + await Promise.all(branch.map(async (release) => releasePackage(release, dev, dry))); + endGroup(); +} + +info( + `Successfully released ${tree.map((branch) => branch.map((entry) => `${entry.name}@${entry.version}`).join(', ')).join(', ')}`, +); diff --git a/packages/actions/src/releasePackages/releasePackage.ts b/packages/actions/src/releasePackages/releasePackage.ts new file mode 100644 index 000000000..6a1dab232 --- /dev/null +++ b/packages/actions/src/releasePackages/releasePackage.ts @@ -0,0 +1,87 @@ +import process from 'node:process'; +import { setInterval, clearInterval } from 'node:timers'; +import { info, warning } from '@actions/core'; +import { getOctokit, context } from '@actions/github'; +import { $ } from 'bun'; +import type { ReleaseEntry } from './generateReleaseTree.js'; + +let octokit: ReturnType | undefined; + +if (process.env.GITHUB_TOKEN) { + octokit = getOctokit(process.env.GITHUB_TOKEN); +} + +async function checkRegistry(release: ReleaseEntry) { + const res = await fetch(`https://registry.npmjs.org/${release.name}/${release.version}`); + return res.ok; +} + +async function gitTagAndRelease(release: ReleaseEntry, dry: boolean) { + const tagName = `${release.name === 'discord.js' ? `` : `${release.name}@`}${release.version}`; + // Don't throw, if this exits non-zero it's probably because the tag already exists + await $`git tag ${tagName}`.nothrow(); + + if (dry) { + info(`[DRY] Tag "${tagName}" created, skipping push and release creation.`); + return; + } + + await $`git push origin ${tagName}`; + + try { + await octokit?.rest.repos.createRelease({ + ...context.repo, + tag_name: tagName, + name: tagName, + body: release.changelog ?? '', + generate_release_notes: release.changelog === undefined, + make_latest: release.name === 'discord.js' ? 'true' : 'false', + }); + } catch (error) { + warning(`Failed to create github release: ${error}`); + } +} + +export async function releasePackage(release: ReleaseEntry, dev: boolean, dry: boolean) { + // Sanity check against the registry first + if (await checkRegistry(release)) { + info(`${release.name}@${release.version} already published, skipping.`); + return; + } + + if (dry) { + info(`[DRY] Releasing ${release.name}@${release.version}`); + } else { + await $`pnpm --filter=${release.name} publish --provenance --no-git-checks ${dev ? '--tag=dev' : ''}`; + } + + if (!dev) await gitTagAndRelease(release, dry); + + if (dry) return; + + const before = performance.now(); + + // Poll registry to ensure next publishes won't fail + await new Promise((resolve, reject) => { + const interval = setInterval(async () => { + if (await checkRegistry(release)) { + clearInterval(interval); + resolve(); + return; + } + + if (performance.now() > before + 5 * 60 * 1_000) { + clearInterval(interval); + reject(new Error(`Release for ${release.name} failed.`)); + } + }, 15_000); + }); + + if (dev) { + // Send and forget, deprecations are less important than releasing other dev versions and can be done manually + void $`pnpm exec npm-deprecate --name "*dev*" --message "This version is deprecated. Please use a newer version." --package ${release.name}` + .nothrow() + // eslint-disable-next-line promise/prefer-await-to-then + .then(() => {}); + } +} diff --git a/packages/actions/tsconfig.json b/packages/actions/tsconfig.json index b6ea137a0..7f25511aa 100644 --- a/packages/actions/tsconfig.json +++ b/packages/actions/tsconfig.json @@ -1,6 +1,10 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.json", + "compilerOptions": { + "types": ["node"], + "skipLibCheck": true + }, "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.cjs", "src/**/*.mjs", "bin"], "exclude": ["node_modules"] } diff --git a/packages/actions/tsup.config.ts b/packages/actions/tsup.config.ts index 8e29a0485..d1438612c 100644 --- a/packages/actions/tsup.config.ts +++ b/packages/actions/tsup.config.ts @@ -4,6 +4,7 @@ export default createTsupConfig({ entry: [ 'src/index.ts', 'src/formatTag/index.ts', + 'src/releasePackages/index.ts', 'src/uploadDocumentation/index.ts', 'src/uploadSearchIndices/index.ts', 'src/uploadSplitDocumentation/index.ts', @@ -11,4 +12,5 @@ export default createTsupConfig({ dts: false, format: 'esm', minify: 'terser', + target: 'esnext', }); diff --git a/packages/ui/package.json b/packages/ui/package.json index 7811ca19a..8791b0dd8 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@discordjs/ui", + "private": true, "version": "0.1.0", "description": "", "scripts": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c8ca1f810..3045a0b52 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -444,6 +444,9 @@ importers: '@actions/core': specifier: ^1.11.1 version: 1.11.1 + '@actions/github': + specifier: ^6.0.1 + version: 6.0.1 '@actions/glob': specifier: ^0.5.0 version: 0.5.0 @@ -462,6 +465,9 @@ importers: cloudflare: specifier: ^4.4.1 version: 4.4.1(encoding@0.1.13) + commander: + specifier: ^14.0.0 + version: 14.0.0 meilisearch: specifier: ^0.38.0 version: 0.38.0(encoding@0.1.13) @@ -478,6 +484,12 @@ importers: specifier: 7.11.0 version: 7.11.0 devDependencies: + '@npm/types': + specifier: ^2.1.0 + version: 2.1.0 + '@types/bun': + specifier: ^1.2.19 + version: 1.2.19(@types/react@19.1.8) '@types/node': specifier: ^22.16.3 version: 22.16.3 @@ -1940,6 +1952,9 @@ packages: '@actions/exec@1.1.1': resolution: {integrity: sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==} + '@actions/github@6.0.1': + resolution: {integrity: sha512-xbZVcaqD4XnQAe35qSQqskb3SqIAfRyLBrHMd/8TuL7hJSz2QtbDwnNM8zWx4zO5l2fnGtseNE3MbEvD7BxVMw==} + '@actions/glob@0.5.0': resolution: {integrity: sha512-tST2rjPvJLRZLuT9NMUtyBjvj9Yo0MiJS3ow004slMvm8GFM+Zv9HvMJ7HWzfUyJnGrJvDsYkWBaaG3YKXRtCw==} @@ -3475,6 +3490,10 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@npm/types@2.1.0': + resolution: {integrity: sha512-humQVe2BrWR7Yum5hGDYBnIPnnZJvKSOH/I4QN1ZL2bdb4c4zQHaHupEJ3cOkSJ07G3YfN793ptbNh196BWLgA==} + engines: {node: '>=18.6.0'} + '@npmcli/agent@3.0.0': resolution: {integrity: sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==} engines: {node: ^18.17.0 || >=20.5.0} @@ -3511,10 +3530,18 @@ packages: resolution: {integrity: sha512-/1uFzjVcfzqrgCeGW7+SZ4hv0qLWmKXVzFahZGJ6QuJBj6Myt9s17+JL86i76NV9YSnJRcGXJYQbAU0rn1YTCQ==} engines: {node: ^18.17.0 || >=20.5.0} + '@octokit/auth-token@4.0.0': + resolution: {integrity: sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==} + engines: {node: '>= 18'} + '@octokit/auth-token@5.1.1': resolution: {integrity: sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==} engines: {node: '>= 18'} + '@octokit/core@5.2.0': + resolution: {integrity: sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==} + engines: {node: '>= 18'} + '@octokit/core@6.1.2': resolution: {integrity: sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==} engines: {node: '>= 18'} @@ -3523,27 +3550,61 @@ packages: resolution: {integrity: sha512-XybpFv9Ms4hX5OCHMZqyODYqGTZ3H6K6Vva+M9LR7ib/xr1y1ZnlChYv9H680y77Vd/i/k+thXApeRASBQkzhA==} engines: {node: '>= 18'} + '@octokit/endpoint@9.0.6': + resolution: {integrity: sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==} + engines: {node: '>= 18'} + + '@octokit/graphql@7.1.0': + resolution: {integrity: sha512-r+oZUH7aMFui1ypZnAvZmn0KSqAUgE1/tUXIWaqUCa1758ts/Jio84GZuzsvUkme98kv0WFY8//n0J1Z+vsIsQ==} + engines: {node: '>= 18'} + '@octokit/graphql@8.1.2': resolution: {integrity: sha512-bdlj/CJVjpaz06NBpfHhp4kGJaRZfz7AzC+6EwUImRtrwIw8dIgJ63Xg0OzV9pRn3rIzrt5c2sa++BL0JJ8GLw==} engines: {node: '>= 18'} + '@octokit/openapi-types@20.0.0': + resolution: {integrity: sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==} + '@octokit/openapi-types@22.2.0': resolution: {integrity: sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==} + '@octokit/plugin-paginate-rest@9.2.2': + resolution: {integrity: sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '5' + + '@octokit/plugin-rest-endpoint-methods@10.4.1': + resolution: {integrity: sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '5' + '@octokit/plugin-retry@7.1.2': resolution: {integrity: sha512-XOWnPpH2kJ5VTwozsxGurw+svB2e61aWlmk5EVIYZPwFK5F9h4cyPyj9CIKRyMXMHSwpIsI3mPOdpMmrRhe7UQ==} engines: {node: '>= 18'} peerDependencies: '@octokit/core': '>=6' + '@octokit/request-error@5.1.1': + resolution: {integrity: sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==} + engines: {node: '>= 18'} + '@octokit/request-error@6.1.6': resolution: {integrity: sha512-pqnVKYo/at0NuOjinrgcQYpEbv4snvP3bKMRqHaD9kIsk9u1LCpb2smHZi8/qJfgeNqLo5hNW4Z7FezNdEo0xg==} engines: {node: '>= 18'} + '@octokit/request@8.4.1': + resolution: {integrity: sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==} + engines: {node: '>= 18'} + '@octokit/request@9.1.4': resolution: {integrity: sha512-tMbOwGm6wDII6vygP3wUVqFTw3Aoo0FnVQyhihh8vVq12uO3P+vQZeo2CKMpWtPSogpACD0yyZAlVlQnjW71DA==} engines: {node: '>= 18'} + '@octokit/types@12.6.0': + resolution: {integrity: sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==} + '@octokit/types@13.6.2': resolution: {integrity: sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==} @@ -6027,6 +6088,9 @@ packages: '@types/body-parser@1.19.5': resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} + '@types/bun@1.2.19': + resolution: {integrity: sha512-d9ZCmrH3CJ2uYKXQIUuZ/pUnTqIvLDS0SK7pFmbx8ma+ziH/FRMoAq5bYpRG7y+w1gl+HgyNZbtqgMq4W4e2Lg==} + '@types/chai@5.2.2': resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} @@ -7406,6 +7470,9 @@ packages: resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} engines: {node: '>=10.0.0'} + before-after-hook@2.2.3: + resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==} + before-after-hook@3.0.2: resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==} @@ -7485,6 +7552,11 @@ packages: resolution: {integrity: sha512-p1n8zyCkt1BVrKNFymOHjcDSAl7oq/gUvfgULv2EblgpPVQlQr9yHnWjg9IJ2MhfwPqiYqMMrr01OY7yQoK2yA==} engines: {node: '>=18.20'} + bun-types@1.2.19: + resolution: {integrity: sha512-uAOTaZSPuYsWIXRpj7o56Let0g/wjihKCkeRqUBhlLVM/Bt+Fj9xTo+LhC1OV1XDaGkz4hNC80et5xgy+9KTHQ==} + peerDependencies: + '@types/react': ^19 + bundle-require@5.1.0: resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -8354,6 +8426,9 @@ packages: resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} engines: {node: '>= 0.6'} + deprecation@2.3.1: + resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -13379,6 +13454,9 @@ packages: unist-util-visit@5.0.0: resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + universal-user-agent@6.0.1: + resolution: {integrity: sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==} + universal-user-agent@7.0.2: resolution: {integrity: sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==} @@ -13957,6 +14035,16 @@ snapshots: dependencies: '@actions/io': 1.1.3 + '@actions/github@6.0.1': + dependencies: + '@actions/http-client': 2.2.3 + '@octokit/core': 5.2.0 + '@octokit/plugin-paginate-rest': 9.2.2(@octokit/core@5.2.0) + '@octokit/plugin-rest-endpoint-methods': 10.4.1(@octokit/core@5.2.0) + '@octokit/request': 8.4.1 + '@octokit/request-error': 5.1.1 + undici: 5.29.0 + '@actions/glob@0.5.0': dependencies: '@actions/core': 1.11.1 @@ -15885,6 +15973,8 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.18.0 + '@npm/types@2.1.0': {} + '@npmcli/agent@3.0.0': dependencies: agent-base: 7.1.3 @@ -15953,8 +16043,20 @@ snapshots: '@npmcli/redact@3.0.0': {} + '@octokit/auth-token@4.0.0': {} + '@octokit/auth-token@5.1.1': {} + '@octokit/core@5.2.0': + dependencies: + '@octokit/auth-token': 4.0.0 + '@octokit/graphql': 7.1.0 + '@octokit/request': 8.4.1 + '@octokit/request-error': 5.1.1 + '@octokit/types': 13.6.2 + before-after-hook: 2.2.3 + universal-user-agent: 6.0.1 + '@octokit/core@6.1.2': dependencies: '@octokit/auth-token': 5.1.1 @@ -15970,14 +16072,37 @@ snapshots: '@octokit/types': 13.6.2 universal-user-agent: 7.0.2 + '@octokit/endpoint@9.0.6': + dependencies: + '@octokit/types': 13.6.2 + universal-user-agent: 6.0.1 + + '@octokit/graphql@7.1.0': + dependencies: + '@octokit/request': 8.4.1 + '@octokit/types': 13.6.2 + universal-user-agent: 6.0.1 + '@octokit/graphql@8.1.2': dependencies: '@octokit/request': 9.1.4 '@octokit/types': 13.6.2 universal-user-agent: 7.0.2 + '@octokit/openapi-types@20.0.0': {} + '@octokit/openapi-types@22.2.0': {} + '@octokit/plugin-paginate-rest@9.2.2(@octokit/core@5.2.0)': + dependencies: + '@octokit/core': 5.2.0 + '@octokit/types': 12.6.0 + + '@octokit/plugin-rest-endpoint-methods@10.4.1(@octokit/core@5.2.0)': + dependencies: + '@octokit/core': 5.2.0 + '@octokit/types': 12.6.0 + '@octokit/plugin-retry@7.1.2(@octokit/core@6.1.2)': dependencies: '@octokit/core': 6.1.2 @@ -15985,10 +16110,23 @@ snapshots: '@octokit/types': 13.6.2 bottleneck: 2.19.5 + '@octokit/request-error@5.1.1': + dependencies: + '@octokit/types': 13.6.2 + deprecation: 2.3.1 + once: 1.4.0 + '@octokit/request-error@6.1.6': dependencies: '@octokit/types': 13.6.2 + '@octokit/request@8.4.1': + dependencies: + '@octokit/endpoint': 9.0.6 + '@octokit/request-error': 5.1.1 + '@octokit/types': 13.6.2 + universal-user-agent: 6.0.1 + '@octokit/request@9.1.4': dependencies: '@octokit/endpoint': 10.1.2 @@ -15997,6 +16135,10 @@ snapshots: fast-content-type-parse: 2.0.0 universal-user-agent: 7.0.2 + '@octokit/types@12.6.0': + dependencies: + '@octokit/openapi-types': 20.0.0 + '@octokit/types@13.6.2': dependencies: '@octokit/openapi-types': 22.2.0 @@ -19299,6 +19441,12 @@ snapshots: '@types/connect': 3.4.38 '@types/node': 22.16.3 + '@types/bun@1.2.19(@types/react@19.1.8)': + dependencies: + bun-types: 1.2.19(@types/react@19.1.8) + transitivePeerDependencies: + - '@types/react' + '@types/chai@5.2.2': dependencies: '@types/deep-eql': 4.0.2 @@ -21363,6 +21511,8 @@ snapshots: basic-ftp@5.0.5: {} + before-after-hook@2.2.3: {} + before-after-hook@3.0.2: {} bent@7.3.12: @@ -21443,6 +21593,11 @@ snapshots: builtin-modules@4.0.0: {} + bun-types@1.2.19(@types/react@19.1.8): + dependencies: + '@types/node': 22.16.3 + '@types/react': 19.1.8 + bundle-require@5.1.0(esbuild@0.25.5): dependencies: esbuild: 0.25.5 @@ -22359,6 +22514,8 @@ snapshots: depd@1.1.2: {} + deprecation@2.3.1: {} + dequal@2.0.3: {} destr@2.0.3: {} @@ -29085,6 +29242,8 @@ snapshots: unist-util-is: 6.0.0 unist-util-visit-parents: 6.0.1 + universal-user-agent@6.0.1: {} + universal-user-agent@7.0.2: {} universalify@2.0.1: {}