From 650b9ea00f6566ae74bd4af414c1b6ea68c401aa Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Fri, 9 Aug 2024 16:55:23 -0400 Subject: [PATCH 1/3] chore: add script for generating an initial draft of release notes --- scripts/release-notes.sh | 7 + scripts/release-notes/README.md | 5 + scripts/release-notes/dedent.ts | 86 +++ scripts/release-notes/package.json | 12 + scripts/release-notes/pnpm-lock.yaml | 969 +++++++++++++++++++++++++ scripts/release-notes/release-notes.ts | 327 +++++++++ 6 files changed, 1406 insertions(+) create mode 100644 scripts/release-notes.sh create mode 100644 scripts/release-notes/README.md create mode 100644 scripts/release-notes/dedent.ts create mode 100644 scripts/release-notes/package.json create mode 100644 scripts/release-notes/pnpm-lock.yaml create mode 100644 scripts/release-notes/release-notes.ts diff --git a/scripts/release-notes.sh b/scripts/release-notes.sh new file mode 100644 index 00000000..411d5f85 --- /dev/null +++ b/scripts/release-notes.sh @@ -0,0 +1,7 @@ +if [ ! -d "scripts/release-notes/node_modules" ]; then + echo "Node modules not found. Installing dependencies..." + pnpm install -C scripts/release-notes +fi + +cd scripts/release-notes +pnpm -s tsx release-notes.ts diff --git a/scripts/release-notes/README.md b/scripts/release-notes/README.md new file mode 100644 index 00000000..37e34c6c --- /dev/null +++ b/scripts/release-notes/README.md @@ -0,0 +1,5 @@ +This script collects all "fix" and "feat" commits since the last stable version. Then it concatenates their `git log -p` outputs into a single file, which can be fed into an LLM for high-quality release notes. + +``` +tsx scripts/release-notes/release-notes.ts > release-notes.diff +``` diff --git a/scripts/release-notes/dedent.ts b/scripts/release-notes/dedent.ts new file mode 100644 index 00000000..ab9e3099 --- /dev/null +++ b/scripts/release-notes/dedent.ts @@ -0,0 +1,86 @@ +import { isArray } from 'radashi' + +/** + * Remove indentation from a string. The given string is expected to + * be consistently indented (i.e. the leading whitespace of the first + * non-empty line is the minimum required for all non-empty lines). + * + * If the `indent` argument is nullish, the indentation is detected + * from the first non-empty line. Detection is cheap and robust for + * most use cases, so you should only set an explicit `indent` if + * necessary. + * + * @see https://radashi-org.github.io/reference/string/dedent + * @example + * ```ts + * // This is indented with 4 spaces. + * const input = ` + * Hello + * World + * ` + * + * // Explicit indentation + * dedent(input, ' ') + * // => ' Hello\n World\n' + * + * // Detected indentation + * dedent(input) + * // => 'Hello\nWorld\n' + * + * // Tagged template strings + * const str = dedent` + * Foo ${1 + 1} + * Bar ${2 * 2} + * ` + * // => 'Foo 2\nBar 4' + * ``` + */ +export function dedent( + template: TemplateStringsArray, + ...values: unknown[] +): string + +export function dedent(text: string, indent?: string | null): string + +export function dedent( + text: string | TemplateStringsArray, + ...values: unknown[] +): string { + // Support tagged template strings + if (isArray(text)) { + if (values.length > 0) { + return dedent( + text.reduce((acc, input, i) => { + let value = String(values[i] ?? '') + + // Detect the indentation before this embedded string. + const indent = + value.includes('\n') && input.match(/[ \t]*(?=[^\n]*$)/)?.[0] + + // Ensure the multi-line, embedded string can be correctly + // dedented. + if (indent) { + value = value.replace(/\n(?=[^\n]*?\S)/g, '\n' + indent) + } + + return acc + input + value + }, ''), + ) + } + + text = text[0] + } + + const indent = values[0] ?? detectIndent(text) + const output = indent + ? text.replace(new RegExp(`^${indent}`, 'gm'), '') + : text + + // Remove the first and last lines (if empty). + return output.replace(/^[ \t]*\n|\n[ \t]*$/g, '') +} + +// Find the indentation of the first non-empty line. +function detectIndent(text: string) { + return text.match(/^[ \t]*(?=\S)/m)?.[0] +} diff --git a/scripts/release-notes/package.json b/scripts/release-notes/package.json new file mode 100644 index 00000000..91570ac7 --- /dev/null +++ b/scripts/release-notes/package.json @@ -0,0 +1,12 @@ +{ + "private": true, + "dependencies": { + "@anthropic-ai/sdk": "^0.25.0", + "@types/node": "^22.1.0", + "execa": "^9.3.0", + "mri": "^1.2.0", + "octokit": "^4.0.2", + "radashi": "12.2.0-beta.7fb6e89", + "tsx": "^4.17.0" + } +} \ No newline at end of file diff --git a/scripts/release-notes/pnpm-lock.yaml b/scripts/release-notes/pnpm-lock.yaml new file mode 100644 index 00000000..b2d85b46 --- /dev/null +++ b/scripts/release-notes/pnpm-lock.yaml @@ -0,0 +1,969 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@anthropic-ai/sdk': + specifier: ^0.25.0 + version: 0.25.0 + '@types/node': + specifier: ^22.1.0 + version: 22.1.0 + execa: + specifier: ^9.3.0 + version: 9.3.0 + mri: + specifier: ^1.2.0 + version: 1.2.0 + octokit: + specifier: ^4.0.2 + version: 4.0.2 + radashi: + specifier: 12.2.0-beta.7fb6e89 + version: 12.2.0-beta.7fb6e89 + tsx: + specifier: ^4.17.0 + version: 4.17.0 + +packages: + + '@anthropic-ai/sdk@0.25.0': + resolution: {integrity: sha512-nill47zLtX+Tx6YacvuML1WMA7vuFA+I2uGh+8mGig4D3HwKFLThf45cS1itcmYVnjUQ+ohrSnkRyu1t+Xbh2w==} + + '@esbuild/aix-ppc64@0.23.0': + resolution: {integrity: sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.23.0': + resolution: {integrity: sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.23.0': + resolution: {integrity: sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.23.0': + resolution: {integrity: sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.23.0': + resolution: {integrity: sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.23.0': + resolution: {integrity: sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.23.0': + resolution: {integrity: sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.23.0': + resolution: {integrity: sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.23.0': + resolution: {integrity: sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.23.0': + resolution: {integrity: sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.23.0': + resolution: {integrity: sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.23.0': + resolution: {integrity: sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.23.0': + resolution: {integrity: sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.23.0': + resolution: {integrity: sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.23.0': + resolution: {integrity: sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.23.0': + resolution: {integrity: sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.23.0': + resolution: {integrity: sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.23.0': + resolution: {integrity: sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.23.0': + resolution: {integrity: sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.23.0': + resolution: {integrity: sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.23.0': + resolution: {integrity: sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.23.0': + resolution: {integrity: sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.23.0': + resolution: {integrity: sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.23.0': + resolution: {integrity: sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@octokit/app@15.1.0': + resolution: {integrity: sha512-TkBr7QgOmE6ORxvIAhDbZsqPkF7RSqTY4pLTtUQCvr6dTXqvi2fFo46q3h1lxlk/sGMQjqyZ0kEahkD/NyzOHg==} + engines: {node: '>= 18'} + + '@octokit/auth-app@7.1.0': + resolution: {integrity: sha512-cazGaJPSgeZ8NkVYeM/C5l/6IQ5vZnsI8p1aMucadCkt/bndI+q+VqwrlnWbASRmenjOkf1t1RpCKrif53U8gw==} + engines: {node: '>= 18'} + + '@octokit/auth-oauth-app@8.1.1': + resolution: {integrity: sha512-5UtmxXAvU2wfcHIPPDWzVSAWXVJzG3NWsxb7zCFplCWEmMCArSZV0UQu5jw5goLQXbFyOr5onzEH37UJB3zQQg==} + engines: {node: '>= 18'} + + '@octokit/auth-oauth-device@7.1.1': + resolution: {integrity: sha512-HWl8lYueHonuyjrKKIup/1tiy0xcmQCdq5ikvMO1YwkNNkxb6DXfrPjrMYItNLyCP/o2H87WuijuE+SlBTT8eg==} + engines: {node: '>= 18'} + + '@octokit/auth-oauth-user@5.1.1': + resolution: {integrity: sha512-rRkMz0ErOppdvEfnemHJXgZ9vTPhBuC6yASeFaB7I2yLMd7QpjfrL1mnvRPlyKo+M6eeLxrKanXJ9Qte29SRsw==} + engines: {node: '>= 18'} + + '@octokit/auth-token@5.1.1': + resolution: {integrity: sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==} + engines: {node: '>= 18'} + + '@octokit/auth-unauthenticated@6.1.0': + resolution: {integrity: sha512-zPSmfrUAcspZH/lOFQnVnvjQZsIvmfApQH6GzJrkIunDooU1Su2qt2FfMTSVPRp7WLTQyC20Kd55lF+mIYaohQ==} + engines: {node: '>= 18'} + + '@octokit/core@6.1.2': + resolution: {integrity: sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==} + engines: {node: '>= 18'} + + '@octokit/endpoint@10.1.1': + resolution: {integrity: sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==} + engines: {node: '>= 18'} + + '@octokit/graphql@8.1.1': + resolution: {integrity: sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==} + engines: {node: '>= 18'} + + '@octokit/oauth-app@7.1.3': + resolution: {integrity: sha512-EHXbOpBkSGVVGF1W+NLMmsnSsJRkcrnVmDKt0TQYRBb6xWfWzoi9sBD4DIqZ8jGhOWO/V8t4fqFyJ4vDQDn9bg==} + engines: {node: '>= 18'} + + '@octokit/oauth-authorization-url@7.1.1': + resolution: {integrity: sha512-ooXV8GBSabSWyhLUowlMIVd9l1s2nsOGQdlP2SQ4LnkEsGXzeCvbSbCPdZThXhEFzleGPwbapT0Sb+YhXRyjCA==} + engines: {node: '>= 18'} + + '@octokit/oauth-methods@5.1.2': + resolution: {integrity: sha512-C5lglRD+sBlbrhCUTxgJAFjWgJlmTx5bQ7Ch0+2uqRjYv7Cfb5xpX4WuSC9UgQna3sqRGBL9EImX9PvTpMaQ7g==} + engines: {node: '>= 18'} + + '@octokit/openapi-types@22.2.0': + resolution: {integrity: sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==} + + '@octokit/openapi-webhooks-types@8.3.0': + resolution: {integrity: sha512-vKLsoR4xQxg4Z+6rU/F65ItTUz/EXbD+j/d4mlq2GW8TsA4Tc8Kdma2JTAAJ5hrKWUQzkR/Esn2fjsqiVRYaQg==} + + '@octokit/plugin-paginate-graphql@5.2.2': + resolution: {integrity: sha512-7znSVvlNAOJisCqAnjN1FtEziweOHSjPGAuc5W58NeGNAr/ZB57yCsjQbXDlWsVryA7hHQaEQPcBbJYFawlkyg==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-paginate-rest@11.3.3': + resolution: {integrity: sha512-o4WRoOJZlKqEEgj+i9CpcmnByvtzoUYC6I8PD2SA95M+BJ2x8h7oLcVOg9qcowWXBOdcTRsMZiwvM3EyLm9AfA==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-rest-endpoint-methods@13.2.4': + resolution: {integrity: sha512-gusyAVgTrPiuXOdfqOySMDztQHv6928PQ3E4dqVGEtOvRXAKRbJR4b1zQyniIT9waqaWk/UDaoJ2dyPr7Bk7Iw==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-retry@7.1.1': + resolution: {integrity: sha512-G9Ue+x2odcb8E1XIPhaFBnTTIrrUDfXN05iFXiqhR+SeeeDMMILcAnysOsxUpEWcQp2e5Ft397FCXTcPkiPkLw==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-throttling@9.3.1': + resolution: {integrity: sha512-Qd91H4liUBhwLB2h6jZ99bsxoQdhgPk6TdwnClPyTBSDAdviGPceViEgUwj+pcQDmB/rfAXAXK7MTochpHM3yQ==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': ^6.0.0 + + '@octokit/request-error@6.1.4': + resolution: {integrity: sha512-VpAhIUxwhWZQImo/dWAN/NpPqqojR6PSLgLYAituLM6U+ddx9hCioFGwBr5Mi+oi5CLeJkcAs3gJ0PYYzU6wUg==} + engines: {node: '>= 18'} + + '@octokit/request@9.1.3': + resolution: {integrity: sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==} + engines: {node: '>= 18'} + + '@octokit/types@13.5.0': + resolution: {integrity: sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==} + + '@octokit/webhooks-methods@5.1.0': + resolution: {integrity: sha512-yFZa3UH11VIxYnnoOYCVoJ3q4ChuSOk2IVBBQ0O3xtKX4x9bmKb/1t+Mxixv2iUhzMdOl1qeWJqEhouXXzB3rQ==} + engines: {node: '>= 18'} + + '@octokit/webhooks@13.3.0': + resolution: {integrity: sha512-TUkJLtI163Bz5+JK0O+zDkQpn4gKwN+BovclUvCj6pI/6RXrFqQvUMRS2M+Rt8Rv0qR3wjoMoOPmpJKeOh0nBg==} + engines: {node: '>= 18'} + + '@sec-ant/readable-stream@0.4.1': + resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + + '@sindresorhus/merge-streams@4.0.0': + resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + engines: {node: '>=18'} + + '@types/aws-lambda@8.10.143': + resolution: {integrity: sha512-u5vzlcR14ge/4pMTTMDQr3MF0wEe38B2F9o84uC4F43vN5DGTy63npRrB6jQhyt+C0lGv4ZfiRcRkqJoZuPnmg==} + + '@types/node-fetch@2.6.11': + resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==} + + '@types/node@18.19.43': + resolution: {integrity: sha512-Mw/YlgXnyJdEwLoFv2dpuJaDFriX+Pc+0qOBJ57jC1H6cDxIj2xc5yUrdtArDVG0m+KV6622a4p2tenEqB3C/g==} + + '@types/node@22.1.0': + resolution: {integrity: sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw==} + + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + + agentkeepalive@4.5.0: + resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} + engines: {node: '>= 8.0.0'} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + before-after-hook@3.0.2: + resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==} + + bottleneck@2.19.5: + resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + esbuild@0.23.0: + resolution: {integrity: sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==} + engines: {node: '>=18'} + hasBin: true + + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + execa@9.3.0: + resolution: {integrity: sha512-l6JFbqnHEadBoVAVpN5dl2yCyfX28WoBAGaoQcNmLLSedOxTxcn2Qa83s8I/PA5i56vWru2OHOtrwF7Om2vqlg==} + engines: {node: ^18.19.0 || >=20.5.0} + + figures@6.1.0: + resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} + engines: {node: '>=18'} + + form-data-encoder@1.7.2: + resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} + + form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + + formdata-node@4.4.1: + resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} + engines: {node: '>= 12.20'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + get-stream@9.0.1: + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + engines: {node: '>=18'} + + get-tsconfig@4.7.6: + resolution: {integrity: sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==} + + human-signals@7.0.0: + resolution: {integrity: sha512-74kytxOUSvNbjrT9KisAbaTZ/eJwD/LrbM/kh5j0IhPuJzwuA19dWvniFGwBzN9rVjg+O/e+F310PjObDXS+9Q==} + engines: {node: '>=18.18.0'} + + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-stream@4.0.1: + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} + + is-unicode-supported@2.0.0: + resolution: {integrity: sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q==} + engines: {node: '>=18'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + octokit@4.0.2: + resolution: {integrity: sha512-wbqF4uc1YbcldtiBFfkSnquHtECEIpYD78YUXI6ri1Im5OO2NLo6ZVpRdbJpdnpZ05zMrVPssNiEo6JQtea+Qg==} + engines: {node: '>= 18'} + + parse-ms@4.0.0: + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + engines: {node: '>=18'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + pretty-ms@9.1.0: + resolution: {integrity: sha512-o1piW0n3tgKIKCwk2vpM/vOV13zjJzvP37Ioze54YlTHE06m4tjEbzg9WsKkvTuyYln2DHjo5pY4qrZGI0otpw==} + engines: {node: '>=18'} + + radashi@12.2.0-beta.7fb6e89: + resolution: {integrity: sha512-zBJajRnOB3vnX1mBNOMBrfblepRxSJ7/peTndW0wnfvw53mZ2Ra77ZXU9XCfpyO003y6cjJBqEioLX39jhyAsg==} + engines: {node: '>=16.0.0'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + strip-final-newline@4.0.0: + resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} + engines: {node: '>=18'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + tsx@4.17.0: + resolution: {integrity: sha512-eN4mnDA5UMKDt4YZixo9tBioibaMBpoxBkD+rIPAjVmYERSG0/dWEY1CEFuV89CgASlKL499q8AhmkMnnjtOJg==} + engines: {node: '>=18.0.0'} + hasBin: true + + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + + undici-types@6.13.0: + resolution: {integrity: sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==} + + universal-github-app-jwt@2.2.0: + resolution: {integrity: sha512-G5o6f95b5BggDGuUfKDApKaCgNYy2x7OdHY0zSMF081O0EJobw+1130VONhrA7ezGSV2FNOGyM+KQpQZAr9bIQ==} + + universal-user-agent@7.0.2: + resolution: {integrity: sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==} + + web-streams-polyfill@4.0.0-beta.3: + resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} + engines: {node: '>= 14'} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + yoctocolors@2.1.1: + resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==} + engines: {node: '>=18'} + +snapshots: + + '@anthropic-ai/sdk@0.25.0': + dependencies: + '@types/node': 18.19.43 + '@types/node-fetch': 2.6.11 + abort-controller: 3.0.0 + agentkeepalive: 4.5.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + + '@esbuild/aix-ppc64@0.23.0': + optional: true + + '@esbuild/android-arm64@0.23.0': + optional: true + + '@esbuild/android-arm@0.23.0': + optional: true + + '@esbuild/android-x64@0.23.0': + optional: true + + '@esbuild/darwin-arm64@0.23.0': + optional: true + + '@esbuild/darwin-x64@0.23.0': + optional: true + + '@esbuild/freebsd-arm64@0.23.0': + optional: true + + '@esbuild/freebsd-x64@0.23.0': + optional: true + + '@esbuild/linux-arm64@0.23.0': + optional: true + + '@esbuild/linux-arm@0.23.0': + optional: true + + '@esbuild/linux-ia32@0.23.0': + optional: true + + '@esbuild/linux-loong64@0.23.0': + optional: true + + '@esbuild/linux-mips64el@0.23.0': + optional: true + + '@esbuild/linux-ppc64@0.23.0': + optional: true + + '@esbuild/linux-riscv64@0.23.0': + optional: true + + '@esbuild/linux-s390x@0.23.0': + optional: true + + '@esbuild/linux-x64@0.23.0': + optional: true + + '@esbuild/netbsd-x64@0.23.0': + optional: true + + '@esbuild/openbsd-arm64@0.23.0': + optional: true + + '@esbuild/openbsd-x64@0.23.0': + optional: true + + '@esbuild/sunos-x64@0.23.0': + optional: true + + '@esbuild/win32-arm64@0.23.0': + optional: true + + '@esbuild/win32-ia32@0.23.0': + optional: true + + '@esbuild/win32-x64@0.23.0': + optional: true + + '@octokit/app@15.1.0': + dependencies: + '@octokit/auth-app': 7.1.0 + '@octokit/auth-unauthenticated': 6.1.0 + '@octokit/core': 6.1.2 + '@octokit/oauth-app': 7.1.3 + '@octokit/plugin-paginate-rest': 11.3.3(@octokit/core@6.1.2) + '@octokit/types': 13.5.0 + '@octokit/webhooks': 13.3.0 + + '@octokit/auth-app@7.1.0': + dependencies: + '@octokit/auth-oauth-app': 8.1.1 + '@octokit/auth-oauth-user': 5.1.1 + '@octokit/request': 9.1.3 + '@octokit/request-error': 6.1.4 + '@octokit/types': 13.5.0 + lru-cache: 10.4.3 + universal-github-app-jwt: 2.2.0 + universal-user-agent: 7.0.2 + + '@octokit/auth-oauth-app@8.1.1': + dependencies: + '@octokit/auth-oauth-device': 7.1.1 + '@octokit/auth-oauth-user': 5.1.1 + '@octokit/request': 9.1.3 + '@octokit/types': 13.5.0 + universal-user-agent: 7.0.2 + + '@octokit/auth-oauth-device@7.1.1': + dependencies: + '@octokit/oauth-methods': 5.1.2 + '@octokit/request': 9.1.3 + '@octokit/types': 13.5.0 + universal-user-agent: 7.0.2 + + '@octokit/auth-oauth-user@5.1.1': + dependencies: + '@octokit/auth-oauth-device': 7.1.1 + '@octokit/oauth-methods': 5.1.2 + '@octokit/request': 9.1.3 + '@octokit/types': 13.5.0 + universal-user-agent: 7.0.2 + + '@octokit/auth-token@5.1.1': {} + + '@octokit/auth-unauthenticated@6.1.0': + dependencies: + '@octokit/request-error': 6.1.4 + '@octokit/types': 13.5.0 + + '@octokit/core@6.1.2': + dependencies: + '@octokit/auth-token': 5.1.1 + '@octokit/graphql': 8.1.1 + '@octokit/request': 9.1.3 + '@octokit/request-error': 6.1.4 + '@octokit/types': 13.5.0 + before-after-hook: 3.0.2 + universal-user-agent: 7.0.2 + + '@octokit/endpoint@10.1.1': + dependencies: + '@octokit/types': 13.5.0 + universal-user-agent: 7.0.2 + + '@octokit/graphql@8.1.1': + dependencies: + '@octokit/request': 9.1.3 + '@octokit/types': 13.5.0 + universal-user-agent: 7.0.2 + + '@octokit/oauth-app@7.1.3': + dependencies: + '@octokit/auth-oauth-app': 8.1.1 + '@octokit/auth-oauth-user': 5.1.1 + '@octokit/auth-unauthenticated': 6.1.0 + '@octokit/core': 6.1.2 + '@octokit/oauth-authorization-url': 7.1.1 + '@octokit/oauth-methods': 5.1.2 + '@types/aws-lambda': 8.10.143 + universal-user-agent: 7.0.2 + + '@octokit/oauth-authorization-url@7.1.1': {} + + '@octokit/oauth-methods@5.1.2': + dependencies: + '@octokit/oauth-authorization-url': 7.1.1 + '@octokit/request': 9.1.3 + '@octokit/request-error': 6.1.4 + '@octokit/types': 13.5.0 + + '@octokit/openapi-types@22.2.0': {} + + '@octokit/openapi-webhooks-types@8.3.0': {} + + '@octokit/plugin-paginate-graphql@5.2.2(@octokit/core@6.1.2)': + dependencies: + '@octokit/core': 6.1.2 + + '@octokit/plugin-paginate-rest@11.3.3(@octokit/core@6.1.2)': + dependencies: + '@octokit/core': 6.1.2 + '@octokit/types': 13.5.0 + + '@octokit/plugin-rest-endpoint-methods@13.2.4(@octokit/core@6.1.2)': + dependencies: + '@octokit/core': 6.1.2 + '@octokit/types': 13.5.0 + + '@octokit/plugin-retry@7.1.1(@octokit/core@6.1.2)': + dependencies: + '@octokit/core': 6.1.2 + '@octokit/request-error': 6.1.4 + '@octokit/types': 13.5.0 + bottleneck: 2.19.5 + + '@octokit/plugin-throttling@9.3.1(@octokit/core@6.1.2)': + dependencies: + '@octokit/core': 6.1.2 + '@octokit/types': 13.5.0 + bottleneck: 2.19.5 + + '@octokit/request-error@6.1.4': + dependencies: + '@octokit/types': 13.5.0 + + '@octokit/request@9.1.3': + dependencies: + '@octokit/endpoint': 10.1.1 + '@octokit/request-error': 6.1.4 + '@octokit/types': 13.5.0 + universal-user-agent: 7.0.2 + + '@octokit/types@13.5.0': + dependencies: + '@octokit/openapi-types': 22.2.0 + + '@octokit/webhooks-methods@5.1.0': {} + + '@octokit/webhooks@13.3.0': + dependencies: + '@octokit/openapi-webhooks-types': 8.3.0 + '@octokit/request-error': 6.1.4 + '@octokit/webhooks-methods': 5.1.0 + + '@sec-ant/readable-stream@0.4.1': {} + + '@sindresorhus/merge-streams@4.0.0': {} + + '@types/aws-lambda@8.10.143': {} + + '@types/node-fetch@2.6.11': + dependencies: + '@types/node': 22.1.0 + form-data: 4.0.0 + + '@types/node@18.19.43': + dependencies: + undici-types: 5.26.5 + + '@types/node@22.1.0': + dependencies: + undici-types: 6.13.0 + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + + agentkeepalive@4.5.0: + dependencies: + humanize-ms: 1.2.1 + + asynckit@0.4.0: {} + + before-after-hook@3.0.2: {} + + bottleneck@2.19.5: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + cross-spawn@7.0.3: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + delayed-stream@1.0.0: {} + + esbuild@0.23.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.23.0 + '@esbuild/android-arm': 0.23.0 + '@esbuild/android-arm64': 0.23.0 + '@esbuild/android-x64': 0.23.0 + '@esbuild/darwin-arm64': 0.23.0 + '@esbuild/darwin-x64': 0.23.0 + '@esbuild/freebsd-arm64': 0.23.0 + '@esbuild/freebsd-x64': 0.23.0 + '@esbuild/linux-arm': 0.23.0 + '@esbuild/linux-arm64': 0.23.0 + '@esbuild/linux-ia32': 0.23.0 + '@esbuild/linux-loong64': 0.23.0 + '@esbuild/linux-mips64el': 0.23.0 + '@esbuild/linux-ppc64': 0.23.0 + '@esbuild/linux-riscv64': 0.23.0 + '@esbuild/linux-s390x': 0.23.0 + '@esbuild/linux-x64': 0.23.0 + '@esbuild/netbsd-x64': 0.23.0 + '@esbuild/openbsd-arm64': 0.23.0 + '@esbuild/openbsd-x64': 0.23.0 + '@esbuild/sunos-x64': 0.23.0 + '@esbuild/win32-arm64': 0.23.0 + '@esbuild/win32-ia32': 0.23.0 + '@esbuild/win32-x64': 0.23.0 + + event-target-shim@5.0.1: {} + + execa@9.3.0: + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + cross-spawn: 7.0.3 + figures: 6.1.0 + get-stream: 9.0.1 + human-signals: 7.0.0 + is-plain-obj: 4.1.0 + is-stream: 4.0.1 + npm-run-path: 5.3.0 + pretty-ms: 9.1.0 + signal-exit: 4.1.0 + strip-final-newline: 4.0.0 + yoctocolors: 2.1.1 + + figures@6.1.0: + dependencies: + is-unicode-supported: 2.0.0 + + form-data-encoder@1.7.2: {} + + form-data@4.0.0: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + + formdata-node@4.4.1: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 4.0.0-beta.3 + + fsevents@2.3.3: + optional: true + + get-stream@9.0.1: + dependencies: + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 + + get-tsconfig@4.7.6: + dependencies: + resolve-pkg-maps: 1.0.0 + + human-signals@7.0.0: {} + + humanize-ms@1.2.1: + dependencies: + ms: 2.1.3 + + is-plain-obj@4.1.0: {} + + is-stream@4.0.1: {} + + is-unicode-supported@2.0.0: {} + + isexe@2.0.0: {} + + lru-cache@10.4.3: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mri@1.2.0: {} + + ms@2.1.3: {} + + node-domexception@1.0.0: {} + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + + octokit@4.0.2: + dependencies: + '@octokit/app': 15.1.0 + '@octokit/core': 6.1.2 + '@octokit/oauth-app': 7.1.3 + '@octokit/plugin-paginate-graphql': 5.2.2(@octokit/core@6.1.2) + '@octokit/plugin-paginate-rest': 11.3.3(@octokit/core@6.1.2) + '@octokit/plugin-rest-endpoint-methods': 13.2.4(@octokit/core@6.1.2) + '@octokit/plugin-retry': 7.1.1(@octokit/core@6.1.2) + '@octokit/plugin-throttling': 9.3.1(@octokit/core@6.1.2) + '@octokit/request-error': 6.1.4 + '@octokit/types': 13.5.0 + + parse-ms@4.0.0: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + pretty-ms@9.1.0: + dependencies: + parse-ms: 4.0.0 + + radashi@12.2.0-beta.7fb6e89: {} + + resolve-pkg-maps@1.0.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@4.1.0: {} + + strip-final-newline@4.0.0: {} + + tr46@0.0.3: {} + + tsx@4.17.0: + dependencies: + esbuild: 0.23.0 + get-tsconfig: 4.7.6 + optionalDependencies: + fsevents: 2.3.3 + + undici-types@5.26.5: {} + + undici-types@6.13.0: {} + + universal-github-app-jwt@2.2.0: {} + + universal-user-agent@7.0.2: {} + + web-streams-polyfill@4.0.0-beta.3: {} + + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + yoctocolors@2.1.1: {} diff --git a/scripts/release-notes/release-notes.ts b/scripts/release-notes/release-notes.ts new file mode 100644 index 00000000..a67febcc --- /dev/null +++ b/scripts/release-notes/release-notes.ts @@ -0,0 +1,327 @@ +import { Anthropic } from '@anthropic-ai/sdk' +import { execa } from 'execa' +import mri from 'mri' +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' +import { uid } from 'radashi' +import { dedent } from './dedent' + +/** + * Generate release notes using `git log -p` output and Anthropic + * Claude. + * + * Required Environment Variables: + * + * - ANTHROPIC_API_KEY + * - GITHUB_TOKEN (only required if --publish is used) + * + * Usage: + * + * ```sh + * # Generate release notes for changes between the last two + * # stable versions. + * scripts/release-notes.sh + * + * # Generate release notes for changes between the last stable + * # version and a given commit ref (e.g. HEAD). + * scripts/release-notes.sh + * ``` + * + * Options: + * + * -o, --output \ + * Write the release notes to a file. + * + * --publish \ + * Publish the release notes to GitHub. + * + * --draft \ + * Publish the release notes as a draft. + * + * --prerelease \ + * Publish the release notes as a prerelease. + */ +async function main() { + if (!process.env.ANTHROPIC_API_KEY) { + console.error('Error: ANTHROPIC_API_KEY is not set') + process.exit(1) + } + + const argv = mri(process.argv.slice(2)) + const outFile = argv.o || argv.output + + if (!outFile && !argv.publish) { + console.error('Error: No --output file or --publish flag provided') + process.exit(1) + } else if (outFile && argv.publish) { + console.error('Error: Cannot use --output file and --publish flag together') + process.exit(1) + } + + if (argv.publish && !process.env.GITHUB_TOKEN) { + console.error('Error: GITHUB_TOKEN is not set') + process.exit(1) + } + + const version: string = JSON.parse( + fs.readFileSync('package.json', 'utf8'), + ).version + + log('Generating release notes for v' + version) + + /** + * If no commit ref is provided, use the latest two tags. + */ + let commitRange: string + if (argv._.length) { + commitRange = 'v' + version + '..' + argv._[0] + } else { + const tags = await execa('git', [ + 'tag', + '--format=%(refname:short)', + '--sort=-version:refname', + '-n', + 'v*', + ]).then(result => + result.stdout + .split('\n') + .filter(tag => !tag.includes('-')) + .slice(0, 2), + ) + + const [currentVersion, previousVersion] = tags + commitRange = previousVersion + '..' + currentVersion + } + + const commits = await execa('git', [ + 'log', + '--format=%H %s', + commitRange, + ]).then(result => + result.stdout + .trim() + .split('\n') + .map(line => { + const [, sha, message] = /^(\w+) (.+)$/.exec(line)! + return { sha, message, diff: '' } + }), + ) + + log('Grouping commits and fetching diffs...') + + const sections = getSections() + for (const commit of commits) { + for (const section of sections) { + if ( + section.match.test(commit.message) && + !section.exclude?.test(commit.message) + ) { + const diff = await execa('git', [ + 'log', + '-p', + commit.sha + '^..' + commit.sha, + 'src', + 'docs', + ]) + commit.diff = diff.stdout + section.commits ??= [] + section.commits.push(commit) + } + } + } + + const anthropic = new Anthropic() + + for (const section of sections) { + if (!section.commits?.length) { + continue + } + + log('Generating release notes for', section.name) + + // Truncate for testing. + section.commits.length = Math.min(section.commits.length, 3) + + const linkLocation = + section.noun === 'feature' || section.noun === 'fix' + ? 'immediately after the heading' + : 'at the end' + + const rules = [ + `You're tasked with writing in-depth release notes (using Markdown) in a professional tone.`, + 'Never converse with me.', + 'Always mention every change I give you.', + `Always link to the relevant PR (or the commit if there's no PR) ${linkLocation} of each ${section.noun} in a format like "[→ PR #110](…)" or "[→ commit {short-hash}](…)". The GitHub URL is "https://github.com/radashi-org/radashi".`, + `Never include headings like "Release Notes" or "v1.0.0".`, + ...section.rules(section.noun), + ] + + log('Sending request to Anthropic...') + + const response = await anthropic.messages.create({ + model: 'claude-3-haiku-20240307', + max_tokens: 4096, + messages: [ + { + role: 'user', + content: dedent` + - ${rules.join('\n- ')} + + The following changes are from \`git log -p\`: + + + ${section.commits.map(commit => commit.diff).join('\n\n')} + + `, + }, + ], + }) + + const [message] = response.content + if (message.type !== 'text') { + console.error('Expected a text message, got:', message) + process.exit(1) + } + + section.notes = message.text + } + + let notes = sections + .filter(section => section.notes) + .map(section => `## ${section.name}\n\n${section.notes}`) + .join('\n\n') + + const tmpFile = path.join(os.tmpdir(), 'release-notes.' + uid(20) + '.md') + fs.writeFileSync(tmpFile, notes) + + try { + const editor = await getPreferredEditor() + log('Opening', tmpFile, 'with', editor) + + // Open the generated release notes in the user's preferred text editor + await execa(editor, [tmpFile], { stdio: 'inherit' }) + + // Read the potentially modified content after the editor is closed + notes = fs.readFileSync(tmpFile, 'utf-8') + } finally { + fs.unlinkSync(tmpFile) + } + + if (argv.publish) { + const { Octokit } = await import('octokit') + const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN }) + + log('Publishing release notes for version', version) + + try { + await octokit.rest.repos.createRelease({ + owner: 'radashi-org', + repo: 'radashi', + tag_name: `v${version}`, + name: `v${version}`, + body: notes, + draft: !!argv.draft, + prerelease: !!argv.prerelease, + }) + + log('Successfully published release notes to GitHub') + } catch (error) { + console.error('Failed to publish release notes:', error) + process.exit(1) + } + } else { + fs.writeFileSync(outFile, notes) + log('Saved release notes to', path.resolve(outFile)) + } +} + +main() + +function getSections(): Section[] { + const getFormattingRules = (noun: string) => [ + `Use an H4 (####) for the heading of each ${noun}.`, + noun === 'feature' && + `Each heading must describe what the ${noun} enables, not simply what the change is (e.g. "Allow throttled function to be triggered immediately" instead of "Add trigger method to throttle function").`, + 'Be concise but not vague.', + `The paragraph(s) after each heading must describe the ${noun} in more detail (but be brief where possible).`, + ] + + const getCodeExampleRules = (noun: string) => [ + dedent` + Every ${noun} needs a concise code example to showcase it. In each example, import the functions or types like this: + \`\`\`ts + import { sum } from 'radashi' + \`\`\` + `, + ] + + return [ + { + name: 'Features', + match: /^feat/, + exclude: /\((types|perf)\)/, + noun: 'feature', + rules: noun => [ + ...getFormattingRules(noun), + ...getCodeExampleRules(noun), + ], + }, + { + name: 'Bug Fixes', + match: /^fix/, + exclude: /\((types|perf)\)/, + noun: 'fix', + rules: noun => [ + ...getFormattingRules(noun), + ...getCodeExampleRules(noun), + ], + }, + { + name: 'Performance', + match: /^(perf|\w+\(perf\))/, + noun: 'improvement', + rules: noun => [ + `Briefly describe each ${noun} in a bulleted list, without being vague.`, + ], + }, + { + name: 'Types', + match: /^(fix|feat)\(types\)/, + noun: 'change', + rules: noun => [ + `Briefly describe each ${noun} in a bulleted list, without being vague.`, + 'Never use headings.', + ], + }, + ] +} + +function log(message: string, ...args: any[]) { + console.log('• ' + message, ...args) +} + +async function getPreferredEditor() { + const { stdout: gitEditor } = await execa('git', [ + 'config', + '--global', + 'core.editor', + ]) + return gitEditor.trim() || process.env.EDITOR || 'nano' +} + +type Section = { + name: string + match: RegExp + exclude?: RegExp + noun: string + rules: (noun: string) => (string | false)[] + commits?: Commit[] + notes?: string +} + +type Commit = { + sha: string + message: string + diff: string +} From add38b75cc7eb754cbfd23947e71552781ada0e3 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Fri, 9 Aug 2024 17:17:52 -0400 Subject: [PATCH 2/3] fixes --- scripts/release-notes.sh | 5 +++-- scripts/release-notes/release-notes.ts | 25 +++++++++++++++---------- 2 files changed, 18 insertions(+), 12 deletions(-) mode change 100644 => 100755 scripts/release-notes.sh diff --git a/scripts/release-notes.sh b/scripts/release-notes.sh old mode 100644 new mode 100755 index 411d5f85..d411cf32 --- a/scripts/release-notes.sh +++ b/scripts/release-notes.sh @@ -1,7 +1,8 @@ +#!/usr/bin/env bash + if [ ! -d "scripts/release-notes/node_modules" ]; then echo "Node modules not found. Installing dependencies..." pnpm install -C scripts/release-notes fi -cd scripts/release-notes -pnpm -s tsx release-notes.ts +pnpm -s tsx scripts/release-notes/release-notes.ts "$@" diff --git a/scripts/release-notes/release-notes.ts b/scripts/release-notes/release-notes.ts index a67febcc..6ad67ce5 100644 --- a/scripts/release-notes/release-notes.ts +++ b/scripts/release-notes/release-notes.ts @@ -50,6 +50,7 @@ async function main() { const argv = mri(process.argv.slice(2)) const outFile = argv.o || argv.output + const limit = argv.limit ? +argv.limit : Number.POSITIVE_INFINITY if (!outFile && !argv.publish) { console.error('Error: No --output file or --publish flag provided') @@ -140,8 +141,7 @@ async function main() { log('Generating release notes for', section.name) - // Truncate for testing. - section.commits.length = Math.min(section.commits.length, 3) + section.commits.length = Math.min(section.commits.length, limit) const linkLocation = section.noun === 'feature' || section.noun === 'fix' @@ -241,21 +241,31 @@ main() function getSections(): Section[] { const getFormattingRules = (noun: string) => [ `Use an H4 (####) for the heading of each ${noun}.`, + 'Headings must be in sentence case.', noun === 'feature' && `Each heading must describe what the ${noun} enables, not simply what the change is (e.g. "Allow throttled function to be triggered immediately" instead of "Add trigger method to throttle function").`, 'Be concise but not vague.', + 'Omit prefixes like "Fix:" from headings.', `The paragraph(s) after each heading must describe the ${noun} in more detail (but be brief where possible).`, ] const getCodeExampleRules = (noun: string) => [ + `Every ${noun} needs a concise code example to showcase it.`, + 'Never preface examples with "Example:" or similar.', dedent` - Every ${noun} needs a concise code example to showcase it. In each example, import the functions or types like this: + In each example, import the functions or types like this: \`\`\`ts import { sum } from 'radashi' \`\`\` `, ] + const getBulletedListRules = (noun: string) => [ + `Describe each ${noun} in a bulleted list, without being vague.`, + 'Never use headings.', + 'Only give me the bulleted list. No prefacing like “Here are the changes” or similar.', + ] + return [ { name: 'Features', @@ -281,18 +291,13 @@ function getSections(): Section[] { name: 'Performance', match: /^(perf|\w+\(perf\))/, noun: 'improvement', - rules: noun => [ - `Briefly describe each ${noun} in a bulleted list, without being vague.`, - ], + rules: noun => [...getBulletedListRules(noun)], }, { name: 'Types', match: /^(fix|feat)\(types\)/, noun: 'change', - rules: noun => [ - `Briefly describe each ${noun} in a bulleted list, without being vague.`, - 'Never use headings.', - ], + rules: noun => [...getBulletedListRules(noun)], }, ] } From 135456766e250382069f851eb336c5cdd613ae94 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Fri, 9 Aug 2024 17:18:29 -0400 Subject: [PATCH 3/3] mention the --limit flag --- scripts/release-notes/release-notes.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/release-notes/release-notes.ts b/scripts/release-notes/release-notes.ts index 6ad67ce5..00eff53a 100644 --- a/scripts/release-notes/release-notes.ts +++ b/scripts/release-notes/release-notes.ts @@ -41,6 +41,9 @@ import { dedent } from './dedent' * * --prerelease \ * Publish the release notes as a prerelease. + * + * --limit \ + * Limit the number of commits to include in each section. (For testing purposes) */ async function main() { if (!process.env.ANTHROPIC_API_KEY) {