├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── config.yml ├── release.yml └── workflows │ └── publish.yml ├── .gitignore ├── .prettierrc ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── biome.json ├── bun.lock ├── package.json ├── src ├── docs │ └── google.md ├── index.ts └── strategies │ ├── discord.ts │ ├── facebook.ts │ ├── github.ts │ ├── google.ts │ ├── linkedin.ts │ ├── microsoft.ts │ └── twitter.ts ├── tsconfig.json └── typedoc.json /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: '🐛 Bug report' 2 | description: Create a report to help us improve 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | Thank you for reporting an issue :pray:. 8 | 9 | This issue tracker is for reporting bugs found in `remix-auth-socials` (https://github.com/TheRealFlyingCoder/remix-auth-socials). 10 | If you have a question about how to achieve something and are struggling, please post a question 11 | inside of `remix-auth-socials` Discussions tab: https://github.com/TheRealFlyingCoder/remix-auth-socials/discussions 12 | 13 | Before submitting a new bug/issue, please check the links below to see if there is a solution or question posted there already: 14 | - `remix-auth-socials` Issues tab: https://github.com/TheRealFlyingCoder/remix-auth-socials/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc 15 | - `remix-auth-socials` closed issues tab: https://github.com/TheRealFlyingCoder/remix-auth-socials/issues?q=is%3Aissue+sort%3Aupdated-desc+is%3Aclosed 16 | - `remix-auth-socials` Discussions tab: https://github.com/TheRealFlyingCoder/remix-auth-socials/discussions 17 | 18 | The more information you fill in, the better the community can help you. 19 | - type: textarea 20 | id: description 21 | attributes: 22 | label: Describe the bug 23 | description: Provide a clear and concise description of the challenge you are running into. 24 | validations: 25 | required: true 26 | - type: input 27 | id: link 28 | attributes: 29 | label: Your Example Website or App 30 | description: | 31 | Which website or app were you using when the bug happened? 32 | Note: 33 | - Your bug will may get fixed much faster if we can run your code and it doesn't have dependencies other than the `remix-auth-socials` npm package. 34 | - To create a shareable code example you can use Stackblitz (https://stackblitz.com/). Please no localhost URLs. 35 | - Please read these tips for providing a minimal example: https://stackoverflow.com/help/mcve. 36 | placeholder: | 37 | e.g. https://stackblitz.com/edit/...... OR Github Repo 38 | validations: 39 | required: true 40 | - type: textarea 41 | id: steps 42 | attributes: 43 | label: Steps to Reproduce the Bug or Issue 44 | description: Describe the steps we have to take to reproduce the behavior. 45 | placeholder: | 46 | 1. Go to '...' 47 | 2. Click on '....' 48 | 3. Scroll down to '....' 49 | 4. See error 50 | validations: 51 | required: true 52 | - type: textarea 53 | id: expected 54 | attributes: 55 | label: Expected behavior 56 | description: Provide a clear and concise description of what you expected to happen. 57 | placeholder: | 58 | As a user, I expected ___ behavior but i am seeing ___ 59 | validations: 60 | required: true 61 | - type: textarea 62 | id: screenshots_or_videos 63 | attributes: 64 | label: Screenshots or Videos 65 | description: | 66 | If applicable, add screenshots or a video to help explain your problem. 67 | For more information on the supported file image/file types and the file size limits, please refer 68 | to the following link: https://docs.github.com/en/github/writing-on-github/working-with-advanced-formatting/attaching-files 69 | placeholder: | 70 | You can drag your video or image files inside of this editor ↓ 71 | - type: textarea 72 | id: platform 73 | attributes: 74 | label: Platform 75 | value: | 76 | - OS: [e.g. macOS, Windows, Linux] 77 | - Browser: [e.g. Chrome, Safari, Firefox] 78 | - Version: [e.g. 91.1] 79 | validations: 80 | required: true 81 | - type: textarea 82 | id: additional 83 | attributes: 84 | label: Additional context 85 | description: Add any other context about the problem here. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 🤔 Feature Requests & Questions 4 | url: https://github.com/TheRealFlyingCoder/remix-auth-socials/discussions 5 | about: Please ask and answer questions here. 6 | - name: 💬 Remix Discord Channel 7 | url: https://rmx.as/discord 8 | about: Interact with other people using Remix 📀 9 | - name: 💬 New Updates (Twitter) 10 | url: https://twitter.com/remix_run 11 | about: Stay up to date with Remix news on twitter 12 | - name: 🍿 Remix YouTube Channel 13 | url: https://www.youtube.com/channel/UC_9cztXyAZCli9Cky6NWWwQ 14 | about: Are you a techlead or wanting to learn more about Remix in depth? Checkout the Remix YouTube Channel -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | categories: 3 | - title: Documentation Changes 4 | labels: 5 | - documentation 6 | - title: New Features 7 | labels: 8 | - enhancement 9 | - title: Bug Fixes 10 | labels: 11 | - bug 12 | - title: Other Changes 13 | labels: 14 | - "*" 15 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v1 12 | - uses: actions/setup-node@v1 13 | with: 14 | node-version: 14 15 | registry-url: https://registry.npmjs.org/ 16 | - run: npm install 17 | - run: npm run build 18 | - run: npm publish --access public 19 | env: 20 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /build 3 | /coverage 4 | /docs 5 | *.log 6 | .DS_Store -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "auto", 3 | "singleQuote": true, 4 | "tabWidth": 4, 5 | "trailingComma": "all", 6 | "useTabs": true 7 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution 2 | If you'd like to contribute by adding a new community package, please ensure they have followed the main requirements of exporting the default strategy name, and having fully typed scopes in the form of an array or string. 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Sergio Xalambrí 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Remix Auth Socials 2 | 3 | WARNING: V3 Drops outdated packages that dont work with RR7. So V3 is considered broken / beta / use at your own risk 4 | 5 | I have started the process of updating some and moving them temporarily into the package to provide access to RR7. I dont maintain this super often but if you want to PR one of the others feel free. 6 | 7 | Currently Unavailable in V3: 8 | ❌ Discord 9 | ❌ Linkedin 10 | ❌ X / Twitter 11 | 12 | Facebook and Google are untested as of yet 13 | 14 | > A collection of Remix Auth strategies for Oauth2 Social logins. 15 | 16 | It's rare to see only one social login button, and no one likes a big package.json so here we are 👀 17 | 18 | Remix auth socials collates community Oauth packages in a way that allows you to set up multiple social logins with ease. 19 | 20 | ## The Collection: 21 | 22 | Please visit the repo's of each package to understand the specifics on their usage, and raise issues. 23 | 24 | [remix-auth-discord](https://github.com/JonnyBnator/remix-auth-discord) - By [Jonny](https://github.com/JonnyBnator) 25 | 26 | [remix-auth-facebook](https://github.com/manosim/remix-auth-facebook) - By [Manos](https://github.com/manosim) 27 | 28 | [remix-auth-github](https://github.com/sergiodxa/remix-auth-github) - By [Sergio](https://github.com/sergiodxa) 29 | 30 | [remix-auth-google](https://github.com/pbteja1998/remix-auth-google) - By [Bhanu](https://github.com/pbteja1998) 31 | 32 | [remix-auth-microsoft](https://github.com/juhanakristian/remix-auth-microsoft) - By [Juhana](https://github.com/juhanakristian) 33 | 34 | [remix-auth-twitter](https://github.com/na2hiro/remix-auth-twitter) - By [na2hiro](https://github.com/na2hiro) 35 | 36 | [remix-auth-linkedin](https://github.com/Lzok/remix-auth-linkedin) - By [Lzok](https://github.com/Lzok) 37 | 38 | ## Supported runtimes 39 | 40 | All strategies will support cloudflare 41 | 42 | | Runtime | Has Support | 43 | | ---------- | ----------- | 44 | | Node.js | ✅ | 45 | | Cloudflare | ✅ | 46 | 47 | ## How to use 48 | 49 | ### Setup your routes 50 | 51 | To begin we will set up a dynamic route, that can handle each social on the fly 52 | 53 | ```tsx 54 | // app/routes/auth/$provider.tsx 55 | import { ActionArgs, redirect } from '@remix-run/node'; 56 | import { authenticator } from '~/server/auth.server'; 57 | 58 | export let loader = () => redirect('/login'); 59 | 60 | export let action = ({ request, params }: ActionArgs) => { 61 | return authenticator.authenticate(params.provider, request); 62 | }; 63 | ``` 64 | 65 | ```tsx 66 | // app/routes/auth/$provider.callback.tsx 67 | import { LoaderArgs } from '@remix-run/node'; 68 | import { authenticator } from '~/server/auth.server'; 69 | 70 | export let loader = ({ request, params }: LoaderArgs) => { 71 | return authenticator.authenticate(params.provider, request, { 72 | successRedirect: '/dashboard', 73 | failureRedirect: '/login', 74 | }); 75 | }; 76 | ``` 77 | 78 | Now you are free to include social buttons on the login page however you like 79 | 80 | ```tsx 81 | // app/routes/login.tsx 82 | import { Form } from '@remix-run/react'; 83 | import { SocialsProvider } from 'remix-auth-socials'; 84 | 85 | interface SocialButtonProps { 86 | provider: SocialsProvider; 87 | label: string; 88 | } 89 | 90 | const SocialButton: React.FC = ({ provider, label }) => ( 91 |
92 | 93 |
94 | ); 95 | 96 | export default function Login() { 97 | return ( 98 | <> 99 | 103 | 107 | 111 | 115 | 119 | 123 | 124 | ); 125 | } 126 | ``` 127 | 128 | You will also need a logout route 129 | 130 | ```ts 131 | // app/routes/logout.tsx 132 | import { ActionArgs } from '@remix-run/node'; 133 | import { authenticator } from '~/server/auth.server'; 134 | 135 | export let action = async ({ request, params }: ActionArgs) => { 136 | await authenticator.logout(request, { redirectTo: '/' }); 137 | }; 138 | ``` 139 | 140 | ### Create the strategy instance 141 | 142 | For each social you want to use, you must initialise it in your `auth.server.ts` file. 143 | 144 | ```ts 145 | // app/server/auth.server.ts 146 | import { Authenticator } from 'remix-auth'; 147 | import { 148 | GoogleStrategy, 149 | FacebookStrategy, 150 | SocialsProvider, 151 | } from 'remix-auth-socials'; 152 | import { sessionStorage } from '~/services/session.server'; 153 | 154 | // Create an instance of the authenticator 155 | export let authenticator = new Authenticator(sessionStorage, { 156 | sessionKey: '_session', 157 | }); 158 | // You may specify a type which the strategies will return (this will be stored in the session) 159 | // export let authenticator = new Authenticator(sessionStorage, { sessionKey: '_session' }); 160 | 161 | const getCallback = (provider: SocialsProvider) => { 162 | return `http://localhost:3333/auth/${provider}/callback`; 163 | }; 164 | 165 | authenticator.use( 166 | new GoogleStrategy( 167 | { 168 | clientID: 'YOUR_CLIENT_ID', 169 | clientSecret: 'YOUR_CLIENT_SECRET', 170 | callbackURL: getCallback(SocialsProvider.GOOGLE), 171 | }, 172 | async ({ profile }) => { 173 | // here you would find or create a user in your database 174 | return profile; 175 | }, 176 | ), 177 | ); 178 | 179 | authenticator.use( 180 | new FacebookStrategy( 181 | { 182 | clientID: 'YOUR_CLIENT_ID', 183 | clientSecret: 'YOUR_CLIENT_SECRET', 184 | callbackURL: getCallback(SocialsProvider.FACEBOOK), 185 | }, 186 | async ({ profile }) => {}, 187 | ), 188 | ); 189 | ``` 190 | 191 | ### Add a protected route and an automatic success redirect 192 | 193 | Here's an example of a protected route 194 | 195 | ```tsx 196 | // app/routes/dashboard.tsx 197 | import { useLoaderData, Form } from '@remix-run/react'; 198 | import { LoaderArgs } from '@remix-run/node'; 199 | import { authenticator } from '~/server/auth.server'; 200 | 201 | export let loader = async ({ request, params }: LoaderArgs) => { 202 | const user = await authenticator.isAuthenticated(request, { 203 | failureRedirect: '/', 204 | }); 205 | 206 | return { user }; 207 | }; 208 | 209 | export default function Dashboard() { 210 | const { user } = useLoaderData(); 211 | 212 | return ( 213 |
214 |

