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] 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 + } +}