From 9afbaa4c02264d1d40202ea4576c87a831ea669f Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Tue, 23 Jul 2024 11:52:43 -0400 Subject: [PATCH 01/35] chore: fix test-branch script --- scripts/test-branch.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/test-branch.sh b/scripts/test-branch.sh index d1f21488..2241e16f 100644 --- a/scripts/test-branch.sh +++ b/scripts/test-branch.sh @@ -39,7 +39,7 @@ while [ $index -lt ${#SOURCE_FILES[@]} ]; do func_name=$(basename $file) func_name=${func_name/.ts/} - if command -v rgz &> /dev/null; then + if command -v rg &> /dev/null; then IMPORTERS=$(rg "import[^}]*?\b$func_name\b" -U -l -- src) for importer in $IMPORTERS; do From 6ab6caef37b1c919108644540d8fa571963488c2 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Tue, 23 Jul 2024 12:47:01 -0400 Subject: [PATCH 02/35] chore: only use footnote once per "Bundle Impact" report --- scripts/weigh-changed.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/weigh-changed.sh b/scripts/weigh-changed.sh index 6d1d694f..6ab9fdff 100644 --- a/scripts/weigh-changed.sh +++ b/scripts/weigh-changed.sh @@ -111,10 +111,13 @@ for file in "${FILE_NAMES[@]}"; do fi if [[ -n "$CI" ]]; then + if [ $i -eq 0 ]; then + bytes="$bytes [^1337]" + fi if [ "$column_count" -gt 2 ]; then - echo "| $status | \`$file\` | $bytes[^1337] | $diff$ratio |" + echo "| $status | \`$file\` | $bytes | $diff$ratio |" else - echo "| $status | \`$file\` | $bytes[^1337] |" + echo "| $status | \`$file\` | $bytes |" fi else if [ "$column_count" -gt 2 ] && [ "$prev_bytes" -ne 0 ]; then From 5e19d66f324a6f9c491da13f3c9930ac7dcfc036 Mon Sep 17 00:00:00 2001 From: Nano Miratus Date: Tue, 23 Jul 2024 18:51:03 +0200 Subject: [PATCH 03/35] fix(types): let `shift` accept a readonly array type (#126) --- src/array/shift.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/array/shift.ts b/src/array/shift.ts index 2bfbfcec..1837be94 100644 --- a/src/array/shift.ts +++ b/src/array/shift.ts @@ -10,7 +10,7 @@ * shift([1, 2, 3], -1) // [2, 3, 1] * ``` */ -export function shift(arr: T[], n: number): T[] { +export function shift(arr: readonly T[], n: number): T[] { if (arr.length === 0) { return arr } From 3273682f14919d74584f886793c4061a717132d2 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Tue, 23 Jul 2024 12:51:26 -0400 Subject: [PATCH 04/35] chore: upgrade to vitest v2 (#96) Co-authored-by: Marlon Passos --- package.json | 4 +- pnpm-lock.yaml | 328 ++++++++++++++++++----------------------------- vitest.config.ts | 7 +- 3 files changed, 125 insertions(+), 214 deletions(-) diff --git a/package.json b/package.json index d5d1c381..a7da0047 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "@biomejs/biome": "^1.8.3", "@codspeed/vitest-plugin": "^3.1.0", "@typescript-eslint/parser": "^7.16.1", - "@vitest/coverage-v8": "1.6.0", + "@vitest/coverage-v8": "2.0.3", "concurrently": "^8.2.2", "eslint-plugin-compat": "^6.0.0", "glob": "^11.0.0", @@ -60,7 +60,7 @@ "prettier-plugin-sh": "^0.14.0", "tsup": "^8.1.0", "typescript": "^5.5.2", - "vitest": "1.6.0" + "vitest": "2.0.2" }, "sideEffects": false, "browserslist": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 75c63bb7..549cd38d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,13 +18,13 @@ importers: version: 1.8.3 '@codspeed/vitest-plugin': specifier: ^3.1.0 - version: 3.1.0(patch_hash=6rp3ze2ne4vx24jyfnecpajldi)(vite@5.3.1(@types/node@20.14.8))(vitest@1.6.0(@types/node@20.14.8)) + version: 3.1.0(patch_hash=6rp3ze2ne4vx24jyfnecpajldi)(vite@5.3.1(@types/node@20.14.8))(vitest@2.0.2(@types/node@20.14.8)) '@typescript-eslint/parser': specifier: ^7.16.1 version: 7.16.1(eslint@8.57.0)(typescript@5.5.2) '@vitest/coverage-v8': - specifier: 1.6.0 - version: 1.6.0(vitest@1.6.0(@types/node@20.14.8)) + specifier: 2.0.3 + version: 2.0.3(vitest@2.0.2(@types/node@20.14.8)) concurrently: specifier: ^8.2.2 version: 8.2.2 @@ -50,8 +50,8 @@ importers: specifier: ^5.5.2 version: 5.5.2 vitest: - specifier: 1.6.0 - version: 1.6.0(@types/node@20.14.8) + specifier: 2.0.2 + version: 2.0.2(@types/node@20.14.8) packages: @@ -322,10 +322,6 @@ packages: resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} engines: {node: '>=8'} - '@jest/schemas@29.6.3': - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - '@jridgewell/gen-mapping@0.3.5': resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} engines: {node: '>=6.0.0'} @@ -443,9 +439,6 @@ packages: cpu: [x64] os: [win32] - '@sinclair/typebox@0.27.8': - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} @@ -486,35 +479,37 @@ packages: '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - '@vitest/coverage-v8@1.6.0': - resolution: {integrity: sha512-KvapcbMY/8GYIG0rlwwOKCVNRc0OL20rrhFkg/CHNzncV03TE2XWvO5w9uZYoxNiMEBacAJt3unSOiZ7svePew==} + '@vitest/coverage-v8@2.0.3': + resolution: {integrity: sha512-53d+6jXFdYbasXBmsL6qaGIfcY5eBQq0sP57AjdasOcSiGNj4qxkkpDKIitUNfjxcfAfUfQ8BD0OR2fSey64+g==} peerDependencies: - vitest: 1.6.0 + vitest: 2.0.3 + + '@vitest/expect@2.0.2': + resolution: {integrity: sha512-nKAvxBYqcDugYZ4nJvnm5OR8eDJdgWjk4XM9owQKUjzW70q0icGV2HVnQOyYsp906xJaBDUXw0+9EHw2T8e0mQ==} - '@vitest/expect@1.6.0': - resolution: {integrity: sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==} + '@vitest/pretty-format@2.0.2': + resolution: {integrity: sha512-SBCyOXfGVvddRd9r2PwoVR0fonQjh9BMIcBMlSzbcNwFfGr6ZhOhvBzurjvi2F4ryut2HcqiFhNeDVGwru8tLg==} - '@vitest/runner@1.6.0': - resolution: {integrity: sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==} + '@vitest/pretty-format@2.0.4': + resolution: {integrity: sha512-RYZl31STbNGqf4l2eQM1nvKPXE0NhC6Eq0suTTePc4mtMQ1Fn8qZmjV4emZdEdG2NOWGKSCrHZjmTqDCDoeFBw==} - '@vitest/snapshot@1.6.0': - resolution: {integrity: sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==} + '@vitest/runner@2.0.2': + resolution: {integrity: sha512-OCh437Vi8Wdbif1e0OvQcbfM3sW4s2lpmOjAE7qfLrpzJX2M7J1IQlNvEcb/fu6kaIB9n9n35wS0G2Q3en5kHg==} - '@vitest/spy@1.6.0': - resolution: {integrity: sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==} + '@vitest/snapshot@2.0.2': + resolution: {integrity: sha512-Yc2ewhhZhx+0f9cSUdfzPRcsM6PhIb+S43wxE7OG0kTxqgqzo8tHkXFuFlndXeDMp09G3sY/X5OAo/RfYydf1g==} - '@vitest/utils@1.6.0': - resolution: {integrity: sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==} + '@vitest/spy@2.0.2': + resolution: {integrity: sha512-MgwJ4AZtCgqyp2d7WcQVE8aNG5vQ9zu9qMPYQHjsld/QVsrvg78beNrXdO4HYkP0lDahCO3P4F27aagIag+SGQ==} + + '@vitest/utils@2.0.2': + resolution: {integrity: sha512-pxCY1v7kmOCWYWjzc0zfjGTA3Wmn8PKnlPvSrsA643P1NHl1fOyXj2Q9SaNlrlFE+ivCsxM80Ov3AR82RmHCWQ==} acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn-walk@8.3.3: - resolution: {integrity: sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==} - engines: {node: '>=0.4.0'} - acorn@8.12.0: resolution: {integrity: sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==} engines: {node: '>=0.4.0'} @@ -535,10 +530,6 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} @@ -557,8 +548,9 @@ packages: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} - assertion-error@1.1.0: - resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} ast-metadata-inferer@0.8.0: resolution: {integrity: sha512-jOMKcHht9LxYIEQu+RVd22vtgrPaVCtDRQ/16IGmurdzxvYbDd5ynxjnyrzLnieG96eTcAyaoj/wN/4/1FyyeA==} @@ -608,16 +600,17 @@ packages: caniuse-lite@1.0.30001642: resolution: {integrity: sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA==} - chai@4.4.1: - resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} - engines: {node: '>=4'} + chai@5.1.1: + resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==} + engines: {node: '>=12'} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - check-error@1.0.3: - resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} @@ -650,9 +643,6 @@ packages: engines: {node: ^14.13.0 || >=16.0.0} hasBin: true - confbox@0.1.7: - resolution: {integrity: sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==} - cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -670,8 +660,8 @@ packages: supports-color: optional: true - deep-eql@4.1.4: - resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} deep-is@0.1.4: @@ -681,10 +671,6 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -961,8 +947,8 @@ packages: resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} engines: {node: '>=10'} - istanbul-lib-source-maps@5.0.4: - resolution: {integrity: sha512-wHOoEsNJTVltaJp8eVkm8w+GVkVNHT2YDYo53YdzQEL2gWm1hBX5cGFR9hQJtuGLebidVX7et3+dmDZrmclduw==} + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} engines: {node: '>=10'} istanbul-reports@3.1.7: @@ -1015,10 +1001,6 @@ packages: resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - local-pkg@0.5.0: - resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} - engines: {node: '>=14'} - locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -1039,8 +1021,8 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - loupe@2.3.7: - resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + loupe@3.1.1: + resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==} lru-cache@10.2.2: resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} @@ -1102,9 +1084,6 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - mlly@1.7.1: - resolution: {integrity: sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==} - ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} @@ -1168,10 +1147,6 @@ packages: resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - p-limit@5.0.0: - resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} - engines: {node: '>=18'} - p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} @@ -1222,8 +1197,9 @@ packages: pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - pathval@1.1.1: - resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} picocolors@1.0.1: resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} @@ -1236,9 +1212,6 @@ packages: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} - pkg-types@1.1.1: - resolution: {integrity: sha512-ko14TjmDuQJ14zsotODv7dBlwxKhUKQEhuhmbqo1uCi9BB0Z2alo/wAXg6q1dTR5TyuqYyWhjtfe/Tsh+X28jQ==} - postcss-load-config@4.0.2: resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} engines: {node: '>= 14'} @@ -1276,10 +1249,6 @@ packages: engines: {node: '>=14'} hasBin: true - pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} @@ -1290,9 +1259,6 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -1427,9 +1393,9 @@ packages: resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} engines: {node: '>=10'} - test-exclude@6.0.0: - resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} - engines: {node: '>=8'} + test-exclude@7.0.1: + resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} + engines: {node: '>=18'} text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -1444,12 +1410,16 @@ packages: tinybench@2.8.0: resolution: {integrity: sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==} - tinypool@0.8.4: - resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} + tinypool@1.0.0: + resolution: {integrity: sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} engines: {node: '>=14.0.0'} - tinyspy@2.2.1: - resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} + tinyspy@3.0.0: + resolution: {integrity: sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==} engines: {node: '>=14.0.0'} to-fast-properties@2.0.0: @@ -1502,10 +1472,6 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - type-detect@4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} - engines: {node: '>=4'} - type-fest@0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} @@ -1515,9 +1481,6 @@ packages: engines: {node: '>=14.17'} hasBin: true - ufo@1.5.3: - resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==} - undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} @@ -1530,8 +1493,8 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - vite-node@1.6.0: - resolution: {integrity: sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==} + vite-node@2.0.2: + resolution: {integrity: sha512-w4vkSz1Wo+NIQg8pjlEn0jQbcM/0D+xVaYjhw3cvarTanLLBh54oNiRbsT8PNK5GfuST0IlVXjsNRoNlqvY/fw==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -1563,15 +1526,15 @@ packages: terser: optional: true - vitest@1.6.0: - resolution: {integrity: sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==} + vitest@2.0.2: + resolution: {integrity: sha512-WlpZ9neRIjNBIOQwBYfBSr0+of5ZCbxT2TVGKW4Lv0c8+srCFIiRdsP7U009t8mMn821HQ4XKgkx5dVWpyoyLw==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 1.6.0 - '@vitest/ui': 1.6.0 + '@vitest/browser': 2.0.2 + '@vitest/ui': 2.0.2 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -1715,11 +1678,11 @@ snapshots: transitivePeerDependencies: - debug - '@codspeed/vitest-plugin@3.1.0(patch_hash=6rp3ze2ne4vx24jyfnecpajldi)(vite@5.3.1(@types/node@20.14.8))(vitest@1.6.0(@types/node@20.14.8))': + '@codspeed/vitest-plugin@3.1.0(patch_hash=6rp3ze2ne4vx24jyfnecpajldi)(vite@5.3.1(@types/node@20.14.8))(vitest@2.0.2(@types/node@20.14.8))': dependencies: '@codspeed/core': 3.1.0 vite: 5.3.1(@types/node@20.14.8) - vitest: 1.6.0(@types/node@20.14.8) + vitest: 2.0.2(@types/node@20.14.8) transitivePeerDependencies: - debug @@ -1838,10 +1801,6 @@ snapshots: '@istanbuljs/schema@0.1.3': {} - '@jest/schemas@29.6.3': - dependencies: - '@sinclair/typebox': 0.27.8 - '@jridgewell/gen-mapping@0.3.5': dependencies: '@jridgewell/set-array': 1.2.1 @@ -1924,8 +1883,6 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.18.0': optional: true - '@sinclair/typebox@0.27.8': {} - '@types/estree@1.0.5': {} '@types/node@20.14.8': @@ -1975,62 +1932,66 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@20.14.8))': + '@vitest/coverage-v8@2.0.3(vitest@2.0.2(@types/node@20.14.8))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 debug: 4.3.5 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 5.0.4 + istanbul-lib-source-maps: 5.0.6 istanbul-reports: 3.1.7 magic-string: 0.30.10 magicast: 0.3.4 - picocolors: 1.0.1 std-env: 3.7.0 strip-literal: 2.1.0 - test-exclude: 6.0.0 - vitest: 1.6.0(@types/node@20.14.8) + test-exclude: 7.0.1 + tinyrainbow: 1.2.0 + vitest: 2.0.2(@types/node@20.14.8) transitivePeerDependencies: - supports-color - '@vitest/expect@1.6.0': + '@vitest/expect@2.0.2': + dependencies: + '@vitest/spy': 2.0.2 + '@vitest/utils': 2.0.2 + chai: 5.1.1 + tinyrainbow: 1.2.0 + + '@vitest/pretty-format@2.0.2': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/pretty-format@2.0.4': dependencies: - '@vitest/spy': 1.6.0 - '@vitest/utils': 1.6.0 - chai: 4.4.1 + tinyrainbow: 1.2.0 - '@vitest/runner@1.6.0': + '@vitest/runner@2.0.2': dependencies: - '@vitest/utils': 1.6.0 - p-limit: 5.0.0 + '@vitest/utils': 2.0.2 pathe: 1.1.2 - '@vitest/snapshot@1.6.0': + '@vitest/snapshot@2.0.2': dependencies: + '@vitest/pretty-format': 2.0.2 magic-string: 0.30.10 pathe: 1.1.2 - pretty-format: 29.7.0 - '@vitest/spy@1.6.0': + '@vitest/spy@2.0.2': dependencies: - tinyspy: 2.2.1 + tinyspy: 3.0.0 - '@vitest/utils@1.6.0': + '@vitest/utils@2.0.2': dependencies: - diff-sequences: 29.6.3 + '@vitest/pretty-format': 2.0.2 estree-walker: 3.0.3 - loupe: 2.3.7 - pretty-format: 29.7.0 + loupe: 3.1.1 + tinyrainbow: 1.2.0 acorn-jsx@5.3.2(acorn@8.12.0): dependencies: acorn: 8.12.0 - acorn-walk@8.3.3: - dependencies: - acorn: 8.12.0 - acorn@8.12.0: {} ajv@6.12.6: @@ -2048,8 +2009,6 @@ snapshots: dependencies: color-convert: 2.0.1 - ansi-styles@5.2.0: {} - ansi-styles@6.2.1: {} any-promise@1.3.0: {} @@ -2063,7 +2022,7 @@ snapshots: array-union@2.1.0: {} - assertion-error@1.1.0: {} + assertion-error@2.0.1: {} ast-metadata-inferer@0.8.0: dependencies: @@ -2114,24 +2073,20 @@ snapshots: caniuse-lite@1.0.30001642: {} - chai@4.4.1: + chai@5.1.1: dependencies: - assertion-error: 1.1.0 - check-error: 1.0.3 - deep-eql: 4.1.4 - get-func-name: 2.0.2 - loupe: 2.3.7 - pathval: 1.1.1 - type-detect: 4.0.8 + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.1 + pathval: 2.0.0 chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - check-error@1.0.3: - dependencies: - get-func-name: 2.0.2 + check-error@2.1.1: {} chokidar@3.6.0: dependencies: @@ -2177,8 +2132,6 @@ snapshots: tree-kill: 1.2.2 yargs: 17.7.2 - confbox@0.1.7: {} - cross-spawn@7.0.3: dependencies: path-key: 3.1.1 @@ -2193,16 +2146,12 @@ snapshots: dependencies: ms: 2.1.2 - deep-eql@4.1.4: - dependencies: - type-detect: 4.0.8 + deep-eql@5.0.2: {} deep-is@0.1.4: {} delayed-stream@1.0.0: {} - diff-sequences@29.6.3: {} - dir-glob@3.0.1: dependencies: path-type: 4.0.0 @@ -2533,7 +2482,7 @@ snapshots: make-dir: 4.0.0 supports-color: 7.2.0 - istanbul-lib-source-maps@5.0.4: + istanbul-lib-source-maps@5.0.6: dependencies: '@jridgewell/trace-mapping': 0.3.25 debug: 4.3.5 @@ -2587,11 +2536,6 @@ snapshots: load-tsconfig@0.2.5: {} - local-pkg@0.5.0: - dependencies: - mlly: 1.7.1 - pkg-types: 1.1.1 - locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -2608,7 +2552,7 @@ snapshots: lodash@4.17.21: {} - loupe@2.3.7: + loupe@3.1.1: dependencies: get-func-name: 2.0.2 @@ -2663,13 +2607,6 @@ snapshots: minipass@7.1.2: {} - mlly@1.7.1: - dependencies: - acorn: 8.12.0 - pathe: 1.1.2 - pkg-types: 1.1.1 - ufo: 1.5.3 - ms@2.1.2: {} mvdan-sh@0.10.1: {} @@ -2729,10 +2666,6 @@ snapshots: dependencies: yocto-queue: 1.0.0 - p-limit@5.0.0: - dependencies: - yocto-queue: 1.0.0 - p-locate@5.0.0: dependencies: p-limit: 3.1.0 @@ -2771,7 +2704,7 @@ snapshots: pathe@1.1.2: {} - pathval@1.1.1: {} + pathval@2.0.0: {} picocolors@1.0.1: {} @@ -2779,12 +2712,6 @@ snapshots: pirates@4.0.6: {} - pkg-types@1.1.1: - dependencies: - confbox: 0.1.7 - mlly: 1.7.1 - pathe: 1.1.2 - postcss-load-config@4.0.2(postcss@8.4.38): dependencies: lilconfig: 3.1.2 @@ -2812,20 +2739,12 @@ snapshots: prettier@3.3.2: {} - pretty-format@29.7.0: - dependencies: - '@jest/schemas': 29.6.3 - ansi-styles: 5.2.0 - react-is: 18.3.1 - proxy-from-env@1.1.0: {} punycode@2.3.1: {} queue-microtask@1.2.3: {} - react-is@18.3.1: {} - readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -2956,11 +2875,11 @@ snapshots: dependencies: has-flag: 4.0.0 - test-exclude@6.0.0: + test-exclude@7.0.1: dependencies: '@istanbuljs/schema': 0.1.3 - glob: 7.2.3 - minimatch: 3.1.2 + glob: 10.4.2 + minimatch: 9.0.4 text-table@0.2.0: {} @@ -2974,9 +2893,11 @@ snapshots: tinybench@2.8.0: {} - tinypool@0.8.4: {} + tinypool@1.0.0: {} + + tinyrainbow@1.2.0: {} - tinyspy@2.2.1: {} + tinyspy@3.0.0: {} to-fast-properties@2.0.0: {} @@ -3025,14 +2946,10 @@ snapshots: dependencies: prelude-ls: 1.2.1 - type-detect@4.0.8: {} - type-fest@0.20.2: {} typescript@5.5.2: {} - ufo@1.5.3: {} - undici-types@5.26.5: optional: true @@ -3046,12 +2963,12 @@ snapshots: dependencies: punycode: 2.3.1 - vite-node@1.6.0(@types/node@20.14.8): + vite-node@2.0.2(@types/node@20.14.8): dependencies: cac: 6.7.14 debug: 4.3.5 pathe: 1.1.2 - picocolors: 1.0.1 + tinyrainbow: 1.2.0 vite: 5.3.1(@types/node@20.14.8) transitivePeerDependencies: - '@types/node' @@ -3072,27 +2989,26 @@ snapshots: '@types/node': 20.14.8 fsevents: 2.3.3 - vitest@1.6.0(@types/node@20.14.8): + vitest@2.0.2(@types/node@20.14.8): dependencies: - '@vitest/expect': 1.6.0 - '@vitest/runner': 1.6.0 - '@vitest/snapshot': 1.6.0 - '@vitest/spy': 1.6.0 - '@vitest/utils': 1.6.0 - acorn-walk: 8.3.3 - chai: 4.4.1 + '@ampproject/remapping': 2.3.0 + '@vitest/expect': 2.0.2 + '@vitest/pretty-format': 2.0.4 + '@vitest/runner': 2.0.2 + '@vitest/snapshot': 2.0.2 + '@vitest/spy': 2.0.2 + '@vitest/utils': 2.0.2 + chai: 5.1.1 debug: 4.3.5 execa: 8.0.1 - local-pkg: 0.5.0 magic-string: 0.30.10 pathe: 1.1.2 - picocolors: 1.0.1 std-env: 3.7.0 - strip-literal: 2.1.0 tinybench: 2.8.0 - tinypool: 0.8.4 + tinypool: 1.0.0 + tinyrainbow: 1.2.0 vite: 5.3.1(@types/node@20.14.8) - vite-node: 1.6.0(@types/node@20.14.8) + vite-node: 2.0.2(@types/node@20.14.8) why-is-node-running: 2.2.2 optionalDependencies: '@types/node': 20.14.8 diff --git a/vitest.config.ts b/vitest.config.ts index 9ea38a34..086e895a 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -9,12 +9,7 @@ export default defineConfig(({ mode }) => ({ globals: true, coverage: { thresholds: { 100: true }, - exclude: [ - '*.config.ts', - 'benchmarks/**', - 'scripts/**', - 'tests/**/*.test-d.ts', - ], + include: ['src/**'], }, }, resolve: { From dac01ccb918629cc4191b8b7db47bb3cb050f9cc Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Tue, 23 Jul 2024 12:51:57 -0400 Subject: [PATCH 05/35] feat: add `similarity` function (#122) --- benchmarks/string/similarity.bench.ts | 11 ++++ docs/string/similarity.mdx | 42 +++++++++++++ src/mod.ts | 1 + src/string/similarity.ts | 85 +++++++++++++++++++++++++++ tests/string/similarity.test.ts | 33 +++++++++++ 5 files changed, 172 insertions(+) create mode 100644 benchmarks/string/similarity.bench.ts create mode 100644 docs/string/similarity.mdx create mode 100644 src/string/similarity.ts create mode 100644 tests/string/similarity.test.ts diff --git a/benchmarks/string/similarity.bench.ts b/benchmarks/string/similarity.bench.ts new file mode 100644 index 00000000..280cb5e7 --- /dev/null +++ b/benchmarks/string/similarity.bench.ts @@ -0,0 +1,11 @@ +import * as _ from 'radashi' +import { bench } from 'vitest' + +describe('similarity', () => { + const string1 = 'h'.repeat(100) + const string2 = 'ha'.repeat(50) + + bench('with 50% similar characters', () => { + _.similarity(string1, string2) + }) +}) diff --git a/docs/string/similarity.mdx b/docs/string/similarity.mdx new file mode 100644 index 00000000..cdd489a4 --- /dev/null +++ b/docs/string/similarity.mdx @@ -0,0 +1,42 @@ +--- +title: similarity +description: Calculate the similarity between two strings using the Levenshtein distance algorithm +--- + +### Usage + +The `similarity` function computes the Levenshtein distance between two input strings. This distance represents the minimum number of single-character edits (insertions, deletions, or substitutions) required to change one string into the other. + +This function is useful for various applications, including: + +- Spell checking and autocorrect features +- Fuzzy string matching +- DNA sequence analysis +- Plagiarism detection + +The function is case-sensitive and treats whitespace as significant characters. The order of the input strings doesn't affect the result, as the Levenshtein distance is symmetric. + +```typescript +import * as _ from 'radashi' + +// Identical strings +_.similarity('hello', 'hello') // => 0 + +// One character difference +_.similarity('kitten', 'sitten') // => 1 + +// Multiple differences +_.similarity('saturday', 'sunday') // => 3 + +// Case sensitivity +_.similarity('foo', 'FOO') // => 3 + +// Whitespace significance +_.similarity('bar ', 'bar') // => 1 + +// Argument order doesn't matter +_.similarity('abc', 'cba') // => 2 +_.similarity('cba', 'abc') // => 2 +``` + +The function returns a `number` representing the Levenshtein distance between the two input strings. A lower number indicates higher similarity, with 0 meaning the strings are identical. diff --git a/src/mod.ts b/src/mod.ts index 426f63a5..ae47f9bf 100644 --- a/src/mod.ts +++ b/src/mod.ts @@ -100,6 +100,7 @@ export * from './string/camel.ts' export * from './string/capitalize.ts' export * from './string/dash.ts' export * from './string/pascal.ts' +export * from './string/similarity.ts' export * from './string/snake.ts' export * from './string/template.ts' export * from './string/title.ts' diff --git a/src/string/similarity.ts b/src/string/similarity.ts new file mode 100644 index 00000000..7d5facf8 --- /dev/null +++ b/src/string/similarity.ts @@ -0,0 +1,85 @@ +/** + * Calculate the similarity between two strings using the Levenshtein + * distance algorithm. + * + * One thing to note is that the argument order is unimportant. The + * algorithm will always return the same result regardless of the + * order of the arguments. + * + * Adapted from + * [@fabiospampinato/tiny-levenshtein](https://github.com/fabiospampinato/tiny-levenshtein) + * with ❤️. + * + * @see https://radashi-org.github.io/reference/string/similarity + * @example + * ```ts + * similarity('abc', 'abc') // 0 + * similarity('a', 'b') // 1 + * similarity('ab', 'ac') // 1 + * similarity('ac', 'bc') // 1 + * similarity('abc', 'axc') // 1 + * similarity('kitten', 'sitting') // 3 + * ``` + */ +export function similarity(str1: string, str2: string): number { + // Early return if strings are identical + if (str1 === str2) { + return 0 + } + + // Find common prefix and suffix + let start = 0 + let end1 = str1.length - 1 + let end2 = str2.length - 1 + + while (start <= end1 && start <= end2 && str1[start] === str2[start]) { + start++ + } + + while (end1 >= start && end2 >= start && str1[end1] === str2[end2]) { + end1-- + end2-- + } + + // Calculate lengths of trimmed strings + const length1 = end1 - start + 1 + const length2 = end2 - start + 1 + + // Handle cases where one string is a substring of the other + if (length1 === 0) { + return length2 + } + if (length2 === 0) { + return length1 + } + + const numRows = length1 + 1 + const numColumns = length2 + 1 + + const distances = new Array(numRows * numColumns).fill(0) + + for (let x = 1; x < numColumns; x++) { + distances[x] = x + } + for (let y = 1; y < numRows; y++) { + distances[y * numColumns] = y + } + + for (let x = 1; x < numColumns; x++) { + for (let y = 1; y < numRows; y++) { + const i = y * numColumns + x + distances[i] = Math.min( + // Cost of a deletion. + distances[i - numColumns] + 1, + // Cost of an insertion. + distances[i - 1] + 1, + // Cost of a substitution. + distances[i - numColumns - 1] + + (str1[start + y - 1] === str2[start + x - 1] ? 0 : 1), + ) + } + } + + // Return the Levenshtein distance + return distances[length1 * numColumns + length2] +} diff --git a/tests/string/similarity.test.ts b/tests/string/similarity.test.ts new file mode 100644 index 00000000..ebb6f3a9 --- /dev/null +++ b/tests/string/similarity.test.ts @@ -0,0 +1,33 @@ +import * as _ from 'radashi' + +describe('similarity', () => { + // https://github.com/fabiospampinato/tiny-levenshtein/blob/master/test/index.js + test('returns the distance between two strings', () => { + expect(_.similarity('abc', 'abc')).toBe(0) + expect(_.similarity('a', 'b')).toBe(1) + expect(_.similarity('ab', 'ac')).toBe(1) + expect(_.similarity('ac', 'bc')).toBe(1) + expect(_.similarity('abc', 'axc')).toBe(1) + expect(_.similarity('kitten', 'sitting')).toBe(3) + expect(_.similarity('xabxcdxxefxgx', '1ab2cd34ef5g6')).toBe(6) + expect(_.similarity('cat', 'cow')).toBe(2) + expect(_.similarity('xabxcdxxefxgx', 'abcdefg')).toBe(6) + expect(_.similarity('javawasneat', 'scalaisgreat')).toBe(7) + expect(_.similarity('example', 'samples')).toBe(3) + expect(_.similarity('sturgeon', 'urgently')).toBe(6) + expect(_.similarity('levenshtein', 'frankenstein')).toBe(6) + expect(_.similarity('distance', 'difference')).toBe(5) + expect( + _.similarity( + '因為我是中國人所以我會說中文', + '因為我是英國人所以我會說英文', + ), + ).toBe(2) + }) + test('containment', () => { + expect(_.similarity('abababab', 'ab')).toBe(6) + expect(_.similarity('ab', 'abababab')).toBe(6) + expect(_.similarity('abc', 'ab')).toBe(1) + expect(_.similarity('ab', 'abc')).toBe(1) + }) +}) From 39ce2e43ef82b98240f5c6d07e48f8c6f0d7b6d4 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Tue, 23 Jul 2024 13:15:47 -0400 Subject: [PATCH 06/35] chore: tweak weigh-changed.sh for non-CI envs --- scripts/weigh-changed.sh | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scripts/weigh-changed.sh b/scripts/weigh-changed.sh index 6ab9fdff..02ae0712 100644 --- a/scripts/weigh-changed.sh +++ b/scripts/weigh-changed.sh @@ -73,9 +73,8 @@ if [ ${#PREV_SIZES[@]} -gt 0 ]; then done fi -echo -e "\n\n" - if [[ -n "$CI" ]]; then + echo -e "\n\n" if [ "$column_count" -gt 2 ]; then echo "| Status | File | Size | Difference (%) |" echo "|---|---|---|---|" @@ -130,6 +129,8 @@ for file in "${FILE_NAMES[@]}"; do i=$((i + 1)) done -echo "" -echo "[^1337]: Function size includes the \`import\` dependencies of the function." -echo "" +if [[ -n "$CI" ]]; then + echo "" + echo "[^1337]: Function size includes the \`import\` dependencies of the function." + echo "" +fi From 5b629468fcf2ad70e4b3a661b539b3ee8a146a69 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Tue, 23 Jul 2024 13:22:35 -0400 Subject: [PATCH 07/35] chore(fix): ensure `shift` result is mutable --- src/array/shift.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/array/shift.ts b/src/array/shift.ts index 1837be94..67fec418 100644 --- a/src/array/shift.ts +++ b/src/array/shift.ts @@ -12,13 +12,13 @@ */ export function shift(arr: readonly T[], n: number): T[] { if (arr.length === 0) { - return arr + return [...arr] } const shiftNumber = n % arr.length if (shiftNumber === 0) { - return arr + return [...arr] } return [...arr.slice(-shiftNumber, arr.length), ...arr.slice(0, -shiftNumber)] From a1aa0ff1501e4df63c759c991c9c88c7eaff31b4 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Tue, 23 Jul 2024 13:30:39 -0400 Subject: [PATCH 08/35] ci: include more logs for bundle-impact job --- scripts/pr-bundle-impact.cjs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/pr-bundle-impact.cjs b/scripts/pr-bundle-impact.cjs index 4bfad9e8..bec903b5 100644 --- a/scripts/pr-bundle-impact.cjs +++ b/scripts/pr-bundle-impact.cjs @@ -3,12 +3,16 @@ const { execSync } = require('child_process') exports.run = async function run({ github, core, context }, exec = execSync) { try { + core.info('running bundle-impact script...') + // 1. Run `pnpm bundle-impact` to get the bundle impact const bundleImpact = exec('pnpm -s bundle-impact').toString().trim() if (!bundleImpact) { return } + core.info('fetching PR data...') + // 2. Update the original post of the pull request const { data: pullRequest } = await github.rest.pulls.get({ owner: context.repo.owner, @@ -30,6 +34,7 @@ exports.run = async function run({ github, core, context }, exec = execSync) { } if (updatedBody !== originalBody) { + core.info('updating PR description...') await github.rest.pulls.update({ owner: context.repo.owner, repo: context.repo.repo, From 0480a167e5496ba0b379e3d8a144082902f4c459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Rish=C3=B8j?= Date: Tue, 23 Jul 2024 20:38:52 +0200 Subject: [PATCH 09/35] feat(throttle): add `trailing` option (#127) Co-authored-by: Alec Larson <1925840+aleclarson@users.noreply.github.com> --- docs/curry/throttle.mdx | 66 ++++++++++++++++++++++-------------- src/curry/throttle.ts | 37 ++++++++++++-------- tests/curry/throttle.test.ts | 47 +++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 39 deletions(-) diff --git a/docs/curry/throttle.mdx b/docs/curry/throttle.mdx index 94b62e7d..6f52b743 100644 --- a/docs/curry/throttle.mdx +++ b/docs/curry/throttle.mdx @@ -1,45 +1,61 @@ --- title: throttle -description: Create a throttled callback function +description: Creates a throttled function that limits invocations to a specified interval --- ### Usage -Throttle accepts an options object with an `interval` and a source function to call -when invoked. When the returned function is invoked it will only call the source -function if the `interval` milliseconds of time has passed. Otherwise, it will ignore -the invocation. +The `throttle` function creates a new function that, when called, will only execute the original function at most once per specified time interval. This is useful for limiting the rate at which a function can fire, especially for performance-intensive operations like handling scroll or resize events. -```ts -import * as _ from 'radashi' +The function accepts two parameters: -const onMouseMove = () => { - rerender() -} +1. An options object with: + - `interval`: The minimum time (in milliseconds) between function invocations + - `trailing` (optional): If true, also calls the function after the throttle period if it was invoked during the throttle +2. The function to be throttled + +The returned throttled function also includes an `isThrottled` method to check if there's currently an active throttle. + +```typescript +import { throttle } from 'radashi' -addEventListener('mousemove', _.throttle({ interval: 200 }, onMouseMove)) +// Throttle a scroll event handler +const handleScroll = () => { + console.log('Scroll position:', window.scrollY) +} +const throttledScroll = throttle({ interval: 200 }, handleScroll) +window.addEventListener('scroll', throttledScroll) + +// Throttle an API call +const throttledFetch = throttle( + { interval: 5000, trailing: true }, + async () => { + const response = await fetch('https://api.example.com/data') + const data = await response.json() + console.log(data) + }, +) + +// Check if throttled +console.log('Is throttled:', throttledFetch.isThrottled()) ``` -## Timing +### Timing -A visual of the throttle behavior when `interval` is `200`. The throttle function -returned by `throttle` can be called every millisecond but it will only call -the given callback after `interval` milliseconds have passed. +A visual representation of the throttle behavior when `interval` is set to `200ms`: -```sh +``` Time: 0ms - - - - 100ms - - - - 200ms - - - - 300ms - - - - 400ms - - - - Throttle Invocations: x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x - - - Source Invocations: x - - - - - - - - - - - - x - - - - - - - - - - - - - x - - - - - - ``` -### isThrottled - -The function returned by `throttle` has a `isThrottled` method that when called will return if there is any active throttle. +When the `trailing` option is set to `true`, an additional invocation occurs after the throttle period if any calls were made during the throttled time: -```ts -const debounced = _.throttle({ interval: 200 }, onMouseMove) - -// ... sometime later - -debounced.isThrottled() ``` +Time: 0ms - - - - 100ms - - - - 200ms - - - - 300ms - - - - 400ms - - - - +Throttle Invocations: x x x x x x x x x x x x x x x x x x x x x - - - - - - - - - - - - - +Source Invocations: x - - - - - - - - - - - - x - - - - - - - - - - - - - x - - - - - - +``` + +In this diagram, 'x' represents function invocations, and '-' represents time passing. diff --git a/src/curry/throttle.ts b/src/curry/throttle.ts index 42b0f6ff..c93226fa 100644 --- a/src/curry/throttle.ts +++ b/src/curry/throttle.ts @@ -26,25 +26,34 @@ export type ThrottledFunction = { * ``` */ export function throttle( - { interval }: { interval: number }, + { interval, trailing }: { interval: number; trailing?: boolean }, func: (...args: TArgs) => any, ): ThrottledFunction { - let ready = true - let timer: unknown = undefined + let lastCalled = 0 + let trailingArgs: TArgs | undefined const throttled: ThrottledFunction = (...args: TArgs) => { - if (!ready) { - return + if (!isThrottled()) { + func(...args) + lastCalled = Date.now() + + if (trailing) { + trailingArgs = undefined + setTimeout(() => { + if (trailingArgs) { + func(...trailingArgs) + lastCalled = Date.now() + trailingArgs = undefined + } + }, interval) + } + } else if (trailing) { + trailingArgs = args } - func(...args) - ready = false - timer = setTimeout(() => { - ready = true - timer = undefined - }, interval) - } - throttled.isThrottled = () => { - return timer !== undefined } + + const isThrottled = () => Date.now() - lastCalled < interval + throttled.isThrottled = isThrottled + return throttled } diff --git a/tests/curry/throttle.test.ts b/tests/curry/throttle.test.ts index edfacce1..158e5600 100644 --- a/tests/curry/throttle.test.ts +++ b/tests/curry/throttle.test.ts @@ -35,4 +35,51 @@ describe('throttle', () => { results.push(func.isThrottled()) assert.deepEqual(results, [false, true, true, true, false]) }) + + describe('trailing option', () => { + test('single call with trailing option is set to `true` calls source function once', async () => { + let calls = 0 + const func = _.throttle({ interval, trailing: true }, () => calls++) + func() + expect(calls).toBe(1) + vi.advanceTimersByTime(interval + 10) + expect(calls).toBe(1) + }) + + test('repeated calls with trailing option is set to `true` calls source function again on trailing edge', async () => { + let calls = 0 + const func = _.throttle({ interval, trailing: true }, () => calls++) + func() + expect(calls).toBe(1) + vi.advanceTimersByTime(10) + func() + vi.advanceTimersByTime(interval + 10) + expect(calls).toBe(2) + }) + + test('with trailing option is set to `true`, throttling is still effective after a trailing invocation', async () => { + let calls = 0 + const func = _.throttle({ interval, trailing: true }, () => calls++) + func() + func() + expect(calls).toBe(1) + vi.advanceTimersByTime(10) + func() + func() + expect(calls).toBe(1) + vi.advanceTimersByTime(interval) + // By now, the trailing call should have occurred + expect(calls).toBe(2) + + vi.advanceTimersByTime(10) + func() + func() + // This call should still be throttled + expect(calls).toBe(2) + + vi.advanceTimersByTime(interval + 10) + // By now another trailing call should have happened + expect(calls).toBe(3) + }) + }) }) From 8b4b1c3848c17479df870d509bef47ff5320ba6a Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Tue, 23 Jul 2024 14:55:16 -0400 Subject: [PATCH 10/35] chore(test): include debug logs in bundle-impact snapshot --- scripts/pr-bundle-impact.test.cjs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scripts/pr-bundle-impact.test.cjs b/scripts/pr-bundle-impact.test.cjs index 9150b125..c10473e0 100644 --- a/scripts/pr-bundle-impact.test.cjs +++ b/scripts/pr-bundle-impact.test.cjs @@ -72,6 +72,15 @@ _Calculating..._ `) expect(env.core.info.mock.calls).toMatchInlineSnapshot(` [ + [ + "running bundle-impact script...", + ], + [ + "fetching PR data...", + ], + [ + "updating PR description...", + ], [ "PR description updated with bundle impact.", ], From ac3f6d9e4806cb46428a0265a07bd58b37e95a13 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Tue, 23 Jul 2024 15:02:40 -0400 Subject: [PATCH 11/35] feat(throttle): add `trigger` method to ThrottleFunction (#135) --- docs/curry/throttle.mdx | 5 +- src/curry/throttle.ts | 54 ++++++++++++---- tests/curry/throttle.test.ts | 122 +++++++++++++++++++++++++---------- 3 files changed, 134 insertions(+), 47 deletions(-) diff --git a/docs/curry/throttle.mdx b/docs/curry/throttle.mdx index 6f52b743..3691013e 100644 --- a/docs/curry/throttle.mdx +++ b/docs/curry/throttle.mdx @@ -14,7 +14,10 @@ The function accepts two parameters: - `trailing` (optional): If true, also calls the function after the throttle period if it was invoked during the throttle 2. The function to be throttled -The returned throttled function also includes an `isThrottled` method to check if there's currently an active throttle. +The returned throttled function also includes these methods: + +- `isThrottled(): boolean`: To check if there's currently an active throttle +- `trigger(...args): void`: To invoke the wrapped function without waiting for the next interval ```typescript import { throttle } from 'radashi' diff --git a/src/curry/throttle.ts b/src/curry/throttle.ts index c93226fa..546ada72 100644 --- a/src/curry/throttle.ts +++ b/src/curry/throttle.ts @@ -1,4 +1,5 @@ declare const setTimeout: (fn: () => void, ms: number) => unknown +declare const clearTimeout: (timer: unknown) => void export type ThrottledFunction = { (...args: TArgs): void @@ -6,6 +7,29 @@ export type ThrottledFunction = { * Checks if there is any invocation throttled */ isThrottled(): boolean + /** + * Call the throttled function immediately, ignoring any throttling + * that may be in effect. After, a new throttled call will be allowed + * after the interval has passed. + * + * @example + * ```ts + * const logMessage = (message: string) => { + * console.log(`Message: ${message}`) + * } + * const throttledLog = throttle({ interval: 1000 }, logMessage) + * + * throttledLog('First call') // Logs immediately + * throttledLog('Throttled') // Doesn't log (throttled) + * + * // Force a log, bypassing the throttle + * throttledLog.trigger('Forced log') // Logs immediately + * + * // Check if it's still throttled + * throttledLog.isThrottled() // => true + * ``` + */ + trigger(...args: TArgs): void } /** @@ -29,24 +53,13 @@ export function throttle( { interval, trailing }: { interval: number; trailing?: boolean }, func: (...args: TArgs) => any, ): ThrottledFunction { + let timer: unknown let lastCalled = 0 let trailingArgs: TArgs | undefined const throttled: ThrottledFunction = (...args: TArgs) => { if (!isThrottled()) { - func(...args) - lastCalled = Date.now() - - if (trailing) { - trailingArgs = undefined - setTimeout(() => { - if (trailingArgs) { - func(...trailingArgs) - lastCalled = Date.now() - trailingArgs = undefined - } - }, interval) - } + trigger(...args) } else if (trailing) { trailingArgs = args } @@ -55,5 +68,20 @@ export function throttle( const isThrottled = () => Date.now() - lastCalled < interval throttled.isThrottled = isThrottled + const trigger = (throttled.trigger = (...args: TArgs) => { + func(...args) + lastCalled = Date.now() + + if (trailing) { + trailingArgs = undefined + + clearTimeout(timer) + timer = setTimeout( + () => trailingArgs && trigger(...trailingArgs), + interval, + ) + } + }) + return throttled } diff --git a/tests/curry/throttle.test.ts b/tests/curry/throttle.test.ts index 158e5600..692670cf 100644 --- a/tests/curry/throttle.test.ts +++ b/tests/curry/throttle.test.ts @@ -2,6 +2,7 @@ import * as _ from 'radashi' describe('throttle', () => { const interval = 600 + const smidge = 10 beforeEach(() => { vi.useFakeTimers() @@ -14,72 +15,127 @@ describe('throttle', () => { func() func() expect(calls).toBe(1) - vi.advanceTimersByTime(interval + 10) + vi.advanceTimersByTime(interval + smidge) func() func() func() expect(calls).toBe(2) }) - test('returns if the throttle is active', async () => { - const results = [] - const func = _.throttle({ interval }, () => {}) - results.push(func.isThrottled()) - func() - results.push(func.isThrottled()) - func() - results.push(func.isThrottled()) - func() - results.push(func.isThrottled()) - vi.advanceTimersByTime(interval + 10) - results.push(func.isThrottled()) - assert.deepEqual(results, [false, true, true, true, false]) - }) - describe('trailing option', () => { - test('single call with trailing option is set to `true` calls source function once', async () => { + test('single call with trailing set to true', async () => { let calls = 0 const func = _.throttle({ interval, trailing: true }, () => calls++) func() expect(calls).toBe(1) - vi.advanceTimersByTime(interval + 10) + vi.advanceTimersByTime(interval + smidge) expect(calls).toBe(1) }) - test('repeated calls with trailing option is set to `true` calls source function again on trailing edge', async () => { + test('repeated calls with trailing set to true', async () => { let calls = 0 const func = _.throttle({ interval, trailing: true }, () => calls++) func() expect(calls).toBe(1) - vi.advanceTimersByTime(10) + vi.advanceTimersByTime(smidge) func() - vi.advanceTimersByTime(interval + 10) + vi.advanceTimersByTime(interval + smidge) expect(calls).toBe(2) }) - test('with trailing option is set to `true`, throttling is still effective after a trailing invocation', async () => { - let calls = 0 - const func = _.throttle({ interval, trailing: true }, () => calls++) + test('', async () => { + const wrapped = vi.fn() + const func = _.throttle({ interval, trailing: true }, wrapped) + func() func() - expect(calls).toBe(1) - vi.advanceTimersByTime(10) + + expect(wrapped).toHaveBeenCalledTimes(1) + + // Advance time a bit (but still before the interval). + vi.advanceTimersByTime(smidge) + + // Still throttled. func() func() - expect(calls).toBe(1) + + expect(wrapped).toHaveBeenCalledTimes(1) + vi.advanceTimersByTime(interval) + // By now, the trailing call should have occurred - expect(calls).toBe(2) + expect(wrapped).toHaveBeenCalledTimes(2) + + // The trailing call should re-throttle the function. + expect(func.isThrottled()).toBe(true) - vi.advanceTimersByTime(10) + // So these will be throttled. func() func() - // This call should still be throttled - expect(calls).toBe(2) + expect(wrapped).toHaveBeenCalledTimes(2) + + vi.advanceTimersByTime(interval + smidge) - vi.advanceTimersByTime(interval + 10) // By now another trailing call should have happened - expect(calls).toBe(3) + expect(wrapped).toHaveBeenCalledTimes(3) + }) + }) + + describe('isThrottled method', () => { + test('returns if the throttle is active', async () => { + const results = [] + const func = _.throttle({ interval }, () => {}) + results.push(func.isThrottled()) + func() + results.push(func.isThrottled()) + func() + results.push(func.isThrottled()) + func() + results.push(func.isThrottled()) + vi.advanceTimersByTime(interval + smidge) + results.push(func.isThrottled()) + assert.deepEqual(results, [false, true, true, true, false]) + }) + }) + + describe('trigger method', () => { + test('ignore any throttle in place', () => { + const wrapped = vi.fn() + const func = _.throttle({ interval }, wrapped) + + func() + expect(wrapped).toHaveBeenCalledTimes(1) + func() + expect(wrapped).toHaveBeenCalledTimes(1) + func() + expect(wrapped).toHaveBeenCalledTimes(1) + + func.trigger() + expect(wrapped).toHaveBeenCalledTimes(2) + }) + + test('clears trailing state', () => { + const wrapped = vi.fn() + const func = _.throttle({ interval, trailing: true }, wrapped) + + func() + func() // <-- trailing call + func.trigger() + expect(wrapped).toHaveBeenCalledTimes(2) + + // Since trigger was called, the trailing call was cancelled. + vi.advanceTimersByTime(interval + smidge) + expect(wrapped).toHaveBeenCalledTimes(2) + + func() + func.trigger() + func() // <-- trailing call after trigger + expect(wrapped).toHaveBeenCalledTimes(4) + + // Since the trailing call was queued after the trigger, it will + // still be called. + vi.advanceTimersByTime(interval + smidge) + expect(wrapped).toHaveBeenCalledTimes(5) }) }) }) From bea56485a942a2bce466925852c083e2ff8d0e2f Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Tue, 23 Jul 2024 18:15:00 -0400 Subject: [PATCH 12/35] docs: rewrite `selectFirst` page --- docs/array/selectFirst.mdx | 72 +++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 28 deletions(-) diff --git a/docs/array/selectFirst.mdx b/docs/array/selectFirst.mdx index 65bec505..136c7a8b 100644 --- a/docs/array/selectFirst.mdx +++ b/docs/array/selectFirst.mdx @@ -1,37 +1,53 @@ --- title: selectFirst -description: Array find + map +description: Find and map the first array element meeting a condition --- ### Usage -Returns the mapped value for the first element that satisfies the specified condition--else undefined. -If the filter is omitted, returns the first non-nullish mapped value. - -```ts -import * as _ from 'radashi' - -const fish = [ - { - name: 'Marlin', - weight: 105, - source: 'ocean', - }, - { - name: 'Bass', - weight: 8, - source: 'lake', - }, - { - name: 'Trout', - weight: 13, - source: 'lake', - }, +The `selectFirst` function combines the functionality of `find` and `map` operations on an array. It iterates through the array, applying a mapper function to each element, and returns the first mapped value that satisfies a given condition. If no condition is provided, it returns the first non-nullish mapped value. + +This function is particularly useful when you need to find and transform an element in a single operation, potentially saving time and improving code readability. + +**Key features:** + +- Short-circuits on the first element that satisfies the condition +- Allows for separate mapping and condition functions +- Returns `undefined` if no element satisfies the condition or if the array is empty/nullish + +```typescript +import { selectFirst } from './selectFirst' + +// Find the first even number and double it +selectFirst( + [1, 3, 4, 6, 8], + x => x * 2, + x => x % 2 === 0, +) +// => 8 + +// Find the first non-empty string and convert to uppercase +selectFirst( + ['', null, 'hello', 'world'], + s => s?.toUpperCase(), + s => s !== null && s !== '', +) +// => 'HELLO' + +// Find the first object with a specific property and extract a value +const users = [ + { id: 1, name: 'Alice', age: 30 }, + { id: 2, name: 'Bob', age: 25 }, + { id: 3, name: 'Charlie', age: 35 }, ] +selectFirst( + users, + user => user.name, + user => user.age > 30, +) +// => 'Charlie' -_.selectFirst( - fish, - f => f.weight, - f => f.source === 'lake', -) // => 8 +// Using default condition (non-nullish) +selectFirst([null, undefined, 0, '', false, 'found'], x => x) +// => 0 ``` From ad2e6208ff51f69ae6a2e941e7d70b26598d254a Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Tue, 23 Jul 2024 18:22:04 -0400 Subject: [PATCH 13/35] chore: fix typo in example --- docs/array/selectFirst.mdx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/array/selectFirst.mdx b/docs/array/selectFirst.mdx index 136c7a8b..0753c6f0 100644 --- a/docs/array/selectFirst.mdx +++ b/docs/array/selectFirst.mdx @@ -16,10 +16,10 @@ This function is particularly useful when you need to find and transform an elem - Returns `undefined` if no element satisfies the condition or if the array is empty/nullish ```typescript -import { selectFirst } from './selectFirst' +import * as _ from 'radashi' // Find the first even number and double it -selectFirst( +_.selectFirst( [1, 3, 4, 6, 8], x => x * 2, x => x % 2 === 0, @@ -27,7 +27,7 @@ selectFirst( // => 8 // Find the first non-empty string and convert to uppercase -selectFirst( +_.selectFirst( ['', null, 'hello', 'world'], s => s?.toUpperCase(), s => s !== null && s !== '', @@ -40,7 +40,7 @@ const users = [ { id: 2, name: 'Bob', age: 25 }, { id: 3, name: 'Charlie', age: 35 }, ] -selectFirst( +_.selectFirst( users, user => user.name, user => user.age > 30, @@ -48,6 +48,6 @@ selectFirst( // => 'Charlie' // Using default condition (non-nullish) -selectFirst([null, undefined, 0, '', false, 'found'], x => x) +_.selectFirst([null, undefined, 0, '', false, 'found'], x => x) // => 0 ``` From d56d807aa02d91d79f2b800d4841bc2161be1591 Mon Sep 17 00:00:00 2001 From: aleclarson Date: Wed, 24 Jul 2024 05:07:52 +0000 Subject: [PATCH 14/35] chore(release): 12.2.0-beta.83909af [skip ci] --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38a200ed..18340c41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add reverse argument to `castComparator` by [@aleclarson](https://github.com/aleclarson) in [1d7937e](https://github.com/radashi-org/radashi/commit/1d7937ef006139883aedac782ad032c1d6269c7a) - Add `isBoolean` function by [@aleclarson](https://github.com/aleclarson) in [adc419d](https://github.com/radashi-org/radashi/commit/adc419d5bbb1786d75619ed3d7f41a45f68c9857) - Add `noop` and `always` functions by [@aleclarson](https://github.com/aleclarson) in [eb77c8f](https://github.com/radashi-org/radashi/commit/eb77c8f004a35f1499968f6e40d01b3595384848) +- Add `similarity` function by [@aleclarson](https://github.com/aleclarson) in [#122](https://github.com/radashi-org/radashi/pull/122) + +- **(throttle)** Add `trailing` option by [@crishoj](https://github.com/crishoj) in [#127](https://github.com/radashi-org/radashi/pull/127) + +- **(throttle)** Add `trigger` method to ThrottleFunction by [@aleclarson](https://github.com/aleclarson) in [#135](https://github.com/radashi-org/radashi/pull/135) + #### Changed - **(intersects)** Let `identity` callback return any value by [@aleclarson](https://github.com/aleclarson) in [#11](https://github.com/radashi-org/radashi/pull/11) @@ -120,8 +126,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improve the return type of `filterKey` by [@aleclarson](https://github.com/aleclarson) in [bc298c6](https://github.com/radashi-org/radashi/commit/bc298c6cfcaaf74726e1f2b901e210dea1fed641) - Handle tuples in `isArray` return type by [@aleclarson](https://github.com/aleclarson) in [9257535](https://github.com/radashi-org/radashi/commit/925753578761bda277838bf8fbbcc24b3813f2b9) - Make `select` more option-friendly by [@aleclarson](https://github.com/aleclarson) in [c9cfcd0](https://github.com/radashi-org/radashi/commit/c9cfcd0a7eb1af98682f5d9b56555162c92b7085) +- Let `shift` accept a readonly array type by [@nnmrts](https://github.com/nnmrts) in [#126](https://github.com/radashi-org/radashi/pull/126) + ### New Contributors +* [@crishoj](https://github.com/crishoj) made their first contribution in [#127](https://github.com/radashi-org/radashi/pull/127) +* [@nnmrts](https://github.com/nnmrts) made their first contribution in [#126](https://github.com/radashi-org/radashi/pull/126) * [@stefaanv](https://github.com/stefaanv) made their first contribution in [#95](https://github.com/radashi-org/radashi/pull/95) * [@eumkz](https://github.com/eumkz) made their first contribution in [#76](https://github.com/radashi-org/radashi/pull/76) * [@cimbraien](https://github.com/cimbraien) made their first contribution in [#58](https://github.com/radashi-org/radashi/pull/58) From 9e726b458affa4512cc8f58d2f35c81f3c7bf80d Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Wed, 24 Jul 2024 20:26:29 -0400 Subject: [PATCH 15/35] docs: fix headings --- docs/array/list.mdx | 2 -- docs/async/all.mdx | 4 ++-- docs/async/parallel.mdx | 2 +- docs/curry/debounce.mdx | 2 +- docs/curry/memo.mdx | 4 ++-- docs/curry/once.mdx | 2 +- docs/number/lerp.mdx | 2 +- docs/number/round.mdx | 4 ++-- docs/series/series.mdx | 2 +- 9 files changed, 11 insertions(+), 13 deletions(-) diff --git a/docs/array/list.mdx b/docs/array/list.mdx index 97e4e476..0117056f 100644 --- a/docs/array/list.mdx +++ b/docs/array/list.mdx @@ -26,8 +26,6 @@ _.list(0, 6, i => i, 2) // [0, 2, 4, 6] ## Signatures -The list function can do a lot with different arguments. - ### list(size) When givin a single argument, it's treated as the `size`. Returns a list with values from 0 to `size`. diff --git a/docs/async/all.mdx b/docs/async/all.mdx index 693e96c2..a20c7168 100644 --- a/docs/async/all.mdx +++ b/docs/async/all.mdx @@ -7,7 +7,7 @@ The `all` function is similar to the builtin Promise.all or Promise.allSettled functions. Given a list (or object) of promises, if any errors are thrown, all errors are gathered and thrown in an AggregateError. -## Using an Array +### Using an Array Passing an array as an argument will return the resolved promise values as an array in the same order. @@ -21,7 +21,7 @@ const [user] = await _.all([ ]) ``` -## Using an Object +### Using an Object Passing an object as an argument will return an object with the same keys and the values as the resolved promise values. diff --git a/docs/async/parallel.mdx b/docs/async/parallel.mdx index 35dd59a2..38f4bc19 100644 --- a/docs/async/parallel.mdx +++ b/docs/async/parallel.mdx @@ -21,7 +21,7 @@ const users = await _.parallel(3, userIds, async userId => { }) ``` -## Errors +### Errors When all work is complete parallel will check for errors. If any occurred they will all be thrown in a single `AggregateError` that diff --git a/docs/curry/debounce.mdx b/docs/curry/debounce.mdx index e2bd9ddb..d79b9c16 100644 --- a/docs/curry/debounce.mdx +++ b/docs/curry/debounce.mdx @@ -20,7 +20,7 @@ const makeSearchRequest = event => { input.addEventListener('change', _.debounce({ delay: 100 }, makeSearchRequest)) ``` -## Timing +### Timing A visual of the debounce behavior when `delay` is `100`. The debounce function returned by `debounce` can be called every millisecond but it will only call diff --git a/docs/curry/memo.mdx b/docs/curry/memo.mdx index 8976de3f..67bbff50 100644 --- a/docs/curry/memo.mdx +++ b/docs/curry/memo.mdx @@ -18,7 +18,7 @@ const later = timestamp() now === later // => true ``` -## Expiration +### Expiration You can optionally pass a `ttl` (time to live) that will expire memoized results. In versions prior to version 10, `ttl` had a value of 300 milliseconds if not specified. @@ -40,7 +40,7 @@ now === later // => true now === muchLater // => false ``` -## Key Function +### Key Function You can optionally customize how values are stored when memoized. diff --git a/docs/curry/once.mdx b/docs/curry/once.mdx index 8dd8bce2..76175f60 100644 --- a/docs/curry/once.mdx +++ b/docs/curry/once.mdx @@ -15,7 +15,7 @@ fn() // 0.5 fn() // 0.5 ``` -## Resetting the function +### Resetting the function The `once.reset` function clears the stored result of a function that was previously wrapped with `once`. This allows the function to be executed again as if it were never called before, enabling dynamic reuse of the function with fresh computations. diff --git a/docs/number/lerp.mdx b/docs/number/lerp.mdx index fcec1206..4b27abcd 100644 --- a/docs/number/lerp.mdx +++ b/docs/number/lerp.mdx @@ -15,7 +15,7 @@ _.lerp(5, 15, 0.2) // => 7 _.lerp(-10, 10, 0.75) // => 5 ``` -## Etymology +### Etymology The name `lerp` is short for "linear interpolation". It's a term from computer graphics that means "interpolate linearly between two values". diff --git a/docs/number/round.mdx b/docs/number/round.mdx index 7557374d..849ccaa5 100644 --- a/docs/number/round.mdx +++ b/docs/number/round.mdx @@ -14,12 +14,12 @@ _.round(123.456) // => 123 _.round(1234.56, -2) // => 1200 ``` -## Precision +### Precision The `precision` argument is limited to be within the range of -323 to +292. Without this limit, precision values outside this range can result in NaN. -## Rounding Method +### Rounding Method You may provide a custom rounding method. The default is `Math.round`. diff --git a/docs/series/series.mdx b/docs/series/series.mdx index 36fc2497..2caa01f0 100644 --- a/docs/series/series.mdx +++ b/docs/series/series.mdx @@ -31,7 +31,7 @@ weekdays.next('friday', weekdays.first()) // => 'monday' weekdays.spin('monday', 3) // => 'thursday' ``` -## Complex Data Types +### Complex Data Types When working with objects you'll want to provide a second argument to `series`, a function that converts non-primitive values into an identity that can be checked for equality. From 3639db0c2c3c05f16a93957cc460d37742c1d95b Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Mon, 29 Jul 2024 01:42:29 +0000 Subject: [PATCH 16/35] chore: update extension recommendations --- .vscode/extensions.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 575b3d21..982e18f7 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,7 +1,7 @@ { "recommendations": [ "vitest.explorer", - "rohit-gohri.format-code-action", - "biomejs.biome" + "biomejs.biome", + "esbenp.prettier-vscode" ] } From 3ae53da60b0345f83273aaaf0b202db35094488c Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Sun, 4 Aug 2024 19:49:19 -0400 Subject: [PATCH 17/35] chore: remove comment about structuredClone --- src/object/set.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/object/set.ts b/src/object/set.ts index 468e02c7..180307c5 100644 --- a/src/object/set.ts +++ b/src/object/set.ts @@ -23,9 +23,6 @@ export function set( return initial } - // NOTE: One day, when structuredClone has more compatability use it - // to clone the value - // https://developer.mozilla.org/en-US/docs/Web/API/structuredClone const root: any = clone(initial) const keys = path.match(/[^.[\]]+/g) if (keys) { From d6a290b07605ffa1200a0c2e13980dc968f69029 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Tue, 6 Aug 2024 18:22:17 -0400 Subject: [PATCH 18/35] chore: improve tree-shaking - Treat `Symbol()` as pure - Use the "smallest" treeshake preset, with `propertyReadSideEffects` and `moduleSideEffects` set to false --- tsup.config.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tsup.config.ts b/tsup.config.ts index afa03779..b5cc7789 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -5,4 +5,10 @@ export default defineConfig({ format: ['cjs', 'esm'], dts: true, target: 'node16', + pure: ['Symbol'], + treeshake: { + preset: 'smallest', + propertyReadSideEffects: false, + moduleSideEffects: false, + }, }) From be8314432c602f2d0fda86a211cf55f3507f9723 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Tue, 6 Aug 2024 18:40:46 -0400 Subject: [PATCH 19/35] chore: help esbuild tree-shake with pure annotations (#154) --- src/async/AggregateError.ts | 27 ++++++++-------- src/curry/once.ts | 63 ++++++++++++++++++++----------------- src/typed/isArray.ts | 2 +- src/typed/isInt.ts | 4 ++- 4 files changed, 52 insertions(+), 44 deletions(-) diff --git a/src/async/AggregateError.ts b/src/async/AggregateError.ts index 6c0d630f..42eed88c 100644 --- a/src/async/AggregateError.ts +++ b/src/async/AggregateError.ts @@ -11,19 +11,20 @@ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError */ const AggregateErrorOrPolyfill: AggregateErrorConstructor = - // eslint-disable-next-line compat/compat - globalThis.AggregateError ?? - (class AggregateError extends Error { - errors: Error[] - constructor(errors: Error[] = []) { - super() - const name = errors.find(e => e.name)?.name ?? '' - this.name = `AggregateError(${name}...)` - this.message = `AggregateError with ${errors.length} errors` - this.stack = errors.find(e => e.stack)?.stack ?? this.stack - this.errors = errors - } - } as unknown as AggregateErrorConstructor) + /* @__PURE__ */ (() => + // eslint-disable-next-line compat/compat + globalThis.AggregateError ?? + (class AggregateError extends Error { + errors: Error[] + constructor(errors: Error[] = []) { + super() + const name = errors.find(e => e.name)?.name ?? '' + this.name = `AggregateError(${name}...)` + this.message = `AggregateError with ${errors.length} errors` + this.stack = errors.find(e => e.stack)?.stack ?? this.stack + this.errors = errors + } + } as unknown as AggregateErrorConstructor))() // Do not export directly, so the polyfill isn't renamed to // `AggregateError2` at build time (which ESBuild does to prevent diff --git a/src/curry/once.ts b/src/curry/once.ts index 5aa962f0..47090729 100644 --- a/src/curry/once.ts +++ b/src/curry/once.ts @@ -12,23 +12,7 @@ export interface OnceFunction< [onceSymbol]?: Return | typeof onceSymbol } -/** - * Create a function that runs at most once, no matter how many times - * it's called. If it was already called before, returns the result - * from the first call. This is a lighter version of `memo()`. - * - * To allow your `once`-wrapped function to be called again, see the - * `once.reset` function. - * - * @see https://radashi-org.github.io/reference/curry/once - * @example - * ```ts - * const fn = once(() => Math.random()) - * fn() // 0.5 - * fn() // 0.5 - * ``` - */ -export const once: { +type OnceImplementation = { ( fn: (this: This, ...args: Args) => Return, ): (this: This, ...args: Args) => Return @@ -47,17 +31,38 @@ export const once: { * ``` */ reset(fn: OnceFunction): void -} = fn => { - const onceFn = function (...args: any) { - if (onceFn[onceSymbol] === onceSymbol) { - onceFn[onceSymbol] = fn.apply(this as any, args) - } - return onceFn[onceSymbol] - } as OnceFunction - onceFn[onceSymbol] = onceSymbol - return onceFn as typeof fn } -once.reset = (fn: OnceFunction): void => { - fn[onceSymbol] = onceSymbol -} +/** + * Create a function that runs at most once, no matter how many times + * it's called. If it was already called before, returns the result + * from the first call. This is a lighter version of `memo()`. + * + * To allow your `once`-wrapped function to be called again, see the + * `once.reset` function. + * + * @see https://radashi-org.github.io/reference/curry/once + * @example + * ```ts + * const fn = once(() => Math.random()) + * fn() // 0.5 + * fn() // 0.5 + * ``` + */ +export const once: OnceImplementation = /* @__PURE__ */ (() => { + const once: OnceImplementation = fn => { + const onceFn = function (...args: any) { + if (onceFn[onceSymbol] === onceSymbol) { + onceFn[onceSymbol] = fn.apply(this as any, args) + } + return onceFn[onceSymbol] + } as OnceFunction + + onceFn[onceSymbol] = onceSymbol + return onceFn as typeof fn + } + once.reset = (fn: OnceFunction): void => { + fn[onceSymbol] = onceSymbol + } + return once +})() diff --git a/src/typed/isArray.ts b/src/typed/isArray.ts index 35dfb4df..e19b0726 100644 --- a/src/typed/isArray.ts +++ b/src/typed/isArray.ts @@ -10,7 +10,7 @@ import type { StrictExtract } from 'radashi' * isArray('hello') // => false * ``` */ -export const isArray = Array.isArray as ( +export const isArray = /* @__PURE__ */ (() => Array.isArray)() as ( value: Input, ) => value is ExtractArray diff --git a/src/typed/isInt.ts b/src/typed/isInt.ts index 537d5822..2273b575 100644 --- a/src/typed/isInt.ts +++ b/src/typed/isInt.ts @@ -8,4 +8,6 @@ * isInt(0.1) // => false * ``` */ -export const isInt = Number.isInteger as (value: unknown) => value is number +export const isInt = /* @__PURE__ */ (() => Number.isInteger)() as ( + value: unknown, +) => value is number From 24307efdb7a84fa0b17df1b71d1d19bbc681eb25 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Tue, 6 Aug 2024 19:12:21 -0400 Subject: [PATCH 20/35] chore: split check-pr workflow into 3 workflows For the "Bundle Impact" section of a PR to be updated by GitHub actions, the workflow must be triggered with the `pull_request_target` event. For security, keep using the `pull_request` event for linting and testing of PRs. --- .github/workflows/bundle-impact.yml | 30 +++++++++++++++++++++++++++++ .github/workflows/check-pr.yml | 29 ---------------------------- .github/workflows/test-pr.yml | 24 +++++++++++++++++++++++ 3 files changed, 54 insertions(+), 29 deletions(-) create mode 100644 .github/workflows/bundle-impact.yml create mode 100644 .github/workflows/test-pr.yml diff --git a/.github/workflows/bundle-impact.yml b/.github/workflows/bundle-impact.yml new file mode 100644 index 00000000..d8b9a051 --- /dev/null +++ b/.github/workflows/bundle-impact.yml @@ -0,0 +1,30 @@ +name: Bundle Impact + +on: + pull_request_target: + branches: [main, next] + types: [opened, synchronize] + +jobs: + bundle-impact: + name: Calculate Bundle Impact + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: '22.x' + cache: pnpm + - run: pnpm install + - name: Run Bundle Impact + uses: actions/github-script@v7 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + console.log(process.env) + delete process.env.GITHUB_TOKEN + const script = require('./scripts/pr-bundle-impact.cjs') + await script.run({github, core, context}) diff --git a/.github/workflows/check-pr.yml b/.github/workflows/check-pr.yml index 02b54158..80a7f131 100644 --- a/.github/workflows/check-pr.yml +++ b/.github/workflows/check-pr.yml @@ -6,23 +6,6 @@ on: types: [opened, synchronize, reopened, edited] jobs: - test: - name: Test - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [18.x, 20.x, 22.x] - steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - cache: pnpm - - run: pnpm install - - run: pnpm test - validate: name: Validate runs-on: ubuntu-latest @@ -40,18 +23,6 @@ jobs: - run: pnpm install - run: pnpm install @commitlint/config-conventional@19.x - - name: Refresh Bundle Impact - uses: actions/github-script@v6 - if: ${{ github.head_ref != 'next' }} - continue-on-error: true - env: - TARGET_BRANCH: ${{ github.event.pull_request.base.ref }} - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { run } = require('./scripts/pr-bundle-impact.cjs'); - run({ github, core, context }); - - name: Lint run: pnpm lint diff --git a/.github/workflows/test-pr.yml b/.github/workflows/test-pr.yml new file mode 100644 index 00000000..4e316da4 --- /dev/null +++ b/.github/workflows/test-pr.yml @@ -0,0 +1,24 @@ +name: Test Pull Request + +on: + pull_request: + branches: [main, next] + types: [opened, synchronize] + +jobs: + test: + name: Test + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [18.x, 20.x, 22.x] + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: pnpm + - run: pnpm install + - run: pnpm test From dea0f504f417b23aaf2b91495943501c894a172a Mon Sep 17 00:00:00 2001 From: Marlon Passos Date: Tue, 6 Aug 2024 20:32:16 -0300 Subject: [PATCH 21/35] fix(types): remove type constraint for mapped array passed to `sum` (#143) Co-authored-by: Alec Larson <1925840+aleclarson@users.noreply.github.com> --- src/number/sum.ts | 18 ++++++++++-------- tests/number/sum.test.ts | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/number/sum.ts b/src/number/sum.ts index 556f90f2..7dc539d1 100644 --- a/src/number/sum.ts +++ b/src/number/sum.ts @@ -1,6 +1,8 @@ /** - * Sum all numbers in an array. Optionally provide a function to - * convert objects in the array to number values. + * Add up numbers related to an array in 1 of 2 ways: + * 1. Sum all numbers in an array of numbers + * 2. Sum all numbers returned by a callback function that maps + * each item in an array to a number. * * @see https://radashi-org.github.io/reference/array/sum * @example @@ -14,14 +16,14 @@ * {value: 3} * ], (item) => item.value) * // => 6 + * + * sum([true, false, true], (item) => item ? 1 : 0) + * // => 2 * ``` */ -export function sum(array: readonly T[]): number -export function sum( - array: readonly T[], - fn: (item: T) => number, -): number -export function sum( +export function sum(array: readonly number[]): number +export function sum(array: readonly T[], fn: (item: T) => number): number +export function sum( array: readonly any[], fn?: (item: T) => number, ): number { diff --git a/tests/number/sum.test.ts b/tests/number/sum.test.ts index 73d9b892..12dbfaa4 100644 --- a/tests/number/sum.test.ts +++ b/tests/number/sum.test.ts @@ -17,4 +17,19 @@ describe('sum', () => { const result = _.sum(cast(null)) expect(result).toBe(0) }) + test('gracefully handles boolean input list', () => { + const list = [true, false, true, false, true] + const result = _.sum(list, x => (x ? 1 : 0)) + expect(result).toBe(3) + }) + + test('gracefully handles matrix input', () => { + const list = [ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9], + ] + const result = _.sum(list, x => _.sum(x)) + expect(result).toBe(45) + }) }) From 21abba61632dd2c40ffea70efd8843508e94440f Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Tue, 6 Aug 2024 19:53:14 -0400 Subject: [PATCH 22/35] chore: remove debug code from pr workflow --- .github/workflows/bundle-impact.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/bundle-impact.yml b/.github/workflows/bundle-impact.yml index d8b9a051..8a5cecf7 100644 --- a/.github/workflows/bundle-impact.yml +++ b/.github/workflows/bundle-impact.yml @@ -24,7 +24,5 @@ jobs: with: github-token: ${{secrets.GITHUB_TOKEN}} script: | - console.log(process.env) - delete process.env.GITHUB_TOKEN const script = require('./scripts/pr-bundle-impact.cjs') await script.run({github, core, context}) From 99527325836f34b85bd983e4aa9e18edbfecace8 Mon Sep 17 00:00:00 2001 From: aleclarson Date: Wed, 7 Aug 2024 05:08:09 +0000 Subject: [PATCH 23/35] chore(release): 12.2.0-beta.7fb6e89 [skip ci] --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18340c41..fc965a18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [radashi@beta] ### Details +#### Security +- Split check-pr workflow into 3 workflows by [@aleclarson](https://github.com/aleclarson) in [24307ef](https://github.com/radashi-org/radashi/commit/24307efdb7a84fa0b17df1b71d1d19bbc681eb25) + #### Added - Add `isIntString` function by [@aleclarson](https://github.com/aleclarson) in [fa500d3](https://github.com/radashi-org/radashi/commit/fa500d329d7e06062e7a42cbf4ff9ad9dcb89191) - Add `isPlainObject` type guard by [@aleclarson](https://github.com/aleclarson) in [#16](https://github.com/radashi-org/radashi/pull/16) @@ -128,6 +131,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Make `select` more option-friendly by [@aleclarson](https://github.com/aleclarson) in [c9cfcd0](https://github.com/radashi-org/radashi/commit/c9cfcd0a7eb1af98682f5d9b56555162c92b7085) - Let `shift` accept a readonly array type by [@nnmrts](https://github.com/nnmrts) in [#126](https://github.com/radashi-org/radashi/pull/126) +- Remove type constraint for mapped array passed to `sum` by [@MarlonPassos-git](https://github.com/MarlonPassos-git) in [dea0f50](https://github.com/radashi-org/radashi/commit/dea0f504f417b23aaf2b91495943501c894a172a) ### New Contributors * [@crishoj](https://github.com/crishoj) made their first contribution in [#127](https://github.com/radashi-org/radashi/pull/127) From 59a1a66bb7cf260d1215850cf13b063df449a67d Mon Sep 17 00:00:00 2001 From: Marlon Passos <1marlonpassos@gmail.com> Date: Wed, 7 Aug 2024 16:06:16 -0300 Subject: [PATCH 24/35] chore: setup dev containers (#138) Co-authored-by: Alec Larson <1925840+aleclarson@users.noreply.github.com> --- .devcontainer/devcontainer.json | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..a9f9af6c --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,25 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node +{ + "name": "Radashi", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/typescript-node:1-20-bookworm", + + // Features to add to the dev container. More info: https://containers.dev/features. + "features": { + "ghcr.io/devcontainers-contrib/features/pnpm": { + "version": "9.1.3" + }, + "ghcr.io/joshuanianji/devcontainer-features/mount-pnpm-store": {} + }, + "postCreateCommand": "pnpm i", + "customizations": { + "vscode": { + "extensions": [ + "vitest.explorer", + "biomejs.biome", + "esbenp.prettier-vscode" + ] + } + } +} From ee66a773d8c965e56a547bcb45795c74c1ba5f0e Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Wed, 7 Aug 2024 15:08:06 -0400 Subject: [PATCH 25/35] chore: add 24307ef to cliffignore --- .cliffignore | 3 ++- CHANGELOG.md | 3 --- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.cliffignore b/.cliffignore index 3e671373..a1815f5d 100644 --- a/.cliffignore +++ b/.cliffignore @@ -1,2 +1,3 @@ 9e4933d7382649f3c3aba8e3ab3eac3d1bb9c735 -069c986ac3649b060d926ec5f8447f0ac6f568b2 \ No newline at end of file +069c986ac3649b060d926ec5f8447f0ac6f568b2 +24307efdb7a84fa0b17df1b71d1d19bbc681eb25 diff --git a/CHANGELOG.md b/CHANGELOG.md index fc965a18..74dc877d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [radashi@beta] ### Details -#### Security -- Split check-pr workflow into 3 workflows by [@aleclarson](https://github.com/aleclarson) in [24307ef](https://github.com/radashi-org/radashi/commit/24307efdb7a84fa0b17df1b71d1d19bbc681eb25) - #### Added - Add `isIntString` function by [@aleclarson](https://github.com/aleclarson) in [fa500d3](https://github.com/radashi-org/radashi/commit/fa500d329d7e06062e7a42cbf4ff9ad9dcb89191) - Add `isPlainObject` type guard by [@aleclarson](https://github.com/aleclarson) in [#16](https://github.com/radashi-org/radashi/pull/16) From 1e60cc197fdb00008d9e68ec48d2ac571e97e40d Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Wed, 7 Aug 2024 17:06:06 -0400 Subject: [PATCH 26/35] chore: fix publish-pr workflow for forks --- .github/workflows/publish-pr.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-pr.yml b/.github/workflows/publish-pr.yml index a5e2c2b0..0a5a9cdb 100644 --- a/.github/workflows/publish-pr.yml +++ b/.github/workflows/publish-pr.yml @@ -19,12 +19,14 @@ jobs: [Your preview release is being published.](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) :hourglass: token: ${{ secrets.GITHUB_TOKEN }} - - uses: xt0rted/pull-request-comment-branch@v2 + - uses: radashi-org/pull-request-comment-branch@v2 id: comment-branch + - uses: actions/checkout@v4 if: success() with: ref: ${{ steps.comment-branch.outputs.head_ref }} + repository: ${{ steps.comment-branch.outputs.head_owner }}/${{ steps.comment-branch.outputs.head_repo }} - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 From 11f37673492261d26b84a4625d4383f50683fa14 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Wed, 7 Aug 2024 22:13:53 -0400 Subject: [PATCH 27/35] docs: fix import statement in example --- docs/object/pick.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/object/pick.mdx b/docs/object/pick.mdx index 6c7613de..7e69a707 100644 --- a/docs/object/pick.mdx +++ b/docs/object/pick.mdx @@ -45,7 +45,7 @@ When used with a predicate function, `pick` is potentially unsafe, because of pa ```typescript // Example demonstrating potential inaccuracy in `key` and `value` types within `_.pick` callback -import _ from 'lodash' +import * as _ from 'radashi' interface User { name: string From e45ed04c2711d62eb4c200fc51a6056b6798da54 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Sat, 10 Aug 2024 13:51:11 -0400 Subject: [PATCH 28/35] chore: functions added by PRs are saved to a Supabase instance (#149) --- .github/workflows/register-pr.yml | 26 + scripts/radashi-db/README.md | 11 + scripts/radashi-db/ci-register-pr.ts | 137 ++ scripts/radashi-db/package.json | 25 + scripts/radashi-db/pnpm-lock.yaml | 1433 +++++++++++++++++ scripts/radashi-db/seed-merged-functions.ts | 88 + scripts/radashi-db/seed-proposed-functions.ts | 182 +++ scripts/radashi-db/src/db.ts | 9 + scripts/radashi-db/src/register-pr.ts | 274 ++++ scripts/radashi-db/src/util/bottleneck.ts | 158 ++ scripts/radashi-db/src/util/markdown.ts | 56 + scripts/radashi-db/tsconfig.json | 16 + 12 files changed, 2415 insertions(+) create mode 100644 .github/workflows/register-pr.yml create mode 100644 scripts/radashi-db/README.md create mode 100644 scripts/radashi-db/ci-register-pr.ts create mode 100644 scripts/radashi-db/package.json create mode 100644 scripts/radashi-db/pnpm-lock.yaml create mode 100644 scripts/radashi-db/seed-merged-functions.ts create mode 100644 scripts/radashi-db/seed-proposed-functions.ts create mode 100644 scripts/radashi-db/src/db.ts create mode 100644 scripts/radashi-db/src/register-pr.ts create mode 100644 scripts/radashi-db/src/util/bottleneck.ts create mode 100644 scripts/radashi-db/src/util/markdown.ts create mode 100644 scripts/radashi-db/tsconfig.json diff --git a/.github/workflows/register-pr.yml b/.github/workflows/register-pr.yml new file mode 100644 index 00000000..29c88209 --- /dev/null +++ b/.github/workflows/register-pr.yml @@ -0,0 +1,26 @@ +name: Register PR In Database + +on: + pull_request_target: + branches: [main] + types: [opened, reopened, synchronize, closed] + +jobs: + register-pr: + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + cache: pnpm + - uses: actions/github-script@v6 + env: + ALGOLIA_KEY: ${{ secrets.ALGOLIA_KEY }} + SUPABASE_KEY: ${{ secrets.SUPABASE_KEY }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const script = await import('${{ github.workspace }}/scripts/radashi-db/ci-register-pr.ts') + await script.run(context, github, console) diff --git a/scripts/radashi-db/README.md b/scripts/radashi-db/README.md new file mode 100644 index 00000000..67c71b24 --- /dev/null +++ b/scripts/radashi-db/README.md @@ -0,0 +1,11 @@ +## ./scripts/radashi-db/ + +This folder contains scripts for managing the Radashi database (hosted on Supabase and Algolia). For example, when a pull request is updated, the `registerPullRequest` function from the `register-pr.ts` module is called to update the database with the latest information about the pull request. This data is then used on certain pages of the Radashi website and the Radashi VSCode extension. + +### Environment Variables + +To use these scripts, you may need these environment variables: + +- `SUPABASE_KEY`: A private API key for Supabase +- `ALGOLIA_KEY`: A private API key for Algolia +- `GITHUB_TOKEN`: A GitHub token with access to the Radashi organization diff --git a/scripts/radashi-db/ci-register-pr.ts b/scripts/radashi-db/ci-register-pr.ts new file mode 100644 index 00000000..b9da804f --- /dev/null +++ b/scripts/radashi-db/ci-register-pr.ts @@ -0,0 +1,137 @@ +import type { Octokit } from '@octokit/rest' +import { registerPullRequest } from './src/register-pr' + +export async function run( + context: Context, + github: Octokit, + console?: Pick, +): Promise { + // https://docs.github.com/en/webhooks/webhook-events-and-payloads?actionType=synchronize#pull_request + const pr = context.payload.pull_request + if (!pr) { + throw new Error('No pull request found in context') + } + + const owner = pr.head.repo.owner.login + const repo = pr.head.repo.name + + const { data: files } = await github.pulls.listFiles({ + owner, + repo, + pull_number: pr.number, + }) + const { data: checkRuns } = await github.checks.listForRef({ + owner, + repo, + ref: pr.head.sha, + }) + + await registerPullRequest(pr.number, { + status: pr.draft ? 'draft' : pr.merged_at ? 'merged' : pr.state, + sha: pr.head.sha, + repo, + owner, + ownerAvatarUrl: pr.user.avatar_url, + branch: pr.head.ref, + checksPassed: checkRuns.check_runs.every( + run => run.conclusion === 'success', + ), + console, + files, + breaking: (pr.labels as { name: string }[]).some( + label => label.name === 'breaking', + ), + getApprovalRating: async () => { + const { data: reactions } = await github.reactions.listForIssue({ + owner: 'radashi-org', + repo: 'radashi', + issue_number: pr.number, + }) + return reactions.filter(reaction => reaction.content === '+1').length + }, + getCommit: async () => { + return { + sha: pr.head.sha, + author: pr.user.name, + date: pr.created_at, + } + }, + getFileContent: async path => { + const { data } = await github.repos.getContent({ + owner, + repo, + path, + ref: pr.head.sha, + }) + if (!('content' in data)) { + throw new Error(`File ${path} has no content`) + } + return Buffer.from(data.content, 'base64').toString('utf-8') + }, + getIssueBody: async () => { + return pr.body ?? null + }, + }) +} + +/** + * @source https://github.com/actions/toolkit/blob/f003268/packages/github/src/context.ts + */ +interface Context { + payload: WebhookPayload + eventName: string + sha: string + ref: string + workflow: string + action: string + actor: string + job: string + runAttempt: number + runNumber: number + runId: number + apiUrl: string + serverUrl: string + graphqlUrl: string +} + +interface PayloadRepository { + [key: string]: any + full_name?: string + name: string + owner: { + [key: string]: any + login: string + name?: string + } + html_url?: string +} + +interface WebhookPayload { + [key: string]: any + repository?: PayloadRepository + issue?: { + [key: string]: any + number: number + html_url?: string + body?: string + } + pull_request?: { + [key: string]: any + number: number + html_url?: string + body?: string + } + sender?: { + [key: string]: any + type: string + } + action?: string + installation?: { + id: number + [key: string]: any + } + comment?: { + id: number + [key: string]: any + } +} diff --git a/scripts/radashi-db/package.json b/scripts/radashi-db/package.json new file mode 100644 index 00000000..3b1c9272 --- /dev/null +++ b/scripts/radashi-db/package.json @@ -0,0 +1,25 @@ +{ + "name": "@radashi-org/radashi-db", + "private": true, + "type": "module", + "main": "./index.js", + "dependencies": { + "@octokit/rest": "^21.0.1", + "@supabase/supabase-js": "^2.45.0", + "algoliasearch": "^4.24.0", + "execa": "^9.3.0", + "fast-glob": "^3.3.2", + "markdown-it": "^14.1.0", + "markdown-it-front-matter": "^0.2.4", + "mri": "^1.2.0", + "radashi": "12.2.0-beta.83909af", + "sucrase": "^3.35.0", + "tsx": "^4.17.0", + "ultrahtml": "^1.5.3", + "yaml": "^2.5.0" + }, + "devDependencies": { + "@types/markdown-it": "^14.1.2", + "@types/node": "^22.0.0" + } +} \ No newline at end of file diff --git a/scripts/radashi-db/pnpm-lock.yaml b/scripts/radashi-db/pnpm-lock.yaml new file mode 100644 index 00000000..9f75f42d --- /dev/null +++ b/scripts/radashi-db/pnpm-lock.yaml @@ -0,0 +1,1433 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@octokit/rest': + specifier: ^21.0.1 + version: 21.0.1 + '@supabase/supabase-js': + specifier: ^2.45.0 + version: 2.45.0 + algoliasearch: + specifier: ^4.24.0 + version: 4.24.0 + execa: + specifier: ^9.3.0 + version: 9.3.0 + fast-glob: + specifier: ^3.3.2 + version: 3.3.2 + markdown-it: + specifier: ^14.1.0 + version: 14.1.0 + markdown-it-front-matter: + specifier: ^0.2.4 + version: 0.2.4 + mri: + specifier: ^1.2.0 + version: 1.2.0 + radashi: + specifier: 12.2.0-beta.83909af + version: 12.2.0-beta.83909af + sucrase: + specifier: ^3.35.0 + version: 3.35.0 + tsx: + specifier: ^4.17.0 + version: 4.17.0 + ultrahtml: + specifier: ^1.5.3 + version: 1.5.3 + yaml: + specifier: ^2.5.0 + version: 2.5.0 + devDependencies: + '@types/markdown-it': + specifier: ^14.1.2 + version: 14.1.2 + '@types/node': + specifier: ^22.0.0 + version: 22.0.0 + +packages: + + '@algolia/cache-browser-local-storage@4.24.0': + resolution: {integrity: sha512-t63W9BnoXVrGy9iYHBgObNXqYXM3tYXCjDSHeNwnsc324r4o5UiVKUiAB4THQ5z9U5hTj6qUvwg/Ez43ZD85ww==} + + '@algolia/cache-common@4.24.0': + resolution: {integrity: sha512-emi+v+DmVLpMGhp0V9q9h5CdkURsNmFC+cOS6uK9ndeJm9J4TiqSvPYVu+THUP8P/S08rxf5x2P+p3CfID0Y4g==} + + '@algolia/cache-in-memory@4.24.0': + resolution: {integrity: sha512-gDrt2so19jW26jY3/MkFg5mEypFIPbPoXsQGQWAi6TrCPsNOSEYepBMPlucqWigsmEy/prp5ug2jy/N3PVG/8w==} + + '@algolia/client-account@4.24.0': + resolution: {integrity: sha512-adcvyJ3KjPZFDybxlqnf+5KgxJtBjwTPTeyG2aOyoJvx0Y8dUQAEOEVOJ/GBxX0WWNbmaSrhDURMhc+QeevDsA==} + + '@algolia/client-analytics@4.24.0': + resolution: {integrity: sha512-y8jOZt1OjwWU4N2qr8G4AxXAzaa8DBvyHTWlHzX/7Me1LX8OayfgHexqrsL4vSBcoMmVw2XnVW9MhL+Y2ZDJXg==} + + '@algolia/client-common@4.24.0': + resolution: {integrity: sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==} + + '@algolia/client-personalization@4.24.0': + resolution: {integrity: sha512-l5FRFm/yngztweU0HdUzz1rC4yoWCFo3IF+dVIVTfEPg906eZg5BOd1k0K6rZx5JzyyoP4LdmOikfkfGsKVE9w==} + + '@algolia/client-search@4.24.0': + resolution: {integrity: sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==} + + '@algolia/logger-common@4.24.0': + resolution: {integrity: sha512-LLUNjkahj9KtKYrQhFKCzMx0BY3RnNP4FEtO+sBybCjJ73E8jNdaKJ/Dd8A/VA4imVHP5tADZ8pn5B8Ga/wTMA==} + + '@algolia/logger-console@4.24.0': + resolution: {integrity: sha512-X4C8IoHgHfiUROfoRCV+lzSy+LHMgkoEEU1BbKcsfnV0i0S20zyy0NLww9dwVHUWNfPPxdMU+/wKmLGYf96yTg==} + + '@algolia/recommend@4.24.0': + resolution: {integrity: sha512-P9kcgerfVBpfYHDfVZDvvdJv0lEoCvzNlOy2nykyt5bK8TyieYyiD0lguIJdRZZYGre03WIAFf14pgE+V+IBlw==} + + '@algolia/requester-browser-xhr@4.24.0': + resolution: {integrity: sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==} + + '@algolia/requester-common@4.24.0': + resolution: {integrity: sha512-k3CXJ2OVnvgE3HMwcojpvY6d9kgKMPRxs/kVohrwF5WMr2fnqojnycZkxPoEg+bXm8fi5BBfFmOqgYztRtHsQA==} + + '@algolia/requester-node-http@4.24.0': + resolution: {integrity: sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==} + + '@algolia/transporter@4.24.0': + resolution: {integrity: sha512-86nI7w6NzWxd1Zp9q3413dRshDqAzSbsQjhcDhPIatEFiZrL1/TjnHL8S7jVKFePlIMzDsZWXAXwXzcok9c5oA==} + + '@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] + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@octokit/auth-token@5.1.1': + resolution: {integrity: sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==} + 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/openapi-types@22.2.0': + resolution: {integrity: sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==} + + '@octokit/plugin-paginate-rest@11.3.3': + resolution: {integrity: sha512-o4WRoOJZlKqEEgj+i9CpcmnByvtzoUYC6I8PD2SA95M+BJ2x8h7oLcVOg9qcowWXBOdcTRsMZiwvM3EyLm9AfA==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-request-log@5.3.1': + resolution: {integrity: sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw==} + 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/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/rest@21.0.1': + resolution: {integrity: sha512-RWA6YU4CqK0h0J6tfYlUFnH3+YgBADlxaHXaKSG+BVr2y4PTfbU2tlKuaQoQZ83qaTbi4CUxLNAmbAqR93A6mQ==} + engines: {node: '>= 18'} + + '@octokit/types@13.5.0': + resolution: {integrity: sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@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'} + + '@supabase/auth-js@2.64.4': + resolution: {integrity: sha512-9ITagy4WP4FLl+mke1rchapOH0RQpf++DI+WSG2sO1OFOZ0rW3cwAM0nCrMOxu+Zw4vJ4zObc08uvQrXx590Tg==} + + '@supabase/functions-js@2.4.1': + resolution: {integrity: sha512-8sZ2ibwHlf+WkHDUZJUXqqmPvWQ3UHN0W30behOJngVh/qHHekhJLCFbh0AjkE9/FqqXtf9eoVvmYgfCLk5tNA==} + + '@supabase/node-fetch@2.6.15': + resolution: {integrity: sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==} + engines: {node: 4.x || >=6.0.0} + + '@supabase/postgrest-js@1.15.8': + resolution: {integrity: sha512-YunjXpoQjQ0a0/7vGAvGZA2dlMABXFdVI/8TuVKtlePxyT71sl6ERl6ay1fmIeZcqxiuFQuZw/LXUuStUG9bbg==} + + '@supabase/realtime-js@2.10.2': + resolution: {integrity: sha512-qyCQaNg90HmJstsvr2aJNxK2zgoKh9ZZA8oqb7UT2LCh3mj9zpa3Iwu167AuyNxsxrUE8eEJ2yH6wLCij4EApA==} + + '@supabase/storage-js@2.6.0': + resolution: {integrity: sha512-REAxr7myf+3utMkI2oOmZ6sdplMZZ71/2NEIEMBZHL9Fkmm3/JnaOZVSRqvG4LStYj2v5WhCruCzuMn6oD/Drw==} + + '@supabase/supabase-js@2.45.0': + resolution: {integrity: sha512-j66Mfs8RhzCQCKxKogAFQYH9oNhRmgIdKk6pexguI2Oc7hi+nL9UNJug5aL1tKnBdaBM3h65riPLQSdL6sWa3Q==} + + '@types/linkify-it@5.0.0': + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + + '@types/markdown-it@14.1.2': + resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} + + '@types/mdurl@2.0.0': + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + + '@types/node@22.0.0': + resolution: {integrity: sha512-VT7KSYudcPOzP5Q0wfbowyNLaVR8QWUdw+088uFWwfvpY6uCWaXpqV6ieLAu9WBcnTa7H4Z5RLK8I5t2FuOcqw==} + + '@types/phoenix@1.6.5': + resolution: {integrity: sha512-xegpDuR+z0UqG9fwHqNoy3rI7JDlvaPh2TY47Fl80oq6g+hXT+c/LEuE43X48clZ6lOfANl5WrPur9fYO1RJ/w==} + + '@types/ws@8.5.12': + resolution: {integrity: sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==} + + algoliasearch@4.24.0: + resolution: {integrity: sha512-bf0QV/9jVejssFBmz2HQLxUadxk574t4iwjCKp5E7NBzwKkrDEhKPISIIjAU/p6K5qDx3qoeh4+26zWN1jmw3g==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + before-after-hook@3.0.2: + resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + esbuild@0.23.0: + resolution: {integrity: sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==} + engines: {node: '>=18'} + hasBin: true + + execa@9.3.0: + resolution: {integrity: sha512-l6JFbqnHEadBoVAVpN5dl2yCyfX28WoBAGaoQcNmLLSedOxTxcn2Qa83s8I/PA5i56vWru2OHOtrwF7Om2vqlg==} + engines: {node: ^18.19.0 || >=20.5.0} + + fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + + figures@6.1.0: + resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} + engines: {node: '>=18'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + foreground-child@3.2.1: + resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==} + engines: {node: '>=14'} + + 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==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + human-signals@7.0.0: + resolution: {integrity: sha512-74kytxOUSvNbjrT9KisAbaTZ/eJwD/LrbM/kh5j0IhPuJzwuA19dWvniFGwBzN9rVjg+O/e+F310PjObDXS+9Q==} + engines: {node: '>=18.18.0'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + 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==} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + markdown-it-front-matter@0.2.4: + resolution: {integrity: sha512-25GUs0yjS2hLl8zAemVndeEzThB1p42yxuDEKbd4JlL3jiz+jsm6e56Ya8B0VREOkNxLYB4TTwaoPJ3ElMmW+w==} + + markdown-it@14.1.0: + resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} + hasBin: true + + mdurl@2.0.0: + resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.7: + resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} + engines: {node: '>=8.6'} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + package-json-from-dist@1.0.0: + resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} + + 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'} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + + pretty-ms@9.1.0: + resolution: {integrity: sha512-o1piW0n3tgKIKCwk2vpM/vOV13zjJzvP37Ioze54YlTHE06m4tjEbzg9WsKkvTuyYln2DHjo5pY4qrZGI0otpw==} + engines: {node: '>=18'} + + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + radashi@12.2.0-beta.83909af: + resolution: {integrity: sha512-nPQqyCP3iDi9I85WypLXJgPCP1XTeXwBP+vhRnnFWDoKOeslwJy5IXdM84L8sE0YJ1zypicJfvo+wPwFvWgYyw==} + engines: {node: '>=16.0.0'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + 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'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-final-newline@4.0.0: + resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} + engines: {node: '>=18'} + + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tsx@4.17.0: + resolution: {integrity: sha512-eN4mnDA5UMKDt4YZixo9tBioibaMBpoxBkD+rIPAjVmYERSG0/dWEY1CEFuV89CgASlKL499q8AhmkMnnjtOJg==} + engines: {node: '>=18.0.0'} + hasBin: true + + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + + ultrahtml@1.5.3: + resolution: {integrity: sha512-GykOvZwgDWZlTQMtp5jrD4BVL+gNn2NVlVafjcFUJ7taY20tqYdwdoWBFy6GBJsNTZe1GkGPkSl5knQAjtgceg==} + + undici-types@6.11.1: + resolution: {integrity: sha512-mIDEX2ek50x0OlRgxryxsenE5XaQD4on5U2inY7RApK3SOJpofyw7uW2AyfMKkhAxXIceo2DeWGVGwyvng1GNQ==} + + universal-user-agent@7.0.2: + resolution: {integrity: sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==} + + 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 + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + yaml@2.5.0: + resolution: {integrity: sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==} + engines: {node: '>= 14'} + hasBin: true + + yoctocolors@2.1.1: + resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==} + engines: {node: '>=18'} + +snapshots: + + '@algolia/cache-browser-local-storage@4.24.0': + dependencies: + '@algolia/cache-common': 4.24.0 + + '@algolia/cache-common@4.24.0': {} + + '@algolia/cache-in-memory@4.24.0': + dependencies: + '@algolia/cache-common': 4.24.0 + + '@algolia/client-account@4.24.0': + dependencies: + '@algolia/client-common': 4.24.0 + '@algolia/client-search': 4.24.0 + '@algolia/transporter': 4.24.0 + + '@algolia/client-analytics@4.24.0': + dependencies: + '@algolia/client-common': 4.24.0 + '@algolia/client-search': 4.24.0 + '@algolia/requester-common': 4.24.0 + '@algolia/transporter': 4.24.0 + + '@algolia/client-common@4.24.0': + dependencies: + '@algolia/requester-common': 4.24.0 + '@algolia/transporter': 4.24.0 + + '@algolia/client-personalization@4.24.0': + dependencies: + '@algolia/client-common': 4.24.0 + '@algolia/requester-common': 4.24.0 + '@algolia/transporter': 4.24.0 + + '@algolia/client-search@4.24.0': + dependencies: + '@algolia/client-common': 4.24.0 + '@algolia/requester-common': 4.24.0 + '@algolia/transporter': 4.24.0 + + '@algolia/logger-common@4.24.0': {} + + '@algolia/logger-console@4.24.0': + dependencies: + '@algolia/logger-common': 4.24.0 + + '@algolia/recommend@4.24.0': + dependencies: + '@algolia/cache-browser-local-storage': 4.24.0 + '@algolia/cache-common': 4.24.0 + '@algolia/cache-in-memory': 4.24.0 + '@algolia/client-common': 4.24.0 + '@algolia/client-search': 4.24.0 + '@algolia/logger-common': 4.24.0 + '@algolia/logger-console': 4.24.0 + '@algolia/requester-browser-xhr': 4.24.0 + '@algolia/requester-common': 4.24.0 + '@algolia/requester-node-http': 4.24.0 + '@algolia/transporter': 4.24.0 + + '@algolia/requester-browser-xhr@4.24.0': + dependencies: + '@algolia/requester-common': 4.24.0 + + '@algolia/requester-common@4.24.0': {} + + '@algolia/requester-node-http@4.24.0': + dependencies: + '@algolia/requester-common': 4.24.0 + + '@algolia/transporter@4.24.0': + dependencies: + '@algolia/cache-common': 4.24.0 + '@algolia/logger-common': 4.24.0 + '@algolia/requester-common': 4.24.0 + + '@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 + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + + '@octokit/auth-token@5.1.1': {} + + '@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/openapi-types@22.2.0': {} + + '@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-request-log@5.3.1(@octokit/core@6.1.2)': + dependencies: + '@octokit/core': 6.1.2 + + '@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/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/rest@21.0.1': + dependencies: + '@octokit/core': 6.1.2 + '@octokit/plugin-paginate-rest': 11.3.3(@octokit/core@6.1.2) + '@octokit/plugin-request-log': 5.3.1(@octokit/core@6.1.2) + '@octokit/plugin-rest-endpoint-methods': 13.2.4(@octokit/core@6.1.2) + + '@octokit/types@13.5.0': + dependencies: + '@octokit/openapi-types': 22.2.0 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@sec-ant/readable-stream@0.4.1': {} + + '@sindresorhus/merge-streams@4.0.0': {} + + '@supabase/auth-js@2.64.4': + dependencies: + '@supabase/node-fetch': 2.6.15 + + '@supabase/functions-js@2.4.1': + dependencies: + '@supabase/node-fetch': 2.6.15 + + '@supabase/node-fetch@2.6.15': + dependencies: + whatwg-url: 5.0.0 + + '@supabase/postgrest-js@1.15.8': + dependencies: + '@supabase/node-fetch': 2.6.15 + + '@supabase/realtime-js@2.10.2': + dependencies: + '@supabase/node-fetch': 2.6.15 + '@types/phoenix': 1.6.5 + '@types/ws': 8.5.12 + ws: 8.18.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@supabase/storage-js@2.6.0': + dependencies: + '@supabase/node-fetch': 2.6.15 + + '@supabase/supabase-js@2.45.0': + dependencies: + '@supabase/auth-js': 2.64.4 + '@supabase/functions-js': 2.4.1 + '@supabase/node-fetch': 2.6.15 + '@supabase/postgrest-js': 1.15.8 + '@supabase/realtime-js': 2.10.2 + '@supabase/storage-js': 2.6.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@types/linkify-it@5.0.0': {} + + '@types/markdown-it@14.1.2': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + + '@types/mdurl@2.0.0': {} + + '@types/node@22.0.0': + dependencies: + undici-types: 6.11.1 + + '@types/phoenix@1.6.5': {} + + '@types/ws@8.5.12': + dependencies: + '@types/node': 22.0.0 + + algoliasearch@4.24.0: + dependencies: + '@algolia/cache-browser-local-storage': 4.24.0 + '@algolia/cache-common': 4.24.0 + '@algolia/cache-in-memory': 4.24.0 + '@algolia/client-account': 4.24.0 + '@algolia/client-analytics': 4.24.0 + '@algolia/client-common': 4.24.0 + '@algolia/client-personalization': 4.24.0 + '@algolia/client-search': 4.24.0 + '@algolia/logger-common': 4.24.0 + '@algolia/logger-console': 4.24.0 + '@algolia/recommend': 4.24.0 + '@algolia/requester-browser-xhr': 4.24.0 + '@algolia/requester-common': 4.24.0 + '@algolia/requester-node-http': 4.24.0 + '@algolia/transporter': 4.24.0 + + ansi-regex@5.0.1: {} + + ansi-regex@6.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} + + any-promise@1.3.0: {} + + argparse@2.0.1: {} + + balanced-match@1.0.2: {} + + before-after-hook@3.0.2: {} + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@4.1.1: {} + + cross-spawn@7.0.3: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + eastasianwidth@0.2.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + entities@4.5.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 + + 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 + + fast-glob@3.3.2: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.7 + + fastq@1.17.1: + dependencies: + reusify: 1.0.4 + + figures@6.1.0: + dependencies: + is-unicode-supported: 2.0.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + foreground-child@3.2.1: + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + + 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 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob@10.4.5: + dependencies: + foreground-child: 3.2.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.0 + path-scurry: 1.11.1 + + human-signals@7.0.0: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-plain-obj@4.1.0: {} + + is-stream@4.0.1: {} + + is-unicode-supported@2.0.0: {} + + isexe@2.0.0: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + lines-and-columns@1.2.4: {} + + linkify-it@5.0.0: + dependencies: + uc.micro: 2.1.0 + + lru-cache@10.4.3: {} + + markdown-it-front-matter@0.2.4: {} + + markdown-it@14.1.0: + dependencies: + argparse: 2.0.1 + entities: 4.5.0 + linkify-it: 5.0.0 + mdurl: 2.0.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 + + mdurl@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.7: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + minipass@7.1.2: {} + + mri@1.2.0: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + + object-assign@4.1.1: {} + + package-json-from-dist@1.0.0: {} + + parse-ms@4.0.0: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + picomatch@2.3.1: {} + + pirates@4.0.6: {} + + pretty-ms@9.1.0: + dependencies: + parse-ms: 4.0.0 + + punycode.js@2.3.1: {} + + queue-microtask@1.2.3: {} + + radashi@12.2.0-beta.83909af: {} + + resolve-pkg-maps@1.0.0: {} + + reusify@1.0.4: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@4.1.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.0.1 + + strip-final-newline@4.0.0: {} + + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tr46@0.0.3: {} + + ts-interface-checker@0.1.13: {} + + tsx@4.17.0: + dependencies: + esbuild: 0.23.0 + get-tsconfig: 4.7.6 + optionalDependencies: + fsevents: 2.3.3 + + uc.micro@2.1.0: {} + + ultrahtml@1.5.3: {} + + undici-types@6.11.1: {} + + universal-user-agent@7.0.2: {} + + 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 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + ws@8.18.0: {} + + yaml@2.5.0: {} + + yoctocolors@2.1.1: {} diff --git a/scripts/radashi-db/seed-merged-functions.ts b/scripts/radashi-db/seed-merged-functions.ts new file mode 100644 index 00000000..cb2cab80 --- /dev/null +++ b/scripts/radashi-db/seed-merged-functions.ts @@ -0,0 +1,88 @@ +import { execa } from 'execa' +import glob from 'fast-glob' +import fs from 'node:fs/promises' +import path from 'node:path' +import { algolia, supabase } from './src/db' +import { renderPageMarkdown } from './src/util/markdown' + +async function seedMergedFunctions() { + const rootDir = new URL('../../', import.meta.url).pathname + const sourceFiles = await glob(['src/**/*.ts', '!src/*.ts'], { cwd: rootDir }) + + for (const sourceFile of sourceFiles) { + // How romantic. + const firstDateCmd = await execa( + 'git', + ['log', '--format=%aI', '--reverse', sourceFile], + { cwd: rootDir }, + ) + + const firstCommitDate = firstDateCmd.stdout.split('\n')[0] + console.log('Got original commit date', firstCommitDate) + + const firstAuthorCmd = await execa( + 'git', + ['log', '--format=%an', '--reverse', '--follow', sourceFile], + { cwd: rootDir }, + ) + const firstCommitAuthor = firstAuthorCmd.stdout.split('\n')[0] + console.log('Got original commit author', firstCommitAuthor) + + const docFile = sourceFile.replace(/^src/, 'docs').replace(/\.ts$/, '.mdx') + const docFilePath = path.join(rootDir, docFile) + + let name: string | undefined + let description: string | undefined + let documentation: string | undefined + try { + interface PageData { + title: string + description?: string + } + const docContent = await fs.readFile(docFilePath, 'utf-8') + const renderResult = await renderPageMarkdown(docContent) + if (renderResult) { + name = renderResult.data?.title + description = renderResult.data?.description + documentation = renderResult.text + } + } catch (error) { + console.error(`Error reading or parsing ${docFile}:`, error) + continue + } + + name ??= path.basename(sourceFile, '.ts') + + const record = { + ref: 'radashi-org/radashi#main', + group: sourceFile.split('/').slice(1, -1).join('/'), + name, + description, + documentation, + committed_at: firstCommitDate, + committed_by: firstCommitAuthor, + } + + console.log('Would insert', record) + + try { + await supabase.from('merged_functions').insert(record) + console.log(`Inserted ${name} into Supabase`) + } catch (error) { + console.error(`Error inserting ${name} into Supabase:`, error) + } + + try { + const index = algolia.initIndex('merged_functions') + await index.saveObject({ + objectID: name, + ...record, + }) + console.log(`Inserted ${name} into Algolia`) + } catch (error) { + console.error(`Error inserting ${name} into Algolia:`, error) + } + } +} + +seedMergedFunctions() diff --git a/scripts/radashi-db/seed-proposed-functions.ts b/scripts/radashi-db/seed-proposed-functions.ts new file mode 100644 index 00000000..041a0205 --- /dev/null +++ b/scripts/radashi-db/seed-proposed-functions.ts @@ -0,0 +1,182 @@ +import { Octokit, type RestEndpointMethodTypes } from '@octokit/rest' +import { parallel } from 'radashi' +import { registerPullRequest } from './src/register-pr' +import { bottleneck } from './util/bottleneck' + +async function seedProposedFunctions() { + const octokit = new Octokit({ + auth: process.env.GITHUB_TOKEN, + }) + + const limit = bottleneck( + { interval: 1000 / 5 }, + async (fn: () => Promise) => fn(), + ) + + type ListPullsResponse = RestEndpointMethodTypes['pulls']['list']['response'] + type GetPullResponse = RestEndpointMethodTypes['pulls']['get']['response'] + + type ListPullsResponseItem = ListPullsResponse['data'][number] + type GetPullResponseData = GetPullResponse['data'] + type PullRequest = GetPullResponseData | ListPullsResponseItem + type PullRequestRepo = Exclude + + async function onPullRequest(pr: { + number: PullRequest['number'] + head: { + sha: PullRequest['head']['sha'] + ref: PullRequest['head']['ref'] + repo: { + owner: PullRequestRepo['owner'] + name: PullRequestRepo['name'] + } | null + } + title: PullRequest['title'] + state: PullRequest['state'] + draft?: PullRequest['draft'] + merged_at: PullRequest['merged_at'] + // mergeable: PullRequest[''] + user: PullRequest['user'] + labels: { name: string }[] + }) { + console.log(`Processing PR ${pr.number}`) + + const status = + pr.state === 'open' + ? pr.draft + ? 'draft' + : 'open' + : pr.merged_at != null + ? 'merged' + : 'closed' + + const { data: files } = await limit(() => + octokit.pulls.listFiles({ + owner: 'radashi-org', + repo: 'radashi', + pull_number: pr.number, + }), + ) + + const repoOwner = pr.head.repo?.owner + const repoName = pr.head.repo?.name + + // Get the status of CI checks for the PR + const { data: checkRuns } = await limit(() => + octokit.checks.listForRef({ + owner: 'radashi-org', + repo: 'radashi', + ref: pr.head.sha, + }), + ) + + const checksPassed = checkRuns.check_runs.every( + run => run.conclusion === 'success', + ) + + await registerPullRequest(pr.number, { + sha: pr.head.sha, + files, + status, + branch: pr.head.ref, + owner: repoOwner?.login, + ownerAvatarUrl: repoOwner?.avatar_url, + repo: repoName, + breaking: pr.labels.some(label => label.name === 'BREAKING CHANGE'), + checksPassed, + getApprovalRating: async () => { + const { data: reactions } = await limit(() => + octokit.reactions.listForIssue({ + owner: 'radashi-org', + repo: 'radashi', + issue_number: pr.number, + }), + ) + + return reactions.filter(reaction => reaction.content === '+1').length + }, + getIssueBody: async () => { + const { data } = await limit(() => + octokit.issues.get({ + owner: 'radashi-org', + repo: 'radashi', + issue_number: pr.number, + }), + ) + return data.body ?? null + }, + getCommit: async (ref, owner = 'radashi-org', repo = 'radashi') => { + const { data: commit } = await limit(() => + octokit.repos.getCommit({ + owner, + repo, + ref, + }), + ) + return { + sha: commit.sha, + date: commit.commit.author?.date, + author: commit.commit.author?.name, + } + }, + getFileContent: async path => { + const { data } = await limit(() => + octokit.repos.getContent({ + owner: 'radashi-org', + repo: 'radashi', + path, + ref: pr.head.sha, + }), + ) + if (!('content' in data)) { + throw new Error(`File ${path} has no content`) + } + return Buffer.from(data.content, 'base64').toString('utf-8') + }, + }) + } + + try { + for await (const response of octokit.paginate.iterator( + octokit.rest.pulls.list, + { + owner: 'radashi-org', + repo: 'radashi', + state: 'open', + per_page: 100, + }, + )) { + await parallel(3, response.data, onPullRequest) + } + + for await (const response of octokit.paginate.iterator( + octokit.rest.search.issuesAndPullRequests, + { + q: 'repo:radashi-org/radashi is:pr is:closed is:unmerged label:"open library" feat', + per_page: 100, + }, + )) { + await parallel(3, response.data, async result => { + if (!result.pull_request) { + return + } + + const { data: pr } = await limit(() => + octokit.pulls.get({ + owner: 'radashi-org', + repo: 'radashi', + pull_number: result.number, + }), + ) + + onPullRequest(pr) + }) + } + + console.log('Radashi database seeded successfully') + } catch (error) { + console.error('Error seeding Radashi database:', error) + } +} + +seedProposedFunctions() diff --git a/scripts/radashi-db/src/db.ts b/scripts/radashi-db/src/db.ts new file mode 100644 index 00000000..5876dc1a --- /dev/null +++ b/scripts/radashi-db/src/db.ts @@ -0,0 +1,9 @@ +import { createClient as createSupabase } from '@supabase/supabase-js' +import { default as createAlgolia } from 'algoliasearch' + +export const supabase = createSupabase( + 'https://yucyhkpmrdbucitpovyj.supabase.co', + process.env.SUPABASE_KEY!, +) + +export const algolia = createAlgolia('7YYOXVJ9K7', process.env.ALGOLIA_KEY!) diff --git a/scripts/radashi-db/src/register-pr.ts b/scripts/radashi-db/src/register-pr.ts new file mode 100644 index 00000000..03da181f --- /dev/null +++ b/scripts/radashi-db/src/register-pr.ts @@ -0,0 +1,274 @@ +import path from 'node:path' +import { memo } from 'radashi' +import { algolia, supabase } from './db' +import { renderPageMarkdown } from './util/markdown' + +type PrFileStatus = + | 'added' + | 'removed' + | 'modified' + | 'renamed' + | 'copied' + | 'changed' + | 'unchanged' + +type PrStatus = 'draft' | 'open' | 'merged' | 'closed' + +interface PrFile { + status: PrFileStatus + filename: string + sha: string +} + +export interface Commit { + sha: string + date: string | undefined + author: string | undefined +} + +interface Context { + sha: string + /** + * The branch name (e.g. "main") + * + * @default "main" + */ + branch?: string + /** + * The owner name (e.g. "radashi-org" or your GitHub username) + * + * @default "radashi-org" + */ + owner?: string + ownerAvatarUrl?: string + /** + * The repository name (e.g. "radashi") + * + * @default "radashi" + */ + repo?: string + status: PrStatus + breaking: boolean + checksPassed: boolean + files: PrFile[] + getCommit: (ref: string, owner?: string, repo?: string) => Promise + getFileContent: (file: string) => Promise + getApprovalRating: () => Promise + getIssueBody: () => Promise + console?: Pick +} + +/** + * Register a pull request with the Radashi database. + * + * @param prNumber - The pull request number. + * @param context - The context object containing PR information. + */ +export const registerPullRequest = async ( + prNumber: number, + context: Context, +): Promise => { + const { console = globalThis.console } = context + const getCommit = memo(context.getCommit) + + try { + const newFunctions = context.files.filter( + file => + file.status === 'added' && + file.filename.startsWith('src/') && + file.filename.endsWith('.ts') && + !file.filename.endsWith('.test.ts'), + ) + + if (newFunctions.length === 0) { + console.log('No new functions added in this PR.') + return + } + + const newFunctionNames = newFunctions.map(f => + path.basename(f.filename, '.ts'), + ) + + console.log(`Fetching existing functions for PR #${prNumber}`) + const { data: existingFunctions, error: fetchError } = await supabase + .from('proposed_functions') + .select('name') + .eq('pr_number', prNumber) + + if (fetchError) { + console.error('Error fetching existing functions:', fetchError) + return + } + + const existingFunctionNames = existingFunctions.map(f => f.name) + + for (const name of existingFunctionNames) { + if (!newFunctionNames.includes(name)) { + console.log(`Deleting ${name}#${prNumber} from Radashi database`) + } + + await supabase + .from('proposed_functions') + .delete() + .match({ pr_number: prNumber, name }) + } + + let body: string | null | undefined + let approvalRating: number | null = null + + for (const file of newFunctions) { + const name = path.basename(file.filename, '.ts') + + if (context.status === 'merged') { + if (!existingFunctionNames.includes(name)) { + console.log(`Deleting ${name}#${prNumber} from Radashi database`) + + await supabase + .from('proposed_functions') + .delete() + .match({ pr_number: prNumber, name }) + + // Delete from Algolia + if (process.env.ALGOLIA_KEY) { + const index = algolia.initIndex('proposed_functions') + + try { + console.log(`Deleting ${name}#${prNumber} from Algolia index`) + await index.deleteObject(`${name}#${prNumber}`) + } catch (error) { + console.error( + `Error deleting ${name}#${prNumber} from Algolia:`, + error, + ) + } + } + } + continue + } + + const docFilename = file.filename + .replace(/^src/, 'docs') + .replace(/\.ts$/, '.mdx') + + let documentation: string | null = null + try { + documentation = await context.getFileContent(docFilename) + } catch { + console.log(`Documentation file not found for ${name}#${prNumber}`) + } + + let description: string | null = null + + if (documentation == null) { + if (body === undefined) { + body = await context.getIssueBody() + } + + if (body !== null) { + console.log(`Falling back to PR body for ${name}#${prNumber}`) + + const sections = body.split(/^(#+\s+)/m) + const summaryIndex = sections.findIndex( + (_section, index) => + index % 2 === 1 && + /^(Summary|Description)\b/.test(sections[index + 1]), + ) + if (summaryIndex !== -1) { + const summaryDepth = sections[summaryIndex].trim().length + const summaryContent = sections + .slice(summaryIndex + 1) + .join('') + .split(new RegExp(`^#{1,${summaryDepth}}\\s+`, 'm'))[0] + .trim() + + documentation = summaryContent + } else { + console.log( + `No "Summary" or "Description" section found in PR body for ${name}#${prNumber}`, + ) + } + } + } + + if (documentation) { + interface PageData { + title: string + description?: string + } + const renderResult = await renderPageMarkdown(documentation) + if (renderResult) { + documentation = renderResult.text + if (renderResult.data?.description) { + description = renderResult.data.description + } + } + } + + if (approvalRating === null) { + try { + approvalRating = await context.getApprovalRating() + } catch (error) { + console.error( + `Error getting approval rating for ${name}#${prNumber}:`, + error, + ) + approvalRating = 0 + } + } + + const commit = await getCommit(context.sha, context.owner, context.repo) + + const record = { + ref: `${context.owner ?? 'radashi-org'}/${context.repo ?? 'radashi'}#${context.branch ?? 'main'}`, + group: file.filename.split('/').slice(1, -1).join('/'), + name, + pr_number: prNumber, + approval_rating: approvalRating, + documentation, + status: context.status, + breaking: context.breaking, + description, + committed_at: commit?.date, + committed_by: commit?.author, + checks_passed: context.checksPassed, + pr_author: + context.owner && context.ownerAvatarUrl + ? { login: context.owner, avatar_url: context.ownerAvatarUrl } + : undefined, + } + + const { error } = await supabase.from('proposed_functions').insert(record) + + if (error) { + console.error( + `Error inserting ${name}#${prNumber} into Supabase:`, + error, + ) + } else { + console.log(`Successfully registered ${name}#${prNumber} in Supabase`) + } + + // Insert record into Algolia + if (process.env.ALGOLIA_KEY) { + try { + const index = algolia.initIndex('proposed_functions') + + const algoliaRecord = { + objectID: `${name}#${prNumber}`, + ...record, + } + + await index.saveObject(algoliaRecord) + console.log(`Successfully indexed ${name}#${prNumber} in Algolia`) + } catch (algoliaError) { + console.error( + `Error indexing ${name}#${prNumber} in Algolia:`, + algoliaError, + ) + } + } + } + } catch (error) { + console.error('Error processing PR:', error) + } +} diff --git a/scripts/radashi-db/src/util/bottleneck.ts b/scripts/radashi-db/src/util/bottleneck.ts new file mode 100644 index 00000000..06d268eb --- /dev/null +++ b/scripts/radashi-db/src/util/bottleneck.ts @@ -0,0 +1,158 @@ +declare const setTimeout: (callback: () => void, delay: number) => unknown + +/** + * The options for the `bottleneck` function. + * + * @see https://radashi-org.github.io/reference/async/bottleneck + */ +export interface BottleneckOptions { + /** + * The maximum number of calls to allow per interval. + * + * @default 1 + */ + max?: number + /** + * The interval at which to allow the maximum number of calls. + */ + interval: number + /** + * The maximum number of calls to allow at once. + * + * @default Infinity + */ + concurrency?: number +} + +/** + * The return type of the `bottleneck` function. + * + * @see https://radashi-org.github.io/reference/async/bottleneck + */ +export type BottledFunction any> = Fn & { + /** + * Prevent any throttled calls from ever running. + * + * Currently executing calls are not affected. + * + * @example + * ```ts + * const fn = bottleneck({ interval: 1000 }, () => console.log('hello')) + * fn() // <- Runs immediately + * fn() // <- Queued + * + * fn.cancel() + * // Now, your function won't run until another call. + * ``` + */ + cancel(): void +} + +/** + * Limit the rate at which a function is called. + * + * A maximum of `max` calls are allowed per `interval` milliseconds. + * + * Use the `concurrency` option for limiting the number of concurrent + * calls. + * + * @see https://radashi-org.github.io/reference/async/bottleneck + * @example + * ```ts + * const double = bottleneck( + * { max: 1, interval: 1000 }, + * async (x: number) => x * 2 + * ) + * double(1) // <- Runs immediately + * double(2) // <- Will wait 1 second + * double(3) // <- Will wait 2 seconds + * ``` + * @example Limited concurrency + * ```ts + * const double = bottleneck( + * { max: 5, interval: 1000, concurrency: 1 }, + * async (x: number) => x * 2 + * ) + * double(1) // <- Runs immediately + * double(2) // <- Will wait for 1 to finish + * double(3) // <- Will wait for 2 to finish + * ``` + */ +export function bottleneck any>( + { + max = 1, + interval, + concurrency = Number.POSITIVE_INFINITY, + }: BottleneckOptions, + fn: Fn, +): BottledFunction { + let numCalls = 0 + let numRunning = 0 + let startTime: number | undefined + + type QueueItem = { + args: TArgs + resolve: (value: TReturn | PromiseLike) => void + reject: (error: any) => void + } + + const queue: QueueItem[] = [] + + async function run(input: TArgs | QueueItem) { + const now = Date.now() + startTime ??= now + + if (now - startTime >= interval) { + startTime = now + numCalls = 0 + } + + if (numCalls < max && numRunning < concurrency) { + // If this is the first call, schedule the flush. + if (!numCalls && Number.isFinite(interval)) { + setTimeout(next, interval) + } + + let result: any + + numCalls++ + numRunning++ + try { + const args = Array.isArray(input) ? input : input.args + result = await fn(...args) + } catch (error) { + if (Array.isArray(input)) { + throw error + } + return input.reject(error) + } finally { + numRunning-- + next() + } + + return Array.isArray(input) ? result : input.resolve(result) + } + + if (Array.isArray(input)) { + // Return a queue promise for the throttled call. + return new Promise((resolve, reject) => { + queue.push({ args: input, resolve, reject }) + }) + } + + // Return the unused queue item to the queue. + queue.unshift(input) + } + + // This function is called when the interval has elapsed and after + // every finished call. + const next = () => queue.length && run(queue.shift()!) + + const bottled: BottledFunction = (...args) => run(args) + + bottled.cancel = () => { + queue.length = 0 + } + + return bottled +} diff --git a/scripts/radashi-db/src/util/markdown.ts b/scripts/radashi-db/src/util/markdown.ts new file mode 100644 index 00000000..94b909b3 --- /dev/null +++ b/scripts/radashi-db/src/util/markdown.ts @@ -0,0 +1,56 @@ +import MarkdownIt from 'markdown-it' +import mdFrontMatter from 'markdown-it-front-matter' +import { transform } from 'ultrahtml' +import sanitize from 'ultrahtml/transformers/sanitize' +import * as yaml from 'yaml' + +interface RenderedPage { + text: string + data: Data | undefined +} + +/** + * Render a markdown page and extract the front matter. + * + * If the front matter contains a `title` key, it will be added as a + * level 1 heading to the top of the rendered markdown. + */ +export async function renderPageMarkdown( + text: string | null | undefined, +): Promise | null> { + if (text == null) { + return null + } + + const markdown = new MarkdownIt({ html: true }) + + let data!: Data | undefined + markdown.use(mdFrontMatter, (text: string) => { + data = yaml.parse(text) + }) + + try { + text = markdown.render(text) + if (data && 'title' in data) { + text = markdown.render('# ' + data.title + '\n\n') + '\n\n' + text + } + } catch (error) { + if (error instanceof Error) { + error.message = 'Markdown renderer failed. ' + error.message + } + throw error + } + + if (text) { + try { + text = await transform(text, [sanitize({ allowComments: true })]) + } catch (error) { + if (error instanceof Error) { + error.message = 'Sanitization failed. ' + error.message + } + throw error + } + } + + return { text, data } +} diff --git a/scripts/radashi-db/tsconfig.json b/scripts/radashi-db/tsconfig.json new file mode 100644 index 00000000..bdb959bf --- /dev/null +++ b/scripts/radashi-db/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "allowImportingTsExtensions": true, + "moduleResolution": "node", + "outDir": "./dist/tmp", + "noEmitOnError": true, + "declaration": true, + "emitDeclarationOnly": true, + "module": "esnext", + "target": "esnext", + "lib": ["es2020"], + "esModuleInterop": true, + "skipLibCheck": true, + "strict": true + } +} From d98ae1865dd07075189c70ade84a205e69e6a69c Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Sat, 10 Aug 2024 13:52:07 -0400 Subject: [PATCH 29/35] chore: add script for generating an initial draft of release notes (#162) --- scripts/release-notes.sh | 8 + 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 | 335 +++++++++ 6 files changed, 1415 insertions(+) create mode 100755 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 100755 index 00000000..d411cf32 --- /dev/null +++ b/scripts/release-notes.sh @@ -0,0 +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 + +pnpm -s tsx scripts/release-notes/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..00eff53a --- /dev/null +++ b/scripts/release-notes/release-notes.ts @@ -0,0 +1,335 @@ +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. + * + * --limit \ + * Limit the number of commits to include in each section. (For testing purposes) + */ +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 + const limit = argv.limit ? +argv.limit : Number.POSITIVE_INFINITY + + 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) + + section.commits.length = Math.min(section.commits.length, limit) + + 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}.`, + '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` + 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', + 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 => [...getBulletedListRules(noun)], + }, + { + name: 'Types', + match: /^(fix|feat)\(types\)/, + noun: 'change', + rules: noun => [...getBulletedListRules(noun)], + }, + ] +} + +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 a2177eb44f5907300534e371ae4c15f34f3ee9ed Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Sat, 10 Aug 2024 13:52:51 -0400 Subject: [PATCH 30/35] ci: refine the pr-bundle-impact script (#163) --- .github/pull_request_template.md | 6 - .github/workflows/bundle-impact.yml | 4 +- package.json | 1 - scripts/ci/dedent.ts | 86 ++++++++++++ scripts/ci/package.json | 7 + scripts/ci/pnpm-lock.yaml | 187 +++++++++++++++++++++++++++ scripts/ci/pr-bundle-impact.test.cjs | 138 ++++++++++++++++++++ scripts/ci/pr-bundle-impact.ts | 40 ++++++ scripts/ci/weigh-changed.ts | 176 +++++++++++++++++++++++++ scripts/pr-bundle-impact.cjs | 50 ------- scripts/pr-bundle-impact.test.cjs | 89 ------------- scripts/weigh-changed.sh | 136 ------------------- 12 files changed, 637 insertions(+), 283 deletions(-) create mode 100644 scripts/ci/dedent.ts create mode 100644 scripts/ci/package.json create mode 100644 scripts/ci/pnpm-lock.yaml create mode 100644 scripts/ci/pr-bundle-impact.test.cjs create mode 100644 scripts/ci/pr-bundle-impact.ts create mode 100644 scripts/ci/weigh-changed.ts delete mode 100644 scripts/pr-bundle-impact.cjs delete mode 100644 scripts/pr-bundle-impact.test.cjs delete mode 100644 scripts/weigh-changed.sh diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 7ef377e3..0d28faeb 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -31,9 +31,3 @@ Yes No - -## Bundle impact - - - -_Calculating..._ diff --git a/.github/workflows/bundle-impact.yml b/.github/workflows/bundle-impact.yml index 8a5cecf7..eb00b495 100644 --- a/.github/workflows/bundle-impact.yml +++ b/.github/workflows/bundle-impact.yml @@ -21,8 +21,10 @@ jobs: - run: pnpm install - name: Run Bundle Impact uses: actions/github-script@v7 + env: + TARGET_BRANCH: ${{ github.base_ref }} with: github-token: ${{secrets.GITHUB_TOKEN}} script: | - const script = require('./scripts/pr-bundle-impact.cjs') + const script = await import('${{ github.workspace }}/scripts/ci/pr-bundle-impact.ts') await script.run({github, core, context}) diff --git a/package.json b/package.json index a7da0047..51336dc0 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,6 @@ "add-function": "bash ./scripts/add-function.sh", "bench": "vitest bench", "build": "tsup --clean", - "bundle-impact": "bash ./scripts/weigh-changed.sh", "dev": "tsup --clean --watch --sourcemap", "format": "bash ./scripts/format.sh", "lint": "concurrently -c=auto -g --kill-others-on-fail 'npm:lint:*'", diff --git a/scripts/ci/dedent.ts b/scripts/ci/dedent.ts new file mode 100644 index 00000000..ab9e3099 --- /dev/null +++ b/scripts/ci/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/ci/package.json b/scripts/ci/package.json new file mode 100644 index 00000000..0bd5c63f --- /dev/null +++ b/scripts/ci/package.json @@ -0,0 +1,7 @@ +{ + "private": true, + "dependencies": { + "execa": "^9.3.0", + "radashi": "12.2.0-beta.7fb6e89" + } +} diff --git a/scripts/ci/pnpm-lock.yaml b/scripts/ci/pnpm-lock.yaml new file mode 100644 index 00000000..ab532082 --- /dev/null +++ b/scripts/ci/pnpm-lock.yaml @@ -0,0 +1,187 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + execa: + specifier: ^9.3.0 + version: 9.3.0 + radashi: + specifier: 12.2.0-beta.7fb6e89 + version: 12.2.0-beta.7fb6e89 + +packages: + + '@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'} + + cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + + 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'} + + get-stream@9.0.1: + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + engines: {node: '>=18'} + + human-signals@7.0.0: + resolution: {integrity: sha512-74kytxOUSvNbjrT9KisAbaTZ/eJwD/LrbM/kh5j0IhPuJzwuA19dWvniFGwBzN9rVjg+O/e+F310PjObDXS+9Q==} + engines: {node: '>=18.18.0'} + + 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==} + + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + 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'} + + 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'} + + 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: + + '@sec-ant/readable-stream@0.4.1': {} + + '@sindresorhus/merge-streams@4.0.0': {} + + cross-spawn@7.0.3: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + 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 + + get-stream@9.0.1: + dependencies: + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 + + human-signals@7.0.0: {} + + is-plain-obj@4.1.0: {} + + is-stream@4.0.1: {} + + is-unicode-supported@2.0.0: {} + + isexe@2.0.0: {} + + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.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: {} + + 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: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + yoctocolors@2.1.1: {} diff --git a/scripts/ci/pr-bundle-impact.test.cjs b/scripts/ci/pr-bundle-impact.test.cjs new file mode 100644 index 00000000..b31a4999 --- /dev/null +++ b/scripts/ci/pr-bundle-impact.test.cjs @@ -0,0 +1,138 @@ +import { test, vi } from 'vitest' +import { dedent } from './dedent' +import { run } from './pr-bundle-impact' + + +vi.mock('./weigh-changed', () => ({ + weighChangedFunctions: vi.fn(), +})) + +test('adds the bundle impact to the PR body', async () => { + const pulls = { + 'radashi-org/radashi#1': { + body: dedent` + ## Summary + + This is a summary of the PR. + `, + }, + } + + const env = { + context: { + repo: { + owner: 'radashi-org', + repo: 'radashi', + }, + issue: { number: 1 }, + }, + github: { + rest: { + pulls: { + get({ owner, repo, pull_number }) { + return Promise.resolve({ + data: pulls[`${owner}/${repo}#${pull_number}`], + }) + }, + update: vi.fn(), + }, + }, + }, + core: { + info: vi.fn(), + setFailed: vi.fn(), + }, + } + + const { weighChangedFunctions } = await import('./weigh-changed') + + weighChangedFunctions.mockResolvedValue( + dedent` + | Status | File | Size | Difference (%) | + | --- | --- | --- | --- | + | M | src/foo/bar.ts | 110 | +10 (+10%) | + `, + ) + + await run(env) + + expect(env.github.rest.pulls.update).toHaveBeenCalled() + expect(env.core.info).toHaveBeenCalled() + expect(env.core.setFailed.mock.calls).toEqual([]) + + expect(env.github.rest.pulls.update.mock.calls).toMatchInlineSnapshot(` + [ + [ + { + "body": "## Summary + + This is a summary of the PR. + + ## Bundle impact + + | Status | File | Size | Difference (%) | + | --- | --- | --- | --- | + | M | src/foo/bar.ts | 110 | +10 (+10%) | + + ", + "owner": "radashi-org", + "pull_number": 1, + "repo": "radashi", + }, + ], + ] + `) + expect(env.core.info.mock.calls).toMatchInlineSnapshot(` + [ + [ + "fetching PR #1 data from radashi-org/radashi...", + ], + [ + "calculating bundle impact...", + ], + [ + "updating PR description...", + ], + [ + "PR description updated with bundle impact.", + ], + ] + `) + + // Now that we've tested adding the "Bundle Impact" section, let's test + // updating that section upon a subsequent run. + weighChangedFunctions.mockResolvedValue( + dedent` + | Status | File | Size | Difference (%) | + | --- | --- | --- | --- | + | M | src/foo/bar.ts | 120 | +20 (+20%) | + `, + ) + + env.github.rest.pulls.update.mockClear() + + await run(env) + + expect(env.github.rest.pulls.update.mock.calls).toMatchInlineSnapshot(` + [ + [ + { + "body": "## Summary + + This is a summary of the PR. + + ## Bundle impact + + | Status | File | Size | Difference (%) | + | --- | --- | --- | --- | + | M | src/foo/bar.ts | 120 | +20 (+20%) | + + ", + "owner": "radashi-org", + "pull_number": 1, + "repo": "radashi", + }, + ], + ] + `) +}) diff --git a/scripts/ci/pr-bundle-impact.ts b/scripts/ci/pr-bundle-impact.ts new file mode 100644 index 00000000..f51fb01a --- /dev/null +++ b/scripts/ci/pr-bundle-impact.ts @@ -0,0 +1,40 @@ +import { weighChangedFunctions } from './weigh-changed' + +export async function run({ github, core, context }) { + const repo = `${context.repo.owner}/${context.repo.repo}` + core.info(`fetching PR #${context.issue.number} data from ${repo}...`) + + const { data: pullRequest } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number, + }) + + core.info('calculating bundle impact...') + let bundleImpact = await weighChangedFunctions() + if (bundleImpact) { + bundleImpact = `## Bundle impact\n\n${bundleImpact}\n\n` + } + + const originalBody = pullRequest.body + const bundleImpactRegex = /## Bundle impact[\s\S]*?(?=##|$)/ + + let updatedBody: string + if (bundleImpactRegex.test(originalBody)) { + updatedBody = originalBody.replace(bundleImpactRegex, bundleImpact) + } else { + updatedBody = `${originalBody}\n\n${bundleImpact}` + } + + if (updatedBody !== originalBody) { + core.info('updating PR description...') + await github.rest.pulls.update({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number, + body: updatedBody, + }) + + core.info('PR description updated with bundle impact.') + } +} diff --git a/scripts/ci/weigh-changed.ts b/scripts/ci/weigh-changed.ts new file mode 100644 index 00000000..ba408d06 --- /dev/null +++ b/scripts/ci/weigh-changed.ts @@ -0,0 +1,176 @@ +import { execa } from 'execa' +import * as readline from 'node:readline/promises' +import { cluster, map, select } from 'radashi' + +export async function weighChangedFunctions() { + const targetBranch = await getTargetBranch() + const changedFiles = await getChangedFiles(targetBranch) + + await ensureEsbuildInstalled() + + const prevSizes = await getPreviousSizes(changedFiles, targetBranch) + + const columnCount = prevSizes.some(size => size !== 0) ? 3 : 2 + + let result = '' + + const addLine = (line: string) => (result += line + '\n') + + if (process.env.CI && changedFiles.length > 0) { + if (columnCount > 2) { + addLine('| Status | File | Size | Difference (%) |') + addLine('|---|---|---|---|') + } else { + addLine('| Status | File | Size |') + addLine('|---|---|---|') + } + } + + await map(changedFiles, async ({ status, name }, i) => { + const prevBytes = prevSizes[i] + + const bytes = await getFileSize(name, status) + + const diff = bytes - prevBytes + const diffStr = (diff >= 0 ? '+' : '') + diff + + let ratioStr = '' + if (columnCount > 2 && prevBytes !== 0) { + const ratio = Math.round((bytes / prevBytes - 1) * 100) + ratioStr = ` (${ratio >= 0 ? '+' : ''}${ratio}%)` + } + + if (process.env.CI) { + const sizeStr = i === 0 ? `${bytes} [^1337]` : `${bytes}` + if (columnCount > 2) { + addLine( + `| ${status} | \`${name}\` | ${sizeStr} | ${diffStr}${ratioStr} |`, + ) + } else { + addLine(`| ${status} | \`${name}\` | ${sizeStr} |`) + } + } else { + if (columnCount > 2 && prevBytes !== 0) { + addLine(`${name}: ${bytes} bytes (${diffStr} bytes)${ratioStr}`) + } else { + addLine(`${name}: ${bytes} bytes`) + } + } + }) + + if (process.env.CI && changedFiles.length > 0) { + addLine('') + addLine( + '[^1337]: Function size includes the `import` dependencies of the function.', + ) + addLine('') + } + + return result +} + +async function getTargetBranch(): Promise { + if (process.env.TARGET_BRANCH) { + return process.env.TARGET_BRANCH + } + + try { + const { stdout } = await execa('gh', [ + 'pr', + 'view', + '--json', + 'baseRefName', + '--jq', + '.baseRefName', + ]) + return stdout.trim() || 'main' + } catch { + return 'main' + } +} + +async function getChangedFiles(targetBranch: string) { + const { stdout } = await execa('git', [ + 'diff', + '--name-status', + `origin/${targetBranch}`, + 'HEAD', + '--', + 'src/**/*.ts', + ]) + + return select( + cluster(stdout.trim().split(/[\r\n\t]+/), 2), + ([status, name]) => ({ status, name }), + // Ignore changes to files like src/mod.ts and src/types.ts + ([, name]) => /[\\/]/.test(name), + ) +} + +async function installEsbuild(): Promise { + if (!process.env.CI) { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }) + + const answer = await rl.question( + 'Install esbuild to pnpm global store? (Y/n) ', + ) + + rl.close() + + if (answer.toLowerCase() === 'n') { + process.exit(1) + } + } + + await execa('pnpm', ['install', '-g', 'esbuild']) +} + +async function ensureEsbuildInstalled(): Promise { + try { + await execa('which', ['esbuild']) + } catch { + await installEsbuild() + } +} + +async function getPreviousSizes( + changedFiles: { status: string; name: string }[], + targetBranch: string, +): Promise { + const prevSizes: number[] = [] + + if ( + process.env.CI || + (await execa('git', ['status', '-s'])).stdout.trim() === '' + ) { + await execa('git', ['checkout', targetBranch]) + + for (const { status, name } of changedFiles) { + if (status === 'A') { + prevSizes.push(0) + } else { + const { stdout } = await execa('esbuild', [ + '--bundle', + '--minify', + name, + ]) + prevSizes.push(stdout.length) + } + } + + await execa('git', ['checkout', '-']) + } + + return prevSizes +} + +async function getFileSize(file: string, status: string): Promise { + if (status === 'D') { + return 0 + } + const { stdout } = await execa('esbuild', ['--bundle', '--minify', file]) + return stdout.length +} diff --git a/scripts/pr-bundle-impact.cjs b/scripts/pr-bundle-impact.cjs deleted file mode 100644 index bec903b5..00000000 --- a/scripts/pr-bundle-impact.cjs +++ /dev/null @@ -1,50 +0,0 @@ -// @ts-check -const { execSync } = require('child_process') - -exports.run = async function run({ github, core, context }, exec = execSync) { - try { - core.info('running bundle-impact script...') - - // 1. Run `pnpm bundle-impact` to get the bundle impact - const bundleImpact = exec('pnpm -s bundle-impact').toString().trim() - if (!bundleImpact) { - return - } - - core.info('fetching PR data...') - - // 2. Update the original post of the pull request - const { data: pullRequest } = await github.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.issue.number, - }) - - const originalBody = pullRequest.body - const bundleImpactRegex = /## Bundle impact[\s\S]*?(?=##|$)/ - - let updatedBody - if (bundleImpactRegex.test(originalBody)) { - updatedBody = originalBody.replace( - bundleImpactRegex, - `## Bundle impact\n\n${bundleImpact}\n\n`, - ) - } else { - updatedBody = `${originalBody}\n\n## Bundle impact\n\n${bundleImpact}\n\n` - } - - if (updatedBody !== originalBody) { - core.info('updating PR description...') - await github.rest.pulls.update({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.issue.number, - body: updatedBody, - }) - - core.info('PR description updated with bundle impact.') - } - } catch (error) { - core.setFailed(`Action failed with error: ${error}`) - } -} diff --git a/scripts/pr-bundle-impact.test.cjs b/scripts/pr-bundle-impact.test.cjs deleted file mode 100644 index c10473e0..00000000 --- a/scripts/pr-bundle-impact.test.cjs +++ /dev/null @@ -1,89 +0,0 @@ -import { test, vi } from 'vitest' -import { run } from './pr-bundle-impact.cjs' - -test('updates PR body with bundle impact', async () => { - const pulls = { - 'radashi-org/radashi#1': { - body: ` -## Bundle impact - -_Calculating..._ -`, - }, - } - - const env = { - context: { - repo: { - owner: 'radashi-org', - repo: 'radashi', - }, - issue: { number: 1 }, - }, - github: { - rest: { - pulls: { - get({ owner, repo, pull_number }) { - return Promise.resolve({ - data: pulls[`${owner}/${repo}#${pull_number}`], - }) - }, - update: vi.fn(), - }, - }, - }, - core: { - info: vi.fn(), - setFailed: vi.fn(), - }, - } - - await run(env, command => { - expect(command).toEqual('pnpm -s bundle-impact') - return ` -| File | Size | Difference (%) | -| --- | --- | --- | -| src/foo/bar.ts | 110 | +10 (+10%) | -` - }) - - expect(env.github.rest.pulls.update).toHaveBeenCalled() - expect(env.core.info).toHaveBeenCalled() - expect(env.core.setFailed.mock.calls).toEqual([]) - - expect(env.github.rest.pulls.update.mock.calls).toMatchInlineSnapshot(` - [ - [ - { - "body": " - ## Bundle impact - - | File | Size | Difference (%) | - | --- | --- | --- | - | src/foo/bar.ts | 110 | +10 (+10%) | - - ", - "owner": "radashi-org", - "pull_number": 1, - "repo": "radashi", - }, - ], - ] - `) - expect(env.core.info.mock.calls).toMatchInlineSnapshot(` - [ - [ - "running bundle-impact script...", - ], - [ - "fetching PR data...", - ], - [ - "updating PR description...", - ], - [ - "PR description updated with bundle impact.", - ], - ] - `) -}) diff --git a/scripts/weigh-changed.sh b/scripts/weigh-changed.sh deleted file mode 100644 index 02ae0712..00000000 --- a/scripts/weigh-changed.sh +++ /dev/null @@ -1,136 +0,0 @@ -#!/bin/bash -set -e - -# Try using gh to get the target branch, otherwise use 'main' as a fallback. -if [[ -z "$TARGET_BRANCH" ]]; then - TARGET_BRANCH=$(which gh > /dev/null 2>&1 && gh pr view --json baseRefName --jq .baseRefName 2> /dev/null || echo "") - TARGET_BRANCH=${TARGET_BRANCH:-main} -fi - -# Get the list of changed source files relative to the target branch. -CHANGES=$(git diff --name-status "origin/$TARGET_BRANCH" HEAD -- 'src/*/*.ts') - -# Separate the file statuses and file names. -FILE_STATUSES=() -FILE_NAMES=() - -i=0 -for item in $CHANGES; do - if [ $((i % 2)) -eq 0 ]; then - FILE_STATUSES+=($item) - else - FILE_NAMES+=($item) - fi - i=$((i + 1)) -done - -# If esbuild is not found, install it. -if ! which esbuild > /dev/null 2>&1; then - # If not CI, confirm the installation: - if [[ -z "$CI" ]]; then - read -n 1 -p "Install esbuild to pnpm global store? (Y/n) " confirm - echo - if [ "$confirm" == "n" ]; then - exit 1 - fi - - pnpm install -g esbuild - else - pnpm -s install -g esbuild - fi -fi - -# The sizes of the changed functions before the changes. -PREV_SIZES=() - -# Collect previous sizes if there are no uncommitted changes or it's CI. -if [ -n "$CI" ] || [ -z "$(git status -s)" ]; then - git checkout "$TARGET_BRANCH" &> /dev/null - - i=0 - for file in "${FILE_NAMES[@]}"; do - status=${FILE_STATUSES[$i]} - - if [ ! -f "$file" ]; then - PREV_SIZES+=(0) - else - PREV_SIZES+=($(esbuild --bundle --minify "$file" | wc -c | tr -d '[:space:]')) - fi - - i=$((i + 1)) - done - - git checkout - &> /dev/null -fi - -column_count=2 -if [ ${#PREV_SIZES[@]} -gt 0 ]; then - for size in "${PREV_SIZES[@]}"; do - if [ "$size" -ne 0 ]; then - column_count=3 - break - fi - done -fi - -if [[ -n "$CI" ]]; then - echo -e "\n\n" - if [ "$column_count" -gt 2 ]; then - echo "| Status | File | Size | Difference (%) |" - echo "|---|---|---|---|" - else - echo "| Status | File | Size |" - echo "|---|---|---|" - fi -fi - -i=0 -for file in "${FILE_NAMES[@]}"; do - status=${FILE_STATUSES[$i]} - - if [ "$column_count" -gt 2 ]; then - prev_bytes=${PREV_SIZES[$i]} - fi - - if [ "$status" == "D" ]; then - bytes=0 - else - bytes=$(esbuild --bundle --minify "$file" | wc -c | tr -d '[:space:]') - fi - - diff=$((bytes - prev_bytes)) - diff=$(if [ "$diff" -ge 0 ]; then echo "+"; fi)$diff - - if [ "$column_count" -gt 2 ] && [ "$prev_bytes" -ne 0 ]; then - ratio=$(echo "scale=0; (100 * $bytes / $prev_bytes) - 100" | bc -l) - ratio=$(if [ "$ratio" -ge 0 ]; then echo "+"; fi)$ratio - ratio=" ($ratio%)" - else - ratio="" - fi - - if [[ -n "$CI" ]]; then - if [ $i -eq 0 ]; then - bytes="$bytes [^1337]" - fi - if [ "$column_count" -gt 2 ]; then - echo "| $status | \`$file\` | $bytes | $diff$ratio |" - else - echo "| $status | \`$file\` | $bytes |" - fi - else - if [ "$column_count" -gt 2 ] && [ "$prev_bytes" -ne 0 ]; then - echo "$file: $bytes bytes ($diff bytes)$ratio" - else - echo "$file: $bytes bytes" - fi - fi - - i=$((i + 1)) -done - -if [[ -n "$CI" ]]; then - echo "" - echo "[^1337]: Function size includes the \`import\` dependencies of the function." - echo "" -fi From e150d5848791d3ab782e80f78372f86d8973dfb1 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Sat, 10 Aug 2024 14:30:55 -0400 Subject: [PATCH 31/35] chore: restore `pnpm bundle-impact` command --- package.json | 1 + scripts/bundle-impact.sh | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 scripts/bundle-impact.sh diff --git a/package.json b/package.json index 51336dc0..f0d77c69 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "add-function": "bash ./scripts/add-function.sh", "bench": "vitest bench", "build": "tsup --clean", + "bundle-impact": "bash ./scripts/bundle-impact.sh", "dev": "tsup --clean --watch --sourcemap", "format": "bash ./scripts/format.sh", "lint": "concurrently -c=auto -g --kill-others-on-fail 'npm:lint:*'", diff --git a/scripts/bundle-impact.sh b/scripts/bundle-impact.sh new file mode 100644 index 00000000..00a3ce2e --- /dev/null +++ b/scripts/bundle-impact.sh @@ -0,0 +1,11 @@ +#!/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 + +pnpm -s tsx -e ' +import {weighChangedFunctions} from "./scripts/ci/weigh-changed.ts"; +weighChangedFunctions().then(console.log) +' From c2ec02d042066cd4a6989acda33804a474ad8042 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Sat, 10 Aug 2024 18:39:41 -0400 Subject: [PATCH 32/35] chore: fix bundle-impact workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Obviously, we cannot import .ts files directly. Add "tsx" dependency and import that in such a way that the transformed module is still ESM (important since execa is ESM only). Also, the workflow was installing the wrong node_modules, so that‘s been fixed too. --- .github/workflows/bundle-impact.yml | 10 +- scripts/ci/package.json | 4 +- scripts/ci/pnpm-lock.yaml | 283 ++++++++++++++++++++++++++++ 3 files changed, 293 insertions(+), 4 deletions(-) diff --git a/.github/workflows/bundle-impact.yml b/.github/workflows/bundle-impact.yml index eb00b495..74bfde7e 100644 --- a/.github/workflows/bundle-impact.yml +++ b/.github/workflows/bundle-impact.yml @@ -18,13 +18,17 @@ jobs: with: node-version: '22.x' cache: pnpm - - run: pnpm install + + - run: | + pnpm install -C scripts/ci + - name: Run Bundle Impact uses: actions/github-script@v7 env: TARGET_BRANCH: ${{ github.base_ref }} + NODE_OPTIONS: '--import ./scripts/ci/node_modules/tsx/dist/esm/index.mjs' with: - github-token: ${{secrets.GITHUB_TOKEN}} + github-token: ${{ secrets.GITHUB_TOKEN }} script: | const script = await import('${{ github.workspace }}/scripts/ci/pr-bundle-impact.ts') - await script.run({github, core, context}) + await script.run({ github, core, context }) diff --git a/scripts/ci/package.json b/scripts/ci/package.json index 0bd5c63f..3b522f4f 100644 --- a/scripts/ci/package.json +++ b/scripts/ci/package.json @@ -1,7 +1,9 @@ { "private": true, + "type": "module", "dependencies": { "execa": "^9.3.0", - "radashi": "12.2.0-beta.7fb6e89" + "radashi": "12.2.0-beta.7fb6e89", + "tsx": "^4.17.0" } } diff --git a/scripts/ci/pnpm-lock.yaml b/scripts/ci/pnpm-lock.yaml index ab532082..1c4cec16 100644 --- a/scripts/ci/pnpm-lock.yaml +++ b/scripts/ci/pnpm-lock.yaml @@ -14,9 +14,156 @@ importers: radashi: specifier: 12.2.0-beta.7fb6e89 version: 12.2.0-beta.7fb6e89 + tsx: + specifier: ^4.17.0 + version: 4.17.0 packages: + '@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] + '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} @@ -28,6 +175,11 @@ packages: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} + esbuild@0.23.0: + resolution: {integrity: sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==} + engines: {node: '>=18'} + hasBin: true + execa@9.3.0: resolution: {integrity: sha512-l6JFbqnHEadBoVAVpN5dl2yCyfX28WoBAGaoQcNmLLSedOxTxcn2Qa83s8I/PA5i56vWru2OHOtrwF7Om2vqlg==} engines: {node: ^18.19.0 || >=20.5.0} @@ -36,10 +188,18 @@ packages: resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} engines: {node: '>=18'} + 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'} @@ -83,6 +243,9 @@ packages: 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'} @@ -99,6 +262,11 @@ packages: resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} engines: {node: '>=18'} + tsx@4.17.0: + resolution: {integrity: sha512-eN4mnDA5UMKDt4YZixo9tBioibaMBpoxBkD+rIPAjVmYERSG0/dWEY1CEFuV89CgASlKL499q8AhmkMnnjtOJg==} + engines: {node: '>=18.0.0'} + hasBin: true + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -110,6 +278,78 @@ packages: snapshots: + '@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 + '@sec-ant/readable-stream@0.4.1': {} '@sindresorhus/merge-streams@4.0.0': {} @@ -120,6 +360,33 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + 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 + execa@9.3.0: dependencies: '@sindresorhus/merge-streams': 4.0.0 @@ -139,11 +406,18 @@ snapshots: dependencies: is-unicode-supported: 2.0.0 + 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: {} is-plain-obj@4.1.0: {} @@ -170,6 +444,8 @@ snapshots: radashi@12.2.0-beta.7fb6e89: {} + resolve-pkg-maps@1.0.0: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -180,6 +456,13 @@ snapshots: strip-final-newline@4.0.0: {} + tsx@4.17.0: + dependencies: + esbuild: 0.23.0 + get-tsconfig: 4.7.6 + optionalDependencies: + fsevents: 2.3.3 + which@2.0.2: dependencies: isexe: 2.0.0 From 0105a55e8986f800c8fcbaf05b06caa559cf157b Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Sat, 10 Aug 2024 18:40:43 -0400 Subject: [PATCH 33/35] chore: fix import in Supabase seeding script --- scripts/radashi-db/seed-proposed-functions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/radashi-db/seed-proposed-functions.ts b/scripts/radashi-db/seed-proposed-functions.ts index 041a0205..392bc9c9 100644 --- a/scripts/radashi-db/seed-proposed-functions.ts +++ b/scripts/radashi-db/seed-proposed-functions.ts @@ -1,7 +1,7 @@ import { Octokit, type RestEndpointMethodTypes } from '@octokit/rest' import { parallel } from 'radashi' import { registerPullRequest } from './src/register-pr' -import { bottleneck } from './util/bottleneck' +import { bottleneck } from './src/util/bottleneck' async function seedProposedFunctions() { const octokit = new Octokit({ From 7870e13487bdd81135f720359f67f4a6af423c18 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Sat, 10 Aug 2024 19:10:17 -0400 Subject: [PATCH 34/35] chore: lint --- src/object/cloneDeep.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/object/cloneDeep.ts b/src/object/cloneDeep.ts index 76f588aa..9e516023 100644 --- a/src/object/cloneDeep.ts +++ b/src/object/cloneDeep.ts @@ -42,7 +42,7 @@ export interface CloningStrategy { ) => T | null } -export const DefaultCloningStrategy = { +export const DefaultCloningStrategy: CloningStrategy = { cloneMap( input: Map, track: (newParent: Map) => Map, From f7e06acb8910ca25f07c9319a48f12279a6082d0 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Sat, 10 Aug 2024 22:42:25 -0400 Subject: [PATCH 35/35] chore: rename folder inside ./scripts/ --- .github/workflows/bundle-impact.yml | 6 +++--- scripts/bundle-impact.sh | 2 +- scripts/{ci => bundle-impact}/dedent.ts | 0 scripts/{ci => bundle-impact}/package.json | 0 scripts/{ci => bundle-impact}/pnpm-lock.yaml | 0 scripts/{ci => bundle-impact}/pr-bundle-impact.test.cjs | 0 scripts/{ci => bundle-impact}/pr-bundle-impact.ts | 0 scripts/{ci => bundle-impact}/weigh-changed.ts | 0 8 files changed, 4 insertions(+), 4 deletions(-) rename scripts/{ci => bundle-impact}/dedent.ts (100%) rename scripts/{ci => bundle-impact}/package.json (100%) rename scripts/{ci => bundle-impact}/pnpm-lock.yaml (100%) rename scripts/{ci => bundle-impact}/pr-bundle-impact.test.cjs (100%) rename scripts/{ci => bundle-impact}/pr-bundle-impact.ts (100%) rename scripts/{ci => bundle-impact}/weigh-changed.ts (100%) diff --git a/.github/workflows/bundle-impact.yml b/.github/workflows/bundle-impact.yml index 74bfde7e..0bd090df 100644 --- a/.github/workflows/bundle-impact.yml +++ b/.github/workflows/bundle-impact.yml @@ -20,15 +20,15 @@ jobs: cache: pnpm - run: | - pnpm install -C scripts/ci + pnpm install -C scripts/bundle-impact - name: Run Bundle Impact uses: actions/github-script@v7 env: TARGET_BRANCH: ${{ github.base_ref }} - NODE_OPTIONS: '--import ./scripts/ci/node_modules/tsx/dist/esm/index.mjs' + NODE_OPTIONS: '--import ./scripts/bundle-impact/node_modules/tsx/dist/esm/index.mjs' with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | - const script = await import('${{ github.workspace }}/scripts/ci/pr-bundle-impact.ts') + const script = await import('${{ github.workspace }}/scripts/bundle-impact/pr-bundle-impact.ts') await script.run({ github, core, context }) diff --git a/scripts/bundle-impact.sh b/scripts/bundle-impact.sh index 00a3ce2e..54092057 100644 --- a/scripts/bundle-impact.sh +++ b/scripts/bundle-impact.sh @@ -6,6 +6,6 @@ if [ ! -d "scripts/release-notes/node_modules" ]; then fi pnpm -s tsx -e ' -import {weighChangedFunctions} from "./scripts/ci/weigh-changed.ts"; +import {weighChangedFunctions} from "./scripts/bundle-impact/weigh-changed.ts"; weighChangedFunctions().then(console.log) ' diff --git a/scripts/ci/dedent.ts b/scripts/bundle-impact/dedent.ts similarity index 100% rename from scripts/ci/dedent.ts rename to scripts/bundle-impact/dedent.ts diff --git a/scripts/ci/package.json b/scripts/bundle-impact/package.json similarity index 100% rename from scripts/ci/package.json rename to scripts/bundle-impact/package.json diff --git a/scripts/ci/pnpm-lock.yaml b/scripts/bundle-impact/pnpm-lock.yaml similarity index 100% rename from scripts/ci/pnpm-lock.yaml rename to scripts/bundle-impact/pnpm-lock.yaml diff --git a/scripts/ci/pr-bundle-impact.test.cjs b/scripts/bundle-impact/pr-bundle-impact.test.cjs similarity index 100% rename from scripts/ci/pr-bundle-impact.test.cjs rename to scripts/bundle-impact/pr-bundle-impact.test.cjs diff --git a/scripts/ci/pr-bundle-impact.ts b/scripts/bundle-impact/pr-bundle-impact.ts similarity index 100% rename from scripts/ci/pr-bundle-impact.ts rename to scripts/bundle-impact/pr-bundle-impact.ts diff --git a/scripts/ci/weigh-changed.ts b/scripts/bundle-impact/weigh-changed.ts similarity index 100% rename from scripts/ci/weigh-changed.ts rename to scripts/bundle-impact/weigh-changed.ts