Welcome {user.displayName}!

215 |

This is a protected page

216 |
217 | 218 |
219 |
220 | ); 221 | } 222 | ``` 223 | 224 | You might also want your index route to redirect to the dashboard for logged in users. 225 | 226 | ```tsx 227 | // app/routes/index.tsx 228 | import { useLoaderData } from '@remix-run/react'; 229 | import { LoaderArgs } from '@remix-run/node'; 230 | import { authenticator } from '~/server/auth.server'; 231 | 232 | export let loader = async ({ request, params }: LoaderArgs) => { 233 | const user = await authenticator.isAuthenticated(request, { 234 | successRedirect: '/dashboard', 235 | }); 236 | return user; 237 | }; 238 | 239 | export default function Index() { 240 | return ( 241 |
242 |

Welcome!

243 |

244 | Please log in 245 |

246 |
247 | ); 248 | } 249 | ``` 250 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.7.1/schema.json", 3 | "organizeImports": { 4 | "enabled": true 5 | }, 6 | "linter": { 7 | "enabled": true, 8 | "rules": { 9 | "recommended": true, 10 | "correctness": { 11 | "useHookAtTopLevel": "error" 12 | }, 13 | "performance": { 14 | "noBarrelFile": "error", 15 | "noReExportAll": "error" 16 | }, 17 | "style": { 18 | "noDefaultExport": "error", 19 | "noNegationElse": "error", 20 | "useConst": "off", 21 | "useExportType": "off", 22 | "useImportType": "off" 23 | }, 24 | "suspicious": { 25 | "noConsoleLog": "warn", 26 | "noEmptyBlockStatements": "warn", 27 | "noSkippedTests": "error" 28 | } 29 | } 30 | }, 31 | "formatter": { "enabled": true }, 32 | "vcs": { 33 | "enabled": true, 34 | "clientKind": "git", 35 | "defaultBranch": "main", 36 | "useIgnoreFile": true 37 | }, 38 | "overrides": [ 39 | { 40 | "include": ["**/*.md"], 41 | "formatter": { "indentStyle": "tab" } 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /bun.lock: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1, 3 | "workspaces": { 4 | "": { 5 | "name": "remix-auth-socials", 6 | "dependencies": { 7 | "remix-auth": "^4.2.0", 8 | "remix-auth-github": "^3.0.2", 9 | "remix-auth-microsoft": "^3.0.1", 10 | "remix-auth-oauth2": "^3.4.0", 11 | }, 12 | "devDependencies": { 13 | "@arethetypeswrong/cli": "^0.17.1", 14 | "@biomejs/biome": "^1.8.3", 15 | "@mjackson/headers": "^0.10.0", 16 | "@total-typescript/tsconfig": "^1.0.4", 17 | "@types/bun": "^1.1.14", 18 | "msw": "^2.7.0", 19 | "typedoc": "^0.28.0", 20 | "typedoc-plugin-mdn-links": "^5.0.1", 21 | "typescript": "^5.5.4", 22 | }, 23 | }, 24 | }, 25 | "packages": { 26 | "@andrewbranch/untar.js": ["@andrewbranch/untar.js@1.0.3", "", {}, "sha512-Jh15/qVmrLGhkKJBdXlK1+9tY4lZruYjsgkDFj08ZmDiWVBLJcqkok7Z0/R0In+i1rScBpJlSvrTS2Lm41Pbnw=="], 27 | 28 | "@arethetypeswrong/cli": ["@arethetypeswrong/cli@0.17.4", "", { "dependencies": { "@arethetypeswrong/core": "0.17.4", "chalk": "^4.1.2", "cli-table3": "^0.6.3", "commander": "^10.0.1", "marked": "^9.1.2", "marked-terminal": "^7.1.0", "semver": "^7.5.4" }, "bin": { "attw": "dist/index.js" } }, "sha512-AeiKxtf67XD/NdOqXgBOE5TZWH3EOCt+0GkbUpekOzngc+Q/cRZ5azjWyMxISxxfp0EItgm5NoSld9p7BAA5xQ=="], 29 | 30 | "@arethetypeswrong/core": ["@arethetypeswrong/core@0.17.4", "", { "dependencies": { "@andrewbranch/untar.js": "^1.0.3", "@loaderkit/resolve": "^1.0.2", "cjs-module-lexer": "^1.2.3", "fflate": "^0.8.2", "lru-cache": "^10.4.3", "semver": "^7.5.4", "typescript": "5.6.1-rc", "validate-npm-package-name": "^5.0.0" } }, "sha512-Izvir8iIoU+X4SKtDAa5kpb+9cpifclzsbA8x/AZY0k0gIfXYQ1fa1B6Epfe6vNA2YfDX8VtrZFgvnXB6aPEoQ=="], 31 | 32 | "@biomejs/biome": ["@biomejs/biome@1.9.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "1.9.4", "@biomejs/cli-darwin-x64": "1.9.4", "@biomejs/cli-linux-arm64": "1.9.4", "@biomejs/cli-linux-arm64-musl": "1.9.4", "@biomejs/cli-linux-x64": "1.9.4", "@biomejs/cli-linux-x64-musl": "1.9.4", "@biomejs/cli-win32-arm64": "1.9.4", "@biomejs/cli-win32-x64": "1.9.4" }, "bin": { "biome": "bin/biome" } }, "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog=="], 33 | 34 | "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@1.9.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw=="], 35 | 36 | "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@1.9.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg=="], 37 | 38 | "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g=="], 39 | 40 | "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA=="], 41 | 42 | "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg=="], 43 | 44 | "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg=="], 45 | 46 | "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@1.9.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg=="], 47 | 48 | "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@1.9.4", "", { "os": "win32", "cpu": "x64" }, "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA=="], 49 | 50 | "@braidai/lang": ["@braidai/lang@1.1.1", "", {}, "sha512-5uM+no3i3DafVgkoW7ayPhEGHNNBZCSj5TrGDQt0ayEKQda5f3lAXlmQg0MR5E0gKgmTzUUEtSWHsEC3h9jUcg=="], 51 | 52 | "@bundled-es-modules/cookie": ["@bundled-es-modules/cookie@2.0.1", "", { "dependencies": { "cookie": "^0.7.2" } }, "sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw=="], 53 | 54 | "@bundled-es-modules/statuses": ["@bundled-es-modules/statuses@1.0.1", "", { "dependencies": { "statuses": "^2.0.1" } }, "sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg=="], 55 | 56 | "@bundled-es-modules/tough-cookie": ["@bundled-es-modules/tough-cookie@0.1.6", "", { "dependencies": { "@types/tough-cookie": "^4.0.5", "tough-cookie": "^4.1.4" } }, "sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw=="], 57 | 58 | "@colors/colors": ["@colors/colors@1.5.0", "", {}, "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="], 59 | 60 | "@edgefirst-dev/data": ["@edgefirst-dev/data@0.0.4", "", {}, "sha512-VLhlvEPDJ0Sd0pE6sAYTQkIqZCXVonaWlgRJIQQHzfjTXCadF77qqHj5NxaPSc4wCul0DJO/0MnejVqJAXUiRg=="], 61 | 62 | "@gerrit0/mini-shiki": ["@gerrit0/mini-shiki@3.3.0", "", { "dependencies": { "@shikijs/engine-oniguruma": "^3.3.0", "@shikijs/langs": "^3.3.0", "@shikijs/themes": "^3.3.0", "@shikijs/types": "^3.3.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-frvArO0+s5Viq68uSod5SieLPVM2cLpXoQ1e07lURwgADXpL/MOypM7jPz9otks0g2DIe2YedDAeVrDyYJZRxA=="], 63 | 64 | "@inquirer/confirm": ["@inquirer/confirm@5.1.9", "", { "dependencies": { "@inquirer/core": "^10.1.10", "@inquirer/type": "^3.0.6" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-NgQCnHqFTjF7Ys2fsqK2WtnA8X1kHyInyG+nMIuHowVTIgIuS10T4AznI/PvbqSpJqjCUqNBlKGh1v3bwLFL4w=="], 65 | 66 | "@inquirer/core": ["@inquirer/core@10.1.10", "", { "dependencies": { "@inquirer/figures": "^1.0.11", "@inquirer/type": "^3.0.6", "ansi-escapes": "^4.3.2", "cli-width": "^4.1.0", "mute-stream": "^2.0.0", "signal-exit": "^4.1.0", "wrap-ansi": "^6.2.0", "yoctocolors-cjs": "^2.1.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-roDaKeY1PYY0aCqhRmXihrHjoSW2A00pV3Ke5fTpMCkzcGF64R8e0lw3dK+eLEHwS4vB5RnW1wuQmvzoRul8Mw=="], 67 | 68 | "@inquirer/figures": ["@inquirer/figures@1.0.11", "", {}, "sha512-eOg92lvrn/aRUqbxRyvpEWnrvRuTYRifixHkYVpJiygTgVSBIHDqLh0SrMQXkafvULg3ck11V7xvR+zcgvpHFw=="], 69 | 70 | "@inquirer/type": ["@inquirer/type@3.0.6", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-/mKVCtVpyBu3IDarv0G+59KC4stsD5mDsGpYh+GKs1NZT88Jh52+cuoA1AtLk2Q0r/quNl+1cSUyLRHBFeD0XA=="], 71 | 72 | "@loaderkit/resolve": ["@loaderkit/resolve@1.0.4", "", { "dependencies": { "@braidai/lang": "^1.0.0" } }, "sha512-rJzYKVcV4dxJv+vW6jlvagF8zvGxHJ2+HTr1e2qOejfmGhAApgJHl8Aog4mMszxceTRiKTTbnpgmTO1bEZHV/A=="], 73 | 74 | "@mjackson/headers": ["@mjackson/headers@0.10.0", "", {}, "sha512-U1Eu1gF979k7ZoIBsJyD+T5l9MjtPONsZfoXfktsQHPJD0s7SokBGx+tLKDLsOY+gzVYAWS0yRFDNY8cgbQzWQ=="], 75 | 76 | "@mswjs/interceptors": ["@mswjs/interceptors@0.37.6", "", { "dependencies": { "@open-draft/deferred-promise": "^2.2.0", "@open-draft/logger": "^0.3.0", "@open-draft/until": "^2.0.0", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "strict-event-emitter": "^0.5.1" } }, "sha512-wK+5pLK5XFmgtH3aQ2YVvA3HohS3xqV/OxuVOdNx9Wpnz7VE/fnC+e1A7ln6LFYeck7gOJ/dsZV6OLplOtAJ2w=="], 77 | 78 | "@open-draft/deferred-promise": ["@open-draft/deferred-promise@2.2.0", "", {}, "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA=="], 79 | 80 | "@open-draft/logger": ["@open-draft/logger@0.3.0", "", { "dependencies": { "is-node-process": "^1.2.0", "outvariant": "^1.4.0" } }, "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ=="], 81 | 82 | "@open-draft/until": ["@open-draft/until@2.1.0", "", {}, "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg=="], 83 | 84 | "@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="], 85 | 86 | "@oslojs/binary": ["@oslojs/binary@1.0.0", "", {}, "sha512-9RCU6OwXU6p67H4NODbuxv2S3eenuQ4/WFLrsq+K/k682xrznH5EVWA7N4VFk9VYVcbFtKqur5YQQZc0ySGhsQ=="], 87 | 88 | "@oslojs/crypto": ["@oslojs/crypto@1.0.1", "", { "dependencies": { "@oslojs/asn1": "1.0.0", "@oslojs/binary": "1.0.0" } }, "sha512-7n08G8nWjAr/Yu3vu9zzrd0L9XnrJfpMioQcvCMxBIiF5orECHe5/3J0jmXRVvgfqMm/+4oxlQ+Sq39COYLcNQ=="], 89 | 90 | "@oslojs/encoding": ["@oslojs/encoding@1.1.0", "", {}, "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ=="], 91 | 92 | "@oslojs/jwt": ["@oslojs/jwt@0.2.0", "", { "dependencies": { "@oslojs/encoding": "0.4.1" } }, "sha512-bLE7BtHrURedCn4Mco3ma9L4Y1GR2SMBuIvjWr7rmQ4/W/4Jy70TIAgZ+0nIlk0xHz1vNP8x8DCns45Sb2XRbg=="], 93 | 94 | "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.3.0", "", { "dependencies": { "@shikijs/types": "3.3.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-l0vIw+GxeNU7uGnsu6B+Crpeqf+WTQ2Va71cHb5ZYWEVEPdfYwY5kXwYqRJwHrxz9WH+pjSpXQz+TJgAsrkA5A=="], 95 | 96 | "@shikijs/langs": ["@shikijs/langs@3.3.0", "", { "dependencies": { "@shikijs/types": "3.3.0" } }, "sha512-zt6Kf/7XpBQKSI9eqku+arLkAcDQ3NHJO6zFjiChI8w0Oz6Jjjay7pToottjQGjSDCFk++R85643WbyINcuL+g=="], 97 | 98 | "@shikijs/themes": ["@shikijs/themes@3.3.0", "", { "dependencies": { "@shikijs/types": "3.3.0" } }, "sha512-tXeCvLXBnqq34B0YZUEaAD1lD4lmN6TOHAhnHacj4Owh7Ptb/rf5XCDeROZt2rEOk5yuka3OOW2zLqClV7/SOg=="], 99 | 100 | "@shikijs/types": ["@shikijs/types@3.3.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-KPCGnHG6k06QG/2pnYGbFtFvpVJmC3uIpXrAiPrawETifujPBv0Se2oUxm5qYgjCvGJS9InKvjytOdN+bGuX+Q=="], 101 | 102 | "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], 103 | 104 | "@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="], 105 | 106 | "@total-typescript/tsconfig": ["@total-typescript/tsconfig@1.0.4", "", {}, "sha512-fO4ctMPGz1kOFOQ4RCPBRBfMy3gDn+pegUfrGyUFRMv/Rd0ZM3/SHH3hFCYG4u6bPLG8OlmOGcBLDexvyr3A5w=="], 107 | 108 | "@types/bun": ["@types/bun@1.2.12", "", { "dependencies": { "bun-types": "1.2.12" } }, "sha512-lY/GQTXDGsolT/TiH72p1tuyUORuRrdV7VwOTOjDOt8uTBJQOJc5zz3ufwwDl0VBaoxotSk4LdP0hhjLJ6ypIQ=="], 109 | 110 | "@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="], 111 | 112 | "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="], 113 | 114 | "@types/node": ["@types/node@22.15.3", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw=="], 115 | 116 | "@types/statuses": ["@types/statuses@2.0.5", "", {}, "sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A=="], 117 | 118 | "@types/tough-cookie": ["@types/tough-cookie@4.0.5", "", {}, "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA=="], 119 | 120 | "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], 121 | 122 | "ansi-escapes": ["ansi-escapes@7.0.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw=="], 123 | 124 | "ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], 125 | 126 | "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], 127 | 128 | "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], 129 | 130 | "arctic": ["arctic@3.6.0", "", { "dependencies": { "@oslojs/crypto": "1.0.1", "@oslojs/encoding": "1.1.0", "@oslojs/jwt": "0.2.0" } }, "sha512-egHDsCqEacb6oSHz5QSSxNhp07J+QJwJdPvs0katL+mNM5LaGQVqxmcdq1KwfaSNSAlVumBBs0MRExS88TxbMg=="], 131 | 132 | "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], 133 | 134 | "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], 135 | 136 | "brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], 137 | 138 | "bun-types": ["bun-types@1.2.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-tvWMx5vPqbRXgE8WUZI94iS1xAYs8bkqESR9cxBB1Wi+urvfTrF1uzuDgBHFAdO0+d2lmsbG3HmeKMvUyj6pWA=="], 139 | 140 | "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], 141 | 142 | "char-regex": ["char-regex@1.0.2", "", {}, "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="], 143 | 144 | "cjs-module-lexer": ["cjs-module-lexer@1.4.3", "", {}, "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q=="], 145 | 146 | "cli-highlight": ["cli-highlight@2.1.11", "", { "dependencies": { "chalk": "^4.0.0", "highlight.js": "^10.7.1", "mz": "^2.4.0", "parse5": "^5.1.1", "parse5-htmlparser2-tree-adapter": "^6.0.0", "yargs": "^16.0.0" }, "bin": { "highlight": "bin/highlight" } }, "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg=="], 147 | 148 | "cli-table3": ["cli-table3@0.6.5", "", { "dependencies": { "string-width": "^4.2.0" }, "optionalDependencies": { "@colors/colors": "1.5.0" } }, "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ=="], 149 | 150 | "cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="], 151 | 152 | "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], 153 | 154 | "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], 155 | 156 | "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], 157 | 158 | "commander": ["commander@10.0.1", "", {}, "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug=="], 159 | 160 | "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], 161 | 162 | "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], 163 | 164 | "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], 165 | 166 | "emojilib": ["emojilib@2.4.0", "", {}, "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw=="], 167 | 168 | "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], 169 | 170 | "environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="], 171 | 172 | "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], 173 | 174 | "fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], 175 | 176 | "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], 177 | 178 | "graphql": ["graphql@16.11.0", "", {}, "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw=="], 179 | 180 | "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], 181 | 182 | "headers-polyfill": ["headers-polyfill@4.0.3", "", {}, "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ=="], 183 | 184 | "highlight.js": ["highlight.js@10.7.3", "", {}, "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="], 185 | 186 | "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], 187 | 188 | "is-node-process": ["is-node-process@1.2.0", "", {}, "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw=="], 189 | 190 | "linkify-it": ["linkify-it@5.0.0", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ=="], 191 | 192 | "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], 193 | 194 | "lunr": ["lunr@2.3.9", "", {}, "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow=="], 195 | 196 | "markdown-it": ["markdown-it@14.1.0", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg=="], 197 | 198 | "marked": ["marked@9.1.6", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q=="], 199 | 200 | "marked-terminal": ["marked-terminal@7.3.0", "", { "dependencies": { "ansi-escapes": "^7.0.0", "ansi-regex": "^6.1.0", "chalk": "^5.4.1", "cli-highlight": "^2.1.11", "cli-table3": "^0.6.5", "node-emoji": "^2.2.0", "supports-hyperlinks": "^3.1.0" }, "peerDependencies": { "marked": ">=1 <16" } }, "sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw=="], 201 | 202 | "mdurl": ["mdurl@2.0.0", "", {}, "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="], 203 | 204 | "minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], 205 | 206 | "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 207 | 208 | "msw": ["msw@2.7.6", "", { "dependencies": { "@bundled-es-modules/cookie": "^2.0.1", "@bundled-es-modules/statuses": "^1.0.1", "@bundled-es-modules/tough-cookie": "^0.1.6", "@inquirer/confirm": "^5.0.0", "@mswjs/interceptors": "^0.37.0", "@open-draft/deferred-promise": "^2.2.0", "@open-draft/until": "^2.1.0", "@types/cookie": "^0.6.0", "@types/statuses": "^2.0.4", "graphql": "^16.8.1", "headers-polyfill": "^4.0.2", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "path-to-regexp": "^6.3.0", "picocolors": "^1.1.1", "strict-event-emitter": "^0.5.1", "type-fest": "^4.26.1", "yargs": "^17.7.2" }, "peerDependencies": { "typescript": ">= 4.8.x" }, "optionalPeers": ["typescript"], "bin": { "msw": "cli/index.js" } }, "sha512-P+rwn43ktxN8ghcl8q+hSAUlEi0PbJpDhGmDkw4zeUnRj3hBCVynWD+dTu38yLYKCE9ZF1OYcvpy7CTBRcqkZA=="], 209 | 210 | "mute-stream": ["mute-stream@2.0.0", "", {}, "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA=="], 211 | 212 | "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], 213 | 214 | "node-emoji": ["node-emoji@2.2.0", "", { "dependencies": { "@sindresorhus/is": "^4.6.0", "char-regex": "^1.0.2", "emojilib": "^2.4.0", "skin-tone": "^2.0.0" } }, "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw=="], 215 | 216 | "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], 217 | 218 | "outvariant": ["outvariant@1.4.3", "", {}, "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA=="], 219 | 220 | "parse5": ["parse5@5.1.1", "", {}, "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug=="], 221 | 222 | "parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@6.0.1", "", { "dependencies": { "parse5": "^6.0.1" } }, "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA=="], 223 | 224 | "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], 225 | 226 | "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], 227 | 228 | "psl": ["psl@1.15.0", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w=="], 229 | 230 | "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], 231 | 232 | "punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="], 233 | 234 | "querystringify": ["querystringify@2.2.0", "", {}, "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="], 235 | 236 | "remix-auth": ["remix-auth@4.2.0", "", {}, "sha512-3LSfWEvSgG2CgbG/p4ge5hbV8tTXWNnnYIGbTr9oSSiHz9dD7wh6S0MEyo3pwh7MlKezB2WIlevGeyqUZykk7g=="], 237 | 238 | "remix-auth-github": ["remix-auth-github@3.0.2", "", { "dependencies": { "@mjackson/headers": "^0.9.0", "arctic": "^3.0.0", "debug": "^4.3.7" }, "peerDependencies": { "remix-auth": "^4.0.0" } }, "sha512-3XxykdwMrcPSyMsdGtBDl3DBc19gJM3t7q/1uzfz3g/SJRsxEytjGiQ17ztKykebCGM454Z0lVJMvSb+LF/yHA=="], 239 | 240 | "remix-auth-microsoft": ["remix-auth-microsoft@3.0.1", "", { "dependencies": { "remix-auth-oauth2": "^3.2.2" } }, "sha512-0lxYBhFUH68v7HQfYQ8GZb2EZBlQ783vCXMbsPtRAcR9E/a8zDCb6/PMI963mSpxARjrxltXplkJiVohzHbpHg=="], 241 | 242 | "remix-auth-oauth2": ["remix-auth-oauth2@3.4.0", "", { "dependencies": { "@edgefirst-dev/data": "^0.0.4", "@mjackson/headers": "^0.10.0", "arctic": "^3.0.0", "debug": "^4.3.7" }, "peerDependencies": { "remix-auth": "^4.0.0" } }, "sha512-aU2TfxqQcJTV3pt7bwXCqUtemE+nXSJnIcWdlCRDUrH+1bwj9WXOkteCOx17fq9xfeXsGpt9xOKasrPoBpjelw=="], 243 | 244 | "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], 245 | 246 | "requires-port": ["requires-port@1.0.0", "", {}, "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="], 247 | 248 | "semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], 249 | 250 | "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], 251 | 252 | "skin-tone": ["skin-tone@2.0.0", "", { "dependencies": { "unicode-emoji-modifier-base": "^1.0.0" } }, "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA=="], 253 | 254 | "statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="], 255 | 256 | "strict-event-emitter": ["strict-event-emitter@0.5.1", "", {}, "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ=="], 257 | 258 | "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], 259 | 260 | "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], 261 | 262 | "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], 263 | 264 | "supports-hyperlinks": ["supports-hyperlinks@3.2.0", "", { "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" } }, "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig=="], 265 | 266 | "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], 267 | 268 | "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], 269 | 270 | "tough-cookie": ["tough-cookie@4.1.4", "", { "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", "universalify": "^0.2.0", "url-parse": "^1.5.3" } }, "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag=="], 271 | 272 | "type-fest": ["type-fest@4.40.1", "", {}, "sha512-9YvLNnORDpI+vghLU/Nf+zSv0kL47KbVJ1o3sKgoTefl6i+zebxbiDQWoe/oWWqPhIgQdRZRT1KA9sCPL810SA=="], 273 | 274 | "typedoc": ["typedoc@0.28.4", "", { "dependencies": { "@gerrit0/mini-shiki": "^3.2.2", "lunr": "^2.3.9", "markdown-it": "^14.1.0", "minimatch": "^9.0.5", "yaml": "^2.7.1" }, "peerDependencies": { "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x" }, "bin": { "typedoc": "bin/typedoc" } }, "sha512-xKvKpIywE1rnqqLgjkoq0F3wOqYaKO9nV6YkkSat6IxOWacUCc/7Es0hR3OPmkIqkPoEn7U3x+sYdG72rstZQA=="], 275 | 276 | "typedoc-plugin-mdn-links": ["typedoc-plugin-mdn-links@5.0.1", "", { "peerDependencies": { "typedoc": "0.27.x || 0.28.x" } }, "sha512-eofdcc2nZZpipz/ubjG+7UYMi6Xu95svUwnZ+ClJh6NJdrv7kAOerL9N3iDOpo5kwQeK86GqPWwnv6LUGo5Wrw=="], 277 | 278 | "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], 279 | 280 | "uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="], 281 | 282 | "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], 283 | 284 | "unicode-emoji-modifier-base": ["unicode-emoji-modifier-base@1.0.0", "", {}, "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g=="], 285 | 286 | "universalify": ["universalify@0.2.0", "", {}, "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="], 287 | 288 | "url-parse": ["url-parse@1.5.10", "", { "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" } }, "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ=="], 289 | 290 | "validate-npm-package-name": ["validate-npm-package-name@5.0.1", "", {}, "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ=="], 291 | 292 | "wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], 293 | 294 | "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], 295 | 296 | "yaml": ["yaml@2.7.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ=="], 297 | 298 | "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], 299 | 300 | "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], 301 | 302 | "yoctocolors-cjs": ["yoctocolors-cjs@2.1.2", "", {}, "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA=="], 303 | 304 | "@arethetypeswrong/core/typescript": ["typescript@5.6.1-rc", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ=="], 305 | 306 | "@inquirer/core/ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="], 307 | 308 | "@oslojs/jwt/@oslojs/encoding": ["@oslojs/encoding@0.4.1", "", {}, "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="], 309 | 310 | "cli-highlight/yargs": ["yargs@16.2.0", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw=="], 311 | 312 | "cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], 313 | 314 | "marked-terminal/chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="], 315 | 316 | "parse5-htmlparser2-tree-adapter/parse5": ["parse5@6.0.1", "", {}, "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="], 317 | 318 | "remix-auth-github/@mjackson/headers": ["@mjackson/headers@0.9.0", "", {}, "sha512-1WFCu2iRaqbez9hcYYI611vcH1V25R+fDfOge/CyKc8sdbzniGfy/FRhNd3DgvFF4ZEEX2ayBrvFHLtOpfvadw=="], 319 | 320 | "strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], 321 | 322 | "@inquirer/core/ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], 323 | 324 | "cli-highlight/yargs/cliui": ["cliui@7.0.4", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ=="], 325 | 326 | "cli-highlight/yargs/yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="], 327 | 328 | "cli-highlight/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remix-auth-socials", 3 | "version": "3.0.0", 4 | "description": "A collection of social media strategies for remix-auth", 5 | "author": { 6 | "name": "Tom Rowe", 7 | "email": "theflyingcoder@hotmail.com", 8 | "url": "https://github.com/TheRealFlyingCoder" 9 | }, 10 | "repository": { 11 | "url": "https://github.com/TheRealFlyingCoder/remix-auth-socials", 12 | "type": "git" 13 | }, 14 | "homepage": "https://github.com/TheRealFlyingCoder/remix-auth-socials#readme", 15 | "scripts": { 16 | "build": "tsc", 17 | "typecheck": "tsc --noEmit", 18 | "quality": "biome check .", 19 | "quality:fix": "biome check . --write --unsafe", 20 | "exports": "bun run ./scripts/exports.ts" 21 | }, 22 | "keywords": [ 23 | "remix", 24 | "remix-auth", 25 | "auth", 26 | "authentication", 27 | "strategy", 28 | "oauth", 29 | "socials" 30 | ], 31 | "license": "MIT", 32 | "exports": { 33 | ".": "./build/index.js", 34 | "./package.json": "./package.json" 35 | }, 36 | "files": [ 37 | "build", 38 | "src", 39 | "package.json", 40 | "README.md" 41 | ], 42 | "sideEffects": false, 43 | "type": "module", 44 | "devDependencies": { 45 | "@arethetypeswrong/cli": "^0.17.1", 46 | "@biomejs/biome": "^1.8.3", 47 | "@mjackson/headers": "^0.10.0", 48 | "@total-typescript/tsconfig": "^1.0.4", 49 | "@types/bun": "^1.1.14", 50 | "msw": "^2.7.0", 51 | "typedoc": "^0.28.0", 52 | "typedoc-plugin-mdn-links": "^5.0.1", 53 | "typescript": "^5.5.4" 54 | }, 55 | "dependencies": { 56 | "remix-auth": "^4.2.0", 57 | "remix-auth-github": "^3.0.2", 58 | "remix-auth-microsoft": "^3.0.1", 59 | "remix-auth-oauth2": "^3.4.0" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/docs/google.md: -------------------------------------------------------------------------------- 1 | # GoogleStrategy 2 | 3 | 4 | 5 | The Google strategy is used to authenticate users against a Google account. It extends the OAuth2Strategy. 6 | 7 | ## Supported runtimes 8 | 9 | | Runtime | Has Support | 10 | | ---------- | ----------- | 11 | | Node.js | ✅ | 12 | | Cloudflare | ✅ | 13 | 14 | 15 | 16 | ## Usage 17 | 18 | ### Create an OAuth application 19 | 20 | Follow the steps on [the Google documentation](https://developers.google.com/identity/protocols/oauth2/web-server#creatingcred) to create a new application and get a client ID and secret. 21 | 22 | ### Create the strategy instance 23 | 24 | ```ts 25 | // app/services/auth.server.ts 26 | import { Authenticator } from 'remix-auth'; 27 | import { GoogleStrategy } from 'remix-auth-google'; 28 | import { createCookieSessionStorage, redirect } from 'react-router'; 29 | 30 | export type SessionUser = { 31 | id: string; 32 | email: string; 33 | displayName: string; 34 | pictureUrl: string; 35 | }; 36 | 37 | let SESSION_KEY = 'user'; 38 | export const sessionStorage = createCookieSessionStorage<{ 39 | [SESSION_KEY]: SessionUser; 40 | }>({ 41 | cookie: { 42 | name: '__session', 43 | httpOnly: true, 44 | path: '/', 45 | sameSite: 'lax', 46 | secrets: [process.env.SESSION_SECRET], 47 | secure: process.env.NODE_ENV === 'production', 48 | }, 49 | }); 50 | 51 | export const getSession = async (request: Request) => { 52 | return await sessionStorage.getSession(request.headers.get('Cookie')); 53 | }; 54 | 55 | export const getSessionUser = async (request: Request) => { 56 | const session = await getSession(request); 57 | return session?.get(SESSION_KEY); 58 | }; 59 | 60 | export const saveSession = async (request: Request, user: SessionUser) => { 61 | const session = await getSession(request); 62 | session.set(SESSION_KEY, user); 63 | return new Headers({ 64 | 'Set-Cookie': await sessionStorage.commitSession(session), 65 | }); 66 | }; 67 | 68 | const googleStrategy = new GoogleStrategy( 69 | { 70 | clientId: 'YOUR_CLIENT_ID', 71 | clientSecret: 'YOUR_CLIENT_SECRET', 72 | redirectURI: 'https://example.com/auth/google/callback', 73 | }, 74 | async ({ accessToken, tokens }) => { 75 | // Get the user data from your DB or API using the tokens and profile 76 | const profile = await GoogleStrategy.userProfile(tokens); 77 | return User.findOrCreate({ email: profile.emails[0].value }); 78 | }, 79 | ); 80 | 81 | export const authenticator = new Authenticator(); 82 | authenticator.use(googleStrategy); 83 | ``` 84 | 85 | ### Setup your routes 86 | 87 | ```tsx 88 | // app/routes/login.tsx 89 | export default function Login() { 90 | return ( 91 |
92 | 93 |
94 | ); 95 | } 96 | ``` 97 | 98 | ```tsx 99 | // app/routes/auth.google.tsx 100 | import { authenticator } from '~/services/auth.server'; 101 | import type { Route } from './+types/auth.google'; 102 | 103 | export const loader = async ({ request }: Route.LoaderArgs) => { 104 | return await authenticator.authenticate('google', request); 105 | }; 106 | ``` 107 | 108 | ```tsx 109 | // app/routes/auth.google.callback.tsx 110 | import { redirect } from 'react-router'; 111 | import { authenticator, saveSession } from '~/services/auth.server'; 112 | import type { Route } from './+types/auth.google.callback'; 113 | 114 | export let loader = ({ request }: Route.LoaderArgs) => { 115 | const user = authenticator.authenticate('google', request); 116 | const headers = await saveSession(user); 117 | return redirect('/dashboard', { headers }); 118 | }; 119 | ``` 120 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './strategies/google.js'; 2 | export * from './strategies/github.js'; 3 | export * from './strategies/microsoft.js'; 4 | export * from './strategies/facebook.js'; 5 | 6 | export const SocialsProvider = { 7 | DISCORD: 'discord', 8 | FACEBOOK: 'facebook', 9 | GITHUB: 'github', 10 | GOOGLE: 'google', 11 | MICROSOFT: 'microsoft', 12 | TWITTER: 'twitter', 13 | TWITTER2: 'twitter2', 14 | LINKEDIN: 'linkedin', 15 | } as const; 16 | 17 | export type SocialsProvider = 18 | (typeof SocialsProvider)[keyof typeof SocialsProvider]; 19 | -------------------------------------------------------------------------------- /src/strategies/discord.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheRealFlyingCoder/remix-auth-socials/4f614d9ff5e95b34cd2df8fca4e0eb9093419d76/src/strategies/discord.ts -------------------------------------------------------------------------------- /src/strategies/facebook.ts: -------------------------------------------------------------------------------- 1 | import { OAuth2Strategy } from 'remix-auth-oauth2'; 2 | 3 | export const baseProfileFields = [ 4 | 'id', 5 | 'email', 6 | 'name', 7 | 'first_name', 8 | 'middle_name', 9 | 'last_name', 10 | 'picture', 11 | ] as const; 12 | 13 | export const FacebookStrategyName = 'facebook'; 14 | export const FacebookStrategyDefaultScopes: FacebookScope[] = [ 15 | 'public_profile', 16 | 'email', 17 | ]; 18 | export const FacebookStrategyScopeSeperator = ','; 19 | export type FacebookProfileFields = [ 20 | ...typeof baseProfileFields, 21 | ...AdditionalFacebookProfileField[], 22 | ]; 23 | 24 | export class FacebookStrategy extends OAuth2Strategy { 25 | public override name = FacebookStrategyName; 26 | private readonly scope: FacebookScope[]; 27 | private readonly userInfoURL = 'https://graph.facebook.com/me'; 28 | 29 | private readonly profileFields: FacebookProfileFields; 30 | 31 | constructor( 32 | { 33 | clientId, 34 | clientSecret, 35 | redirectURI, 36 | scopes, 37 | extraProfileFields, 38 | }: FacebookStrategyOptions, 39 | verify: OAuth2Strategy['verify'], 40 | ) { 41 | super( 42 | { 43 | clientId, 44 | clientSecret, 45 | redirectURI, 46 | authorizationEndpoint: `https://facebook.com/v14.0/dialog/oauth`, 47 | tokenEndpoint: `https://graph.facebook.com/v14.0/oauth/access_token`, 48 | }, 49 | verify, 50 | ); 51 | this.scope = this.getScope(scopes); 52 | // Ensure unique entries in case they include the base fields 53 | this.profileFields = [ 54 | ...new Set([...baseProfileFields, ...(extraProfileFields || [])]), 55 | ] as FacebookProfileFields; 56 | } 57 | 58 | // Allow users the option to pass a scope string, or typed array 59 | private getScope(scopes: FacebookStrategyOptions['scopes']) { 60 | if (!scopes) { 61 | return FacebookStrategyDefaultScopes; 62 | } else if (typeof scopes === 'string') { 63 | return scopes.split( 64 | FacebookStrategyScopeSeperator, 65 | ) as FacebookScope[]; 66 | } 67 | 68 | return scopes; 69 | } 70 | 71 | protected override authorizationParams(): URLSearchParams { 72 | const params = new URLSearchParams({ 73 | scope: this.scope.join(FacebookStrategyScopeSeperator), 74 | }); 75 | 76 | return params; 77 | } 78 | 79 | protected async userProfile(accessToken: string): Promise { 80 | const requestParams = `?fields=${this.profileFields.join(',')}`; 81 | const requestUrl = `${this.userInfoURL}${requestParams}`; 82 | const response = await fetch(requestUrl, { 83 | headers: { 84 | Authorization: `Bearer ${accessToken}`, 85 | }, 86 | }); 87 | const raw: FacebookProfile['_json'] = await response.json(); 88 | const profile: FacebookProfile = { 89 | id: raw.id, 90 | displayName: raw.name, 91 | name: { 92 | familyName: raw.last_name, 93 | givenName: raw.first_name, 94 | }, 95 | emails: [{ value: raw.email }], 96 | photos: [{ value: raw.picture.data.url }], 97 | _json: raw, 98 | }; 99 | return profile; 100 | } 101 | } 102 | 103 | /** 104 | * @see https://developers.facebook.com/docs/permissions/reference 105 | */ 106 | export type FacebookScope = 107 | | 'ads_management' 108 | | 'ads_read' 109 | | 'attribution_read' 110 | | 'catalog_management' 111 | | 'business_management' 112 | | 'email' 113 | | 'gaming_user_locale' 114 | | 'groups_access_member_info' 115 | | 'instagram_basic' 116 | | 'instagram_content_publish' 117 | | 'instagram_manage_comments' 118 | | 'instagram_manage_insights' 119 | | 'instagram_manage_messages' 120 | | 'leads_retrieval' 121 | | 'manage_pages' 122 | | 'page_events' 123 | | 'pages_manage_ads' 124 | | 'pages_manage_cta' 125 | | 'pages_manage_engagement' 126 | | 'pages_manage_instant_articles' 127 | | 'pages_manage_metadata' 128 | | 'pages_manage_posts' 129 | | 'pages_messaging' 130 | | 'pages_read_engagement' 131 | | 'pages_read_user_content' 132 | | 'pages_show_list' 133 | | 'pages_user_gender' 134 | | 'pages_user_locale' 135 | | 'pages_user_timezone' 136 | | 'publish_pages' 137 | | 'public_profile' 138 | | 'publish_to_groups' 139 | | 'publish_video' 140 | | 'read_insights' 141 | | 'research_apis' 142 | | 'user_age_range' 143 | | 'user_birthday' 144 | | 'user_friends' 145 | | 'user_gender' 146 | | 'user_hometown' 147 | | 'user_likes' 148 | | 'user_link' 149 | | 'user_location' 150 | | 'user_messenger_contact' 151 | | 'user_photos' 152 | | 'user_posts' 153 | | 'user_videos'; 154 | 155 | export type AdditionalFacebookProfileField = 156 | | 'about' 157 | | 'birthday' 158 | | 'id' 159 | | 'age_range' 160 | | 'education' 161 | | 'email' 162 | | 'favorite_athletes' 163 | | 'favorite_teams' 164 | | 'first_name' 165 | | 'gender' 166 | | 'hometown' 167 | | 'inspirational_people' 168 | | 'install_type' 169 | | 'installed' 170 | | 'is_guest_user' 171 | | 'languages' 172 | | 'last_name' 173 | | 'link' 174 | | 'location' 175 | | 'meeting_for' 176 | | 'middle_name' 177 | | 'name' 178 | | 'name_format' 179 | | 'payment_pricepoints' 180 | | 'political' 181 | | 'picture' 182 | | 'profile_pic' 183 | | 'quotes' 184 | | 'relationship_status' 185 | | 'shared_login_upgrade_required_by' 186 | | 'short_name' 187 | | 'significant_other' 188 | | 'sports' 189 | | 'supports_donate_button_in_live_video' 190 | | 'token_for_business' 191 | | 'video_upload_limits' 192 | | 'website'; 193 | 194 | export type FacebookStrategyOptions = { 195 | clientId: string; 196 | clientSecret: string; 197 | redirectURI: string; 198 | /** 199 | * @default ["public_profile", "email"] 200 | * 201 | * See all the possible scopes: 202 | * @see https://developers.facebook.com/docs/permissions/reference 203 | */ 204 | scopes?: FacebookScope[] | string; 205 | /** 206 | * Additional fields that will show up in the profile._json object 207 | * 208 | * The following fields are included as part of the Oauth2 basic profile 209 | * ['id', 'email', 'name', 'first_name', 'middle_name', 'last_name'] 210 | * 211 | * Note: some fields require additional scopes 212 | */ 213 | extraProfileFields?: Array; 214 | }; 215 | 216 | export type FacebookProfile = { 217 | id: string; 218 | displayName: string; 219 | name: { 220 | familyName: string; 221 | givenName: string; 222 | }; 223 | emails: [{ value: string }]; 224 | photos: [{ value: string }]; 225 | _json: { 226 | id: string; 227 | name: string; 228 | first_name: string; 229 | last_name: string; 230 | picture: FacebookPicture; 231 | email: string; 232 | }; 233 | }; 234 | 235 | export type FacebookExtraParams = { 236 | expires_in: number; 237 | token_type: 'bearer'; 238 | } & Record; 239 | 240 | export interface FacebookPictureData { 241 | url: string; 242 | width: number; 243 | height: number; 244 | is_silhouette: boolean; 245 | } 246 | export interface FacebookPicture { 247 | data: FacebookPictureData; 248 | } 249 | -------------------------------------------------------------------------------- /src/strategies/github.ts: -------------------------------------------------------------------------------- 1 | export * from 'remix-auth-github'; 2 | -------------------------------------------------------------------------------- /src/strategies/google.ts: -------------------------------------------------------------------------------- 1 | import { OAuth2Strategy } from 'remix-auth-oauth2'; 2 | import type { OAuth2Tokens } from 'arctic'; 3 | 4 | /** 5 | * @see https://developers.google.com/identity/protocols/oauth2/scopes 6 | */ 7 | export type GoogleScope = string; 8 | 9 | export type GoogleStrategyOptions = { 10 | clientId: string; 11 | clientSecret: string; 12 | redirectURI: string; 13 | /** 14 | * @default "openid profile email" 15 | */ 16 | scopes?: GoogleScope[]; 17 | accessType?: 'online' | 'offline'; 18 | includeGrantedScopes?: boolean; 19 | prompt?: 'none' | 'consent' | 'select_account'; 20 | hd?: string; 21 | loginHint?: string; 22 | }; 23 | 24 | export type GoogleProfile = { 25 | id: string; 26 | displayName: string; 27 | name: { 28 | familyName: string; 29 | givenName: string; 30 | }; 31 | emails: [{ value: string }]; 32 | photos: [{ value: string }]; 33 | _json: { 34 | sub: string; 35 | name: string; 36 | given_name: string; 37 | family_name: string; 38 | picture: string; 39 | locale: string; 40 | email: string; 41 | email_verified: boolean; 42 | hd: string; 43 | }; 44 | }; 45 | 46 | export type GoogleExtraParams = { 47 | expires_in: 3920; 48 | token_type: 'Bearer'; 49 | scope: string; 50 | id_token: string; 51 | } & Record; 52 | 53 | export const GoogleStrategyDefaultScopes = [ 54 | 'openid', 55 | 'https://www.googleapis.com/auth/userinfo.profile', 56 | 'https://www.googleapis.com/auth/userinfo.email', 57 | ]; 58 | export const GoogleStrategyDefaultName = 'google'; 59 | const userInfoURL = 'https://www.googleapis.com/oauth2/v3/userinfo'; 60 | 61 | export class GoogleStrategy extends OAuth2Strategy { 62 | public override name = GoogleStrategyDefaultName; 63 | 64 | private readonly accessType: string; 65 | 66 | private readonly prompt?: 'none' | 'consent' | 'select_account'; 67 | 68 | private readonly includeGrantedScopes: boolean; 69 | 70 | private readonly hd?: string; 71 | 72 | private readonly loginHint?: string; 73 | 74 | constructor( 75 | { 76 | clientId, 77 | clientSecret, 78 | redirectURI, 79 | scopes, 80 | accessType, 81 | includeGrantedScopes, 82 | prompt, 83 | hd, 84 | loginHint, 85 | }: GoogleStrategyOptions, 86 | verify: OAuth2Strategy['verify'], 87 | ) { 88 | super( 89 | { 90 | clientId, 91 | clientSecret, 92 | redirectURI, 93 | authorizationEndpoint: 94 | 'https://accounts.google.com/o/oauth2/v2/auth', 95 | tokenEndpoint: 'https://oauth2.googleapis.com/token', 96 | scopes: scopes ?? GoogleStrategyDefaultScopes, 97 | }, 98 | verify, 99 | ); 100 | this.accessType = accessType ?? 'online'; 101 | this.includeGrantedScopes = includeGrantedScopes ?? false; 102 | this.prompt = prompt; 103 | this.hd = hd; 104 | this.loginHint = loginHint; 105 | } 106 | 107 | protected override authorizationParams( 108 | params: URLSearchParams, 109 | request?: Request, 110 | ): URLSearchParams { 111 | params.set('access_type', this.accessType); 112 | params.set('include_granted_scopes', String(this.includeGrantedScopes)); 113 | if (this.prompt) { 114 | params.set('prompt', this.prompt); 115 | } 116 | if (this.hd) { 117 | params.set('hd', this.hd); 118 | } 119 | if (this.loginHint) { 120 | params.set('login_hint', this.loginHint); 121 | } 122 | return params; 123 | } 124 | 125 | static async userProfile(tokens: OAuth2Tokens): Promise { 126 | const response = await fetch(userInfoURL, { 127 | headers: { 128 | Authorization: `Bearer ${tokens.accessToken()}`, 129 | }, 130 | }); 131 | if (!response.ok) { 132 | throw new Error( 133 | `Failed to fetch user profile: ${response.statusText}`, 134 | ); 135 | } 136 | const raw: GoogleProfile['_json'] = await response.json(); 137 | const profile: GoogleProfile = { 138 | id: raw.sub, 139 | displayName: raw.name, 140 | name: { 141 | familyName: raw.family_name, 142 | givenName: raw.given_name, 143 | }, 144 | emails: [{ value: raw.email }], 145 | photos: [{ value: raw.picture }], 146 | _json: raw, 147 | }; 148 | return profile; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/strategies/linkedin.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheRealFlyingCoder/remix-auth-socials/4f614d9ff5e95b34cd2df8fca4e0eb9093419d76/src/strategies/linkedin.ts -------------------------------------------------------------------------------- /src/strategies/microsoft.ts: -------------------------------------------------------------------------------- 1 | export * from 'remix-auth-microsoft'; -------------------------------------------------------------------------------- /src/strategies/twitter.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheRealFlyingCoder/remix-auth-socials/4f614d9ff5e95b34cd2df8fca4e0eb9093419d76/src/strategies/twitter.ts -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | /* Change to `@total-typescript/tsconfig/tsc/dom/library` for DOM usage */ 3 | "extends": "@total-typescript/tsconfig/tsc/dom/library", 4 | "include": ["src/**/*"], 5 | "exclude": ["src/**/*.test.*"], 6 | "compilerOptions": { 7 | "outDir": "./build" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://typedoc.org/schema.json", 3 | "includeVersion": true, 4 | "entryPoints": ["./src/index.ts"], 5 | "out": "docs", 6 | "json": "docs/index.json", 7 | "cleanOutputDir": true, 8 | "plugin": ["typedoc-plugin-mdn-links"], 9 | "categorizeByGroup": false 10 | } 11 | --------------------------------------------------------------------------------