diff --git a/.node-version b/.node-version index 4a1f488b6c..2b9cabc07c 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -18.17.1 +20.12.0 diff --git a/apps/epic-react/package.json b/apps/epic-react/package.json index cc22bb843d..2305b5b21c 100644 --- a/apps/epic-react/package.json +++ b/apps/epic-react/package.json @@ -4,6 +4,7 @@ "scripts": { "dev": "next dev -p 3024", "dev:sanity": "sanity dev", + "dev:party": "pnpx partykit dev", "dev:stripe": "echo 'Run epic-web `pnpm dev` to handle stripe webhooks for kcd-products' && exit 1", "build": "next build", "postbuild": "NODE_ENV=production next-sitemap", @@ -113,6 +114,8 @@ "nodemailer": "^6.7.2", "nodemailer-postmark-transport": "^5.2.1", "openai": "^4.12.1", + "partysocket": "1.0.2", + "partykit": "0.0.110", "pluralize": "^8.0.0", "postmark": "^3.1.1", "prism-react-renderer": "^2.3.1", diff --git a/apps/epic-react/party/index.ts b/apps/epic-react/party/index.ts new file mode 100644 index 0000000000..d830ec93bc --- /dev/null +++ b/apps/epic-react/party/index.ts @@ -0,0 +1,70 @@ +import type * as Party from 'partykit/server' + +const CORS = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET', + 'Access-Control-Allow-Headers': + 'Origin, X-Requested-With, Content-Type, Accept', +} + +export default class Server implements Party.Server { + constructor(readonly party: Party.Room) {} + + async onConnect(conn: Party.Connection, ctx: Party.ConnectionContext) { + // A websocket just connected! + console.log( + `Connected: + id: ${conn.id} + room: ${this.party.id} + url: ${new URL(ctx.request.url).pathname}`, + ) + } + + messages: string[] = [] + + async onStart() { + this.messages = (await this.party.storage.get('messages')) ?? [] + } + + async onRequest(req: Party.Request) { + if (req.method === 'GET') { + // For SSR, return the current presence of all connections + // const users = [...this.party.getConnections()].reduce( + // (acc, user) => ({...acc, [user.id]: this.getUser(user)}), + // {}, + // ) + return Response.json({users: []}, {status: 200, headers: CORS}) + } + + // respond to cors preflight requests + if (req.method === 'OPTIONS') { + return Response.json({ok: true}, {status: 200, headers: CORS}) + } + + if (req.method === 'POST') { + const messageBody: {requestId: string; body: string; name: string} = + await req.json() + + this.party.broadcast(JSON.stringify(messageBody)) + + return new Response( + `Party ${this.party.id} has received ${this.messages.length} messages`, + ) + } + + return new Response('Method Not Allowed', {status: 405}) + } + + onMessage(message: string, sender: Party.Connection) { + // let's log the message + console.log(`connection ${sender.id} sent message: ${message}`) + // as well as broadcast it to all the other connections in the room... + this.party.broadcast( + `${sender.id}: ${message}`, + // ...except for the connection it came from + [sender.id], + ) + } +} + +Server satisfies Party.Worker diff --git a/apps/epic-react/partykit.json b/apps/epic-react/partykit.json new file mode 100644 index 0000000000..3b6a63e247 --- /dev/null +++ b/apps/epic-react/partykit.json @@ -0,0 +1,6 @@ +{ + "name": "epic-react", + "main": "party/index.ts", + "compatibilityDate": "2023-10-20", + "team": "skillrecordings" +} diff --git a/apps/epic-react/src/app/layout.tsx b/apps/epic-react/src/app/layout.tsx index 9f3f0081cd..bcfcc526d7 100644 --- a/apps/epic-react/src/app/layout.tsx +++ b/apps/epic-react/src/app/layout.tsx @@ -27,6 +27,10 @@ export default function RootLayout({children}: {children: React.ReactNode}) { > {children} + ) diff --git a/apps/epic-react/src/components/primary-newsletter-cta.tsx b/apps/epic-react/src/components/primary-newsletter-cta.tsx index 431c6975c8..c4afba6327 100644 --- a/apps/epic-react/src/components/primary-newsletter-cta.tsx +++ b/apps/epic-react/src/components/primary-newsletter-cta.tsx @@ -16,6 +16,7 @@ type PrimaryNewsletterCtaProps = { actionLabel?: string id?: string className?: string + formId?: string trackProps?: { event?: string params?: Record @@ -33,6 +34,7 @@ export const PrimaryNewsletterCta: React.FC< actionLabel = common['primary-newsletter-button-cta-label'], trackProps = {event: 'subscribed', params: {}}, onSuccess, + formId, }) => { const router = useRouter() const handleOnSuccess = (subscriber: Subscriber | undefined) => { @@ -57,6 +59,7 @@ export const PrimaryNewsletterCta: React.FC< )} diff --git a/apps/epic-react/src/pages/react-19-cheatsheet.tsx b/apps/epic-react/src/pages/react-19-cheatsheet.tsx index ada6951cd6..b126ff9952 100644 --- a/apps/epic-react/src/pages/react-19-cheatsheet.tsx +++ b/apps/epic-react/src/pages/react-19-cheatsheet.tsx @@ -5,6 +5,7 @@ import {Button} from '@skillrecordings/ui' import Share from '@/components/share' import {HeartIcon} from '@heroicons/react/solid' import {track} from '@/utils/analytics' +import {PrimaryNewsletterCta} from '@/components/primary-newsletter-cta' const CheatSheetPage = () => { return ( @@ -83,6 +84,72 @@ const CheatSheetPage = () => { + +
+ +
+

+ Get more React 19 resources +

+

+ Sign up and we will send you emails when we have new React 19 + tips, tools, and techniques like this cheat sheet by + + {' '} + Kent C. Dodds + +

+
+
+

+ + I respect your privacy. Unsubscribe at any time. +

+
) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7f399f035b..5df75d1dc4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1550,6 +1550,12 @@ importers: openai: specifier: ^4.12.1 version: 4.26.0 + partykit: + specifier: 0.0.110 + version: 0.0.110 + partysocket: + specifier: 1.0.2 + version: 1.0.2 pluralize: specifier: ^8.0.0 version: 8.0.0 @@ -10617,6 +10623,55 @@ packages: prettier: 2.8.8 dev: false + /@cloudflare/workerd-darwin-64@1.20240718.0: + resolution: {integrity: sha512-BsPZcSCgoGnufog2GIgdPuiKicYTNyO/Dp++HbpLRH+yQdX3x4aWx83M+a0suTl1xv76dO4g9aw7SIB6OSgIyQ==} + engines: {node: '>=16'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@cloudflare/workerd-darwin-arm64@1.20240718.0: + resolution: {integrity: sha512-nlr4gaOO5gcJerILJQph3+2rnas/nx/lYsuaot1ntHu4LAPBoQo1q/Pucj2cSIav4UiMzTbDmoDwPlls4Kteog==} + engines: {node: '>=16'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@cloudflare/workerd-linux-64@1.20240718.0: + resolution: {integrity: sha512-LJ/k3y47pBcjax0ee4K+6ZRrSsqWlfU4lbU8Dn6u5tSC9yzwI4YFNXDrKWInB0vd7RT3w4Yqq1S6ZEbfRrqVUg==} + engines: {node: '>=16'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@cloudflare/workerd-linux-arm64@1.20240718.0: + resolution: {integrity: sha512-zBEZvy88EcAMGRGfuVtS00Yl7lJdUM9sH7i651OoL+q0Plv9kphlCC0REQPwzxrEYT1qibSYtWcD9IxQGgx2/g==} + engines: {node: '>=16'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@cloudflare/workerd-windows-64@1.20240718.0: + resolution: {integrity: sha512-YpCRvvT47XanFum7C3SedOZKK6BfVhqmwdAAVAQFyc4gsCdegZo0JkUkdloC/jwuWlbCACOG2HTADHOqyeolzQ==} + engines: {node: '>=16'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@cloudflare/workers-types@4.20240718.0: + resolution: {integrity: sha512-7RqxXIM9HyhjfZ9ztXjITuc7mL0w4s+zXgypqKmMuvuObC3DgXutJ3bOYbQ+Ss5QbywrzWSNMlmGdL/ldg/yZg==} + dev: false + /@cloudinary/html@1.11.2: resolution: {integrity: sha512-IibBZliI7MOK3dxvz0WlsEyTIsqZhY3SiCBQM0CNwCKxn3N1TO8OgetfU26Now1lJbdoF5JQ8S7Cn6ZWNuU1KA==} dependencies: @@ -12202,6 +12257,11 @@ packages: resolution: {integrity: sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@fastify/busboy@2.1.1: + resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} + engines: {node: '>=14'} + dev: false + /@fec/remark-a11y-emoji@3.1.0: resolution: {integrity: sha512-cYjCutvrValbQ2dykKhEE6OBTsEGpAJzeJnDtGKXhk2JYkMNSlKPnUk/kRFHH+PHmrAF+FlNIU28XKD+nYy7jQ==} engines: {node: '>=10.0'} @@ -21555,6 +21615,12 @@ packages: engines: {node: '>=12'} dev: true + /as-table@1.0.55: + resolution: {integrity: sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==} + dependencies: + printable-characters: 1.0.42 + dev: false + /asn1@0.2.6: resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} dependencies: @@ -22622,6 +22688,15 @@ packages: resolution: {integrity: sha512-0CNTVCLZggSh7bc5VkX5WWPWO+cyZbNd07IHIsSXLia/eAq+r836hgk+8BKoEh7949Mda87VUOitx5OddVj64A==} dev: false + /capnp-ts@0.7.0: + resolution: {integrity: sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g==} + dependencies: + debug: 4.3.7 + tslib: 2.7.0 + transitivePeerDependencies: + - supports-color + dev: false + /capture-exit@2.0.0: resolution: {integrity: sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==} engines: {node: 6.* || 8.* || >= 10.*} @@ -23836,6 +23911,10 @@ packages: resolution: {integrity: sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ==} dev: false + /data-uri-to-buffer@2.0.2: + resolution: {integrity: sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==} + dev: false + /data-urls@1.1.0: resolution: {integrity: sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==} dependencies: @@ -26122,6 +26201,11 @@ packages: engines: {node: '>=6'} dev: false + /event-target-shim@6.0.2: + resolution: {integrity: sha512-8q3LsZjRezbFZ2PN+uP+Q7pnHUMmAOziU2vA2OwoFaKIXxlxl38IylhSSgUorWu/rf4er67w0ikBqjBFk/pomA==} + engines: {node: '>=10.13.0'} + dev: false + /eventemitter3@3.1.2: resolution: {integrity: sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==} dev: false @@ -26236,6 +26320,11 @@ packages: resolution: {integrity: sha512-FXnmK9yJYTa3V3G7DE9BRjUJ0pwXMICAxfbsAuKPTuSlFzMZhQbcvvwx0I8ofNJHxz3tfjze+whxcGpfklAWOQ==} dev: false + /exit-hook@2.2.1: + resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} + engines: {node: '>=6'} + dev: false + /exit@0.1.2: resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} engines: {node: '>= 0.8.0'} @@ -27128,6 +27217,13 @@ packages: global: 4.4.0 dev: false + /get-source@2.0.12: + resolution: {integrity: sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==} + dependencies: + data-uri-to-buffer: 2.0.2 + source-map: 0.6.1 + dev: false + /get-stdin@6.0.0: resolution: {integrity: sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==} engines: {node: '>=4'} @@ -32684,6 +32780,29 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} + /miniflare@3.20240718.0: + resolution: {integrity: sha512-TKgSeyqPBeT8TBLxbDJOKPWlq/wydoJRHjAyDdgxbw59N6wbP8JucK6AU1vXCfu21eKhrEin77ssXOpbfekzPA==} + engines: {node: '>=16.13'} + hasBin: true + dependencies: + '@cspotcode/source-map-support': 0.8.1 + acorn: 8.11.3 + acorn-walk: 8.3.2 + capnp-ts: 0.7.0 + exit-hook: 2.2.1 + glob-to-regexp: 0.4.1 + stoppable: 1.1.0 + undici: 5.28.4 + workerd: 1.20240718.0 + ws: 8.18.0 + youch: 3.3.3 + zod: 3.22.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: false + /minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} dev: false @@ -33289,6 +33408,11 @@ packages: - supports-color dev: true + /mustache@4.2.0: + resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} + hasBin: true + dev: false + /mute-stream@0.0.8: resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} @@ -34468,6 +34592,29 @@ packages: nearley: 2.20.1 dev: false + /partykit@0.0.110: + resolution: {integrity: sha512-lAUeB/b6nZbMr6c789RKlSsGhbMtklNhjJZ0ri4zzaVEqhG2/k+cqTLCYTwHvfSI6eSa7cIF614R7CyMUrSLgA==} + hasBin: true + dependencies: + '@cloudflare/workers-types': 4.20240718.0 + clipboardy: 4.0.0 + esbuild: 0.21.5 + miniflare: 3.20240718.0 + yoga-wasm-web: 0.3.3 + optionalDependencies: + fsevents: 2.3.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: false + + /partysocket@1.0.2: + resolution: {integrity: sha512-rAFOUKImaq+VBk2B+2RTBsWEvlnarEP53nchoUHzpVs8V6fG2/estihOTslTQUWHVuHEKDL5k8htG8K3TngyFA==} + dependencies: + event-target-shim: 6.0.2 + dev: false + /pascal-case@2.0.1: resolution: {integrity: sha512-qjS4s8rBOJa2Xm0jmxXiyh1+OFf6ekCWOvUaRgAQSktzlTbMotS0nmG9gyYAybCWBcuP4fsBeRCKNwGBnMe2OQ==} dependencies: @@ -35842,6 +35989,10 @@ packages: js-beautify: 1.14.11 dev: false + /printable-characters@1.0.42: + resolution: {integrity: sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==} + dev: false + /prism-react-renderer@1.3.5(react@18.3.0-canary-a870b2d54-20240314): resolution: {integrity: sha512-IJ+MSwBWKG+SM3b2SUfdrhC+gu01QkV2KmRQgREThBfSQRoufqRfxfHUxpG1WcaFjP+kojcFyO9Qqtpgt3qLCg==} peerDependencies: @@ -39077,6 +39228,13 @@ packages: type-fest: 0.7.1 dev: false + /stacktracey@2.1.8: + resolution: {integrity: sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==} + dependencies: + as-table: 1.0.55 + get-source: 2.0.12 + dev: false + /starfield-react@3.1.0-dev-5(react-dom@18.3.0-canary-a870b2d54-20240314)(react@18.3.0-canary-a870b2d54-20240314): resolution: {integrity: sha512-M4WwiThU1xKgTetMjojX2vS9uyOJqdfhf7kFBNPZA6NHjZb12XwMxEeSQqDpwG4ANdd6pLqBYXKjFdKYePcZ7Q==} peerDependencies: @@ -39139,6 +39297,11 @@ packages: internal-slot: 1.0.6 dev: true + /stoppable@1.1.0: + resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==} + engines: {node: '>=4', npm: '>=6'} + dev: false + /stream-chain@2.2.5: resolution: {integrity: sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==} dev: true @@ -41163,6 +41326,13 @@ packages: /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + /undici@5.28.4: + resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} + engines: {node: '>=14.0'} + dependencies: + '@fastify/busboy': 2.1.1 + dev: false + /unenv@1.9.0: resolution: {integrity: sha512-QKnFNznRxmbOF1hDgzpqrlIf6NC5sbZ2OJ+5Wl3OX8uM+LUJXbj4TXvLJCtwbPTmbMHCLIz6JLKNinNsMShK9g==} dependencies: @@ -42319,6 +42489,19 @@ packages: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} dev: false + /workerd@1.20240718.0: + resolution: {integrity: sha512-w7lOLRy0XecQTg/ujTLWBiJJuoQvzB3CdQ6/8Wgex3QxFhV9Pbnh3UbwIuUfMw3OCCPQc4o7y+1P+mISAgp6yg==} + engines: {node: '>=16'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@cloudflare/workerd-darwin-64': 1.20240718.0 + '@cloudflare/workerd-darwin-arm64': 1.20240718.0 + '@cloudflare/workerd-linux-64': 1.20240718.0 + '@cloudflare/workerd-linux-arm64': 1.20240718.0 + '@cloudflare/workerd-windows-64': 1.20240718.0 + dev: false + /wrap-ansi@3.0.1: resolution: {integrity: sha512-iXR3tDXpbnTpzjKSylUJRkLuOrEC7hwEB221cgn6wtF8wpmz28puFXAEfPT5zrjM3wahygB//VuWEr1vTkDcNQ==} engines: {node: '>=4'} @@ -42400,6 +42583,19 @@ packages: utf-8-validate: optional: true + /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 + dev: false + /xdg-basedir@4.0.0: resolution: {integrity: sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==} engines: {node: '>=8'} @@ -42574,6 +42770,14 @@ packages: resolution: {integrity: sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==} dev: false + /youch@3.3.3: + resolution: {integrity: sha512-qSFXUk3UZBLfggAW3dJKg0BMblG5biqSF8M34E06o5CSsZtH92u9Hqmj2RzGiHDi64fhe83+4tENFP2DB6t6ZA==} + dependencies: + cookie: 0.5.0 + mustache: 4.2.0 + stacktracey: 2.1.8 + dev: false + /yup@0.32.11: resolution: {integrity: sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg==} engines: {node: '>=10'}