diff --git a/.changeset/breezy-socks-deliver.md b/.changeset/breezy-socks-deliver.md new file mode 100644 index 0000000..2af0dc2 --- /dev/null +++ b/.changeset/breezy-socks-deliver.md @@ -0,0 +1,6 @@ +--- +'@getcronit/pylon-dev': patch +--- + +Integrate builder in dev server. +This simplifies the maintaince and future development. diff --git a/.changeset/curly-tools-itch.md b/.changeset/curly-tools-itch.md new file mode 100644 index 0000000..1ade958 --- /dev/null +++ b/.changeset/curly-tools-itch.md @@ -0,0 +1,13 @@ +--- +'@getcronit/pylon': minor +--- + +Show a fallback page for the landing page and unhandled routes / 404s. + +This behavior can be disabled via the pylon config: + +```ts +export const config: PylonConfig = { + landingPage: false +} +``` diff --git a/.changeset/ninety-toys-shout.md b/.changeset/ninety-toys-shout.md new file mode 100644 index 0000000..ac2b9fd --- /dev/null +++ b/.changeset/ninety-toys-shout.md @@ -0,0 +1,24 @@ +--- +'@getcronit/pylon-dev': minor +'@getcronit/pylon': minor +--- + +Add new plugin build hook to allow custom esbuild builds. +The build hook is currently called before the pylon main build process. +It does not re-run during watch mode, so you need to implement your own watch logic. + +Example: + +```ts +function usePagesPlugin(): Plugin { + return { + build: async () => { + // Custom esbuild build + } + } +} + +export const config: PylonConfig = { + plugins: [usePagesPlugin()] +} +``` diff --git a/.changeset/proud-trees-end.md b/.changeset/proud-trees-end.md new file mode 100644 index 0000000..354b7ab --- /dev/null +++ b/.changeset/proud-trees-end.md @@ -0,0 +1,7 @@ +--- +'create-pylon': minor +'@getcronit/pylon-dev': minor +'@getcronit/pylon': minor +--- + +Add pages plugin and use pm2 for dev server diff --git a/.changeset/real-fans-jog.md b/.changeset/real-fans-jog.md new file mode 100644 index 0000000..62f75b9 --- /dev/null +++ b/.changeset/real-fans-jog.md @@ -0,0 +1,5 @@ +--- +'@getcronit/pylon-dev': patch +--- + +Use esbuild watch mode instead of re-building from scratch. diff --git a/.changeset/six-vans-care.md b/.changeset/six-vans-care.md new file mode 100644 index 0000000..84c5811 --- /dev/null +++ b/.changeset/six-vans-care.md @@ -0,0 +1,5 @@ +--- +'@getcronit/pylon-dev': patch +--- + +replace `treekill` with `ps-tree` diff --git a/.changeset/stale-lobsters-flash.md b/.changeset/stale-lobsters-flash.md new file mode 100644 index 0000000..c50cebd --- /dev/null +++ b/.changeset/stale-lobsters-flash.md @@ -0,0 +1,36 @@ +--- +'@getcronit/pylon': major +--- + +**Summary:** +This changeset introduces a major overhaul to the built-in authentication system. The new implementation automatically sets up `/auth/login`, `/auth/callback`, and `/auth/logout` routes, injects an `auth` object into the context, and manages token cookies. Role-based route protection is now enhanced via `authMiddleware` and the updated `requireAuth` decorator, configurable through the streamlined `useAuth` plugin. + +--- + +**Breaking Changes:** + +- **WHAT:** + The authentication configuration has been completely revamped. The previous manual setup is replaced by the `useAuth` plugin. Custom authentication route definitions are no longer necessary, and existing middleware or decorator usage may require adjustments. + +- **WHY:** + This change was implemented to simplify authentication setup, reduce boilerplate, improve security by automating context and cookie management, and offer better role-based access control. + +- **HOW:** + Consumers should: + 1. Remove any custom authentication route setups. + 2. Update their configuration to use the new `useAuth` plugin as shown below: + ```typescript + export const config: PylonConfig = { + plugins: [ + useAuth({ + issuer: 'https://test-0o6zvq.zitadel.cloud', + endpoint: '/auth', + keyPath: 'key.json' + }) + ] + } + ``` + 3. Replace previous authentication middleware or decorators with the updated `requireAuth` and `authMiddleware` APIs. + 4. Test the new authentication endpoints (`/auth/login`, `/auth/callback`, and `/auth/logout`) to ensure proper integration. + +Ensure you update your code accordingly to avoid disruptions in your authentication flow. diff --git a/.changeset/tall-spies-joke.md b/.changeset/tall-spies-joke.md new file mode 100644 index 0000000..ac7479b --- /dev/null +++ b/.changeset/tall-spies-joke.md @@ -0,0 +1,32 @@ +--- +'@getcronit/pylon': minor +--- + +Extend plugin system with middleware and app setup support. +The viewer is now integrated via a built-in `useViewer` plugin. + +Custom plugins can now acces the app instance and register middleware and setup functions.: + +```ts +import {Plugin} from '@getcronit/pylon' + +export function myPlugin(): Plugin { + return { + setup(app) { + app.use((req, res, next) => { + console.log('Request:', req.url) + next() + }) + + app.get('/hello', (req, res) => { + res.send('Hello, World!') + }) + }, + middleware: (c, next) => { + // This middleware will be inserted higher in the middleware stack + console.log('Middleware:', c.req.url) + next() + } + } +} +``` diff --git a/docs/pages/docs/core-concepts/built-in-authentication-and-authorization.mdx b/docs/pages/docs/core-concepts/built-in-authentication-and-authorization.mdx index 5f8be38..7badc04 100644 --- a/docs/pages/docs/core-concepts/built-in-authentication-and-authorization.mdx +++ b/docs/pages/docs/core-concepts/built-in-authentication-and-authorization.mdx @@ -2,37 +2,59 @@ import {Callout} from '@components/callout' # Built-in Authentication and Authorization -Discover how Pylon simplifies user authentication and authorization with its comprehensive built-in features, empowering you to secure your web services effortlessly. +Pylon now offers an enhanced, streamlined authentication system. With this update, the auth endpoint automatically creates routes for **/auth/login**, **/auth/callback**, and **/auth/logout**. When a user authenticates, Pylon sets an `auth` object in the context variables and automatically manages a cookie with the token—simplifying session management and ensuring a secure experience. + +--- ## General Setup -Before diving into authentication and authorization with Pylon, it's essential to set up your environment and configure the necessary components. Pylon's built-in authentication system follows the OIDC standard and is currently tightly integrated with ZITADEL for user management and access control. +Before you begin, configure your environment to integrate with your authentication provider (e.g., ZITADEL). The new configuration uses the `useAuth` plugin to initialize authentication routes and settings. -1. **Environment Variables:** - Ensure you have the required environment variables set up in your project: +```typescript +import { + app, + PylonConfig, + requireAuth, + useAuth, + authMiddleware +} from '@getcronit/pylon' + +export const config: PylonConfig = { + plugins: [ + useAuth({ + issuer: 'https://test-0o6zvq.zitadel.cloud', + endpoint: '/auth', // optional, default is '/auth' + keyPath: 'key.json' // optional, default is 'key.json' + }) + ] +} +``` - ``` - AUTH_ISSUER=https://test-0o6zvq.zitadel.cloud - AUTH_PROJECT_ID= - ``` +**How it works:** -2. **Integration with ZITADEL:** - To enable Pylon to authenticate users and manage access control, you need to integrate it with ZITADEL. Follow the documentation provided by ZITADEL to set up projects, applications, keys, and roles. - [ZITADEL Projects Documentation](https://zitadel.com/docs/guides/manage/console/projects) +- **Auth Routes:** + The plugin automatically creates routes for: + + - `/auth/login` + - `/auth/callback` + - `/auth/logout` + +- **Context & Cookie:** + After authentication, an `auth` object is added to your context, and a cookie containing the token is set for subsequent requests. - Pylon requires a **API** application with the **Private JWT Key** type to - authenticate users and manage access control. + Ensure that your API application is configured to use a **Private JWT Key** + type for secure token management. +--- + ## Authentication Example -Pylon makes authentication seamless by providing a straightforward integration with ZITADEL. Here's how you can set up authentication in your Pylon project: +To protect sensitive data, use the `requireAuth` decorator. In the example below, any user trying to access the data must be authenticated: ```typescript -import {app, auth, requireAuth} from '@getcronit/pylon' - -// Define your sensitive data service +// Define a service for sensitive data class SensitiveData { @requireAuth() static async getData() { @@ -40,27 +62,26 @@ class SensitiveData { } } +// Expose the resolver via GraphQL export const graphql = { Query: { sensitiveData: SensitiveData.getData } } -app.use('*', auth.initialize()) - export default app ``` -In this example, the `requireAuth()` decorator ensures that users are authenticated before accessing sensitive data. You can also specify roles to restrict access to certain data based on user permissions. +In this setup, the `@requireAuth()` decorator ensures that only authenticated users can access the `getData` method. If the user is not authenticated, they will be redirected to the login flow at `/auth/login`. --- ## Authorization Example -Authorization in Pylon allows you to control access to specific resources based on user roles and permissions. Here's how you can implement authorization in your Pylon project: +If you need to restrict access based on roles, you can pass a roles array to the `requireAuth` decorator. For instance, the following example limits access to users with the `"admin"` role: ```typescript -// Define your sensitive data service +// Define a service for admin-only data class SensitiveData { @requireAuth({ roles: ['admin'] @@ -70,72 +91,63 @@ class SensitiveData { } } -// Define your GraphQL schema +// Expose the resolver via GraphQL export const graphql = { Query: { sensitiveAdminData: SensitiveData.getAdminData } } -app.use('*', auth.initialize()) - export default app ``` -In this example, the `requireAuth()` decorator ensures that only authenticated users with the "admin" role can access the `getAdminData()` function. You can customize roles and permissions according to your application's requirements. +Only authenticated users who have the `"admin"` role will be allowed to access `getAdminData()`. Roles should be managed in your authentication provider (e.g., ZITADEL) for centralized control over permissions. -Roles can be defined in ZITADEL and assigned to users to control access to specific resources. By integrating Pylon with ZITADEL, you can easily manage roles and permissions for your application. -For more information on setting up roles in ZITADEL, refer to the [ZITADEL Roles Documentation](https://zitadel.com/docs/guides/manage/console/roles). +--- -## Securing Routes +## Securing Routes with Middleware -Securing routes in Pylon involves enforcing authentication and, optionally, authorization for specific endpoints or routes. Here's how you can secure a route in your Pylon project: +In addition to securing individual resolvers, you can enforce authentication and authorization for entire routes using the new `authMiddleware`. For example, to secure a specific REST endpoint: ```typescript -import {auth, requireAuth} from '@getcronit/pylon' - -// Define your sensitive data service -class SensitiveData { - static async getData() { - return 'Sensitive Data' - } +import {authMiddleware} from '@getcronit/pylon' - @requireAuth({ +// Secure all routes under /admin to only allow users with the 'admin' role +app.use( + '/admin', + authMiddleware({ roles: ['admin'] }) - static async getAdminData() { - return 'Admin Data' - } -} +) -export const graphql = { - Query: { - sensitiveData: SensitiveData.getData, - sensitiveAdminData: SensitiveData.getAdminData +// Secure specific route to only allow users with the 'admin' role +app.get( + '/secure', + authMiddleware({ + roles: ['admin'] + }), + c => { + return c.json({data: 'sensitive'}) } -} - -// Enforce authentication for all routes -app.use('*', auth.initialize()) - -// Secure a specific route with authentication and authorization -app.use('/admin', auth.requireAuth({roles: ['admin']})) - +) export default app ``` -In this example, we're securing the `/admin` route to ensure that only authenticated users with the "admin" role can access it. By using the `requireAuth()` middleware from Pylon's authentication module, we enforce both authentication and authorization for this specific route. +In this case, any request to the `/admin` route will first pass through `authMiddleware`, ensuring that the user is authenticated and has the required `"admin"` role. +The same applies to the `/secure` route, which is secured with the `authMiddleware` middleware. -You can customize the route and the required roles according to your application's requirements. This ensures that sensitive endpoints are protected, providing a secure environment for your users' data and resources. +--- ## Further Resources -For detailed instructions on setting up projects, applications, keys, and roles in ZITADEL, refer to the ZITADEL documentation: +For additional guidance on integrating with your authentication provider, please refer to the following resources: - [ZITADEL Projects Documentation](https://zitadel.com/docs/guides/manage/console/projects) - [ZITADEL Applications Documentation](https://zitadel.com/docs/guides/manage/console/applications#api) - [ZITADEL Roles Documentation](https://zitadel.com/docs/guides/manage/console/roles) +--- + ## Conclusion -With Pylon's built-in authentication and authorization features, you can easily secure your web services and control access to sensitive data, providing a seamless and secure user experience. +With the new built-in authentication and authorization features, Pylon makes securing your web services simpler than ever. The automatic route creation, context management, and cookie handling streamline the login flow, while decorators and middleware give you granular control over access to your application’s data and routes. Enjoy a secure and seamless user experience with minimal configuration! diff --git a/examples/pages/.dockerignore b/examples/pages/.dockerignore new file mode 100644 index 0000000..f965aed --- /dev/null +++ b/examples/pages/.dockerignore @@ -0,0 +1,15 @@ +node_modules +Dockerfile* +docker-compose* +.dockerignore +.git +.gitignore +README.md +LICENSE +.vscode +Makefile +helm-charts +.env +.editorconfig +.idea +coverage* diff --git a/examples/pages/.gitignore b/examples/pages/.gitignore new file mode 100644 index 0000000..f3150c1 --- /dev/null +++ b/examples/pages/.gitignore @@ -0,0 +1,175 @@ +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +\*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +\*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +\*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +\*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.cache +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +.cache/ + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp +.cache + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.\* + +# wrangler project + +.dev.vars +.wrangler/ + +# Pylon project +.pylon diff --git a/examples/pages/components.json b/examples/pages/components.json new file mode 100644 index 0000000..51bb584 --- /dev/null +++ b/examples/pages/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "globals.css", + "baseColor": "zinc", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} diff --git a/examples/pages/components/ui/button.tsx b/examples/pages/components/ui/button.tsx new file mode 100644 index 0000000..4c72634 --- /dev/null +++ b/examples/pages/components/ui/button.tsx @@ -0,0 +1,58 @@ +import * as React from 'react' +import {Slot} from '@radix-ui/react-slot' +import {cva, type VariantProps} from 'class-variance-authority' + +import {cn} from '@/lib/utils' + +const buttonVariants = cva( + "inline-flexxx items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-[color,box-shadow] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 ring-ring/10 dark:ring-ring/20 dark:outline-ring/40 outline-ring/50 focus-visible:ring-4 focus-visible:outline-1 aria-invalid:focus-visible:ring-0", + { + variants: { + variant: { + default: + 'bg-primary text-primary-foreground shadow-sm hover:bg-primary/90', + destructive: + 'bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90', + outline: + 'border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground', + secondary: + 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80', + ghost: 'hover:bg-accent hover:text-accent-foreground', + link: 'text-primary underline-offset-4 hover:underline' + }, + size: { + default: 'h-9 px-4 py-2 has-[>svg]:px-3', + sm: 'h-8 rounded-md px-3 has-[>svg]:px-2.5', + lg: 'h-10 rounded-md px-6 has-[>svg]:px-4', + icon: 'size-9' + } + }, + defaultVariants: { + variant: 'default', + size: 'default' + } + } +) + +function Button({ + className, + variant, + size, + asChild = false, + ...props +}: React.ComponentProps<'button'> & + VariantProps & { + asChild?: boolean + }) { + const Comp = asChild ? Slot : 'button' + + return ( + + ) +} + +export {Button, buttonVariants} diff --git a/examples/pages/globals.css b/examples/pages/globals.css new file mode 100644 index 0000000..4c622ce --- /dev/null +++ b/examples/pages/globals.css @@ -0,0 +1,192 @@ +@import 'tailwindcss'; + +@plugin 'tailwindcss-animate'; + +@custom-variant dark (&:is(.dark *)); + +@theme { + --color-background: hsl(var(--background)); + --color-foreground: hsl(var(--foreground)); + + --color-card: hsl(var(--card)); + --color-card-foreground: hsl(var(--card-foreground)); + + --color-popover: hsl(var(--popover)); + --color-popover-foreground: hsl(var(--popover-foreground)); + + --color-primary: hsl(var(--primary)); + --color-primary-foreground: hsl(var(--primary-foreground)); + + --color-secondary: hsl(var(--secondary)); + --color-secondary-foreground: hsl(var(--secondary-foreground)); + + --color-muted: hsl(var(--muted)); + --color-muted-foreground: hsl(var(--muted-foreground)); + + --color-accent: hsl(var(--accent)); + --color-accent-foreground: hsl(var(--accent-foreground)); + + --color-destructive: hsl(var(--destructive)); + --color-destructive-foreground: hsl(var(--destructive-foreground)); + + --color-border: hsl(var(--border)); + --color-input: hsl(var(--input)); + --color-ring: hsl(var(--ring)); + + --color-chart-1: hsl(var(--chart-1)); + --color-chart-2: hsl(var(--chart-2)); + --color-chart-3: hsl(var(--chart-3)); + --color-chart-4: hsl(var(--chart-4)); + --color-chart-5: hsl(var(--chart-5)); + + --color-sidebar: hsl(var(--sidebar-background)); + --color-sidebar-foreground: hsl(var(--sidebar-foreground)); + --color-sidebar-primary: hsl(var(--sidebar-primary)); + --color-sidebar-primary-foreground: hsl(var(--sidebar-primary-foreground)); + --color-sidebar-accent: hsl(var(--sidebar-accent)); + --color-sidebar-accent-foreground: hsl(var(--sidebar-accent-foreground)); + --color-sidebar-border: hsl(var(--sidebar-border)); + --color-sidebar-ring: hsl(var(--sidebar-ring)); + + --radius-lg: var(--radius); + --radius-md: calc(var(--radius) - 2px); + --radius-sm: calc(var(--radius) - 4px); + + --animate-accordion-down: accordion-down 0.2s ease-out; + --animate-accordion-up: accordion-up 0.2s ease-out; + + @keyframes accordion-down { + from { + height: 0; + } + to { + height: var(--radix-accordion-content-height); + } + } + @keyframes accordion-up { + from { + height: var(--radix-accordion-content-height); + } + to { + height: 0; + } + } +} + +/* + The default border color has changed to `currentColor` in Tailwind CSS v4, + so we've added these compatibility styles to make sure everything still + looks the same as it did with Tailwind CSS v3. + + If we ever want to remove these styles, we need to add an explicit border + color utility to any element that depends on these defaults. +*/ +@layer base { + *, + ::after, + ::before, + ::backdrop, + ::file-selector-button { + border-color: var(--color-gray-200, currentColor); + } +} + +@layer utilities { + body { + font-family: Arial, Helvetica, sans-serif; + } +} + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 0 0% 3.9%; + --card: 0 0% 100%; + --card-foreground: 0 0% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 0 0% 3.9%; + --primary: 0 0% 9%; + --primary-foreground: 0 0% 98%; + --secondary: 0 0% 96.1%; + --secondary-foreground: 0 0% 9%; + --muted: 0 0% 96.1%; + --muted-foreground: 0 0% 45.1%; + --accent: 0 0% 96.1%; + --accent-foreground: 0 0% 9%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 89.8%; + --input: 0 0% 89.8%; + --ring: 0 0% 3.9%; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --radius: 0.5rem; + --sidebar-background: 0 0% 98%; + --sidebar-foreground: 240 5.3% 26.1%; + --sidebar-primary: 240 5.9% 10%; + --sidebar-primary-foreground: 0 0% 98%; + --sidebar-accent: 240 4.8% 95.9%; + --sidebar-accent-foreground: 240 5.9% 10%; + --sidebar-border: 220 13% 91%; + --sidebar-ring: 217.2 91.2% 59.8%; + } + .dark { + --background: 0 0% 3.9%; + --foreground: 0 0% 98%; + --card: 0 0% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 0 0% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 0 0% 9%; + --secondary: 0 0% 14.9%; + --secondary-foreground: 0 0% 98%; + --muted: 0 0% 14.9%; + --muted-foreground: 0 0% 63.9%; + --accent: 0 0% 14.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 14.9%; + --input: 0 0% 14.9%; + --ring: 0 0% 83.1%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + --sidebar-background: 240 5.9% 10%; + --sidebar-foreground: 240 4.8% 95.9%; + --sidebar-primary: 224.3 76.3% 48%; + --sidebar-primary-foreground: 0 0% 100%; + --sidebar-accent: 240 3.7% 15.9%; + --sidebar-accent-foreground: 240 4.8% 95.9%; + --sidebar-border: 240 3.7% 15.9%; + --sidebar-ring: 217.2 91.2% 59.8%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} + +/* + ---break--- +*/ + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/examples/pages/lib/utils.ts b/examples/pages/lib/utils.ts new file mode 100644 index 0000000..8fb87a3 --- /dev/null +++ b/examples/pages/lib/utils.ts @@ -0,0 +1,6 @@ +import {clsx, type ClassValue} from 'clsx' +import {twMerge} from 'tailwind-merge' + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/examples/pages/package.json b/examples/pages/package.json new file mode 100644 index 0000000..bb09597 --- /dev/null +++ b/examples/pages/package.json @@ -0,0 +1,36 @@ +{ + "name": "example-pages", + "private": true, + "version": "0.0.1", + "type": "module", + "description": "Generated with `npm create pylon`", + "scripts": { + "dev": "pylon dev -c \"PORT=3004 bun run .pylon/index.js\"", + "build": "pylon build" + }, + "dependencies": { + "@getcronit/pylon": "workspace:^", + "@gqty/react": "^3.1.0", + "@radix-ui/react-slot": "^1.1.2", + "bun-types": "^1.1.18", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "gqty": "3.4.0-canary-20250207102900.644ad9fdeafa6318516627577b1d4d754d5c5a98", + "lucide-react": "^0.474.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "tailwind-merge": "^3.0.1", + "tailwindcss": "^4.0.4", + "tailwindcss-animate": "^1.0.7" + }, + "devDependencies": { + "@getcronit/pylon-dev": "workspace:^", + "@tailwindcss/postcss": "^4.0.6", + "@types/react": "^19.0.8" + }, + "repository": { + "type": "git", + "url": "https://github.com/getcronit/pylon.git" + }, + "homepage": "https://pylon.cronit.io" +} diff --git a/examples/pages/pages/foo2/page.tsx b/examples/pages/pages/foo2/page.tsx new file mode 100644 index 0000000..5bd61a7 --- /dev/null +++ b/examples/pages/pages/foo2/page.tsx @@ -0,0 +1,13 @@ +import {Button} from '@/components/ui/button' +import {PageProps} from '@getcronit/pylon' + +const Page: React.FC = props => { + return ( +
+ {props.data.hello} + +
+ ) +} + +export default Page diff --git a/examples/pages/pages/layout.tsx b/examples/pages/pages/layout.tsx new file mode 100644 index 0000000..263bb65 --- /dev/null +++ b/examples/pages/pages/layout.tsx @@ -0,0 +1,9 @@ +import '../globals.css' + +export default function RootLayout({children}: {children: React.ReactNode}) { + return ( + + {children} + + ) +} diff --git a/examples/pages/pages/page.tsx b/examples/pages/pages/page.tsx new file mode 100644 index 0000000..78b4d4f --- /dev/null +++ b/examples/pages/pages/page.tsx @@ -0,0 +1,13 @@ +import { Button } from '@/components/ui/button' +import { PageProps } from '@getcronit/pylon/pages' + +const Page: React.FC = props => { + return ( +
+ {props.data.hello} + +
+ ) +} + +export default Page diff --git a/examples/pages/pages/test2/page.tsx b/examples/pages/pages/test2/page.tsx new file mode 100644 index 0000000..a33ff7e --- /dev/null +++ b/examples/pages/pages/test2/page.tsx @@ -0,0 +1,13 @@ +import { Button } from '@/components/ui/button' +import { PageProps } from '@getcronit/pylon/pages' + +const Page: React.FC = props => { + return ( +
+ {props.data.hello} + +
+ ) +} + +export default Page diff --git a/examples/pages/postcss.config.js b/examples/pages/postcss.config.js new file mode 100644 index 0000000..bcfc5bd --- /dev/null +++ b/examples/pages/postcss.config.js @@ -0,0 +1,5 @@ +import tailwindPostCss from '@tailwindcss/postcss' + +export default { + plugins: [tailwindPostCss] +} diff --git a/examples/pages/pylon.d.ts b/examples/pages/pylon.d.ts new file mode 100644 index 0000000..be56fdd --- /dev/null +++ b/examples/pages/pylon.d.ts @@ -0,0 +1,12 @@ +import '@getcronit/pylon' +import {useQuery} from './.pylon/client' + +declare module '@getcronit/pylon' { + interface Bindings {} + + interface Variables {} +} + +declare module '@getcronit/pylon/pages' { + interface PageData extends ReturnType {} +} diff --git a/examples/pages/src/index.ts b/examples/pages/src/index.ts new file mode 100644 index 0000000..cb9324b --- /dev/null +++ b/examples/pages/src/index.ts @@ -0,0 +1,22 @@ +import {app, usePages, PylonConfig} from '@getcronit/pylon' + +export const graphql = { + Query: { + hello: () => { + return 'Hello, world!22345689121' + }, + someOtherHello: () => { + return 'Hello, world!' + }, + someOtherHello3: () => { + return 'Hello, world2! Nicoo' + } + }, + Mutation: {} +} + +export const config: PylonConfig = { + plugins: [usePages()] +} + +export default app diff --git a/examples/pages/tsconfig.json b/examples/pages/tsconfig.json new file mode 100644 index 0000000..27e0fe3 --- /dev/null +++ b/examples/pages/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "@getcronit/pylon/tsconfig.pylon.json", + "compilerOptions": { + "moduleResolution": "bundler", + // add Bun type definitions + "types": ["bun-types"], + "baseUrl": ".", + "paths": { + "@/*": ["./*"] + }, + "jsx": "react-jsx", // support JSX + "allowJs": true // allow importing `.js` from `.ts` + }, + "include": [ + "pylon.d.ts", + "src/**/*.ts", + + "pages", + "components", + ".pylon/**/*.ts" + ] +} diff --git a/packages/create-pylon/src/index.ts b/packages/create-pylon/src/index.ts index 71fdbca..c0b0d94 100644 --- a/packages/create-pylon/src/index.ts +++ b/packages/create-pylon/src/index.ts @@ -42,7 +42,7 @@ const runtimes: { key: 'bun', name: 'Bun.js', website: 'https://bunjs.dev', - templates: ['default'] + templates: ['default', 'pages'] }, { key: 'node', @@ -78,6 +78,11 @@ const templates: { key: 'database', name: 'Database (Prisma)', description: 'Template with Prisma ORM' + }, + { + key: 'pages', + name: 'Fullstack (Pages)', + description: 'Build fullstack applications with pages and layouts.' } ] @@ -284,14 +289,6 @@ program .addOption( new Option('-pm, --package-manager ', 'Package manager') ) - .addOption( - new Option( - '--client', - 'Enable client generation (https://pylon.cronit.io/docs/integrations/gqty)' - ) - ) - .addOption(new Option('--client-path ', 'Client path')) - .addOption(new Option('--client-port ', 'Client port')) .action(main) type ArgOptions = { @@ -299,9 +296,6 @@ type ArgOptions = { runtime: string template: string packageManager?: PackageManager - client?: boolean - clientPath?: string - clientPort?: string } const getPreferredPmByRuntime = ( @@ -326,10 +320,7 @@ async function main( install: installArg, runtime: runtimeArg, template: templateArg, - packageManager: packageManagerArg, - client: clientArg, - clientPath: clientPathArg, - clientPort: clientPortArg + packageManager: packageManagerArg } = options let target = '' @@ -426,72 +417,6 @@ async function main( await installDependencies({target, packageManager}) } - const client = - clientArg || - (await confirm({ - message: - 'Would you like to enable client generation? (https://pylon.cronit.io/docs/integrations/gqty)', - default: false - })) - - let clientRoot: string = '' - let clientPath: string = '' - let clientPort: string = '' - - if (client) { - if (!clientPathArg) { - clientRoot = await input({ - message: 'Path to the root where the client should be generated', - default: '.' - }) - - clientPath = await input({ - message: 'Path to generate the client to', - default: path.join(clientRoot, 'gqty/index.ts'), - validate: value => { - // Check if the path starts with the client root (take care of .) - if (!value.startsWith(clientRoot === '.' ? '' : clientRoot)) { - return 'Path must start with the client root' - } - - return true - } - }) - } - - clientPort = - clientPortArg || - (await input({ - message: 'Port of the pylon server to generate the client from', - default: '3000' - })) - - consola.start(`Updating pylon dev script to generate client`) - - let packagePath: string - let scriptKey: string - if (runtime.key === 'deno') { - packagePath = path.join(target, 'deno.json') - scriptKey = 'tasks' - } else { - packagePath = path.join(target, 'package.json') - scriptKey = 'scripts' - } - - const devScript = JSON.parse(fs.readFileSync(packagePath, 'utf-8')) - - devScript[scriptKey] = { - ...devScript[scriptKey], - dev: - devScript[scriptKey].dev + - ` --client --client-port ${clientPort} --client-path ${clientPath}` - } - - fs.writeFileSync(packagePath, JSON.stringify(devScript, null, 2)) - - consola.success(`Pylon dev script updated`) - } - const runScript = getRunScript(packageManager) const message = ` @@ -513,9 +438,7 @@ async function main( name: projectName, pylonCreateVersion: version, runtime: runtimeName, - template: templateName, - clientPath: clientPath || undefined, - clientPort: parseInt(clientPort) || undefined + template: templateName }) consola.box(message) diff --git a/packages/create-pylon/templates/bun/pages/components.json b/packages/create-pylon/templates/bun/pages/components.json new file mode 100644 index 0000000..51bb584 --- /dev/null +++ b/packages/create-pylon/templates/bun/pages/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "globals.css", + "baseColor": "zinc", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} diff --git a/packages/create-pylon/templates/bun/pages/components/ui/button.tsx b/packages/create-pylon/templates/bun/pages/components/ui/button.tsx new file mode 100644 index 0000000..9129a78 --- /dev/null +++ b/packages/create-pylon/templates/bun/pages/components/ui/button.tsx @@ -0,0 +1,57 @@ +import * as React from 'react' +import {Slot} from '@radix-ui/react-slot' +import {cva, type VariantProps} from 'class-variance-authority' + +import {cn} from '@/lib/utils' + +const buttonVariants = cva( + 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', + { + variants: { + variant: { + default: + 'bg-primary text-primary-foreground shadow hover:bg-primary/90', + destructive: + 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90', + outline: + 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground', + secondary: + 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80', + ghost: 'hover:bg-accent hover:text-accent-foreground', + link: 'text-primary underline-offset-4 hover:underline' + }, + size: { + default: 'h-9 px-4 py-2', + sm: 'h-8 rounded-md px-3 text-xs', + lg: 'h-10 rounded-md px-8', + icon: 'h-9 w-9' + } + }, + defaultVariants: { + variant: 'default', + size: 'default' + } + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({className, variant, size, asChild = false, ...props}, ref) => { + const Comp = asChild ? Slot : 'button' + return ( + + ) + } +) +Button.displayName = 'Button' + +export {Button, buttonVariants} diff --git a/packages/create-pylon/templates/bun/pages/globals.css b/packages/create-pylon/templates/bun/pages/globals.css new file mode 100644 index 0000000..888cf65 --- /dev/null +++ b/packages/create-pylon/templates/bun/pages/globals.css @@ -0,0 +1,179 @@ +@import 'tailwindcss'; + +@plugin 'tailwindcss-animate'; + +@custom-variant dark (&:is(.dark *)); + +@theme { + --color-background: hsl(var(--background)); + --color-foreground: hsl(var(--foreground)); + + --color-card: hsl(var(--card)); + --color-card-foreground: hsl(var(--card-foreground)); + + --color-popover: hsl(var(--popover)); + --color-popover-foreground: hsl(var(--popover-foreground)); + + --color-primary: hsl(var(--primary)); + --color-primary-foreground: hsl(var(--primary-foreground)); + + --color-secondary: hsl(var(--secondary)); + --color-secondary-foreground: hsl(var(--secondary-foreground)); + + --color-muted: hsl(var(--muted)); + --color-muted-foreground: hsl(var(--muted-foreground)); + + --color-accent: hsl(var(--accent)); + --color-accent-foreground: hsl(var(--accent-foreground)); + + --color-destructive: hsl(var(--destructive)); + --color-destructive-foreground: hsl(var(--destructive-foreground)); + + --color-border: hsl(var(--border)); + --color-input: hsl(var(--input)); + --color-ring: hsl(var(--ring)); + + --color-chart-1: hsl(var(--chart-1)); + --color-chart-2: hsl(var(--chart-2)); + --color-chart-3: hsl(var(--chart-3)); + --color-chart-4: hsl(var(--chart-4)); + --color-chart-5: hsl(var(--chart-5)); + + --color-sidebar: hsl(var(--sidebar-background)); + --color-sidebar-foreground: hsl(var(--sidebar-foreground)); + --color-sidebar-primary: hsl(var(--sidebar-primary)); + --color-sidebar-primary-foreground: hsl(var(--sidebar-primary-foreground)); + --color-sidebar-accent: hsl(var(--sidebar-accent)); + --color-sidebar-accent-foreground: hsl(var(--sidebar-accent-foreground)); + --color-sidebar-border: hsl(var(--sidebar-border)); + --color-sidebar-ring: hsl(var(--sidebar-ring)); + + --radius-lg: var(--radius); + --radius-md: calc(var(--radius) - 2px); + --radius-sm: calc(var(--radius) - 4px); + + --animate-accordion-down: accordion-down 0.2s ease-out; + --animate-accordion-up: accordion-up 0.2s ease-out; + + @keyframes accordion-down { + from { + height: 0; + } + to { + height: var(--radix-accordion-content-height); + } + } + @keyframes accordion-up { + from { + height: var(--radix-accordion-content-height); + } + to { + height: 0; + } + } +} + +/* + The default border color has changed to `currentColor` in Tailwind CSS v4, + so we've added these compatibility styles to make sure everything still + looks the same as it did with Tailwind CSS v3. + + If we ever want to remove these styles, we need to add an explicit border + color utility to any element that depends on these defaults. +*/ +@layer base { + *, + ::after, + ::before, + ::backdrop, + ::file-selector-button { + border-color: var(--color-gray-200, currentColor); + } +} + +@layer utilities { + body { + font-family: Arial, Helvetica, sans-serif; + } +} + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 0 0% 3.9%; + --card: 0 0% 100%; + --card-foreground: 0 0% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 0 0% 3.9%; + --primary: 0 0% 9%; + --primary-foreground: 0 0% 98%; + --secondary: 0 0% 96.1%; + --secondary-foreground: 0 0% 9%; + --muted: 0 0% 96.1%; + --muted-foreground: 0 0% 45.1%; + --accent: 0 0% 96.1%; + --accent-foreground: 0 0% 9%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 89.8%; + --input: 0 0% 89.8%; + --ring: 0 0% 3.9%; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --radius: 0.5rem; + --sidebar-background: 0 0% 98%; + --sidebar-foreground: 240 5.3% 26.1%; + --sidebar-primary: 240 5.9% 10%; + --sidebar-primary-foreground: 0 0% 98%; + --sidebar-accent: 240 4.8% 95.9%; + --sidebar-accent-foreground: 240 5.9% 10%; + --sidebar-border: 220 13% 91%; + --sidebar-ring: 217.2 91.2% 59.8%; + } + .dark { + --background: 0 0% 3.9%; + --foreground: 0 0% 98%; + --card: 0 0% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 0 0% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 0 0% 9%; + --secondary: 0 0% 14.9%; + --secondary-foreground: 0 0% 98%; + --muted: 0 0% 14.9%; + --muted-foreground: 0 0% 63.9%; + --accent: 0 0% 14.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 14.9%; + --input: 0 0% 14.9%; + --ring: 0 0% 83.1%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + --sidebar-background: 240 5.9% 10%; + --sidebar-foreground: 240 4.8% 95.9%; + --sidebar-primary: 224.3 76.3% 48%; + --sidebar-primary-foreground: 0 0% 100%; + --sidebar-accent: 240 3.7% 15.9%; + --sidebar-accent-foreground: 240 4.8% 95.9%; + --sidebar-border: 240 3.7% 15.9%; + --sidebar-ring: 217.2 91.2% 59.8%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/packages/create-pylon/templates/bun/pages/lib/utils.ts b/packages/create-pylon/templates/bun/pages/lib/utils.ts new file mode 100644 index 0000000..8fb87a3 --- /dev/null +++ b/packages/create-pylon/templates/bun/pages/lib/utils.ts @@ -0,0 +1,6 @@ +import {clsx, type ClassValue} from 'clsx' +import {twMerge} from 'tailwind-merge' + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/packages/create-pylon/templates/bun/pages/package.json b/packages/create-pylon/templates/bun/pages/package.json new file mode 100644 index 0000000..7883ee1 --- /dev/null +++ b/packages/create-pylon/templates/bun/pages/package.json @@ -0,0 +1,36 @@ +{ + "name": "examples-pages", + "private": true, + "version": "0.0.1", + "type": "module", + "description": "Generated with `npm create pylon`", + "scripts": { + "dev": "pylon dev -c \"bun run .pylon/index.js\"", + "build": "pylon build" + }, + "dependencies": { + "@getcronit/pylon": "canary", + "@gqty/react": "^3.1.0", + "@radix-ui/react-slot": "^1.1.2", + "bun-types": "^1.1.18", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "gqty": "^3.4.1", + "lucide-react": "^0.474.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "tailwind-merge": "^3.0.1", + "tailwindcss": "^4.0.4", + "tailwindcss-animate": "^1.0.7" + }, + "devDependencies": { + "@getcronit/pylon-dev": "canary", + "@types/react": "^19.0.8", + "@tailwindcss/postcss": "^4.0.6" + }, + "repository": { + "type": "git", + "url": "https://github.com/getcronit/pylon.git" + }, + "homepage": "https://pylon.cronit.io" +} diff --git a/packages/create-pylon/templates/bun/pages/pages/layout.tsx b/packages/create-pylon/templates/bun/pages/pages/layout.tsx new file mode 100644 index 0000000..263bb65 --- /dev/null +++ b/packages/create-pylon/templates/bun/pages/pages/layout.tsx @@ -0,0 +1,9 @@ +import '../globals.css' + +export default function RootLayout({children}: {children: React.ReactNode}) { + return ( + + {children} + + ) +} diff --git a/packages/create-pylon/templates/bun/pages/pages/page.tsx b/packages/create-pylon/templates/bun/pages/pages/page.tsx new file mode 100644 index 0000000..dc706ed --- /dev/null +++ b/packages/create-pylon/templates/bun/pages/pages/page.tsx @@ -0,0 +1,13 @@ +import { Button } from '@/components/ui/button' +import { PageProps } from '@getcronit/pylon/pages' + +const Page: React.FC = ({ data }) => { + return ( +
+ {data.hello} + +
+ ) +} + +export default Page diff --git a/packages/create-pylon/templates/bun/pages/postcss.config.js b/packages/create-pylon/templates/bun/pages/postcss.config.js new file mode 100644 index 0000000..bcfc5bd --- /dev/null +++ b/packages/create-pylon/templates/bun/pages/postcss.config.js @@ -0,0 +1,5 @@ +import tailwindPostCss from '@tailwindcss/postcss' + +export default { + plugins: [tailwindPostCss] +} diff --git a/packages/create-pylon/templates/bun/pages/pylon.d.ts b/packages/create-pylon/templates/bun/pages/pylon.d.ts new file mode 100644 index 0000000..be56fdd --- /dev/null +++ b/packages/create-pylon/templates/bun/pages/pylon.d.ts @@ -0,0 +1,12 @@ +import '@getcronit/pylon' +import {useQuery} from './.pylon/client' + +declare module '@getcronit/pylon' { + interface Bindings {} + + interface Variables {} +} + +declare module '@getcronit/pylon/pages' { + interface PageData extends ReturnType {} +} diff --git a/packages/create-pylon/templates/bun/pages/src/index.ts b/packages/create-pylon/templates/bun/pages/src/index.ts new file mode 100644 index 0000000..4e97029 --- /dev/null +++ b/packages/create-pylon/templates/bun/pages/src/index.ts @@ -0,0 +1,16 @@ +import {app, usePages, PylonConfig} from '@getcronit/pylon' + +export const graphql = { + Query: { + hello: () => { + return 'Hello, world!' + } + }, + Mutation: {} +} + +export const config: PylonConfig = { + plugins: [usePages()] +} + +export default app diff --git a/packages/create-pylon/templates/bun/pages/tsconfig.json b/packages/create-pylon/templates/bun/pages/tsconfig.json new file mode 100644 index 0000000..d2bdff6 --- /dev/null +++ b/packages/create-pylon/templates/bun/pages/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "@getcronit/pylon/tsconfig.pylon.json", + "compilerOptions": { + // add Bun type definitions + "types": ["bun-types"], + "baseUrl": ".", + "paths": { + "@/*": ["./*"] + }, + "jsx": "react-jsx", // support JSX + "allowJs": true // allow importing `.js` from `.ts` + }, + "include": [ + "pylon.d.ts", + "src/**/*.ts", + "pages", + "components", + ".pylon/**/*.ts" + ] +} diff --git a/packages/pylon-builder/CHANGELOG.md b/packages/pylon-builder/CHANGELOG.md deleted file mode 100644 index 7937753..0000000 --- a/packages/pylon-builder/CHANGELOG.md +++ /dev/null @@ -1,13 +0,0 @@ -# @getcronit/pylon-builder - -## 2.6.7 - -### Patch Changes - -- [#67](https://github.com/getcronit/pylon/pull/67) [`eb85d99`](https://github.com/getcronit/pylon/commit/eb85d9920235e0322f39f46576e1098526e871b5) Thanks [@schettn](https://github.com/schettn)! - Missing build before release lead to broken packages - -## 2.6.6 - -### Patch Changes - -- [#62](https://github.com/getcronit/pylon/pull/62) [`084df6d`](https://github.com/getcronit/pylon/commit/084df6daa53ccfe575db1aacbd1a07adebf8a716) Thanks [@schettn](https://github.com/schettn)! - Replace bun with pnpm, replace bun build with esbuild and replace semantic-release with changesets. diff --git a/packages/pylon-builder/package.json b/packages/pylon-builder/package.json deleted file mode 100644 index 2094798..0000000 --- a/packages/pylon-builder/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "@getcronit/pylon-builder", - "version": "2.6.7", - "type": "module", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "rimraf ./dist && esbuild ./src/index.ts --bundle --platform=node --target=node18 --format=esm --minify --outdir=./dist --sourcemap=linked --packages=external && pnpm run build:declarations", - "build:declarations": "tsc --declaration --emitDeclarationOnly --outDir ./dist" - }, - "files": [ - "dist" - ], - "author": "Nico Schett ", - "license": "Apache-2.0", - "repository": { - "type": "git", - "url": "https://github.com/getcronit/pylon.git", - "directory": "packages/pylon-builder" - }, - "homepage": "https://pylon.cronit.io", - "dependencies": { - "chokidar": "^3.5.3", - "consola": "^3.2.3", - "esbuild": "^0.23.1", - "esbuild-plugin-tsc": "^0.4.0", - "source-map-support": "^0.5.21", - "typescript": "^5.0.0" - }, - "engines": { - "node": ">=18.0.0" - } -} diff --git a/packages/pylon-builder/src/bundler/bundler.ts b/packages/pylon-builder/src/bundler/bundler.ts deleted file mode 100644 index 7f41e63..0000000 --- a/packages/pylon-builder/src/bundler/bundler.ts +++ /dev/null @@ -1,203 +0,0 @@ -// bundler.ts -import fs from 'fs' -import chokidar from 'chokidar' -import {Plugin, build} from 'esbuild' -import esbuildPluginTsc from 'esbuild-plugin-tsc' - -import path from 'path' -import consola from 'consola' - -export interface BundlerBuildOptions { - getBuildDefs: () => { - typeDefs: string - resolvers: Record< - string, - { - __resolveType?: (obj: any) => string - } - > - } - watch?: boolean - onWatch?: (output: { - totalFiles: number - totalSize: number - schemaChanged: boolean - duration: number - }) => void -} - -export class Bundler { - sfiFilePath: string - outputDir: string - - private cachedTypeDefs: string | null = null - - constructor(sfiFilePath: string, outputDir: string = './.pylon') { - this.sfiFilePath = sfiFilePath - this.outputDir = outputDir - } - - public async build(options: BundlerBuildOptions) { - const buildOnce = async () => { - const startTime = Date.now() - - const {typeDefs, resolvers} = options.getBuildDefs() - - const preparedResolvers = prepareObjectInjection(resolvers) - - const injectCodePlugin: Plugin = { - name: 'inject-code', - setup(build) { - build.onLoad( - {filter: /src[\/\\]index\.ts$/, namespace: 'file'}, - async args => { - // Convert to relative path to ensure we match `src/index.ts` at root - const relativePath = path.relative(process.cwd(), args.path) - - if (relativePath !== path.join('src', 'index.ts')) { - return - } - - const contents = await fs.promises.readFile(args.path, 'utf-8') - - return { - loader: 'ts', - contents: - contents + - ` - import {handler as __internalPylonHandler} from "@getcronit/pylon" - - let __internalPylonConfig = undefined - - try { - __internalPylonConfig = config - } catch { - // config is not declared, pylonConfig remains undefined - } - - app.use(__internalPylonHandler({ - typeDefs: ${JSON.stringify(typeDefs)}, - graphql, - resolvers: ${preparedResolvers}, - config: __internalPylonConfig - })) - ` - } - } - ) - } - } - - const inputPath = path.join(process.cwd(), this.sfiFilePath) - const dir = path.join(process.cwd(), this.outputDir) - - const output = await build({ - logLevel: 'silent', - metafile: true, - entryPoints: [inputPath], - outdir: dir, - bundle: true, - format: 'esm', - sourcemap: 'inline', - packages: 'external', - plugins: [ - injectCodePlugin, - esbuildPluginTsc({ - tsconfigPath: path.join(process.cwd(), 'tsconfig.json') - }) - ] - }) - - if (output.errors.length > 0) { - for (const error of output.errors) { - consola.error(error) - } - - throw new Error('Failed to build Pylon') - } - - if (output.warnings.length > 0) { - for (const warning of output.warnings) { - consola.warn(warning) - } - } - - const schemaChanged = this.cachedTypeDefs !== typeDefs - - this.cachedTypeDefs = typeDefs - - const duration = Date.now() - startTime - - const totalFiles = Object.keys(output.metafile.inputs).length - const totalSize = Object.values(output.metafile.outputs).reduce( - (acc, output) => acc + output.bytes, - 0 - ) - - // Write the typeDefs to a file - const typeDefsPath = path.join( - process.cwd(), - this.outputDir, - 'schema.graphql' - ) - - await fs.promises.writeFile(typeDefsPath, typeDefs) - - // Write base resolvers to a file - - const resolversPath = path.join( - process.cwd(), - this.outputDir, - 'resolvers.js' - ) - - await fs.promises.writeFile( - resolversPath, - `export const resolvers = ${preparedResolvers}` - ) - - return { - totalFiles, - totalSize, - schemaChanged, - duration - } - } - - if (options.watch) { - const folder = path.dirname(this.sfiFilePath) - - chokidar.watch(folder).on('change', async () => { - try { - const output = await buildOnce() - - if (options.onWatch) { - options.onWatch(output) - } - } catch (e) { - consola.error(e) - } - }) - } - - return await buildOnce() - } -} - -function prepareObjectInjection(obj: object) { - const entries = Object.entries(obj).map(([key, value]) => { - if (value === undefined) { - return undefined - } else if (typeof value === 'string') { - return `${key}:${value}` - } else if (typeof value === 'function') { - return `${key}:${value.toString()}` - } else if (typeof value === 'object' && !Array.isArray(value)) { - return `${key}:${prepareObjectInjection(value)}` - } else { - return `${key}:${JSON.stringify(value)}` - } - }) - - return `{${entries.join(',')}}` -} diff --git a/packages/pylon-builder/src/load-package-json.ts b/packages/pylon-builder/src/load-package-json.ts deleted file mode 100644 index 104a6ad..0000000 --- a/packages/pylon-builder/src/load-package-json.ts +++ /dev/null @@ -1,15 +0,0 @@ -import path from 'path' -import {readFile} from 'fs/promises' - -export async function loadPackageJson(): Promise<{ - baseURL?: string - pylon?: { - external?: string[] - } -}> { - const packageJsonPath = path.resolve(process.cwd(), 'package.json') - - const file = await readFile(packageJsonPath) - const packageJson = JSON.parse(file.toString()) - return packageJson -} diff --git a/packages/pylon-builder/tsconfig.json b/packages/pylon-builder/tsconfig.json deleted file mode 100644 index a49984e..0000000 --- a/packages/pylon-builder/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "include": ["src/**/*"], - "exclude": ["node_modules/**/*", "**/*.test.ts", "**/*.test.tsx", "**/__tests__/**/*"] -} \ No newline at end of file diff --git a/packages/pylon-dev/package.json b/packages/pylon-dev/package.json index d2b367a..94349ee 100644 --- a/packages/pylon-dev/package.json +++ b/packages/pylon-dev/package.json @@ -21,18 +21,27 @@ }, "homepage": "https://pylon.cronit.io", "dependencies": { - "@getcronit/pylon-builder": "workspace:^", "@getcronit/pylon-telemetry": "workspace:^", - "@gqty/cli": "^4.2.0", + "@gqty/cli": "^4.2.5", "commander": "^12.1.0", "consola": "^3.2.3", "dotenv": "^16.4.5", - "treekill": "^1.0.0" + "esbuild": "^0.23.1", + "esbuild-plugin-tsc": "^0.4.0", + "pm2": "^5.4.3" }, "publishConfig": { "access": "public" }, "engines": { "node": ">=18.0.0" + }, + "devDependencies": { + "@types/ps-tree": "^1.1.6", + "typescript": "^5.7.3" + }, + "peerDependencies": { + "@getcronit/pylon": "workspace:^2.0.0", + "graphql": "^16.9.0" } } diff --git a/packages/pylon-dev/src/builder/build-client.ts b/packages/pylon-dev/src/builder/build-client.ts new file mode 100644 index 0000000..e3e644d --- /dev/null +++ b/packages/pylon-dev/src/builder/build-client.ts @@ -0,0 +1,156 @@ +import path from 'path' +import fs from 'fs/promises' +import {generateClient} from '@gqty/cli' +import {buildSchema} from 'graphql' +import {updateFileIfChanged} from './update-file-if-changed' + +const PYLON_SCHEMA_PATH = path.join(process.cwd(), '.pylon/schema.graphql') +const PYLON_CLIENT_PATH = path.join(process.cwd(), '.pylon/client/index.ts') + +export interface BuildClientOptions { + /** + * Client will be generated if the schema has changed or if the client does not exist + */ + schemaChanged: boolean +} + +export const buildClient = async ({schemaChanged}: BuildClientOptions) => { + // Check if the schema exists + + try { + await fs.access(PYLON_SCHEMA_PATH) + } catch (e) { + throw new Error( + 'Schema not found. Please run `pylon build` or `pylon dev` first.' + ) + } + + // Check if the client exists + if (!schemaChanged) { + // If the schema has not changed, we need to check if the client exists + try { + await fs.access(PYLON_CLIENT_PATH) + return + } catch (e) { + // If the client does not exist, we need to generate it + } + } + + const schema = await fs.readFile(PYLON_SCHEMA_PATH, 'utf-8') + + const schemaObj = buildSchema(schema) + + // Write the custom client index file because the default one is not compatible with Pylon + await fs.mkdir(path.dirname(PYLON_CLIENT_PATH), {recursive: true}) + await updateFileIfChanged(PYLON_CLIENT_PATH, customClientIndex) + + await generateClient(schemaObj, { + endpoint: 'will-be-overwritten', + frameworks: ['react'], + destination: PYLON_CLIENT_PATH, + react: true, + scalarTypes: { + Number: 'number', + Object: 'Record' + } + }) +} + +const customClientIndex = `/** + * GQty: You can safely modify this file based on your needs. + */ + +import {createReactClient} from '@gqty/react' +import { + Cache, + createClient, + defaultResponseHandler, + type QueryFetcher +} from 'gqty' +import { + generatedSchema, + scalarsEnumsHash, + type GeneratedSchema +} from './schema.generated' + +const queryFetcher: QueryFetcher = async function ( + {query, variables, operationName}, + fetchOptions +) { + let browserOrInternalFetch: typeof fetch | typeof app.request = fetch + + try { + const moduleNameToPreventBundling = '@getcronit/pylon' + const {app} = await import(moduleNameToPreventBundling) + + browserOrInternalFetch = app.request + } catch (error) { + // Pylon is not found. Maybe we are running in a different environment. + } + + const response = await browserOrInternalFetch('/graphql', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + query, + variables, + operationName + }), + mode: 'cors', + ...fetchOptions + }) + + return await defaultResponseHandler(response) +} + +const cache = new Cache( + undefined, + /** + * Default option is immediate cache expiry but keep it for 5 minutes, + * allowing soft refetches in background. + */ + { + maxAge: Infinity, + staleWhileRevalidate: 5 * 60 * 1000, + normalization: true + } +) + +export const client = createClient({ + schema: generatedSchema, + scalars: scalarsEnumsHash, + cache, + fetchOptions: { + fetcher: queryFetcher + } +}) + +// Core functions +export const {resolve, subscribe, schema} = client + +// Legacy functions +export const {query, mutation, mutate, subscription, resolved, refetch, track} = + client + +export const { + graphql, + useQuery, + usePaginatedQuery, + useTransactionQuery, + useLazyQuery, + useRefetch, + useMutation, + useMetaState, + prepareReactRender, + useHydrateCache, + prepareQuery +} = createReactClient(client, { + defaults: { + // Enable Suspense, you can override this option for each hook. + suspense: false + } +}) + +export * from './schema.generated'` diff --git a/packages/pylon-dev/src/builder/bundler/bundler.ts b/packages/pylon-dev/src/builder/bundler/bundler.ts new file mode 100644 index 0000000..608f103 --- /dev/null +++ b/packages/pylon-dev/src/builder/bundler/bundler.ts @@ -0,0 +1,164 @@ +// bundler.ts +import esbuild, {context} from 'esbuild' +import esbuildPluginTsc from 'esbuild-plugin-tsc' +import type {PylonConfig, Plugin} from '@getcronit/pylon' + +import path from 'path' +import fs from 'fs/promises' +import { + InjectCodePluginOptions, + injectCodePlugin +} from './plugins/inject-code-plugin' +import {NotifyPluginOptions, notifyPlugin} from './plugins/notify-plugin' +import {updateFileIfChanged} from '../update-file-if-changed' + +export interface BundlerBuildOptions { + getBuildDefs: InjectCodePluginOptions['getBuildDefs'] + onBuild?: NotifyPluginOptions['onBuild'] +} + +export class Bundler { + sfiFilePath: string + outputDir: string + + constructor(sfiFilePath: string, outputDir: string = './.pylon') { + this.sfiFilePath = sfiFilePath + this.outputDir = outputDir + } + + private async initBuildPlugins(args: {onBuild: () => void}) { + const configPath = path.join(process.cwd(), this.outputDir, 'index.js') + + let config: PylonConfig | undefined + try { + let configModule = await import(configPath) + + config = configModule.config + } catch (e) { + console.error('Error loading config', e) + } + + const buildContexts: ReturnType>[] = [] + + const plugins = config?.plugins || [] + + for (const plugin of plugins) { + if (plugin.build) { + const ctx = plugin.build({onBuild: args.onBuild}) + + buildContexts.push(ctx) + } + } + + return buildContexts + } + + public async build(options: BundlerBuildOptions) { + const inputPath = path.join(process.cwd(), this.sfiFilePath) + const dir = path.join(process.cwd(), this.outputDir) + + // Create directory if it doesn't exist + await fs.mkdir(dir, {recursive: true}) + + const writeOnEndPlugin: esbuild.Plugin = { + name: 'write-on-end', + setup(build) { + build.onEnd(async result => { + await Promise.all( + result.outputFiles!.map(async file => { + await fs.mkdir(path.dirname(file.path), {recursive: true}) + await updateFileIfChanged(file.path, file.text) + }) + ) + }) + } + } + + const ctx = await context({ + write: false, + logLevel: 'silent', + metafile: true, + entryPoints: [inputPath], + outdir: dir, + bundle: true, + format: 'esm', + sourcemap: 'inline', + packages: 'external', + + plugins: [ + notifyPlugin({ + dir, + onBuild: async output => { + await options.onBuild?.(output) + } + }), + injectCodePlugin({ + getBuildDefs: options.getBuildDefs, + outputDir: this.outputDir + }), + esbuildPluginTsc({ + tsconfigPath: path.join(process.cwd(), 'tsconfig.json') + }), + writeOnEndPlugin + ] + }) + + await ctx.rebuild() + + const pluginCtxs = await this.initBuildPlugins({ + onBuild: () => { + options.onBuild?.({ + totalFiles: 0, + totalSize: 0, + schemaChanged: false, + duration: 0 + }) + } + }) + + await Promise.all( + pluginCtxs.map(async c => { + await (await c).rebuild() + }) + ) + + return { + watch: async () => { + for (const ctx of pluginCtxs) { + const c = await ctx + + await c.watch() + } + + return await ctx.watch() + }, + rebuild: async () => { + for (const ctx of pluginCtxs) { + const c = await ctx + + await c.rebuild() + } + + await ctx.rebuild() + }, + dispose: async () => { + for (const ctx of pluginCtxs) { + const c = await ctx + + await c.dispose() + } + + await ctx.dispose() + }, + cancel: async () => { + for (const ctx of pluginCtxs) { + const c = await ctx + + await c.cancel() + } + + await ctx.cancel() + } + } + } +} diff --git a/packages/pylon-builder/src/bundler/index.ts b/packages/pylon-dev/src/builder/bundler/index.ts similarity index 100% rename from packages/pylon-builder/src/bundler/index.ts rename to packages/pylon-dev/src/builder/bundler/index.ts diff --git a/packages/pylon-dev/src/builder/bundler/plugins/inject-code-plugin.ts b/packages/pylon-dev/src/builder/bundler/plugins/inject-code-plugin.ts new file mode 100644 index 0000000..add9ad7 --- /dev/null +++ b/packages/pylon-dev/src/builder/bundler/plugins/inject-code-plugin.ts @@ -0,0 +1,107 @@ +import {Plugin} from 'esbuild' +import path from 'path' +import fs from 'fs/promises' +import {updateFileIfChanged} from '../../update-file-if-changed' + +export interface InjectCodePluginOptions { + getBuildDefs: () => { + typeDefs: string + resolvers: Record< + string, + { + __resolveType?: (obj: any) => string + } + > + } + outputDir: string +} + +export const injectCodePlugin = ({ + getBuildDefs, + outputDir +}: InjectCodePluginOptions): Plugin => ({ + name: 'inject-code', + setup(build) { + build.onLoad( + {filter: /src[\/\\]index\.ts$/, namespace: 'file'}, + async args => { + // Convert to relative path to ensure we match `src/index.ts` at root + const relativePath = path.relative(process.cwd(), args.path) + + if (relativePath !== path.join('src', 'index.ts')) { + return + } + + const {typeDefs, resolvers} = getBuildDefs() + + const preparedResolvers = prepareObjectInjection(resolvers) + + const contents = await fs.readFile(args.path, 'utf-8') + + // Write the typeDefs to a file + const typeDefsPath = path.join( + process.cwd(), + outputDir, + 'schema.graphql' + ) + + await updateFileIfChanged(typeDefsPath, typeDefs) + + // Write base resolvers to a file + + const resolversPath = path.join( + process.cwd(), + outputDir, + 'resolvers.js' + ) + + await updateFileIfChanged( + resolversPath, + `export const resolvers = ${preparedResolvers}` + ) + + return { + loader: 'ts', + contents: + contents + + ` + import {handler as __internalPylonHandler} from "@getcronit/pylon" + + let __internalPylonConfig = undefined + + try { + __internalPylonConfig = config + } catch { + // config is not declared, pylonConfig remains undefined + } + + app.use(__internalPylonHandler({ + typeDefs: ${JSON.stringify(typeDefs)}, + graphql, + resolvers: ${preparedResolvers}, + config: __internalPylonConfig + })) + ` + } + } + ) + } +}) + +function prepareObjectInjection(obj: object) { + const entries = Object.entries(obj).map(([key, value]) => { + if (value === undefined) { + return undefined + } else if (typeof value === 'string') { + return `${key}:${value}` + } else if (typeof value === 'function') { + return `${key}:${value.toString()}` + } else if (typeof value === 'object' && !Array.isArray(value)) { + return `${key}:${prepareObjectInjection(value)}` + } else { + return `${key}:${JSON.stringify(value)}` + } + }) + + return `{${entries.join(',')}}` +} diff --git a/packages/pylon-dev/src/builder/bundler/plugins/notify-plugin.ts b/packages/pylon-dev/src/builder/bundler/plugins/notify-plugin.ts new file mode 100644 index 0000000..c9b51ea --- /dev/null +++ b/packages/pylon-dev/src/builder/bundler/plugins/notify-plugin.ts @@ -0,0 +1,91 @@ +import {Plugin} from 'esbuild' +import path from 'path' +import fs from 'fs/promises' +import consola from 'consola' + +export interface NotifyPluginOptions { + onBuild?: (output: { + totalFiles: number + totalSize: number + schemaChanged: boolean + duration: number + }) => Promise | void + dir: string +} + +export const notifyPlugin = ({dir, onBuild}: NotifyPluginOptions): Plugin => ({ + name: 'notify', + async setup(build) { + const loadSchema = async () => { + const schemaPath = path.join(dir, 'schema.graphql') + + try { + await fs.access(schemaPath) + } catch { + return null + } + + return await fs.readFile(schemaPath, 'utf-8') + } + + let cachedSchema: string | null = await loadSchema() + + let startTime = Date.now() + build.onStart(async () => { + startTime = Date.now() + consola.start('[Pylon]: Building...') + }) + + build.onEnd(async result => { + if (result.errors.length > 0) { + for (const error of result.errors) { + consola.error(`[Pylon]: ${error.text} +${ + error.location + ? `at ${error.location.file}:${error.location.line}:${error.location.column}` + : '' +} +${error.detail ? error.detail : ''}`) + } + + throw new Error('Failed to build Pylon') + } + + if (result.warnings.length > 0) { + for (const warning of result.warnings) { + consola.warn(warning) + } + } + + const duration = Date.now() - startTime + + const totalFiles = Object.keys(result.metafile!.inputs).length + + const totalSize = Object.values(result.metafile!.outputs).reduce( + (acc, output) => acc + output.bytes, + 0 + ) + + const latestSchema = await loadSchema() + + consola.success(`[Pylon]: Built in ${duration}ms`) + + const schemaChanged = latestSchema !== cachedSchema + + if (schemaChanged) { + consola.info('[Pylon]: Schema updated') + + cachedSchema = latestSchema + } + + if (onBuild) { + await onBuild({ + totalFiles, + totalSize, + schemaChanged, + duration + }) + } + }) + } +}) diff --git a/packages/pylon-builder/src/index.ts b/packages/pylon-dev/src/builder/index.ts similarity index 77% rename from packages/pylon-builder/src/index.ts rename to packages/pylon-dev/src/builder/index.ts index df5e4d1..ad0a69a 100644 --- a/packages/pylon-builder/src/index.ts +++ b/packages/pylon-dev/src/builder/index.ts @@ -5,8 +5,7 @@ import {SchemaBuilder} from './schema/builder.js' export interface BuildOptions { sfiFilePath: string outputFilePath: string - watch?: boolean - onWatch?: (output: { + onBuild?: (output: { totalFiles: number totalSize: number schemaChanged: boolean @@ -19,12 +18,12 @@ export {SchemaBuilder} export const build = async (options: BuildOptions) => { const bundler = new Bundler(options.sfiFilePath, options.outputFilePath) + const builder = new SchemaBuilder( + path.join(process.cwd(), options.sfiFilePath) + ) + return await bundler.build({ getBuildDefs: () => { - const builder = new SchemaBuilder( - path.join(process.cwd(), options.sfiFilePath) - ) - const built = builder.build() const typeDefs = built.typeDefs @@ -34,7 +33,6 @@ export const build = async (options: BuildOptions) => { resolvers: built.resolvers } }, - watch: options.watch, - onWatch: options.onWatch + onBuild: options.onBuild }) } diff --git a/packages/pylon-builder/src/schema/builder.ts b/packages/pylon-dev/src/builder/schema/builder.ts similarity index 100% rename from packages/pylon-builder/src/schema/builder.ts rename to packages/pylon-dev/src/builder/schema/builder.ts diff --git a/packages/pylon-builder/src/schema/schema-parser.ts b/packages/pylon-dev/src/builder/schema/schema-parser.ts similarity index 100% rename from packages/pylon-builder/src/schema/schema-parser.ts rename to packages/pylon-dev/src/builder/schema/schema-parser.ts diff --git a/packages/pylon-builder/src/schema/type-definition-builder.ts b/packages/pylon-dev/src/builder/schema/type-definition-builder.ts similarity index 100% rename from packages/pylon-builder/src/schema/type-definition-builder.ts rename to packages/pylon-dev/src/builder/schema/type-definition-builder.ts diff --git a/packages/pylon-builder/src/schema/types-helper.ts b/packages/pylon-dev/src/builder/schema/types-helper.ts similarity index 100% rename from packages/pylon-builder/src/schema/types-helper.ts rename to packages/pylon-dev/src/builder/schema/types-helper.ts diff --git a/packages/pylon-dev/src/builder/update-file-if-changed.ts b/packages/pylon-dev/src/builder/update-file-if-changed.ts new file mode 100644 index 0000000..d85ad34 --- /dev/null +++ b/packages/pylon-dev/src/builder/update-file-if-changed.ts @@ -0,0 +1,15 @@ +import fs from 'fs/promises' + +export async function updateFileIfChanged(path: string, newContent: string) { + try { + const currentContent = await fs.readFile(path, 'utf8') + if (currentContent === newContent) { + return false // No update needed + } + } catch (err: any) { + if (err.code !== 'ENOENT') throw err // Ignore file not found error + } + + await fs.writeFile(path, newContent, 'utf8') + return true // File created or updated +} diff --git a/packages/pylon-dev/src/index.ts b/packages/pylon-dev/src/index.ts index 9346c95..661f693 100644 --- a/packages/pylon-dev/src/index.ts +++ b/packages/pylon-dev/src/index.ts @@ -1,15 +1,14 @@ #!/usr/bin/env node -import {build} from '@getcronit/pylon-builder' -import {fetchSchema, generateClient} from '@gqty/cli' +import * as telemetry from '@getcronit/pylon-telemetry' import {program, type Command} from 'commander' import consola from 'consola' -import path from 'path' -import {version} from '../package.json' -import {ChildProcess, spawn} from 'child_process' -import kill from 'treekill' -import * as telemetry from '@getcronit/pylon-telemetry' import dotenv from 'dotenv' +import pm2 from 'pm2' + +import {version} from '../package.json' +import {build} from './builder' +import {buildClient} from './builder/build-client' dotenv.config() @@ -19,21 +18,23 @@ program .command('build') .description('Build the Pylon Schema') .action(async () => { - consola.start('[Pylon]: Building schema') - - const {totalFiles, totalSize, duration} = await build({ + const ctx = await build({ sfiFilePath: './src/index.ts', - outputFilePath: './.pylon' - }) + outputFilePath: './.pylon', + onBuild: async ({totalFiles, totalSize, duration, schemaChanged}) => { + await telemetry.sendBuildEvent({ + duration, + totalFiles, + totalSize, + isDevelopment: false + }) - await telemetry.sendBuildEvent({ - duration: duration, - totalFiles, - totalSize, - isDevelopment: false + await buildClient({schemaChanged}) + } }) - consola.success('[Pylon]: Schema built') + await ctx.rebuild() + await ctx.dispose() }) program @@ -43,18 +44,6 @@ program 'Command to run the server', 'bun run .pylon/index.js' ) - .option('--client', "Generate the client from the server's schema") - .option('--test', 'Test') - .option( - '--client-path ', - 'Path to generate the client to', - 'gqty/index.ts' - ) - .option( - '--client-port ', - 'Port of the pylon server to generate the client from', - '3000' - ) .action(main) type ArgOptions = { @@ -64,216 +53,82 @@ type ArgOptions = { clientPort: string } -const start = Date.now() - async function main(options: ArgOptions, command: Command) { - consola.log(`[Pylon]: ${command.name()} version ${command.version()}`) - - let currentProc: ChildProcess | null = null - - let serve = async (shouldGenerateClient: boolean = false) => { - if (currentProc) { - // Remove all listeners to prevent the pylon dev server from crashing - currentProc.removeAllListeners() - - kill(currentProc.pid, 'SIGINT', err => { - if (err) { - consola.error(err) - } - }) - } - - const [commandName, ...args] = options.command.split(' ') - - currentProc = spawn(commandName, args, { - shell: true, - stdio: 'inherit', - env: { - ...process.env, - NODE_ENV: 'development' - } - }) - - currentProc.on('exit', code => { - // if (code === 143 || code === null) { - // return - // } - - if (code === 0) { - consola.success('Pylon server stopped') - process.exit(0) - } - - consola.error( - `Pylon exited with code ${code}, fix the error and save the file to restart the server` - ) - }) - - if ( - shouldGenerateClient && - options.client && - options.clientPath && - options.clientPort - ) { - const clientPath = path.resolve(process.cwd(), options.clientPath) - - const endpoint = `http://localhost:${options.clientPort}/graphql` - - console.log('Generating client...', endpoint) - - const generate = async () => { - consola.start('[Pylon]: Fetching schema from server') - - const schema = await fetchSchema(endpoint, { - silent: true - }) - - consola.success('[Pylon]: Schema fetched') - - consola.start('[Pylon]: Generating client') - - await generateClient(schema, { - endpoint, - destination: clientPath, - react: true, - scalarTypes: { - Number: 'number', - Object: 'Record' - } - }) - - consola.success('[Pylon]: Client generated') - } - - let retries = 0 - - const generateWithRetry = async () => { - try { - await generate() - } catch (e) { - retries++ - - if (retries < 5) { - setTimeout(() => { - generateWithRetry() - }, 1000) - } - } - } - - generateWithRetry() + pm2.connect(async function (err) { + if (err) { + consola.error(err) + process.exit(1) } - } - - consola.start('[Pylon]: Building schema') - try { - const {duration, totalFiles, totalSize} = await build({ + const ctx = await build({ sfiFilePath: './src/index.ts', outputFilePath: `./.pylon`, - watch: true, - onWatch: async ({schemaChanged, totalFiles, totalSize, duration}) => { - const isServerRunning = currentProc !== null - - if (isServerRunning) { - consola.start('[Pylon]: Reloading server') - } else { - consola.start('[Pylon]: Starting server') - } - - await serve(schemaChanged) - - if (isServerRunning) { - consola.ready('[Pylon]: Server reloaded') - } else { - consola.ready('[Pylon]: Server started') - - consola.box(` - Pylon is up and running! - - Press \`Ctrl + C\` to stop the server. - - Encounter any issues? Report them here: - https://github.com/getcronit/pylon/issues - - We value your feedback—help us make Pylon even better! - `) - } - - if (schemaChanged) { - consola.info('[Pylon]: Schema updated') - - await telemetry.sendBuildEvent({ - duration, - totalFiles, - totalSize, - isDevelopment: true - }) - } + onBuild: async ({schemaChanged, totalFiles, totalSize, duration}) => { + await buildClient({schemaChanged}) + // Restart the server + // pm2.restart('pylon-dev', function (err) { + // if (err) throw err + // }) } }) - await telemetry.sendBuildEvent({ - duration, - totalFiles, - totalSize, - isDevelopment: true - }) - - consola.success('[Pylon]: Schema built') - - consola.start('[Pylon]: Starting server') - await serve(true) - consola.ready('[Pylon]: Server started') + await ctx.watch() - consola.box(` - Pylon is up and running! - - Press \`Ctrl + C\` to stop the server. - - Encounter any issues? Report them here: - https://github.com/getcronit/pylon/issues - - We value your feedback—help us make Pylon even better! - `) - } catch (e) { - consola.error("[Pylon]: Couldn't build schema", e) - - // Kill the server if it's running - const proc = currentProc as ChildProcess | null - if (proc) { - proc.removeAllListeners() + pm2.launchBus((err, bus) => { + if (err) { + consola.error(err) + return + } - kill(proc.pid, 'SIGINT', err => { - if (err) { - consola.error(err) - } + bus.on('log:out', data => { + consola.log(data.data.trim()) }) - } - } - process.on('SIGINT', async code => { - try { - if (currentProc) { - currentProc.removeAllListeners() + bus.on('log:err', data => { + consola.error(data.data) + }) + }) - kill(currentProc.pid, 'SIGINT', err => { - if (err) { - consola.error(err) - } - }) + pm2.start( + { + name: 'pylon-dev', + script: options.command, + // args: args, + exec_mode: 'fork', + instances: 1, + autorestart: true, + watch: ['./.pylon'], + restart_delay: 1000, + watch_delay: 1000 as any, + ignore_watch: ['node_modules'], + env: { + ...process.env, + NODE_ENV: 'development' + } + } as any, + function (err, apps) { + // Check if it is a duplicate start + if (err) throw err + + consola.box(` +Pylon is up and running! + +Press \`Ctrl + C\` to stop the server. + +Encounter any issues? Report them here: +https://github.com/getcronit/pylon/issues + +We value your feedback—help us make Pylon even better!`) } - } catch { - // Ignore - } finally { - await telemetry.sendDevEvent({ - duration: Date.now() - start, - clientPath: options.clientPath, - clientPort: parseInt(options.clientPort) - }) + ) - process.exit(0) - } + process.on('SIGINT', async code => { + await ctx.cancel() + pm2.delete('pylon-dev', function (err) { + pm2.disconnect() + process.exit(0) + }) + }) }) } diff --git a/packages/pylon/package.json b/packages/pylon/package.json index d6d10a4..5a6c02f 100644 --- a/packages/pylon/package.json +++ b/packages/pylon/package.json @@ -4,8 +4,21 @@ "type": "module", "main": "dist/index.js", "types": "dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + }, + "./pages": { + "import": "./dist/pages/index.js", + "types": "./dist/pages/index.d.ts" + }, + "./tsconfig.pylon.json": "./tsconfig.pylon.json" + }, "scripts": { - "build": "rimraf ./dist && esbuild ./src/index.ts --bundle --platform=node --target=node18 --format=esm --outdir=./dist --sourcemap=linked --packages=external && pnpm run build:declarations", + "build": "rimraf ./dist && pnpm run build:server && pnpm run build:pages", + "build:server": "esbuild ./src/index.ts --bundle --platform=node --target=node18 --format=esm --outdir=./dist --sourcemap=linked --packages=external && pnpm run build:declarations", + "build:pages": "esbuild ./src/pages/index.ts --bundle --packages=external --platform=browser --target=esnext --format=esm --outdir=./dist/pages --sourcemap=linked", "build:declarations": "tsc --declaration --emitDeclarationOnly --outDir ./dist" }, "files": [ @@ -23,23 +36,51 @@ "dependencies": { "@envelop/core": "^5.0.3", "@getcronit/pylon-telemetry": "workspace:^", + "@gqty/react": "^3.1.0", "@hono/sentry": "^1.2.0", "@sentry/bun": "^8.17.0", "@sentry/node": "^8.54.0", "consola": "^3.2.3", + "gqty": "3.4.0-canary-20250207102900.644ad9fdeafa6318516627577b1d4d754d5c5a98", "graphql": "^16.9.0", "graphql-scalars": "^1.24.0", "graphql-yoga": "^5.6.2", "hono": "^4.0.8", "jsonwebtoken": "^9.0.2", - "openid-client": "^5.6.4", + "openid-client": "^6.1.7", + "react-router": "^7.1.5", + "sharp": "^0.33.5", + "tiny-glob": "^0.2.9", "toucan-js": "^4.1.0", - "winston": "^3.8.2" + "winston": "^3.8.2", + "postcss-load-config": "^6.0.1", + "chokidar": "^4.0.3" }, "engines": { "node": ">=18.0.0" }, "devDependencies": { "@sentry/types": "^8.54.0" + }, + "peerDependencies": { + "@tailwindcss/postcss": "^4.0.4", + "autoprefixer": "^10.4.20", + "postcss": "^8.5.1", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "@tailwindcss/postcss": { + "optional": true + }, + "autoprefixer": { + "optional": true + } } } diff --git a/packages/pylon/src/app/handler/graphql-viewer-handler.ts b/packages/pylon/src/app/handler/graphql-viewer-handler.ts deleted file mode 100644 index 9d0e972..0000000 --- a/packages/pylon/src/app/handler/graphql-viewer-handler.ts +++ /dev/null @@ -1,64 +0,0 @@ -import type {MiddlewareHandler} from 'hono' -import {html} from 'hono/html' - -export const graphqlViewerHandler: MiddlewareHandler = async (c, next) => { - return c.html(html` - - - - Pylon Viewer - - - - - - - - -
Loading...
- - - - `) -} diff --git a/packages/pylon/src/app/index.ts b/packages/pylon/src/app/index.ts index c54403f..d507eca 100644 --- a/packages/pylon/src/app/index.ts +++ b/packages/pylon/src/app/index.ts @@ -1,9 +1,9 @@ -import {Hono} from 'hono' +import {Hono, MiddlewareHandler} from 'hono' import {logger} from 'hono/logger' import {sentry} from '@hono/sentry' +import {except} from 'hono/combine' import {asyncContext, Env} from '../context' -import {graphqlViewerHandler} from './handler/graphql-viewer-handler' export const app = new Hono() @@ -21,7 +21,7 @@ app.use('*', async (c, next) => { }) }) -app.use('*', logger()) +app.use('*', except(['/__pylon/static/*'], logger())) app.use((c, next) => { // @ts-ignore @@ -29,4 +29,18 @@ app.use((c, next) => { return next() }) -app.get('/viewer', graphqlViewerHandler) +export const pluginsMiddleware: MiddlewareHandler[] = [] + +const pluginsMiddlewareLoader: MiddlewareHandler = async (c, next) => { + for (const middleware of pluginsMiddleware) { + const response = await middleware(c, async () => {}) + + if (response) { + return response + } + } + + return next() +} + +app.use(pluginsMiddlewareLoader) diff --git a/packages/pylon/src/app/handler/pylon-handler.ts b/packages/pylon/src/app/pylon-handler.ts similarity index 72% rename from packages/pylon/src/app/handler/pylon-handler.ts rename to packages/pylon/src/app/pylon-handler.ts index fa019c6..21d38ae 100644 --- a/packages/pylon/src/app/handler/pylon-handler.ts +++ b/packages/pylon/src/app/pylon-handler.ts @@ -7,12 +7,15 @@ import { JSONResolver } from 'graphql-scalars' -import {useSentry} from '../envelop/use-sentry' -import {Context} from '../../context' -import {resolversToGraphQLResolvers} from '../../define-pylon' -import {PylonConfig} from '../..' +import {useSentry} from '../plugins/use-sentry' +import {Context} from '../context' +import {resolversToGraphQLResolvers} from '../define-pylon' +import {Plugin, PylonConfig} from '..' import {readFileSync} from 'fs' import path from 'path' +import {app, pluginsMiddleware} from '.' +import {useViewer} from '../plugins/use-viewer' +import {useUnhandledRoute} from '../plugins/use-unhandled-route' interface PylonHandlerOptions { graphql: { @@ -23,12 +26,48 @@ interface PylonHandlerOptions { config?: PylonConfig } +type MaybeLazyObject = T | (() => T) + +const resolveLazyObject = (obj: MaybeLazyObject): T => { + return typeof obj === 'function' ? (obj as () => T)() : obj +} + export const handler = (options: PylonHandlerOptions) => { - let {typeDefs, resolvers, graphql, config} = - options as PylonHandlerOptions & { - typeDefs?: string - resolvers?: Record + let { + typeDefs, + resolvers, + graphql: graphql$, + config: config$ + } = options as PylonHandlerOptions & { + typeDefs?: string + resolvers?: Record + } + + const loadPluginsMiddleware = (plugins: Plugin[]) => { + for (const plugin of plugins) { + plugin.setup?.(app) + + if (plugin.middleware) { + pluginsMiddleware.push(plugin.middleware) + } } + } + + const graphql = resolveLazyObject(graphql$) + + const config = resolveLazyObject(config$) + + const plugins = [useSentry(), useViewer(), ...(config?.plugins || [])] + + if (config?.landingPage ?? true) { + plugins.push( + useUnhandledRoute({ + graphqlEndpoint: '/graphql' + }) + ) + } + + loadPluginsMiddleware(plugins) if (!typeDefs) { // Try to read the schema from the default location @@ -113,7 +152,7 @@ export const handler = (options: PylonHandlerOptions) => { }, graphqlEndpoint: '/graphql', ...config, - plugins: [useSentry(), ...(config?.plugins || [])], + plugins, schema }) diff --git a/packages/pylon/src/auth/decorators/requireAuth.ts b/packages/pylon/src/auth/decorators/requireAuth.ts deleted file mode 100644 index 427a08c..0000000 --- a/packages/pylon/src/auth/decorators/requireAuth.ts +++ /dev/null @@ -1,52 +0,0 @@ -import {sendFunctionEvent} from '@getcronit/pylon-telemetry' -import {HTTPException} from 'hono/http-exception' - -import {AuthRequireChecks, auth} from '..' -import {getContext} from '../../context' -import {ServiceError} from '../../define-pylon' -import {createDecorator} from '../../create-decorator' - -export function requireAuth(checks?: AuthRequireChecks) { - sendFunctionEvent({ - name: 'requireAuth', - duration: 0 - }).then(() => {}) - - const checkAuth = async (c: any) => { - const ctx = await c - - try { - await auth.require(checks)(ctx, async () => {}) - } catch (e) { - if (e instanceof HTTPException) { - if (e.status === 401) { - throw new ServiceError(e.message, { - statusCode: 401, - code: 'AUTH_REQUIRED' - }) - } else if (e.status === 403) { - const res = e.getResponse() - - throw new ServiceError(res.statusText, { - statusCode: res.status, - code: 'AUTHORIZATION_REQUIRED', - details: { - missingRoles: res.headers.get('Missing-Roles')?.split(','), - obtainedRoles: res.headers.get('Obtained-Roles')?.split(',') - } - }) - } else { - throw e - } - } - - throw e - } - } - - return createDecorator(async () => { - const ctx = getContext() - - await checkAuth(ctx) - }) -} diff --git a/packages/pylon/src/auth/index.ts b/packages/pylon/src/auth/index.ts deleted file mode 100644 index 73cc380..0000000 --- a/packages/pylon/src/auth/index.ts +++ /dev/null @@ -1,306 +0,0 @@ -import {MiddlewareHandler} from 'hono' -import jwt from 'jsonwebtoken' -import type {IdTokenClaims, IntrospectionResponse} from 'openid-client' -import path from 'path' -import {HTTPException} from 'hono/http-exception' -import {ContentfulStatusCode} from 'hono/utils/http-status' -import {env} from 'hono/adapter' -import * as Sentry from '@sentry/bun' -import {existsSync, readFileSync} from 'fs' -import {sendFunctionEvent} from '@getcronit/pylon-telemetry' - -export type AuthState = IntrospectionResponse & - IdTokenClaims & { - roles: string[] - } - -const authInitialize = () => { - // Load private key file from cwd - const authKeyFilePath = path.join(process.cwd(), 'key.json') - - // Load private key file from cwd - let API_PRIVATE_KEY_FILE: - | { - type: 'application' - keyId: string - key: string - appId: string - clientId: string - } - | undefined = undefined - - if (existsSync(authKeyFilePath)) { - try { - API_PRIVATE_KEY_FILE = JSON.parse(readFileSync(authKeyFilePath, 'utf-8')) - } catch (error) { - throw new Error( - 'Error while reading key file. Make sure it is valid JSON' - ) - } - } - - const middleware: MiddlewareHandler<{ - Variables: { - auth: AuthState - } - }> = Sentry.startSpan( - { - name: 'AuthMiddleware', - op: 'auth' - }, - () => - async function (ctx, next) { - const AUTH_ISSUER = env(ctx).AUTH_ISSUER - - if (!AUTH_ISSUER) { - throw new Error('AUTH_ISSUER is not set') - } - - if (!API_PRIVATE_KEY_FILE) { - // If the private key file is not loaded, try to load it from the environment - const AUTH_KEY = env(ctx).AUTH_KEY as string | undefined - - API_PRIVATE_KEY_FILE = AUTH_KEY ? JSON.parse(AUTH_KEY) : undefined - } - - if (!API_PRIVATE_KEY_FILE) { - throw new Error( - 'You have initialized the auth middleware without a private key file' - ) - } - - const AUTH_PROJECT_ID = env(ctx).AUTH_PROJECT_ID - - const ZITADEL_INTROSPECTION_URL = `${AUTH_ISSUER}/oauth/v2/introspect` - - async function getRolesFromToken(tokenString: string) { - const response = await fetch( - `${AUTH_ISSUER}/auth/v1/usergrants/me/_search`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${tokenString}` - } - } - ) - - const data = (await response.json()) as any - - const userRoles = (data.result?.map((grant: any) => { - return (grant.roles || []).map((role: any) => { - return `${grant.projectId}:${role}` - }) - }) || []) as string[][] - - const projectScopedRoles = userRoles.flat() - - const rolesSet = new Set(projectScopedRoles) - - // Add unscoped roles based on project id - // This is useful so that it is not necessary to specify the project id for every role check - if (AUTH_PROJECT_ID) { - for (const role of projectScopedRoles) { - const [projectId, ...roleNameParts] = role.split(':') - - const roleName = roleNameParts.join(':') - - if (projectId === AUTH_PROJECT_ID) { - rolesSet.add(roleName) - } - } - } - - return Array.from(rolesSet) - } - - async function introspectToken( - tokenString: string - ): Promise { - if (!API_PRIVATE_KEY_FILE) { - throw new Error('Internal error: API_PRIVATE_KEY_FILE is not set') - } - - // Create JWT for client assertion - const payload = { - iss: API_PRIVATE_KEY_FILE.clientId, - sub: API_PRIVATE_KEY_FILE.clientId, - aud: AUTH_ISSUER, - exp: Math.floor(Date.now() / 1000) + 60 * 60, // Expires in 1 hour - iat: Math.floor(Date.now() / 1000) - } - - const headers = { - alg: 'RS256', - kid: API_PRIVATE_KEY_FILE.keyId - } - const jwtToken = jwt.sign(payload, API_PRIVATE_KEY_FILE.key, { - algorithm: 'RS256', - header: headers - }) - - const scopeSet = new Set() - - scopeSet.add('openid') - scopeSet.add('profile') - scopeSet.add('email') - - if (AUTH_PROJECT_ID) { - scopeSet.add( - `urn:zitadel:iam:org:project:id:${AUTH_PROJECT_ID}:aud` - ) - } - - const scope = Array.from(scopeSet).join(' ') - - // Send introspection request - const body = new URLSearchParams({ - client_assertion_type: - 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', - client_assertion: jwtToken, - token: tokenString, - scope - }).toString() - - try { - const response = await fetch(ZITADEL_INTROSPECTION_URL, { - method: 'POST', - headers: {'Content-Type': 'application/x-www-form-urlencoded'}, - body - }) - - if (!response.ok) { - throw new Error('Network response was not ok') - } - - const tokenData = (await response.json()) as IntrospectionResponse - - const roles = await getRolesFromToken(tokenString) - - const state = { - ...tokenData, - roles - } as AuthState - - return state - } catch (error) { - console.error('Error while introspecting token', error) - throw new Error('Token introspection failed') - } - } - - let token: string | undefined = undefined - - if (ctx.req.header('Authorization')) { - const authHeader = ctx.req.header('Authorization') - - if (authHeader) { - const parts = authHeader.split(' ') - - if (parts.length === 2 && parts[0] === 'Bearer') { - token = parts[1] - } - } - } - - if (!token) { - const queryToken = ctx.req.query('token') - - if (queryToken) { - token = queryToken - } - } - - if (token) { - const auth = await introspectToken(token) - - if (auth.active) { - ctx.set('auth', auth) - - Sentry.setUser({ - id: auth.sub, - username: auth.preferred_username, - email: auth.email, - details: auth - }) - } - } - - return next() - } - ) - - sendFunctionEvent({ - name: 'authInitialize', - duration: 0 - }).then(() => {}) - - return middleware -} - -export type AuthRequireChecks = { - roles?: string[] -} - -const authRequire = (checks: AuthRequireChecks = {}) => { - sendFunctionEvent({ - name: 'authRequire', - duration: 0 - }).then(() => {}) - - const middleware: MiddlewareHandler<{ - Variables: { - auth?: AuthState - } - }> = async (ctx, next) => { - const AUTH_PROJECT_ID = env(ctx).AUTH_PROJECT_ID - - // Check if user is authenticated - const auth = ctx.get('auth') - - if (!auth) { - throw new HTTPException(401, { - message: 'Authentication required' - }) - } - - if (checks.roles) { - const roles = auth.roles - - const hasRole = checks.roles.some(role => { - return ( - roles.includes(role) || roles.includes(`${AUTH_PROJECT_ID}:${role}`) - ) - }) - - if (!hasRole) { - const resError = new Response('Forbidden', { - status: 403, - statusText: 'Forbidden', - headers: { - 'Missing-Roles': checks.roles.join(','), - 'Obtained-Roles': roles.join(',') - } - }) - - throw new HTTPException(resError.status as ContentfulStatusCode, {res: resError}) - } - } - - return next() - } - - sendFunctionEvent({ - name: 'authRequire', - duration: 0 - }).then(() => {}) - - return middleware -} - -export const auth = { - initialize: authInitialize, - require: authRequire -} - -export {requireAuth} from './decorators/requireAuth' diff --git a/packages/pylon/src/context.ts b/packages/pylon/src/context.ts index a268cd6..ce7c55a 100644 --- a/packages/pylon/src/context.ts +++ b/packages/pylon/src/context.ts @@ -1,10 +1,10 @@ import {Context as HonoContext} from 'hono' import type {Toucan} from 'toucan-js' -import {AuthState} from './auth' +import type {AuthState} from './plugins/use-auth' import {AsyncLocalStorage} from 'async_hooks' import {sendFunctionEvent} from '@getcronit/pylon-telemetry' import {env} from 'hono/adapter' -import type { GraphQLResolveInfo } from 'graphql' +import type {GraphQLResolveInfo} from 'graphql' export interface Bindings { NODE_ENV: string diff --git a/packages/pylon/src/define-pylon.ts b/packages/pylon/src/define-pylon.ts index c1a9456..f2a0f7e 100644 --- a/packages/pylon/src/define-pylon.ts +++ b/packages/pylon/src/define-pylon.ts @@ -178,23 +178,22 @@ export const resolversToGraphQLResolvers = ( return Sentry.withScope(async scope => { const ctx = asyncContext.getStore() - if (!ctx) { consola.warn( 'Context is not defined. Make sure AsyncLocalStorage is supported in your environment.' ) } - ctx?.set("graphqlResolveInfo", info) + ctx?.set('graphqlResolveInfo', info) const auth = ctx?.get('auth') - if (auth?.active) { + if (auth?.user) { scope.setUser({ - id: auth.sub, - username: auth.preferred_username, - email: auth.email, - details: auth + id: auth.user.sub, + username: auth.user.preferred_username, + email: auth.user.email, + details: auth.user }) } diff --git a/packages/pylon/src/get-env.ts b/packages/pylon/src/get-env.ts index c03b9df..15dcbe7 100644 --- a/packages/pylon/src/get-env.ts +++ b/packages/pylon/src/get-env.ts @@ -11,7 +11,11 @@ export function getEnv() { // Fall back to process.env or an empty object if no context is available // This is useful for testing // ref: https://hono.dev/docs/guides/testing#env - return context.env || process.env || {} + const ctx = context.env || process.env || {} + + ctx.NODE_ENV = ctx.NODE_ENV || process.env.NODE_ENV || 'development' + + return ctx } catch { return process.env } finally { diff --git a/packages/pylon/src/index.ts b/packages/pylon/src/index.ts index 3a1980d..34c02eb 100644 --- a/packages/pylon/src/index.ts +++ b/packages/pylon/src/index.ts @@ -1,8 +1,7 @@ -import {YogaServerOptions} from 'graphql-yoga' -import {Context} from './context.js' +import {Env} from './context.js' export {ServiceError} from './define-pylon.js' -export * from './auth/index.js' +export {useAuth, requireAuth, authMiddleware} from './plugins/use-auth/index.js' export { Context, Env, @@ -12,13 +11,35 @@ export { getContext, setContext } from './context.js' -export {app} from './app/index.js' -export {handler} from './app/handler/pylon-handler.js' +import {app as pylonApp} from './app/index.js' +export {pylonApp as app} +export {handler} from './app/pylon-handler.js' export {getEnv} from './get-env.js' export {createDecorator} from './create-decorator.js' export {createPubSub as experimentalCreatePubSub} from 'graphql-yoga' -export type PylonConfig = Pick, 'plugins'> +export {usePages} from './plugins/use-pages/index' + +import type {Plugin as YogaPlugin} from 'graphql-yoga' +import {MiddlewareHandler} from 'hono' +import {BuildContext, BuildOptions} from 'esbuild' + +export type Plugin< + PluginContext extends Record = {}, + TServerContext extends Record = {}, + TUserContext = {} +> = YogaPlugin & { + middleware?: MiddlewareHandler + setup?: (app: typeof pylonApp) => void + build?: (args: { + onBuild: () => void + }) => Promise, 'serve'>> +} + +export type PylonConfig = { + landingPage?: boolean + plugins?: Plugin[] +} export type ID = string & {readonly brand?: unique symbol} export type Int = number & {readonly brand?: unique symbol} diff --git a/packages/pylon/src/pages/index.ts b/packages/pylon/src/pages/index.ts new file mode 100644 index 0000000..39bd119 --- /dev/null +++ b/packages/pylon/src/pages/index.ts @@ -0,0 +1,2 @@ +export * as __PYLON_ROUTER_INTERNALS_DO_NOT_USE from 'react-router' +export {type PageProps, type PageData} from '../plugins/use-pages/index' diff --git a/packages/pylon/src/plugins/use-auth/auth-require.ts b/packages/pylon/src/plugins/use-auth/auth-require.ts new file mode 100644 index 0000000..5b17c93 --- /dev/null +++ b/packages/pylon/src/plugins/use-auth/auth-require.ts @@ -0,0 +1,95 @@ +import {MiddlewareHandler} from 'hono' +import {env} from 'hono/adapter' +import {HTTPException} from 'hono/http-exception' +import {ContentfulStatusCode} from 'hono/utils/http-status' +import {ServiceError} from '../../define-pylon' +import {Env, getContext} from '../../context' +import {createDecorator} from '../../create-decorator' + +export type AuthRequireChecks = { + roles?: string[] +} + +export const authMiddleware = (checks: AuthRequireChecks = {}) => { + const middleware: MiddlewareHandler = async (ctx, next) => { + const AUTH_PROJECT_ID = env(ctx).AUTH_PROJECT_ID + + // Check if user is authenticated + const auth = ctx.get('auth') + + if (!auth) { + throw new HTTPException(401, { + message: 'Authentication required' + }) + } + + if (checks.roles && auth.user) { + const roles = auth.user.roles + + const hasRole = checks.roles.some(role => { + return ( + roles.includes(role) || roles.includes(`${AUTH_PROJECT_ID}:${role}`) + ) + }) + + if (!hasRole) { + const resError = new Response('Forbidden', { + status: 403, + statusText: 'Forbidden', + headers: { + 'Missing-Roles': checks.roles.join(','), + 'Obtained-Roles': roles.join(',') + } + }) + + throw new HTTPException(resError.status as ContentfulStatusCode, { + res: resError + }) + } + } + + return next() + } + + return middleware +} + +export function requireAuth(checks?: AuthRequireChecks) { + const checkAuth = async (c: any) => { + const ctx = await c + + try { + await authMiddleware(checks)(ctx, async () => {}) + } catch (e) { + if (e instanceof HTTPException) { + if (e.status === 401) { + throw new ServiceError(e.message, { + statusCode: 401, + code: 'AUTH_REQUIRED' + }) + } else if (e.status === 403) { + const res = e.getResponse() + + throw new ServiceError(res.statusText, { + statusCode: res.status, + code: 'AUTHORIZATION_REQUIRED', + details: { + missingRoles: res.headers.get('Missing-Roles')?.split(','), + obtainedRoles: res.headers.get('Obtained-Roles')?.split(',') + } + }) + } else { + throw e + } + } + + throw e + } + } + + return createDecorator(async () => { + const ctx = getContext() + + await checkAuth(ctx) + }) +} diff --git a/packages/pylon/src/plugins/use-auth/import-private-key.ts b/packages/pylon/src/plugins/use-auth/import-private-key.ts new file mode 100644 index 0000000..16045af --- /dev/null +++ b/packages/pylon/src/plugins/use-auth/import-private-key.ts @@ -0,0 +1,61 @@ +import * as crypto from 'crypto' + +/* +Convert a string into an ArrayBuffer +from https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String +*/ +function str2ab(str) { + const buf = new ArrayBuffer(str.length) + const bufView = new Uint8Array(buf) + for (let i = 0, strLen = str.length; i < strLen; i++) { + bufView[i] = str.charCodeAt(i) + } + return buf +} + +const convertPKCS1ToPKCS8 = (pkcs1: string) => { + // with cryto module + + const key = crypto.createPrivateKey(pkcs1) + + return key.export({ + type: 'pkcs8', + format: 'pem' + }) +} + +/* +Import a PEM encoded RSA private key, to use for RSA-PSS signing. +Takes a string containing the PEM encoded key, and returns a Promise +that will resolve to a CryptoKey representing the private key. +*/ +function importPKCS8PrivateKey(pem) { + // fetch the part of the PEM string between header and footer + const pemHeader = '-----BEGIN PRIVATE KEY-----' + const pemFooter = '-----END PRIVATE KEY-----' + const pemContents = pem.substring( + pemHeader.length, + pem.length - pemFooter.length - 1 + ) + // base64 decode the string to get the binary data + const binaryDerString = atob(pemContents) + // convert from a binary string to an ArrayBuffer + const binaryDer = str2ab(binaryDerString) + + return crypto.subtle.importKey( + 'pkcs8', + binaryDer, + { + name: 'RSASSA-PKCS1-v1_5', + hash: 'SHA-256' + }, + true, + ['sign'] + ) +} + +export const importPrivateKey = async (pkcs1Pem: string) => { + const pkcs8Pem = convertPKCS1ToPKCS8(pkcs1Pem) + + return await importPKCS8PrivateKey(pkcs8Pem) +} diff --git a/packages/pylon/src/plugins/use-auth/index.ts b/packages/pylon/src/plugins/use-auth/index.ts new file mode 100644 index 0000000..dc49559 --- /dev/null +++ b/packages/pylon/src/plugins/use-auth/index.ts @@ -0,0 +1,3 @@ +export {useAuth} from './use-auth' +export {requireAuth, authMiddleware} from './auth-require' +export {AuthState} from './types' diff --git a/packages/pylon/src/plugins/use-auth/types.ts b/packages/pylon/src/plugins/use-auth/types.ts new file mode 100644 index 0000000..a67e391 --- /dev/null +++ b/packages/pylon/src/plugins/use-auth/types.ts @@ -0,0 +1,9 @@ +import * as openid from 'openid-client' + +export type AuthState = { + user?: openid.UserInfoResponse & { + roles: string[] + } + + openidConfig: openid.Configuration +} diff --git a/packages/pylon/src/plugins/use-auth/use-auth.ts b/packages/pylon/src/plugins/use-auth/use-auth.ts new file mode 100644 index 0000000..8ef5daa --- /dev/null +++ b/packages/pylon/src/plugins/use-auth/use-auth.ts @@ -0,0 +1,253 @@ +import {promises as fs} from 'fs' +import {deleteCookie, getCookie, setCookie} from 'hono/cookie' +import {HTTPException} from 'hono/http-exception' +import * as openid from 'openid-client' +import path from 'path' +import {getContext, type Plugin} from '../../index' +import {importPrivateKey} from './import-private-key' + +type AuthKey = { + type: 'application' + keyId: string + key: string + appId: string + clientId: string +} + +const loadAuthKey = async (keyPath: string): Promise => { + const authKeyFilePath = path.join(process.cwd(), keyPath) + + const env = getContext().env + + if (env.AUTH_KEY) { + try { + return JSON.parse(env.AUTH_KEY) + } catch (error) { + throw new Error( + 'Error while reading AUTH_KEY. Make sure it is valid JSON' + ) + } + } + + try { + const ketFileContent = await fs.readFile(authKeyFilePath, 'utf-8') + + try { + return JSON.parse(ketFileContent) + } catch (error) { + throw new Error( + 'Error while reading key file. Make sure it is valid JSON' + ) + } + } catch (error) { + throw new Error('Error while reading key file. Make sure it exists') + } +} + +let openidConfigCache: openid.Configuration | undefined + +const bootstrapAuth = async (issuer: string, keyPath: string) => { + if (!openidConfigCache) { + const authKey = await loadAuthKey(keyPath) + + openidConfigCache = await openid.discovery( + new URL(issuer), + authKey.clientId, + undefined, + openid.PrivateKeyJwt({ + key: await importPrivateKey(authKey.key), + kid: authKey.keyId + }) + ) + } + + return openidConfigCache +} + +class PylonAuthException extends HTTPException { + // Same constructor as HTTPException + constructor(...args: ConstructorParameters) { + // Prefix the message with "PylonAuthException: " + args[1] = { + ...args[1], + message: `PylonAuthException: ${args[1]?.message}` + } + + super(...args) + } +} + +export function useAuth(args: { + issuer: string + endpoint?: string + keyPath?: string +}): Plugin { + const {issuer, endpoint = '/auth', keyPath = 'key.json'} = args + + const loginPath = `${endpoint}/login` + const logoutPath = `${endpoint}/logout` + const callbackPath = `${endpoint}/callback` + + return { + middleware: async (ctx, next) => { + const openidConfig = await bootstrapAuth(issuer, keyPath) + + ctx.set('auth', {openidConfig}) + + // Introspect token + const authCookieToken = getCookie(ctx, 'pylon-auth') + const authHeader = ctx.req.header('Authorization') + const authQueryToken = ctx.req.query('token') + + if (authCookieToken || authHeader || authQueryToken) { + let token: string | undefined + + if (authHeader) { + const [type, value] = authHeader.split(' ') + if (type === 'Bearer') { + token = value + } + } else if (authQueryToken) { + token = authQueryToken + } else if (authCookieToken) { + token = authCookieToken + } + + if (!token) { + throw new PylonAuthException(401, { + message: 'Invalid token' + }) + } + + const introspection = await openid.tokenIntrospection( + openidConfig, + token, + { + scope: 'openid email profile' + } + ) + + if (!introspection.active) { + throw new PylonAuthException(401, { + message: 'Token is not active' + }) + } + + if (!introspection.sub) { + throw new PylonAuthException(401, { + message: 'Token is missing subject' + }) + } + + // Fetch user info + const userInfo = await openid.fetchUserInfo( + openidConfig, + token, + introspection.sub + ) + + const roles = Object.keys( + introspection['urn:zitadel:iam:org:projects:roles']?.valueOf() || {} + ) + + ctx.set('auth', { + user: { + ...userInfo, + roles + }, + openidConfig + }) + + return next() + } + }, + setup(app) { + app.get(loginPath, async ctx => { + const openidConfig = ctx.get('auth').openidConfig + + const codeVerifier = openid.randomPKCECodeVerifier() // PKCE code verifier + const codeChallenge = await openid.calculatePKCECodeChallenge( + codeVerifier + ) + + // Store the code verifier in a secure cookie (not accessible to JavaScript) + setCookie(ctx, 'pylon_code_verifier', codeVerifier, { + httpOnly: true, + maxAge: 300 // 5 minutes + }) + + let scope = + 'openid profile email urn:zitadel:iam:user:resourceowner urn:zitadel:iam:org:projects:roles' + + const parameters: Record = { + scope, + code_challenge: codeChallenge, + code_challenge_method: 'S256', + redirect_uri: new URL(ctx.req.url).origin + '/auth/callback', + state: openid.randomState() + } + + const authorizationUrl = openid.buildAuthorizationUrl( + openidConfig, + parameters + ) + + return ctx.redirect(authorizationUrl) + }) + + app.get(logoutPath, async ctx => { + // Remove auth cookie + deleteCookie(ctx, 'pylon-auth') + + return ctx.redirect('/') + }) + + app.get(callbackPath, async ctx => { + const openidConfig = ctx.get('auth').openidConfig + + const params = ctx.req.query() + const code = params.code + const state = params.state + + if (!code || !state) { + throw new PylonAuthException(400, { + message: 'Missing authorization code or state' + }) + } + + const codeVerifier = getCookie(ctx, 'pylon_code_verifier') + if (!codeVerifier) { + throw new PylonAuthException(400, { + message: 'Missing code verifier' + }) + } + + try { + const cbUrl = new URL(ctx.req.url) + // Exchange the authorization code for tokens + let tokenSet = await openid.authorizationCodeGrant( + openidConfig, + cbUrl, + { + pkceCodeVerifier: codeVerifier, + expectedState: state + }, + cbUrl.searchParams + ) + + // Store tokens in secure cookies + setCookie(ctx, `pylon-auth`, tokenSet.access_token, { + httpOnly: true, + maxAge: tokenSet.expires_in || 3600 // Default to 1 hour if not specified + }) + + return ctx.redirect('/') + } catch (error) { + console.error('Error during token exchange:', error) + + return ctx.text('Authentication failed!', 500) + } + }) + } + } +} diff --git a/packages/pylon/src/plugins/use-pages/build/app-utils.ts b/packages/pylon/src/plugins/use-pages/build/app-utils.ts new file mode 100644 index 0000000..28ef10b --- /dev/null +++ b/packages/pylon/src/plugins/use-pages/build/app-utils.ts @@ -0,0 +1,145 @@ +import path from 'path' +import glob from 'tiny-glob' + +function fnv1aHash(str: string) { + let hash = 0x811c9dc5 // FNV offset basis + for (let i = 0; i < str.length; i++) { + hash ^= str.charCodeAt(i) + hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24) + } + return (hash >>> 0).toString(16) +} + +const APP_DIR = path.join(process.cwd(), 'pages') + +export type PageRoute = { + pagePath: string + slug: string + layouts: string[] +} + +// Helper function to get page routes with layouts +export async function getPageRoutes(dir = APP_DIR) { + const routes: PageRoute[] = [] + + // Glob pattern for `page.tsx` and `page.js` files + const pagePattern = path.join(dir, '**/page.{ts,tsx,js}') // Matches page.tsx, page.js, page.ts + + // Glob pattern for layout files + const layoutPattern = path.join(dir, '**/layout.tsx') // Matches layout.tsx files + + // Get all page files + const pageFiles = await glob(pagePattern) + + // Get all layout files + const layoutFiles = await glob(layoutPattern) + + for (const pagePath of pageFiles) { + const relativePagePath = path.relative(APP_DIR, pagePath) // Get the relative path from the app folder + let slug = + '/' + + relativePagePath + .replace(/page\.(ts|tsx|js)$/, '') + .replace(/\[([\w-]+)\]/g, ':$1') + + // Make sure there is no trailing slash + slug = slug.replace(/\/$/, '') + + // Find layouts relevant to this page + const layouts = layoutFiles.filter(layout => { + return pagePath.startsWith(layout.replace('layout.tsx', '')) + }) + + const layoutsWithoutRootLayout = layouts.slice(1) + + routes.push({ + pagePath: pagePath, + slug: slug || '/', + layouts: layoutsWithoutRootLayout + }) + } + + return routes +} + +export const generateAppFile = (pageRoutes: PageRoute[]): string => { + const makePageMap = (routes: PageRoute[]) => { + const pageMap: Record = {} + for (const route of routes) { + pageMap[route.pagePath] = `Page${fnv1aHash(route.pagePath)}` + } + return pageMap + } + + const makeLayoutMap = (routes: PageRoute[]) => { + const layoutMap: Record = {} + for (const route of routes) { + for (const layout of route.layouts) { + layoutMap[layout] = `Layout${fnv1aHash(layout)}` + } + } + return layoutMap + } + + const pageMap = makePageMap(pageRoutes) + const layoutMap = makeLayoutMap(pageRoutes) + + const importPages = Object.keys(pageMap) + .map((pagePath, index) => { + const importLocation = `../${pagePath}`.replace('.tsx', '.js') + const componentName = pageMap[pagePath] + + return `const ${componentName} = lazy(() => import('${importLocation}')) + ` + }) + .join('\n') + + const importLayouts = Object.keys(layoutMap) + .map((layoutPath, index) => { + const importLocation = `../${layoutPath}`.replace('.tsx', '.js') + const componentName = layoutMap[layoutPath] + + return `const ${componentName} = lazy(() => import('${importLocation}')) + ` + }) + .join('\n') + + // Dynamically build the App component with React Router Routes + const appComponent = `"use client"; + import {lazy, Suspense} from 'react' + import { __PYLON_ROUTER_INTERNALS_DO_NOT_USE } from '@getcronit/pylon/pages'; + const {Routes, Route} = __PYLON_ROUTER_INTERNALS_DO_NOT_USE + ${importPages} + const RootLayout = lazy(() => import('../pages/layout.js')) + ${importLayouts} + + const App: React.FC<{pageProps: any}> = ({pageProps}) => ( + + + + + + ${pageRoutes + .map((route, index) => { + return `...}> + ${route.layouts.reduceRight((child, layoutPath, layoutIndex) => { + const layoutName = layoutMap[layoutPath] + + return `<${layoutName}>${child}` + }, `<${pageMap[route.pagePath]} {...pageProps} />`)} + + } />` + }) + .join('\n')} + + + ); + + export default App; + ` + + return appComponent +} diff --git a/packages/pylon/src/plugins/use-pages/build/index.ts b/packages/pylon/src/plugins/use-pages/build/index.ts new file mode 100644 index 0000000..559a80c --- /dev/null +++ b/packages/pylon/src/plugins/use-pages/build/index.ts @@ -0,0 +1,183 @@ +import path from 'path' +import {Plugin} from '../../..' +import {generateAppFile, getPageRoutes} from './app-utils' +import chokidar, {FSWatcher} from 'chokidar' +import fs from 'fs/promises' +import esbuild from 'esbuild' +import {injectAppHydrationPlugin} from './plugins/inject-app-hydration' +import {imagePlugin} from './plugins/image-plugin' +import {postcssPlugin} from './plugins/postcss-plugin' + +const DIST_STATIC_DIR = path.join(process.cwd(), '.pylon/__pylon/static') +const DIST_PAGES_DIR = path.join(process.cwd(), '.pylon/__pylon/pages') + +async function updateFileIfChanged(path: string, newContent: string) { + try { + const currentContent = await fs.readFile(path, 'utf8') + if (currentContent === newContent) { + return false // No update needed + } + } catch (err: any) { + if (err.code !== 'ENOENT') throw err // Ignore file not found error + } + + await fs.writeFile(path, newContent, 'utf8') + return true // File created or updated +} + +export const build: Plugin['build'] = async () => { + const buildAppFile = async () => { + const pagesRoutes = await getPageRoutes() + const appContent = generateAppFile(pagesRoutes) + + const pagesFile = path.resolve(process.cwd(), '.pylon', 'pages.json') + await updateFileIfChanged(pagesFile, JSON.stringify(pagesRoutes, null, 2)) + + // Write if the file doesn't exist or the content is different + const appFilePath = path.resolve(process.cwd(), '.pylon', 'app.tsx') + + const state = await updateFileIfChanged(appFilePath, appContent) + + if (state) { + } + } + + const copyPublicDir = async () => { + // Copy the ./public directory content to the .pylon/__pylon/static directory + const publicDir = path.resolve(process.cwd(), 'public') + const pylonPublicDir = path.resolve( + process.cwd(), + '.pylon', + '__pylon', + 'public' + ) + + try { + await fs.access(publicDir) + + // Copy recursively the public directory to the static directory + await fs.mkdir(pylonPublicDir, {recursive: true}) + await fs.cp(publicDir, pylonPublicDir, {recursive: true}) + } catch (err: any) { + if (err.code !== 'ENOENT') throw err // Ignore file not found error + } + } + + const writeOnEndPlugin: esbuild.Plugin = { + name: 'write-on-end', + setup(build) { + build.onEnd(async result => { + await Promise.all( + result.outputFiles!.map(async file => { + await fs.mkdir(path.dirname(file.path), {recursive: true}) + await updateFileIfChanged(file.path, file.text) + }) + ) + }) + } + } + + const nodePaths = [ + path.join(process.cwd(), 'node_modules'), + path.join(process.cwd(), 'node_modules', '@getcronit/pylon/node_modules') + ] + + let pagesWatcher: FSWatcher | null = null + + const clientCtx = await esbuild.context({ + write: false, + metafile: true, + nodePaths, + absWorkingDir: process.cwd(), + plugins: [ + injectAppHydrationPlugin, + imagePlugin, + postcssPlugin, + writeOnEndPlugin + ], + publicPath: '/__pylon/static', + assetNames: 'assets/[name]-[hash]', + chunkNames: 'chunks/[name]-[hash]', + format: 'esm', + platform: 'browser', + entryPoints: ['.pylon/app.tsx'], + outdir: DIST_STATIC_DIR, + bundle: true, + splitting: true, + minify: true, + loader: { + // Map file extensions to the file loader + + '.svg': 'file', + '.woff': 'file', + '.woff2': 'file' + }, + define: { + 'process.env.NODE_ENV': '"production"' + }, + mainFields: ['browser', 'module', 'main'] + }) + + const serverCtx = await esbuild.context({ + write: false, + absWorkingDir: process.cwd(), + nodePaths, + plugins: [imagePlugin, postcssPlugin, writeOnEndPlugin], + publicPath: '/__pylon/static', + assetNames: 'assets/[name]-[hash]', + chunkNames: 'chunks/[name]-[hash]', + format: 'esm', + platform: 'node', + entryPoints: ['.pylon/app.tsx'], + packages: 'external', + outdir: DIST_PAGES_DIR, + bundle: true, + splitting: false, + minify: true, + loader: { + // Map file extensions to the file loader + + '.svg': 'file', + '.woff': 'file', + '.woff2': 'file' + } + }) + + return { + watch: async () => { + pagesWatcher = chokidar.watch('pages', {ignoreInitial: true}) + + pagesWatcher!.on('all', async (event, path) => { + if (['add', 'change', 'unlink'].includes(event)) { + await buildAppFile() + await copyPublicDir() + } + }) + + await Promise.all([clientCtx.watch(), serverCtx.watch()]) + }, + dispose: async () => { + if (pagesWatcher) { + pagesWatcher.close() + } + + Promise.all([clientCtx.dispose(), serverCtx.dispose()]) + }, + rebuild: async () => { + await buildAppFile() + + await copyPublicDir() + + await Promise.all([clientCtx.rebuild(), serverCtx.rebuild()]) + + return {} as any + }, + cancel: async () => { + if (pagesWatcher) { + await pagesWatcher.close() + } + + await Promise.all([clientCtx.cancel(), serverCtx.cancel()]) + } + } +} diff --git a/packages/pylon/src/plugins/use-pages/build/plugins/image-plugin.ts b/packages/pylon/src/plugins/use-pages/build/plugins/image-plugin.ts new file mode 100644 index 0000000..3c02485 --- /dev/null +++ b/packages/pylon/src/plugins/use-pages/build/plugins/image-plugin.ts @@ -0,0 +1,85 @@ +import {createHash} from 'crypto' +import {Plugin} from 'esbuild' +import path from 'path' +import sharp from 'sharp' +import fs from 'fs/promises' + +export const imagePlugin: Plugin = { + name: 'image-plugin', + setup(build) { + const outdir = build.initialOptions.outdir + const publicPath = build.initialOptions.publicPath + + if (!outdir || !publicPath) { + throw new Error('outdir and publicPath must be set in esbuild options') + } + + build.onResolve({filter: /\.(png|jpe?g)$/}, async args => { + const filePath = path.resolve(args.resolveDir, args.path) + + const fileName = path.basename(filePath) + const extname = path.extname(filePath) + const hash = createHash('md5') + .update(filePath + (await fs.readFile(filePath))) + .digest('hex') + .slice(0, 8) + const newFilename = `${fileName}-${hash}${extname}` + const newFilePath = path.join(outdir, 'media', newFilename) + + // Ensure the directory exists + await fs.mkdir(path.dirname(newFilePath), {recursive: true}) + + // Copy the file + await fs.copyFile(filePath, newFilePath) + + return { + path: newFilePath, + namespace: 'image' + } + }) + + build.onLoad({filter: /\.png$|\.jpg$/}, async args => { + // Load file and read the dimensions + const image = sharp(args.path) + const metadata = await image.metadata() + + // Build the URL with the publicPath and w/h search params + const url = `${publicPath}/media/${path.basename(args.path)}` + + const searchParams = new URLSearchParams({}) + + if (metadata.width) { + searchParams.set('w', metadata.width.toString()) + } + if (metadata.height) { + searchParams.set('h', metadata.height.toString()) + } + + const output = image + .resize({ + width: Math.min(metadata.width ?? 16, 16), + height: Math.min(metadata.height ?? 16, 16), + fit: 'inside' + }) + .toFormat('webp', { + quality: 20, + alphaQuality: 20, + smartSubsample: true + }) + + const {data, info} = await output.toBuffer({resolveWithObject: true}) + const dataURIBase64 = `data:image/${info.format};base64,${data.toString( + 'base64' + )}` + + if (dataURIBase64) { + searchParams.set('blurDataURL', dataURIBase64) + } + + return { + contents: `${url}?${searchParams.toString()}`, + loader: 'text' + } + }) + } +} diff --git a/packages/pylon/src/plugins/use-pages/build/plugins/inject-app-hydration.ts b/packages/pylon/src/plugins/use-pages/build/plugins/inject-app-hydration.ts new file mode 100644 index 0000000..49f6be6 --- /dev/null +++ b/packages/pylon/src/plugins/use-pages/build/plugins/inject-app-hydration.ts @@ -0,0 +1,71 @@ +import {Plugin} from 'esbuild' +import path from 'path' +import fs from 'fs/promises' + +export const injectAppHydrationPlugin: Plugin = { + name: 'inject-hydration', + setup(build) { + build.onLoad({filter: /.*/, namespace: 'file'}, async args => { + // check if the file is the app.tsx file + if (args.path === path.resolve(process.cwd(), '.pylon', 'app.tsx')) { + let contents = await fs.readFile(args.path, 'utf-8') + + const clientPath = path.resolve(process.cwd(), '.pylon/client') + + const pathToClient = path.relative(path.dirname(args.path), clientPath) + + contents += ` + import {hydrateRoot} from 'react-dom/client' + import * as client from './${pathToClient}' + import { __PYLON_ROUTER_INTERNALS_DO_NOT_USE } from '@getcronit/pylon/pages'; + const {BrowserRouter} = __PYLON_ROUTER_INTERNALS_DO_NOT_USE + import React, {useMemo} from 'react' + + const pylonData = window.__PYLON_DATA__ + + const AppLoader = (props: { + client: any + pylonData: { + pageProps: Omit + cacheSnapshot?: any + } + App: React.FC<{ + pageProps: PageProps + }> + Router: React.FC + routerProps: any + }) => { + props.client.useHydrateCache({cacheSnapshot: props.pylonData.cacheSnapshot}) + + const data = props.client.useQuery() + const page = useMemo(() => { + const page = ( + + ) + + return page + }, [props]) + + return {page} + } + + + hydrateRoot( + document, + + ) + ` + + return { + loader: 'tsx', + contents + } + } + }) + } +} diff --git a/packages/pylon/src/plugins/use-pages/build/plugins/postcss-plugin.ts b/packages/pylon/src/plugins/use-pages/build/plugins/postcss-plugin.ts new file mode 100644 index 0000000..3653e42 --- /dev/null +++ b/packages/pylon/src/plugins/use-pages/build/plugins/postcss-plugin.ts @@ -0,0 +1,28 @@ +import {Plugin} from 'esbuild' +import path from 'path' +import fs from 'fs/promises' +import loadConfig from 'postcss-load-config' +import postcss from 'postcss' + +export const postcssPlugin: Plugin = { + name: 'postcss-plugin', + setup(build) { + build.onLoad({filter: /.css$/, namespace: 'file'}, async args => { + const {plugins, options} = await loadConfig() + + const css = await fs.readFile(args.path, 'utf-8') + + const result = await postcss(plugins) + .process(css, { + ...options, + from: args.path + }) + .then(result => result) + + return { + contents: result.css, + loader: 'css' + } + }) + } +} diff --git a/packages/pylon/src/plugins/use-pages/index.ts b/packages/pylon/src/plugins/use-pages/index.ts new file mode 100644 index 0000000..91280a5 --- /dev/null +++ b/packages/pylon/src/plugins/use-pages/index.ts @@ -0,0 +1,12 @@ +import {Plugin} from '../..' +import {setup, PageData, PageProps} from './setup' +import {build} from './build' + +export {PageData, PageProps} + +export function usePages(): Plugin { + return { + setup, + build + } +} diff --git a/packages/pylon/src/plugins/use-pages/setup/app-loader.tsx b/packages/pylon/src/plugins/use-pages/setup/app-loader.tsx new file mode 100644 index 0000000..6964888 --- /dev/null +++ b/packages/pylon/src/plugins/use-pages/setup/app-loader.tsx @@ -0,0 +1,33 @@ +import React, {useMemo} from 'react' +import {PageProps} from '..' + +export const AppLoader = (props: { + client: any + pylonData: { + pageProps: Omit + cacheSnapshot?: any + } + App: React.FC<{ + pageProps: PageProps + }> + Router: React.FC + routerProps: any +}) => { + props.client.useHydrateCache({cacheSnapshot: props.pylonData.cacheSnapshot}) + + const data = props.client.useQuery() + const page = useMemo(() => { + const page = ( + + ) + + return page + }, [props]) + + return {page} +} diff --git a/packages/pylon/src/plugins/use-pages/setup/index.tsx b/packages/pylon/src/plugins/use-pages/setup/index.tsx new file mode 100644 index 0000000..380cb8f --- /dev/null +++ b/packages/pylon/src/plugins/use-pages/setup/index.tsx @@ -0,0 +1,397 @@ +import fs from 'fs' +import path from 'path' +import reactServer from 'react-dom/server' + +import { UseHydrateCacheOptions } from '@gqty/react' +import { Readable } from 'stream' +import { AppLoader } from './app-loader' +import { getEnv, type Plugin } from '../../../index' +import { cloneElement, createElement } from 'react' +import { trimTrailingSlash } from 'hono/trailing-slash' +import { StaticRouter } from 'react-router' + +export interface PageData { } + +export type PageProps = { + data: PageData + params: Record + searchParams: Record + path: string +} + +const disableCacheMiddleware: MiddlewareHandler = async (c, next) => { + const env = getEnv() + if (env.NODE_ENV === 'development') { + c.header( + 'Cache-Control', + 'no-store, no-cache, must-revalidate, proxy-revalidate' + ) + c.header('Pragma', 'no-cache') + c.header('Expires', '0') + c.header('Surrogate-Control', 'no-store') + } + + return next() +} + +export const setup: Plugin['setup'] = app => { + const pagesFilePath = path.resolve(process.cwd(), '.pylon', 'pages.json') + + let pageRoutes: PageRoute[] = [] + try { + pageRoutes = JSON.parse(fs.readFileSync(pagesFilePath, 'utf-8')) + } catch (error) { + console.error('Error reading pages.json', error) + } + + app.use(trimTrailingSlash()) + + let App: any = undefined + let client: any = undefined + + app.on( + 'GET', + pageRoutes.map(pageRoute => pageRoute.slug), + disableCacheMiddleware, + async c => { + if (!App) { + const module = await import( + `${process.cwd()}/.pylon/__pylon/pages/app.js` + ) + + App = module.default + } + + if (!client) { + client = await import(`${process.cwd()}/.pylon/client`) + } + + const pageProps = { + params: c.req.param(), + searchParams: c.req.query(), + path: c.req.path + } + + let cacheSnapshot: UseHydrateCacheOptions | undefined = undefined + + const prepared = await client.prepareReactRender( + + ) + + cacheSnapshot = prepared.cacheSnapshot + + const stream = await reactServer.renderToReadableStream( + , + { + bootstrapModules: ['/__pylon/static/app.js'], + bootstrapScriptContent: `window.__PYLON_DATA__ = ${JSON.stringify({ + pageProps: pageProps, + cacheSnapshot: cacheSnapshot + })}` + } + ) + + return c.body(stream) + } + ) + + const publicFilesPath = path.resolve(process.cwd(), '.pylon', '__pylon', 'public') + let publicFiles: string[] = [] + + try { + publicFiles = fs.readdirSync(publicFilesPath) + } catch (error) { + console.error('Error reading public files', error) + } + + + app.on('GET', + publicFiles.map(file => `/${file}`), disableCacheMiddleware, async c => { + const publicFilePath = path.resolve( + process.cwd(), + '.pylon', + '__pylon', + 'public', + c.req.path.replace('/', '') + ) + + + try { + await fs.promises.access(publicFilePath) + + if (publicFilePath.endsWith('.js')) { + c.res.headers.set('Content-Type', 'text/javascript') + } else if (publicFilePath.endsWith('.css')) { + c.res.headers.set('Content-Type', 'text/css') + } else if (publicFilePath.endsWith('.html')) { + c.res.headers.set('Content-Type', 'text/html') + } else if (publicFilePath.endsWith('.json')) { + c.res.headers.set('Content-Type', 'application/json') + } else if (publicFilePath.endsWith('.png')) { + c.res.headers.set('Content-Type', 'image/png') + } else if (publicFilePath.endsWith('.jpg') || publicFilePath.endsWith('.jpeg')) { + c.res.headers.set('Content-Type', 'image/jpeg') + } else if (publicFilePath.endsWith('.gif')) { + c.res.headers.set('Content-Type', 'image/gif') + } else if (publicFilePath.endsWith('.svg')) { + c.res.headers.set('Content-Type', 'image/svg+xml') + } else if (publicFilePath.endsWith('.ico')) { + c.res.headers.set('Content-Type', 'image/x-icon') + } + + const stream = fs.createReadStream(publicFilePath) + + const a = Readable.toWeb(stream) as ReadableStream + + return c.body(a) + + } catch { + return c.status(404) + } + }) + + + app.get('/__pylon/static/*', disableCacheMiddleware, async c => { + const filePath = path.resolve( + process.cwd(), + '.pylon', + '__pylon', + 'static', + c.req.path.replace('/__pylon/static/', '') + ) + + if (!fs.existsSync(filePath)) { + return c.status(404) + } + + if (filePath.endsWith('.js')) { + c.res.headers.set('Content-Type', 'text/javascript') + } else if (filePath.endsWith('.css')) { + c.res.headers.set('Content-Type', 'text/css') + } else if (filePath.endsWith('.html')) { + c.res.headers.set('Content-Type', 'text/html') + } else if (filePath.endsWith('.json')) { + c.res.headers.set('Content-Type', 'application/json') + } else if (filePath.endsWith('.png')) { + c.res.headers.set('Content-Type', 'image/png') + } else if (filePath.endsWith('.jpg') || filePath.endsWith('.jpeg')) { + c.res.headers.set('Content-Type', 'image/jpeg') + } else if (filePath.endsWith('.gif')) { + c.res.headers.set('Content-Type', 'image/gif') + } else if (filePath.endsWith('.svg')) { + c.res.headers.set('Content-Type', 'image/svg+xml') + } else if (filePath.endsWith('.ico')) { + c.res.headers.set('Content-Type', 'image/x-icon') + } + + const stream = fs.createReadStream(filePath) + + const a = Readable.toWeb(stream) as ReadableStream + + return c.body(a) + }) + + // Image optimization route + app.get('/__pylon/image', async c => { + try { + const { src, w, h, q = '75', format = 'webp' } = c.req.query() + + const queryStringHash = createHash('sha256') + .update(JSON.stringify(c.req.query())) + .digest('hex') + + if (!src) { + return c.json({ error: 'Missing parameters.' }, 400) + } + + let imagePath = path.join(process.cwd(), '.pylon', src) + + if (src.startsWith('http://') || src.startsWith('https://')) { + imagePath = await downloadImage(src) + } + + // Check if the image exists asynchronously + try { + await fs.promises.access(imagePath) + } catch { + return c.json({ error: 'Image not found' }, 404) + } + + // Get image metadata (width and height) to calculate aspect ratio + const metadata = await sharp(imagePath).metadata() + + // Validate if the metadata contains width and height + if (!metadata.width || !metadata.height) { + return c.json( + { + error: + 'Invalid image metadata. Width and height are required for resizing.' + }, + 400 + ) + } + + // Calculate missing dimension + const { width: finalWidth, height: finalHeight } = calculateDimensions( + metadata.width, + metadata.height, + w ? parseInt(w) : undefined, + h ? parseInt(h) : undefined + ) + + // Check cache first + const cachePath = path.join(IMAGE_CACHE_DIR, queryStringHash) + + let imageFormat = format.toLowerCase() + + if (!isSupportedFormat(imageFormat)) { + throw new Error('Unsupported image format') + } + + // Serve cached image if it exists + // try { + // await fs.promises.access(cachePath); + + // const stream = fs.createReadStream(cachePath) + + // c.res.headers.set('Content-Type', getContentType(imageFormat)); + + // return c.body(Readable.toWeb(stream) as ReadableStream) + // } catch { + // // Proceed to optimize and cache the image if it doesn't exist + // } + + const quality = parseInt(q) + + + // Optimize the image using sharp + const image = await sharp(imagePath) + .resize(finalWidth, finalHeight) + .toFormat(imageFormat, { + quality + }) + .toFile(cachePath) + + + c.res.headers.set('Content-Type', getContentType(image.format)) + + // Serve the optimized image + return c.body( + Readable.toWeb(fs.createReadStream(cachePath)) as ReadableStream + ) + } catch (error) { + console.error('Error processing the image:', error) + return c.json({ error: 'Error processing the image' }, 500) + } + }) +} + +import sharp, { FormatEnum } from 'sharp' +import { createHash } from 'crypto' + +// Cache directory +const IMAGE_CACHE_DIR = path.join(process.cwd(), '.cache/__pylon/images') + +// Ensure the cache directory exists +fs.promises.mkdir(IMAGE_CACHE_DIR, { recursive: true }) + +// Helper function to generate the cached image path +const getCachedImagePath = ( + src: string, + width: number, + height: number, + format: keyof FormatEnum +) => { + const fileName = `${path.basename( + src, + path.extname(src) + )}-${width}x${height}.${format}` + return path.join(IMAGE_CACHE_DIR, fileName) +} + +// Utility function to calculate missing dimension based on aspect ratio +const calculateDimensions = ( + originalWidth: number, + originalHeight: number, + width?: number, + height?: number +) => { + if (!width && !height) { + return { width: originalWidth, height: originalHeight } + } + if (width && !height) { + // Calculate height based on the aspect ratio + height = Math.round((width * originalHeight) / originalWidth) + } else if (height && !width) { + // Calculate width based on the aspect ratio + width = Math.round((height * originalWidth) / originalHeight) + } + return { width, height } +} + +function isSupportedFormat(format: string): format is keyof FormatEnum { + const supportedFormats = sharp.format + return Object.keys(supportedFormats).includes(format) +} + +// Helper function to get the correct Content-Type based on the format +const getContentType = (format: string) => { + switch (format.toLowerCase()) { + case 'webp': + return 'image/webp' + case 'jpeg': + case 'jpg': + return 'image/jpeg' + case 'png': + return 'image/png' + case 'gif': + return 'image/gif' + case 'svg': + return 'image/svg+xml' + default: + return 'application/octet-stream' // Fallback type if format is unknown + } +} + +import { tmpdir } from 'os' +import { promisify } from 'util' +import { pipeline } from 'stream/promises' +import { PageRoute } from '../build/app-utils' +import { MiddlewareHandler } from 'hono' + +const downloadImage = async (url: string): Promise => { + const response = await fetch(url) + if (!response.ok) + throw new Error(`Failed to download image: ${response.statusText}`) + + const ext = path.extname(new URL(url).pathname) || '.jpg' + const tempFilePath = path.join(tmpdir(), `image-${Date.now()}${ext}`) + + const fileStream = fs.createWriteStream(tempFilePath) + + await pipeline(response.body!, fileStream) + + return tempFilePath +} diff --git a/packages/pylon/src/app/envelop/use-sentry.ts b/packages/pylon/src/plugins/use-sentry.ts similarity index 99% rename from packages/pylon/src/app/envelop/use-sentry.ts rename to packages/pylon/src/plugins/use-sentry.ts index 557fb37..eed8977 100644 --- a/packages/pylon/src/app/envelop/use-sentry.ts +++ b/packages/pylon/src/plugins/use-sentry.ts @@ -4,11 +4,11 @@ import { handleStreamOrSingleExecutionResult, isOriginalGraphQLError, OnExecuteDoneHookResultOnNextHook, - TypedExecutionArgs, - type Plugin + TypedExecutionArgs } from '@envelop/core' import * as Sentry from '@sentry/node' import type {Span, TraceparentData} from '@sentry/types' +import {Plugin} from '..' export type SentryPluginOptions> = { /** diff --git a/packages/pylon/src/plugins/use-unhandled-route.ts b/packages/pylon/src/plugins/use-unhandled-route.ts new file mode 100644 index 0000000..22a12b1 --- /dev/null +++ b/packages/pylon/src/plugins/use-unhandled-route.ts @@ -0,0 +1,186 @@ +import {getVersions} from '@getcronit/pylon-telemetry' +import {html} from 'hono/html' +import {type Plugin} from '../index' + +export function useUnhandledRoute(args: {graphqlEndpoint: string}): Plugin { + const versions = getVersions() + + return { + setup: app => { + app.use(async (c, next) => { + if (c.req.method === 'GET' && c.req.path !== args.graphqlEndpoint) { + return c.html( + await html` + + + + Welcome to Pylon + + + + +
+
+ +

Enables TypeScript developers to easily build GraphQL APIs

+ +
+
+

Not the page you are looking for? đź‘€

+

+ This page is shown be default whenever a 404 is hit.
You can disable this by behavior + via the landingPage option in the Pylon config. Edit the src/index.ts file + and add the following code: +

+
+    
+  export const config: PylonConfig = {
+    landingPage: false
+  }
+    
+  
+ +

+ When you define a route, this page will no longer be shown. For example, the following code + will show a "Hello, world!" message at the root of your app: +

+
+    
+  import {app} from '@getcronit/pylon'
+  
+  app.get("/", c => {
+    return c.text("Hello, world!")
+  })
+    
+  
+
+
+ + `, + 404 + ) + } + + return next() + }) + } + } +} diff --git a/packages/pylon/src/plugins/use-viewer.ts b/packages/pylon/src/plugins/use-viewer.ts new file mode 100644 index 0000000..2d73338 --- /dev/null +++ b/packages/pylon/src/plugins/use-viewer.ts @@ -0,0 +1,76 @@ +import {html} from 'hono/html' +import {getContext, type Plugin} from '../index' + +export function useViewer(): Plugin { + return { + onRequest: async ({request, fetchAPI, endResponse, url}) => { + const c = getContext() + + if (request.method === 'GET' && url.pathname === '/viewer') { + endResponse( + c.html( + await html` + + + + Pylon Viewer + + + + + + + + +
Loading...
+ + + + ` + ) + ) + } + } + } +} diff --git a/packages/pylon/tsconfig.json b/packages/pylon/tsconfig.json index 3ce3195..f82dca1 100644 --- a/packages/pylon/tsconfig.json +++ b/packages/pylon/tsconfig.json @@ -1,27 +1,31 @@ { - "compilerOptions": { - // add Bun type definitions - - // enable latest features - "lib": ["esnext"], - "module": "esnext", - "target": "esnext", - - "moduleResolution": "node", // support `import` from `node_modules` - "noImplicitAny": false, - "noImplicitThis": false, - - "jsx": "react-jsx", // support JSX - "allowJs": true, // allow importing `.js` from `.ts` - "esModuleInterop": true, // allow default imports for CommonJS modules - - // best practices - "strict": true, - "forceConsistentCasingInFileNames": true, - "skipLibCheck": true, - "experimentalDecorators": true, - }, - "include": ["src/**/*"], - "exclude": ["node_modules/**/*", "**/*.test.ts", "**/*.test.tsx", "**/__tests__/**/*"] - } - \ No newline at end of file + "compilerOptions": { + // add Bun type definitions + + // enable latest features + "lib": ["esnext"], + "module": "esnext", + "target": "esnext", + + "moduleResolution": "bundler", // support `import` from `node_modules` + "noImplicitAny": false, + "noImplicitThis": false, + + "jsx": "react-jsx", // support JSX + "allowJs": true, // allow importing `.js` from `.ts` + "esModuleInterop": true, // allow default imports for CommonJS modules + + // best practices + "strict": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "experimentalDecorators": true + }, + "include": ["src/**/*"], + "exclude": [ + "node_modules/**/*", + "**/*.test.ts", + "**/*.test.tsx", + "**/__tests__/**/*" + ] +} diff --git a/packages/pylon/tsconfig.pylon.json b/packages/pylon/tsconfig.pylon.json index 47c5de8..048454a 100644 --- a/packages/pylon/tsconfig.pylon.json +++ b/packages/pylon/tsconfig.pylon.json @@ -5,7 +5,7 @@ "module": "esnext", "target": "esnext", - "moduleResolution": "node", // support `import` from `node_modules` + "moduleResolution": "bundler", // support `import` from `node_modules` "noImplicitAny": false, "noImplicitThis": false, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 057acff..e14e1a9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,11 +65,11 @@ importers: version: link:../../packages/pylon drizzle-orm: specifier: ^0.33.0 - version: 0.33.0(@cloudflare/workers-types@4.20250129.0)(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(bun-types@1.2.2) + version: 0.33.0(@cloudflare/workers-types@4.20250129.0)(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(@types/react@19.0.8)(bun-types@1.2.2)(react@19.0.0) devDependencies: '@cloudflare/vitest-pool-workers': specifier: ^0.4.5 - version: 0.4.31(@cloudflare/workers-types@4.20250129.0)(@vitest/runner@1.5.3)(@vitest/snapshot@1.5.3)(vitest@1.5.3(@types/node@22.13.1)) + version: 0.4.31(@cloudflare/workers-types@4.20250129.0)(@vitest/runner@1.5.3)(@vitest/snapshot@1.5.3)(vitest@1.5.3(@types/node@22.13.1)(lightningcss@1.29.1)) '@cloudflare/workers-types': specifier: ^4.20240903.0 version: 4.20250129.0 @@ -143,6 +143,58 @@ importers: specifier: workspace:^ version: link:../../packages/pylon-dev + examples/pages: + dependencies: + '@getcronit/pylon': + specifier: workspace:^ + version: link:../../packages/pylon + '@gqty/react': + specifier: ^3.1.0 + version: 3.1.0(gqty@3.4.0-canary-20250207102900.644ad9fdeafa6318516627577b1d4d754d5c5a98(graphql@16.10.0))(graphql@16.10.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-slot': + specifier: ^1.1.2 + version: 1.1.2(@types/react@19.0.8)(react@19.0.0) + bun-types: + specifier: ^1.1.18 + version: 1.2.2 + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + gqty: + specifier: 3.4.0-canary-20250207102900.644ad9fdeafa6318516627577b1d4d754d5c5a98 + version: 3.4.0-canary-20250207102900.644ad9fdeafa6318516627577b1d4d754d5c5a98(graphql@16.10.0) + lucide-react: + specifier: ^0.474.0 + version: 0.474.0(react@19.0.0) + react: + specifier: ^19.0.0 + version: 19.0.0 + react-dom: + specifier: ^19.0.0 + version: 19.0.0(react@19.0.0) + tailwind-merge: + specifier: ^3.0.1 + version: 3.0.1 + tailwindcss: + specifier: ^4.0.4 + version: 4.0.4 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@4.0.4) + devDependencies: + '@getcronit/pylon-dev': + specifier: workspace:^ + version: link:../../packages/pylon-dev + '@tailwindcss/postcss': + specifier: ^4.0.6 + version: 4.0.6 + '@types/react': + specifier: ^19.0.8 + version: 19.0.8 + packages/create-pylon: dependencies: '@getcronit/pylon-telemetry': @@ -172,6 +224,9 @@ importers: '@getcronit/pylon-telemetry': specifier: workspace:^ version: link:../pylon-telemetry + '@gqty/react': + specifier: ^3.1.0 + version: 3.1.0(gqty@3.4.0-canary-20250207102900.644ad9fdeafa6318516627577b1d4d754d5c5a98(graphql@16.10.0))(graphql@16.10.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@hono/sentry': specifier: ^1.2.0 version: 1.2.0(hono@4.6.20) @@ -181,9 +236,21 @@ importers: '@sentry/node': specifier: ^8.54.0 version: 8.54.0 + '@tailwindcss/postcss': + specifier: ^4.0.4 + version: 4.0.4 + autoprefixer: + specifier: ^10.4.20 + version: 10.4.20(postcss@8.5.1) + chokidar: + specifier: ^4.0.3 + version: 4.0.3 consola: specifier: ^3.2.3 version: 3.4.0 + gqty: + specifier: 3.4.0-canary-20250207102900.644ad9fdeafa6318516627577b1d4d754d5c5a98 + version: 3.4.0-canary-20250207102900.644ad9fdeafa6318516627577b1d4d754d5c5a98(graphql@16.10.0) graphql: specifier: ^16.9.0 version: 16.10.0 @@ -200,8 +267,29 @@ importers: specifier: ^9.0.2 version: 9.0.2 openid-client: - specifier: ^5.6.4 - version: 5.7.1 + specifier: ^6.1.7 + version: 6.1.7 + postcss: + specifier: ^8.5.1 + version: 8.5.1 + postcss-load-config: + specifier: ^6.0.1 + version: 6.0.1(jiti@2.4.2)(postcss@8.5.1) + react: + specifier: ^19.0.0 + version: 19.0.0 + react-dom: + specifier: ^19.0.0 + version: 19.0.0(react@19.0.0) + react-router: + specifier: ^7.1.5 + version: 7.1.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + sharp: + specifier: ^0.33.5 + version: 0.33.5 + tiny-glob: + specifier: ^0.2.9 + version: 0.2.9 toucan-js: specifier: ^4.1.0 version: 4.1.0 @@ -213,38 +301,17 @@ importers: specifier: ^8.54.0 version: 8.54.0 - packages/pylon-builder: - dependencies: - chokidar: - specifier: ^3.5.3 - version: 3.6.0 - consola: - specifier: ^3.2.3 - version: 3.4.0 - esbuild: - specifier: ^0.23.1 - version: 0.23.1 - esbuild-plugin-tsc: - specifier: ^0.4.0 - version: 0.4.0(typescript@5.7.3) - source-map-support: - specifier: ^0.5.21 - version: 0.5.21 - typescript: - specifier: ^5.0.0 - version: 5.7.3 - packages/pylon-dev: dependencies: - '@getcronit/pylon-builder': - specifier: workspace:^ - version: link:../pylon-builder + '@getcronit/pylon': + specifier: workspace:^2.0.0 + version: link:../pylon '@getcronit/pylon-telemetry': specifier: workspace:^ version: link:../pylon-telemetry '@gqty/cli': - specifier: ^4.2.0 - version: 4.2.2(@babel/core@7.26.7)(typescript@5.7.3) + specifier: ^4.2.5 + version: 4.2.5(@babel/core@7.26.7)(@types/node@22.13.1)(typescript@5.7.3) commander: specifier: ^12.1.0 version: 12.1.0 @@ -254,9 +321,25 @@ importers: dotenv: specifier: ^16.4.5 version: 16.4.7 - treekill: - specifier: ^1.0.0 - version: 1.0.0 + esbuild: + specifier: ^0.23.1 + version: 0.23.1 + esbuild-plugin-tsc: + specifier: ^0.4.0 + version: 0.4.0(typescript@5.7.3) + graphql: + specifier: ^16.9.0 + version: 16.10.0 + pm2: + specifier: ^5.4.3 + version: 5.4.3 + devDependencies: + '@types/ps-tree': + specifier: ^1.1.6 + version: 1.1.6 + typescript: + specifier: ^5.7.3 + version: 5.7.3 packages/pylon-telemetry: dependencies: @@ -266,6 +349,10 @@ importers: packages: + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} @@ -678,10 +765,10 @@ packages: resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} engines: {node: '>=0.1.90'} - '@commander-js/extra-typings@12.1.0': - resolution: {integrity: sha512-wf/lwQvWAA0goIghcb91dQYpkLBcyhOhQNqG/VgWhnKzgt+UOMvra7EX/2fv70arm5RW+PUHoQHHDa6/p77Eqg==} + '@commander-js/extra-typings@13.1.0': + resolution: {integrity: sha512-q5P52BYb1hwVWE6dtID7VvuJWrlfbCv4klj7BjUUOqMz4jbSZD4C9fJ9lRjL2jnBGTg+gDDlaXN51rkWcLk4fg==} peerDependencies: - commander: ~12.1.0 + commander: ~13.1.0 '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} @@ -693,6 +780,9 @@ packages: '@drizzle-team/brocli@0.10.2': resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} + '@emnapi/runtime@1.3.1': + resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==} + '@envelop/core@5.0.3': resolution: {integrity: sha512-SE3JxL7odst8igN6x77QWyPpXKXz/Hs5o5Y27r+9Br6WHIhkW90lYYVITWIJQ/qYgn5PkpbaVgeFY9rgqQaZ/A==} engines: {node: '>=18.0.0'} @@ -1407,15 +1497,31 @@ packages: resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} engines: {node: '>=14'} - '@gqty/cli@4.2.2': - resolution: {integrity: sha512-fpNvl8GZ82oTLYcvc6UmhnsKQvKLxLy79RGzhDePkTFMFYFUMnmc5bvuMCsP9M/gn4Z0WfTuMxOwMgMRkanrzQ==} + '@gqty/cli@4.2.5': + resolution: {integrity: sha512-TXlJvoTq1jrJ2fZzmO/K7thOQVeiM3L8vCNHlnCwUi3gGxz6iF7xiHUL/6k2xUJOy7tYPsPBH8k1BjIwCXbUqA==} hasBin: true peerDependencies: - trading-signals: ^5.0.4 + trading-signals: ^6.0.1 peerDependenciesMeta: trading-signals: optional: true + '@gqty/react@3.1.0': + resolution: {integrity: sha512-MFGFmAvDi+N4KX2WUSSiFMGzv5fdmrWd0naVxRRDff/+CeNjXHCmwAKi5B2aezuSXrh+rENh/+6ZlZgB3ffOIg==} + engines: {node: ^12.20.0 || >=14.13.0} + peerDependencies: + gqty: ^3.3.0 + graphql: ^16.9.0 + graphql-sse: ^2.5.4 + graphql-ws: ^5.16.2 + react: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + graphql-sse: + optional: true + graphql-ws: + optional: true + '@graphql-codegen/core@4.0.2': resolution: {integrity: sha512-IZbpkhwVqgizcjNiaVzNAzm/xbWT6YnGgeOLwVjm4KbJn3V2jchVtuzHH09G5/WkkLSk2wgbXNdwjM41JxO6Eg==} peerDependencies: @@ -1492,6 +1598,12 @@ packages: peerDependencies: graphql: ^16.9.0 + '@graphql-tools/utils@10.8.1': + resolution: {integrity: sha512-fI5NNuqeEAHyp7NuCDjvxWR5PTUXM4AqY9BoC59ZcX4nePAJje27ZsFHbAMS6EKDosY1K/D4ADxsO0P5+FH07A==} + engines: {node: '>=16.0.0'} + peerDependencies: + graphql: ^16.9.0 + '@graphql-tools/wrap@10.0.29': resolution: {integrity: sha512-kQdosPBo6EvFhQV5s0XpN6+N0YN+31mCZTV7uwZisaUwwroAT19ujs2Zxz8Zyw4H9XRCsueLT0wqmSupjIFibQ==} engines: {node: '>=18.0.0'} @@ -1526,14 +1638,146 @@ packages: peerDependencies: hono: '>=3.*' + '@img/sharp-darwin-arm64@0.33.5': + resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.33.5': + resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.0.4': + resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.0.4': + resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.0.4': + resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.0.5': + resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.0.4': + resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.0.4': + resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.33.5': + resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.33.5': + resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-s390x@0.33.5': + resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.33.5': + resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.33.5': + resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.33.5': + resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.33.5': + resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-ia32@0.33.5': + resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.33.5': + resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + '@inquirer/checkbox@2.5.0': resolution: {integrity: sha512-sMgdETOfi2dUHT8r7TT1BTKOwNvdDGFDXYWtQ2J69SvlYNntk9I/gJe7r5yvMwwsuKnYbuRs3pNhx4tgNck5aA==} engines: {node: '>=18'} + '@inquirer/checkbox@4.1.1': + resolution: {integrity: sha512-os5kFd/52gZTl/W6xqMfhaKVJHQM8V/U1P8jcSaQJ/C4Qhdrf2jEXdA/HaxfQs9iiUA/0yzYhk5d3oRHTxGDDQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@inquirer/confirm@3.2.0': resolution: {integrity: sha512-oOIwPs0Dvq5220Z8lGL/6LHRTEr9TgLHmiI99Rj1PJ1p1czTys+olrgBqZk4E2qC0YTzeHprxSQmoHioVdJ7Lw==} engines: {node: '>=18'} + '@inquirer/confirm@5.1.5': + resolution: {integrity: sha512-ZB2Cz8KeMINUvoeDi7IrvghaVkYT2RB0Zb31EaLWOE87u276w4wnApv0SH2qWaJ3r0VSUa3BIuz7qAV2ZvsZlg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@10.1.6': + resolution: {integrity: sha512-Bwh/Zk6URrHwZnSSzAZAKH7YgGYi0xICIBDFOqBQoXNNAzBHw/bgXgLmChfp+GyR3PnChcTbiCTZGC6YJNJkMA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@inquirer/core@9.2.1': resolution: {integrity: sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==} engines: {node: '>=18'} @@ -1542,10 +1786,28 @@ packages: resolution: {integrity: sha512-9KHOpJ+dIL5SZli8lJ6xdaYLPPzB8xB9GZItg39MBybzhxA16vxmszmQFrRwbOA918WA2rvu8xhDEg/p6LXKbw==} engines: {node: '>=18'} + '@inquirer/editor@4.2.6': + resolution: {integrity: sha512-l0smvr8g/KAVdXx4I92sFxZiaTG4kFc06cFZw+qqwTirwdUHMFLnouXBB9OafWhpO3cfEkEz2CdPoCmor3059A==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@inquirer/expand@2.3.0': resolution: {integrity: sha512-qnJsUcOGCSG1e5DTOErmv2BPQqrtT6uzqn1vI/aYGiPKq+FgslGZmtdnXbhuI7IlT7OByDoEEqdnhUnVR2hhLw==} engines: {node: '>=18'} + '@inquirer/expand@4.0.8': + resolution: {integrity: sha512-k0ouAC6L+0Yoj/j0ys2bat0fYcyFVtItDB7h+pDFKaDDSFJey/C/YY1rmIOqkmFVZ5rZySeAQuS8zLcKkKRLmg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@inquirer/figures@1.0.10': resolution: {integrity: sha512-Ey6176gZmeqZuY/W/nZiUyvmb1/qInjcpiZjXWi6nON+nxJpD1bxtSoBxNliGISae32n6OwbY+TSXPZ1CfS4bw==} engines: {node: '>=18'} @@ -1554,30 +1816,93 @@ packages: resolution: {integrity: sha512-XfnpCStx2xgh1LIRqPXrTNEEByqQWoxsWYzNRSEUxJ5c6EQlhMogJ3vHKu8aXuTacebtaZzMAHwEL0kAflKOBw==} engines: {node: '>=18'} + '@inquirer/input@4.1.5': + resolution: {integrity: sha512-bB6wR5wBCz5zbIVBPnhp94BHv/G4eKbUEjlpCw676pI2chcvzTx1MuwZSCZ/fgNOdqDlAxkhQ4wagL8BI1D3Zg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@inquirer/number@1.1.0': resolution: {integrity: sha512-ilUnia/GZUtfSZy3YEErXLJ2Sljo/mf9fiKc08n18DdwdmDbOzRcTv65H1jjDvlsAuvdFXf4Sa/aL7iw/NanVA==} engines: {node: '>=18'} + '@inquirer/number@3.0.8': + resolution: {integrity: sha512-CTKs+dT1gw8dILVWATn8Ugik1OHLkkfY82J+Musb57KpmF6EKyskv8zmMiEJPzOnLTZLo05X/QdMd8VH9oulXw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@inquirer/password@2.2.0': resolution: {integrity: sha512-5otqIpgsPYIshqhgtEwSspBQE40etouR8VIxzpJkv9i0dVHIpyhiivbkH9/dGiMLdyamT54YRdGJLfl8TFnLHg==} engines: {node: '>=18'} + '@inquirer/password@4.0.8': + resolution: {integrity: sha512-MgA+Z7o3K1df2lGY649fyOBowHGfrKRz64dx3+b6c1w+h2W7AwBoOkHhhF/vfhbs5S4vsKNCuDzS3s9r5DpK1g==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@inquirer/prompts@5.5.0': resolution: {integrity: sha512-BHDeL0catgHdcHbSFFUddNzvx/imzJMft+tWDPwTm3hfu8/tApk1HrooNngB2Mb4qY+KaRWF+iZqoVUPeslEog==} engines: {node: '>=18'} + '@inquirer/prompts@7.3.1': + resolution: {integrity: sha512-r1CiKuDV86BDpvj9DRFR+V+nIjsVBOsa2++dqdPqLYAef8kgHYvmQ8ySdP/ZeAIOWa27YGJZRkENdP3dK0H3gg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@inquirer/rawlist@2.3.0': resolution: {integrity: sha512-zzfNuINhFF7OLAtGHfhwOW2TlYJyli7lOUoJUXw/uyklcwalV6WRXBXtFIicN8rTRK1XTiPWB4UY+YuW8dsnLQ==} engines: {node: '>=18'} + '@inquirer/rawlist@4.0.8': + resolution: {integrity: sha512-hl7rvYW7Xl4un8uohQRUgO6uc2hpn7PKqfcGkCOWC0AA4waBxAv6MpGOFCEDrUaBCP+pXPVqp4LmnpWmn1E1+g==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@inquirer/search@1.1.0': resolution: {integrity: sha512-h+/5LSj51dx7hp5xOn4QFnUaKeARwUCLs6mIhtkJ0JYPBLmEYjdHSYh7I6GrLg9LwpJ3xeX0FZgAG1q0QdCpVQ==} engines: {node: '>=18'} + '@inquirer/search@3.0.8': + resolution: {integrity: sha512-ihSE9D3xQAupNg/aGDZaukqoUSXG2KfstWosVmFCG7jbMQPaj2ivxWtsB+CnYY/T4D6LX1GHKixwJLunNCffww==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@inquirer/select@2.5.0': resolution: {integrity: sha512-YmDobTItPP3WcEI86GvPo+T2sRHkxxOq/kXmsBjHS5BVXUgvgZ5AfJjkvQvZr03T81NnI3KrrRuMzeuYUQRFOA==} engines: {node: '>=18'} + '@inquirer/select@4.0.8': + resolution: {integrity: sha512-Io2prxFyN2jOCcu4qJbVoilo19caiD3kqkD3WR0q3yDA5HUCo83v4LrRtg55ZwniYACW64z36eV7gyVbOfORjA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@inquirer/type@1.5.5': resolution: {integrity: sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==} engines: {node: '>=18'} @@ -1586,6 +1911,15 @@ packages: resolution: {integrity: sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==} engines: {node: '>=18'} + '@inquirer/type@3.0.4': + resolution: {integrity: sha512-2MNFrDY8jkFYc9Il9DgLsHhMzuHnOYM1+CUYVWbzu9oT0hC7V7EcYvdCKeoll/Fcci04A+ERZ9wcc7cQ8lTkIA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -1891,6 +2225,20 @@ packages: peerDependencies: '@opentelemetry/api': ^1.1.0 + '@pm2/agent@2.0.4': + resolution: {integrity: sha512-n7WYvvTJhHLS2oBb1PjOtgLpMhgImOq8sXkPBw6smeg9LJBWZjiEgPKOpR8mn9UJZsB5P3W4V/MyvNnp31LKeA==} + + '@pm2/io@6.0.1': + resolution: {integrity: sha512-KiA+shC6sULQAr9mGZ1pg+6KVW9MF8NpG99x26Lf/082/Qy8qsTCtnJy+HQReW1A9Rdf0C/404cz0RZGZro+IA==} + engines: {node: '>=6.0'} + + '@pm2/js-api@0.8.0': + resolution: {integrity: sha512-nmWzrA/BQZik3VBz+npRcNIu01kdBhWL0mxKmP1ciF/gTcujPTQqt027N9fc1pK9ERM8RipFhymw7RcmCyOEYA==} + engines: {node: '>=4.0'} + + '@pm2/pm2-version-check@1.0.4': + resolution: {integrity: sha512-SXsM27SGH3yTWKc2fKR4SYNxsmnvuBQ9dd6QHtEWmiZ/VqaOYPAIlS8+vMcn27YLtAEBGvNRSh3TPNvtjZgfqA==} + '@pnpm/config.env-replace@1.1.0': resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} engines: {node: '>=12.22.0'} @@ -1906,6 +2254,37 @@ packages: '@prisma/instrumentation@5.22.0': resolution: {integrity: sha512-LxccF392NN37ISGxIurUljZSh1YWnphO34V5a0+T7FVQG2u9bhAXRTJpgmQ3483woVhkraQZFF7cbRrpbw/F4Q==} + '@radix-ui/react-compose-refs@1.1.1': + resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-slot@1.1.2': + resolution: {integrity: sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@react-hookz/deep-equal@1.0.4': + resolution: {integrity: sha512-N56fTrAPUDz/R423pag+n6TXWbvlBZDtTehaGFjK0InmN+V2OFWLE/WmORhmn6Ce7dlwH5+tQN1LJFw3ngTJVg==} + + '@react-hookz/web@23.1.0': + resolution: {integrity: sha512-fvbURdsa1ukttbLR1ASE/XmqXP09vZ1PiCYppYeR1sNMzCrdkG0iBnjxniFSVjJ8gIw2fRs6nqMTbeBz2uAkuA==} + peerDependencies: + js-cookie: ^3.0.5 + react: ^16.8 || ^17 || ^18 + react-dom: ^16.8 || ^17 || ^18 + peerDependenciesMeta: + js-cookie: + optional: true + '@repeaterjs/repeater@3.0.6': resolution: {integrity: sha512-Javneu5lsuhwNCryN+pXH93VPQ8g0dBX7wItHFgYiwQmzE1sVdg5tWHiOgHywzL2W21XQopa7IwIEnNbmeUJYA==} @@ -2099,38 +2478,202 @@ packages: resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} - '@types/connect@3.4.36': - resolution: {integrity: sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==} - - '@types/estree@1.0.6': - resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@tailwindcss/node@4.0.4': + resolution: {integrity: sha512-VLFq80IyoV1hsHPcCm1mmlyPyUT6NlovQLoO2y7PGm84mW94ZrNJ7ax5H6K4M7Aj/fdMfem5IX7Ka+LXWZpDGg==} - '@types/mute-stream@0.0.4': - resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==} + '@tailwindcss/node@4.0.6': + resolution: {integrity: sha512-jb6E0WeSq7OQbVYcIJ6LxnZTeC4HjMvbzFBMCrQff4R50HBlo/obmYNk6V2GCUXDeqiXtvtrQgcIbT+/boB03Q==} - '@types/mysql@2.15.26': - resolution: {integrity: sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==} + '@tailwindcss/oxide-android-arm64@4.0.4': + resolution: {integrity: sha512-hiGUA8d15ynH/LdurQNObnuTjri7i4ApAzhesusNxoz4br7vhZ6QO5CFgniYAYNZvf8Q8wCTBg0nj61RalBeVQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] - '@types/node-forge@1.3.11': - resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==} + '@tailwindcss/oxide-android-arm64@4.0.6': + resolution: {integrity: sha512-xDbym6bDPW3D2XqQqX3PjqW3CKGe1KXH7Fdkc60sX5ZLVUbzPkFeunQaoP+BuYlLc2cC1FoClrIRYnRzof9Sow==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] - '@types/node@12.20.55': - resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + '@tailwindcss/oxide-darwin-arm64@4.0.4': + resolution: {integrity: sha512-vTca+ysNl8BYmYJTni9pLC+L3S4bvrj0ai1eUV3yYXYa5Cpugr5Fni6ylV0gcTZOyETm2RCCJ/0azU6MgqE6HA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] - '@types/node@18.19.75': - resolution: {integrity: sha512-UIksWtThob6ZVSyxcOqCLOUNg/dyO1Qvx4McgeuhrEtHTLFTf7BBhEazaE4K806FGTPtzd/2sE90qn4fVr7cyw==} + '@tailwindcss/oxide-darwin-arm64@4.0.6': + resolution: {integrity: sha512-1f71/ju/tvyGl5c2bDkchZHy8p8EK/tDHCxlpYJ1hGNvsYihZNurxVpZ0DefpN7cNc9RTT8DjrRoV8xXZKKRjg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] - '@types/node@22.13.1': - resolution: {integrity: sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==} + '@tailwindcss/oxide-darwin-x64@4.0.4': + resolution: {integrity: sha512-rxPWb5AQJ/aAM/5UDCjaQaMYIcrZHe/Dr9xZu9+P9nJf3WAweNsGi+e+SW9EYGRiF3hkBtP2dvxVNAkTiEbNQQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] - '@types/normalize-package-data@2.4.4': - resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + '@tailwindcss/oxide-darwin-x64@4.0.6': + resolution: {integrity: sha512-s/hg/ZPgxFIrGMb0kqyeaqZt505P891buUkSezmrDY6lxv2ixIELAlOcUVTkVh245SeaeEiUVUPiUN37cwoL2g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] - '@types/pg-pool@2.0.6': - resolution: {integrity: sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==} + '@tailwindcss/oxide-freebsd-x64@4.0.4': + resolution: {integrity: sha512-UOnRHzlS5V5cxaMgBo6rk1E92tTDUtO/falc9vOpNiRdWhNcofYNN9zvZP63Wuo5FC6/XCyAnJo6OXUm18TwrQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] - '@types/pg@8.6.1': - resolution: {integrity: sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==} + '@tailwindcss/oxide-freebsd-x64@4.0.6': + resolution: {integrity: sha512-Z3Wo8FWZnmio8+xlcbb7JUo/hqRMSmhQw8IGIRoRJ7GmLR0C+25Wq+bEX/135xe/yEle2lFkhu9JBHd4wZYiig==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.4': + resolution: {integrity: sha512-0Ry9Qfnf22rmJwHxsCFmHQIl5RZw+yOUUGHaqNT42REL8r308cU/bi4UqdrjqVRfAlu51gOGxTRf2NRueczuIA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.6': + resolution: {integrity: sha512-SNSwkkim1myAgmnbHs4EjXsPL7rQbVGtjcok5EaIzkHkCAVK9QBQsWeP2Jm2/JJhq4wdx8tZB9Y7psMzHYWCkA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.0.4': + resolution: {integrity: sha512-5a7WD30nVdI7Rl1ohZ0Ojj9t5yRnZkJBizvh3uIW52h9UeNpon8TfoknF6rU/TwD32dQ0Cjo5CcCHtQ2wW9PCA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.0.6': + resolution: {integrity: sha512-tJ+mevtSDMQhKlwCCuhsFEFg058kBiSy4TkoeBG921EfrHKmexOaCyFKYhVXy4JtkaeeOcjJnCLasEeqml4i+Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.0.4': + resolution: {integrity: sha512-m6s5jKSqos07l6NtHFd49Ljcaw4jIWHE7jq6eNPNz9SCzQqRzs4esP1t7jH8UljQ7JffKOl7yZPwK5Nf+irliw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.0.6': + resolution: {integrity: sha512-IoArz1vfuTR4rALXMUXI/GWWfx2EaO4gFNtBNkDNOYhlTD4NVEwE45nbBoojYiTulajI4c2XH8UmVEVJTOJKxA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.0.4': + resolution: {integrity: sha512-K5dBjGHzby9eyUBwy9YHFhKY+5i8fzIBZM1NBWp6L2xpM7OzW9WJDgNcgESkZami9g+EozkQLt3ZmMZHAieXkw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.0.6': + resolution: {integrity: sha512-QtsUfLkEAeWAC3Owx9Kg+7JdzE+k9drPhwTAXbXugYB9RZUnEWWx5x3q/au6TvUYcL+n0RBqDEO2gucZRvRFgQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.0.4': + resolution: {integrity: sha512-J8sskt+fA5ooq+kxy0Tf4E2TRWZD9Y8j3K+pnBwp9zdilLmSd8OHrB3e0/rO78KveZ6BE9ae75cKOWrT6wONmw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.0.6': + resolution: {integrity: sha512-QthvJqIji2KlGNwLcK/PPYo7w1Wsi/8NK0wAtRGbv4eOPdZHkQ9KUk+oCoP20oPO7i2a6X1aBAFQEL7i08nNMA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-win32-arm64-msvc@4.0.4': + resolution: {integrity: sha512-flFaaMc77NQbz0Fq73wBs9EH2lX1Oc2Z/3JuxoewpnGHpAGJ/j05tvBNMyTaGrKcHvf/+dk+mCDxb6+PmzGgnQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-arm64-msvc@4.0.6': + resolution: {integrity: sha512-+oka+dYX8jy9iP00DJ9Y100XsqvbqR5s0yfMZJuPR1H/lDVtDfsZiSix1UFBQ3X1HWxoEEl6iXNJHWd56TocVw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.0.4': + resolution: {integrity: sha512-WzMA0aL/24/JyNrv2Yhr/Og24QGRPWJMjRyCJ4HRoGMs6/8svOQKrnnZ/9LUFwn56irAndFBjWWnlaqykH+g5Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.0.6': + resolution: {integrity: sha512-+o+juAkik4p8Ue/0LiflQXPmVatl6Av3LEZXpBTfg4qkMIbZdhCGWFzHdt2NjoMiLOJCFDddoV6GYaimvK1Olw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.0.4': + resolution: {integrity: sha512-vPpu30KFLiGyPOoElkYt8WRvzGKVrrOz49KpfiGGtnQGmyUpL8VCbJzzEEcpKT5BpaaQidhFok+OXscf6hHjOQ==} + engines: {node: '>= 10'} + + '@tailwindcss/oxide@4.0.6': + resolution: {integrity: sha512-lVyKV2y58UE9CeKVcYykULe9QaE1dtKdxDEdrTPIdbzRgBk6bdxHNAoDqvcqXbIGXubn3VOl1O/CFF77v/EqSA==} + engines: {node: '>= 10'} + + '@tailwindcss/postcss@4.0.4': + resolution: {integrity: sha512-Up8fB+DUhy8qvDqlHgZAWaL5iVEbypcuOjzlW4K6EyU+aGEvXK0/wrcKBKOTvg3KKP5givJMexJ0aG1hDPOuRg==} + + '@tailwindcss/postcss@4.0.6': + resolution: {integrity: sha512-noTaGPHjGCXTCc487TWnfAEN0VMjqDAecssWDOsfxV2hFrcZR0AHthX7IdY/0xHTg/EtpmIPdssddlZ5/B7JnQ==} + + '@tootallnate/quickjs-emscripten@0.23.0': + resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} + + '@types/connect@3.4.36': + resolution: {integrity: sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==} + + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + + '@types/mute-stream@0.0.4': + resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==} + + '@types/mysql@2.15.26': + resolution: {integrity: sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==} + + '@types/node-forge@1.3.11': + resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==} + + '@types/node@12.20.55': + resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + + '@types/node@18.19.75': + resolution: {integrity: sha512-UIksWtThob6ZVSyxcOqCLOUNg/dyO1Qvx4McgeuhrEtHTLFTf7BBhEazaE4K806FGTPtzd/2sE90qn4fVr7cyw==} + + '@types/node@22.13.1': + resolution: {integrity: sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==} + + '@types/normalize-package-data@2.4.4': + resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + + '@types/pg-pool@2.0.6': + resolution: {integrity: sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==} + + '@types/pg@8.6.1': + resolution: {integrity: sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==} + + '@types/ps-tree@1.1.6': + resolution: {integrity: sha512-PtrlVaOaI44/3pl3cvnlK+GxOM3re2526TJvPvh7W+keHIXdV4TE0ylpPBAcvFQCbGitaTXwL9u+RF7qtVeazQ==} + + '@types/react@19.0.8': + resolution: {integrity: sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw==} '@types/semver@7.5.8': resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} @@ -2211,6 +2754,12 @@ packages: resolution: {integrity: sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==} engines: {node: '>=18'} + amp-message@0.1.2: + resolution: {integrity: sha512-JqutcFwoU1+jhv7ArgW38bqrE+LQdcRv4NxNw0mp0JHQyB6tXesWRjtYKlDgHRY2o3JE5UTaBGUK8kSWUdxWUg==} + + amp@0.3.1: + resolution: {integrity: sha512-OwIuC4yZaRogHKiuU5WlMR5Xk/jAcpPtawWL05Gj8Lvm2F6mwoJt4O/bHI+DHwG79vWd+8OFYM4/BzYqyRd3qw==} + ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -2279,6 +2828,13 @@ packages: assertion-error@1.1.0: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + ast-types@0.13.4: + resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} + engines: {node: '>=4'} + + async@2.6.4: + resolution: {integrity: sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==} + async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} @@ -2286,6 +2842,13 @@ packages: resolution: {integrity: sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ==} engines: {node: '>=8'} + autoprefixer@10.4.20: + resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + babel-plugin-syntax-trailing-function-commas@7.0.0-beta.0: resolution: {integrity: sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ==} @@ -2297,6 +2860,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + basic-ftp@5.0.5: + resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} + engines: {node: '>=10.0.0'} + before-after-hook@3.0.2: resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==} @@ -2314,6 +2881,14 @@ packages: blake3-wasm@2.1.5: resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} + blessed@0.1.81: + resolution: {integrity: sha512-LoF5gae+hlmfORcG1M5+5XZi4LBmvlXTzwJWzUlPryN/SJdSflZvROM2TwkT0GMpq7oqT48NRd4GS7BiVBc5OQ==} + engines: {node: '>= 0.8.0'} + hasBin: true + + bodec@0.1.0: + resolution: {integrity: sha512-Ylo+MAo5BDUq1KA3f3R/MFhh+g8cnHmo8bz3YPGhI1znrMaf77ol1sfvYJzsw3nTE+Y2GryfDxBaR+AqpAkEHQ==} + bottleneck@2.19.5: resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} @@ -2376,6 +2951,10 @@ packages: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} + chalk@3.0.0: + resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} + engines: {node: '>=8'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -2397,6 +2976,9 @@ packages: chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + charm@0.1.2: + resolution: {integrity: sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==} + check-error@1.0.3: resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} @@ -2404,6 +2986,10 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + ci-info@3.9.0: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} @@ -2411,6 +2997,9 @@ packages: cjs-module-lexer@1.4.3: resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} @@ -2428,6 +3017,10 @@ packages: resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} engines: {node: 10.* || >= 12.*} + cli-tableau@2.0.1: + resolution: {integrity: sha512-he+WTicka9cl0Fg/y+YyxcN6/bfQ/1O3QmgxRXDhABKqLzvoOSM4fMzp39uMyLBulAFuywD2N7UaoQE7WaADxQ==} + engines: {node: '>=8.10.0'} + cli-width@4.1.0: resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} engines: {node: '>= 12'} @@ -2439,6 +3032,10 @@ packages: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} @@ -2458,6 +3055,10 @@ packages: color@3.2.1: resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==} + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + colorspace@1.1.4: resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==} @@ -2465,6 +3066,13 @@ packages: resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} engines: {node: '>=18'} + commander@13.1.0: + resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} + engines: {node: '>=18'} + + commander@2.15.1: + resolution: {integrity: sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==} + common-tags@1.8.2: resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} engines: {node: '>=4.0.0'} @@ -2517,6 +3125,10 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} + cookie@1.0.2: + resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + engines: {node: '>=18'} + core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -2529,6 +3141,9 @@ packages: typescript: optional: true + croner@4.1.97: + resolution: {integrity: sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ==} + cross-fetch@3.2.0: resolution: {integrity: sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==} @@ -2551,9 +3166,19 @@ packages: resolution: {integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==} engines: {node: '>=12'} + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + culvert@0.1.2: + resolution: {integrity: sha512-yi1x3EAWKjQTreYWeSd98431AV+IEE0qoDyOoaHJ7KJ21gv6HtBXHVLX74opVSGqcR8/AbjJBHAHpcOy2bj5Gg==} + data-uri-to-buffer@2.0.2: resolution: {integrity: sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==} + data-uri-to-buffer@6.0.2: + resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} + engines: {node: '>= 14'} + dataloader@1.4.0: resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==} @@ -2563,9 +3188,32 @@ packages: date-fns@3.6.0: resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==} + dayjs@1.11.13: + resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + + dayjs@1.8.36: + resolution: {integrity: sha512-3VmRXEtw7RZKAf+4Tv1Ym9AGeo8r8+CjDi26x+7SYQil1UqtqdaokhzoEJohqlzt0m5kacJSDhJQkG/LWhpRBw==} + debounce-microtasks@0.1.8: resolution: {integrity: sha512-7mbJYntoO1FFIuOgwX2vnVwZMfbRpN6s16zTUHm/a1xmpZTGxSKvT6nOpAs1kHWQn7Ly2DGlCKeKdQrKMLBslw==} + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.4.0: resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} @@ -2586,6 +3234,10 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + degenerator@5.0.1: + resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} + engines: {node: '>= 14'} + del@6.1.1: resolution: {integrity: sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==} engines: {node: '>=10'} @@ -2598,6 +3250,15 @@ packages: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} + detect-libc@1.0.3: + resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} + engines: {node: '>=0.10'} + hasBin: true + + detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + engines: {node: '>=8'} + devalue@4.3.3: resolution: {integrity: sha512-UH8EL6H2ifcY8TbD2QsxwCC/pr5xSwPvv85LrLXVihmHVC3T3YqTCIwnR5ak0yO1KYqlxrPVOA/JVZJYPy2ATg==} @@ -2745,6 +3406,14 @@ packages: enabled@2.0.0: resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} + enhanced-resolve@5.18.1: + resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} + engines: {node: '>=10.13.0'} + + enquirer@2.3.6: + resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==} + engines: {node: '>=8.6'} + enquirer@2.4.1: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} @@ -2815,17 +3484,39 @@ packages: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} + escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true + esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} hasBin: true + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + estree-walker@0.6.1: resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==} estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + eventemitter2@0.4.14: + resolution: {integrity: sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ==} + + eventemitter2@5.0.1: + resolution: {integrity: sha512-5EM1GHXycJBS6mauYAbVKT1cVs7POKWb2NXD4Vyt8dDqeZa7LaDK1/sjtL+Zb0lzTpSNil4596Dyu97hz37QLg==} + + eventemitter2@6.4.9: + resolution: {integrity: sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==} + execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -2849,6 +3540,9 @@ packages: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} + extrareqp2@1.0.0: + resolution: {integrity: sha512-Gum0g1QYb6wpPJCVypWP3bbIuaibcFiJcpuPM10YSXp/tzqi84x9PJageob+eN4xVRIOto4wjSGNLyMD54D2xA==} + fast-content-type-parse@2.0.1: resolution: {integrity: sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==} @@ -2856,6 +3550,9 @@ packages: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} + fast-json-patch@3.1.1: + resolution: {integrity: sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==} + fastq@1.19.0: resolution: {integrity: sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==} @@ -2868,6 +3565,9 @@ packages: fbjs@3.0.5: resolution: {integrity: sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==} + fclone@1.0.11: + resolution: {integrity: sha512-GDqVQezKzRABdeqflsgMr7ktzgF9CyS+p2oe0jJqUY6izSSbhPIQJDpoU4PtGcD7VPM9xh/dVrTu6z1nwgmEGw==} + fecha@4.2.3: resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} @@ -2913,6 +3613,15 @@ packages: fn.name@1.1.0: resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + foreground-child@3.3.0: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} @@ -2920,6 +3629,9 @@ packages: forwarded-parse@2.1.2: resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==} + fraction.js@4.3.7: + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + frail-map@1.0.10: resolution: {integrity: sha512-aawqlQUlg9ye61T879jXUoii8lNNHVZJyRL6XBbcNZ4Yu2MZfcQj1Q6yKpC7cAn5xUPMcVSsQ+Yql/AFcrfp9w==} @@ -2990,9 +3702,24 @@ packages: get-tsconfig@4.10.0: resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==} + get-uri@6.0.4: + resolution: {integrity: sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==} + engines: {node: '>= 14'} + git-log-parser@1.2.1: resolution: {integrity: sha512-PI+sPDvHXNPl5WNOErAK05s3j0lgwUzMN6o8cyQrDaKfT3qd7TmNJKeXX+SknI5I0QhG5fVPAEwSY4tRGDtYoQ==} + git-node-fs@1.0.0: + resolution: {integrity: sha512-bLQypt14llVXBg0S0u8q8HmU7g9p3ysH+NvVlae5vILuUvs759665HvmR5+wb04KjHyjFcDRxdYb4kyNnluMUQ==} + peerDependencies: + js-git: ^0.7.8 + peerDependenciesMeta: + js-git: + optional: true + + git-sha1@0.1.2: + resolution: {integrity: sha512-2e/nZezdVlyCopOCYHeW0onkbZg7xP1Ad6pndPy1rCygeRykefUS6r7oA5cJRGEFvseiaz5a/qUHFVX1dd6Isg==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -3013,6 +3740,9 @@ packages: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} + globalyzer@0.1.0: + resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==} + globby@11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} @@ -3021,8 +3751,26 @@ packages: resolution: {integrity: sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==} engines: {node: '>=18'} - gqty@3.3.0: - resolution: {integrity: sha512-5ky4L771GcW2LDPR/k/5lYxn8f440K3oiB4Gglf29mqTgVNjD7V+K+4eiaJRGvduR7gSiaBD6hkFMGoLaYshnw==} + globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + + gqty@3.4.0-canary-20250207102900.644ad9fdeafa6318516627577b1d4d754d5c5a98: + resolution: {integrity: sha512-s/50A1kLL4625nmQ6R8Esl6w6/1Oao0HSJu43wugY6XvFK9G6ag7HUrR5IEoTV7/Ow/Vi0lj3bEprcZT6n0QtA==} + engines: {node: ^12.20.0 || >=14.13.0} + peerDependencies: + graphql: ^16.9.0 + graphql-sse: ^2.5.4 + graphql-ws: ^5.16.2 + peerDependenciesMeta: + graphql: + optional: true + graphql-sse: + optional: true + graphql-ws: + optional: true + + gqty@3.4.1: + resolution: {integrity: sha512-rd/FdqykHaGBfbFnmXQwN02QFz7p/MygGh/QBdTeLm2fP4JzoEvpIQyL4zt7j8b1ZGWv2OMbNotI4udWBsKFvQ==} engines: {node: ^12.20.0 || >=14.13.0} peerDependencies: graphql: ^16.9.0 @@ -3194,6 +3942,10 @@ packages: invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + ip-address@9.0.5: + resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} + engines: {node: '>= 12'} + is-absolute@1.0.0: resolution: {integrity: sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==} engines: {node: '>=0.10.0'} @@ -3300,8 +4052,15 @@ packages: resolution: {integrity: sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==} engines: {node: '>= 0.6.0'} - jose@4.15.9: - resolution: {integrity: sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==} + jiti@2.4.2: + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} + hasBin: true + + jose@5.9.6: + resolution: {integrity: sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==} + + js-git@0.7.8: + resolution: {integrity: sha512-+E5ZH/HeRnoc/LW0AmAyhU+mNcWBzAKE+30+IDMLSLbbK+Tdt02AdkOKq9u15rlJsDEGFqtgckc8ZM59LhhiUA==} js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -3317,6 +4076,9 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + jsbn@1.1.0: + resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -3328,6 +4090,9 @@ packages: json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -3367,6 +4132,78 @@ packages: kuler@2.0.0: resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + lazy@1.0.11: + resolution: {integrity: sha512-Y+CjUfLmIpoUCCRl0ub4smrYtGGr5AOa2AKOaWelGHOGz33X/Y/KizefGqbkwfz44+cnq/+9habclf8vOmu2LA==} + engines: {node: '>=0.2.0'} + + lightningcss-darwin-arm64@1.29.1: + resolution: {integrity: sha512-HtR5XJ5A0lvCqYAoSv2QdZZyoHNttBpa5EP9aNuzBQeKGfbyH5+UipLWvVzpP4Uml5ej4BYs5I9Lco9u1fECqw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.29.1: + resolution: {integrity: sha512-k33G9IzKUpHy/J/3+9MCO4e+PzaFblsgBjSGlpAaFikeBFm8B/CkO3cKU9oI4g+fjS2KlkLM/Bza9K/aw8wsNA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.29.1: + resolution: {integrity: sha512-0SUW22fv/8kln2LnIdOCmSuXnxgxVC276W5KLTwoehiO0hxkacBxjHOL5EtHD8BAXg2BvuhsJPmVMasvby3LiQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.29.1: + resolution: {integrity: sha512-sD32pFvlR0kDlqsOZmYqH/68SqUMPNj+0pucGxToXZi4XZgZmqeX/NkxNKCPsswAXU3UeYgDSpGhu05eAufjDg==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.29.1: + resolution: {integrity: sha512-0+vClRIZ6mmJl/dxGuRsE197o1HDEeeRk6nzycSy2GofC2JsY4ifCRnvUWf/CUBQmlrvMzt6SMQNMSEu22csWQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.29.1: + resolution: {integrity: sha512-UKMFrG4rL/uHNgelBsDwJcBqVpzNJbzsKkbI3Ja5fg00sgQnHw/VrzUTEc4jhZ+AN2BvQYz/tkHu4vt1kLuJyw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.29.1: + resolution: {integrity: sha512-u1S+xdODy/eEtjADqirA774y3jLcm8RPtYztwReEXoZKdzgsHYPl0s5V52Tst+GKzqjebkULT86XMSxejzfISw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.29.1: + resolution: {integrity: sha512-L0Tx0DtaNUTzXv0lbGCLB/c/qEADanHbu4QdcNOXLIe1i8i22rZRpbT3gpWYsCh9aSL9zFujY/WmEXIatWvXbw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.29.1: + resolution: {integrity: sha512-QoOVnkIEFfbW4xPi+dpdft/zAKmgLgsRHfJalEPYuJDOWf7cLQzYg0DEh8/sn737FaeMJxHZRc1oBreiwZCjog==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.29.1: + resolution: {integrity: sha512-NygcbThNBe4JElP+olyTI/doBNGJvLs3bFCRPdvuCcxZCcCZ71B858IHpdm7L1btZex0FvCmM17FK98Y9MRy1Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.29.1: + resolution: {integrity: sha512-FmGoeD4S05ewj+AkhTY+D+myDvXI6eL27FjHIjoyUkO/uw7WZD1fBVs0QxeYWa7E17CUHJaYX/RUGISCtcrG4Q==} + engines: {node: '>= 12.0.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -3460,6 +4297,15 @@ packages: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} + lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + + lucide-react@0.474.0: + resolution: {integrity: sha512-CmghgHkh0OJNmxGKWc0qfPJCYHASPMVSyGY8fj3xgk4v84ItqDg64JNKFZn5hC6E0vHi6gxnbCgwhyVB09wQtA==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + magic-string@0.25.9: resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} @@ -3538,6 +4384,11 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + mlly@1.7.4: resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==} @@ -3558,10 +4409,17 @@ packages: resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} hasBin: true + mute-stream@0.0.8: + resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + mute-stream@1.0.0: resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} + mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} @@ -3570,12 +4428,21 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + needle@2.4.0: + resolution: {integrity: sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==} + engines: {node: '>= 4.4.x'} + hasBin: true + neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} nerf-dart@1.0.0: resolution: {integrity: sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==} + netmask@2.0.2: + resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} + engines: {node: '>= 0.4.0'} + no-case@3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} @@ -3613,6 +4480,10 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} + normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + normalize-url@8.0.1: resolution: {integrity: sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==} engines: {node: '>=14.16'} @@ -3703,28 +4574,23 @@ packages: - which - write-file-atomic + nssocket@0.6.0: + resolution: {integrity: sha512-a9GSOIql5IqgWJR3F/JXG4KpJTA3Z53Cj0MeMvGpglytB1nxE4PdFNC0jINe27CS7cGivoynwc054EzCcT3M3w==} + engines: {node: '>= 0.10.x'} + nullthrows@1.1.1: resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} + oauth4webapi@3.1.4: + resolution: {integrity: sha512-eVfN3nZNbok2s/ROifO0UAc5G8nRoLSbrcKJ09OqmucgnhXEfdIQOR4gq1eJH1rN3gV7rNw62bDEgftsgFtBEg==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - object-hash@2.2.0: - resolution: {integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==} - engines: {node: '>= 6'} - - object-hash@3.0.0: - resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} - engines: {node: '>= 6'} - ohash@1.1.4: resolution: {integrity: sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==} - oidc-token-hash@5.0.3: - resolution: {integrity: sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==} - engines: {node: ^10.13.0 || >=12.0.0} - once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -3739,8 +4605,8 @@ packages: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} - openid-client@5.7.1: - resolution: {integrity: sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew==} + openid-client@6.1.7: + resolution: {integrity: sha512-JfY/KvQgOutmG2P+oVNKInE7zIh+im1MQOaO7g5CtNnTWMociA563WweiEMKfR9ry9XG3K2HGvj9wEqhCQkPMg==} os-tmpdir@1.0.2: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} @@ -3749,7 +4615,11 @@ packages: outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} - p-defer@3.0.0: + p-debounce@4.0.0: + resolution: {integrity: sha512-4Ispi9I9qYGO4lueiLDhe4q4iK5ERK8reLsuzH6BPaXn53EGaua8H66PXIFGrW897hwjXp+pVLrm/DLxN0RF0A==} + engines: {node: '>=12'} + + p-defer@3.0.0: resolution: {integrity: sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw==} engines: {node: '>=8'} @@ -3825,12 +4695,23 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} + pac-proxy-agent@7.1.0: + resolution: {integrity: sha512-Z5FnLVVZSnX7WjBg0mhDtydeRZ1xMcATZThjySQUHqr+0ksP8kqaw23fNKkaaN/Z8gwLUs/W7xdl0I75eP2Xyw==} + engines: {node: '>= 14'} + + pac-resolver@7.0.1: + resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} + engines: {node: '>= 14'} + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} package-manager-detector@0.2.9: resolution: {integrity: sha512-+vYvA/Y31l8Zk8dwxHhL3JfTuHPm6tlxM2A3GeQyl7ovYnSp1+mzAxClxaOr0qO1TtPxbQxetI7v5XqKLJZk7Q==} + pako@0.2.9: + resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} + param-case@3.0.4: resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} @@ -3946,6 +4827,14 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + pidusage@2.0.21: + resolution: {integrity: sha512-cv3xAQos+pugVX+BfXpHsbyz/dLzX+lr44zNMsYiGxUw+kV5sgQCIcLd1z+0vq+KyC7dJ+/ts2PsfgWfSC3WXA==} + engines: {node: '>=8'} + + pidusage@3.0.2: + resolution: {integrity: sha512-g0VU+y08pKw5M8EZ2rIGiEBaB8wrQMjYGFfW2QVIfyT8V+fq8YFLkvlz4bz5ljvFDJYNFCWT3PWqcRr2FKO81w==} + engines: {node: '>=10'} + pify@3.0.0: resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} engines: {node: '>=4'} @@ -3965,6 +4854,50 @@ packages: resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} engines: {node: '>=8'} + pm2-axon-rpc@0.7.1: + resolution: {integrity: sha512-FbLvW60w+vEyvMjP/xom2UPhUN/2bVpdtLfKJeYM3gwzYhoTEEChCOICfFzxkxuoEleOlnpjie+n1nue91bDQw==} + engines: {node: '>=5'} + + pm2-axon@4.0.1: + resolution: {integrity: sha512-kES/PeSLS8orT8dR5jMlNl+Yu4Ty3nbvZRmaAtROuVm9nYYGiaoXqqKQqQYzWQzMYWUKHMQTvBlirjE5GIIxqg==} + engines: {node: '>=5'} + + pm2-deploy@1.0.2: + resolution: {integrity: sha512-YJx6RXKrVrWaphEYf++EdOOx9EH18vM8RSZN/P1Y+NokTKqYAca/ejXwVLyiEpNju4HPZEk3Y2uZouwMqUlcgg==} + engines: {node: '>=4.0.0'} + + pm2-multimeter@0.1.2: + resolution: {integrity: sha512-S+wT6XfyKfd7SJIBqRgOctGxaBzUOmVQzTAS+cg04TsEUObJVreha7lvCfX8zzGVr871XwCSnHUU7DQQ5xEsfA==} + + pm2-sysmonit@1.2.8: + resolution: {integrity: sha512-ACOhlONEXdCTVwKieBIQLSi2tQZ8eKinhcr9JpZSUAL8Qy0ajIgRtsLxG/lwPOW3JEKqPyw/UaHmTWhUzpP4kA==} + + pm2@5.4.3: + resolution: {integrity: sha512-4/I1htIHzZk1Y67UgOCo4F1cJtas1kSds31N8zN0PybO230id1nigyjGuGFzUnGmUFPmrJ0On22fO1ChFlp7VQ==} + engines: {node: '>=12.0.0'} + hasBin: true + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + postcss@8.5.1: resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==} engines: {node: ^10 || ^12 || >=14} @@ -4007,9 +4940,19 @@ packages: promise@7.3.1: resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==} + promptly@2.2.0: + resolution: {integrity: sha512-aC9j+BZsRSSzEsXBNBwDnAxujdx19HycZoKgRgzWnS8eOHg1asuf9heuLprfbe739zY3IdUQx+Egv6Jn135WHA==} + proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + proxy-agent@6.3.1: + resolution: {integrity: sha512-Rb5RVBy1iyqOtNl15Cw/llpeLH8bsb37gM1FUfKQ+Wck6xHlbAhWGUFiTRHtkjqGTA5pSHz6+0hrPW/oECihPQ==} + engines: {node: '>= 14'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -4020,9 +4963,33 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true + react-dom@19.0.0: + resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==} + peerDependencies: + react: ^19.0.0 + react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-router@7.1.5: + resolution: {integrity: sha512-8BUF+hZEU4/z/JD201yK6S+UYhsf58bzYIDq2NS1iGpwxSXDu7F+DeGSkIXMFBuHZB21FSiCzEcUb18cQNdRkA==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + + react-ssr-prepass@1.6.0: + resolution: {integrity: sha512-M10nxc95Sfm00fXm+tLkC1MWG5NLWEBgWoGrPSnAqEFM4BUaoy97JvVw+m3iL74ZHzj86M33rPiFi738hEFLWg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + + react@19.0.0: + resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} + engines: {node: '>=0.10.0'} + read-package-up@11.0.0: resolution: {integrity: sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==} engines: {node: '>=18'} @@ -4039,6 +5006,10 @@ packages: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} engines: {node: '>=6'} + read@1.0.7: + resolution: {integrity: sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==} + engines: {node: '>=0.8'} + readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} @@ -4050,6 +5021,10 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + readdirp@4.1.1: + resolution: {integrity: sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==} + engines: {node: '>= 14.18.0'} + regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} @@ -4064,6 +5039,10 @@ packages: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} + require-in-the-middle@5.2.0: + resolution: {integrity: sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg==} + engines: {node: '>=6'} + require-in-the-middle@7.5.0: resolution: {integrity: sha512-/Tvpny/RVVicqlYTKwt/GtpZRsPG1CmJNhxVKGz+Sy/4MONfXCVNK69MFgGKdUt0/324q3ClI2dICcPgISrC8g==} engines: {node: '>=8.6.0'} @@ -4120,9 +5099,15 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + run-series@1.1.9: + resolution: {integrity: sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g==} + safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-stable-stringify@2.5.0: resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} engines: {node: '>=10'} @@ -4130,6 +5115,12 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + sax@1.4.1: + resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} + + scheduler@0.25.0: + resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==} + selfsigned@2.4.1: resolution: {integrity: sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==} engines: {node: '>=10'} @@ -4165,6 +5156,11 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true + semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + engines: {node: '>=10'} + hasBin: true + semver@7.7.1: resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} engines: {node: '>=10'} @@ -4173,9 +5169,16 @@ packages: sentence-case@3.0.4: resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==} + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + setimmediate@1.0.5: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + sharp@0.33.5: + resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -4219,9 +5222,21 @@ packages: resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} engines: {node: '>=14.16'} + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + snake-case@3.0.4: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} + socks-proxy-agent@8.0.5: + resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} + engines: {node: '>= 14'} + + socks@2.8.4: + resolution: {integrity: sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -4264,6 +5279,12 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + sprintf-js@1.1.2: + resolution: {integrity: sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==} + + sprintf-js@1.1.3: + resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + stack-trace@0.0.10: resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} @@ -4356,6 +5377,30 @@ packages: swap-case@2.0.2: resolution: {integrity: sha512-kc6S2YS/2yXbtkSMunBtKdah4VFETZ8Oh6ONSmSd9bRxhqTrtARUCBUiWXH3xVPpvR7tz2CSnkuXVE42EcGnMw==} + systeminformation@5.25.11: + resolution: {integrity: sha512-jI01fn/t47rrLTQB0FTlMCC+5dYx8o0RRF+R4BPiUNsvg5OdY0s9DKMFmJGrx5SwMZQ4cag0Gl6v8oycso9b/g==} + engines: {node: '>=8.0.0'} + os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android] + hasBin: true + + tailwind-merge@3.0.1: + resolution: {integrity: sha512-AvzE8FmSoXC7nC+oU5GlQJbip2UO7tmOhOfQyOmPhrStOGXHU08j8mZEHZ4BmCqY5dWTCo4ClWkNyRNx1wpT0g==} + + tailwindcss-animate@1.0.7: + resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + + tailwindcss@4.0.4: + resolution: {integrity: sha512-/ezDLEkOLf1lXkr9F2iI5BHJbexJpty5zkV2B8bGHCqAdbc9vk85Jgdkq+ZOvNkNPa3yAaqJ8DjRt584Bc84kw==} + + tailwindcss@4.0.6: + resolution: {integrity: sha512-mysewHYJKaXgNOW6pp5xon/emCsfAMnO8WMaGKZZ35fomnR/T5gYnRg2/yRTTrtXiEl1tiVkeRt0eMO6HxEZqw==} + + tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} + temp-dir@2.0.0: resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} engines: {node: '>=8'} @@ -4393,6 +5438,9 @@ packages: resolution: {integrity: sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==} engines: {node: '>=12'} + tiny-glob@0.2.9: + resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -4425,21 +5473,29 @@ packages: resolution: {integrity: sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==} engines: {node: '>= 0.4'} - treekill@1.0.0: - resolution: {integrity: sha512-yRk5h+uZ6oFKQWf88pzuOSujKvpU8wqo9nuxCMUvWU55sC6A9J0EzjmEwpTjHydhBhBDDZKgA6992aIEWTxDkw==} - engines: {node: '>= 0.10.0'} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - triple-beam@1.4.1: resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} engines: {node: '>= 14.0.0'} + tslib@1.9.3: + resolution: {integrity: sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==} + tslib@2.6.3: resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + turbo-stream@2.4.0: + resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==} + + tv4@1.3.0: + resolution: {integrity: sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw==} + engines: {node: '>= 0.8.0'} + + tx2@1.0.5: + resolution: {integrity: sha512-sJ24w0y03Md/bxzK4FU8J8JveYYUbSs2FViLJ2D/8bytSiyPRbuE3DyL/9UKYXTZlV3yXq0L8GLlhobTnekCVg==} + type-detect@4.1.0: resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} engines: {node: '>=4'} @@ -4555,6 +5611,11 @@ packages: urlpattern-polyfill@10.0.0: resolution: {integrity: sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==} + use-sync-external-store@1.4.0: + resolution: {integrity: sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -4626,6 +5687,10 @@ packages: jsdom: optional: true + vizion@2.2.1: + resolution: {integrity: sha512-sfAcO2yeSU0CSPFI/DmZp3FsFE9T+8913nv1xWBOyzODv13fwkn6Vl7HqxGpkr9F608M+8SuFId3s+BlZqfXww==} + engines: {node: '>=4.0'} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -4698,6 +5763,18 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@7.5.10: + resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + ws@8.18.0: resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} engines: {node: '>=10.0.0'} @@ -4767,6 +5844,8 @@ packages: snapshots: + '@alloc/quick-lru@5.2.0': {} + '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.8 @@ -5269,7 +6348,7 @@ snapshots: dependencies: mime: 3.0.0 - '@cloudflare/vitest-pool-workers@0.4.31(@cloudflare/workers-types@4.20250129.0)(@vitest/runner@1.5.3)(@vitest/snapshot@1.5.3)(vitest@1.5.3(@types/node@22.13.1))': + '@cloudflare/vitest-pool-workers@0.4.31(@cloudflare/workers-types@4.20250129.0)(@vitest/runner@1.5.3)(@vitest/snapshot@1.5.3)(vitest@1.5.3(@types/node@22.13.1)(lightningcss@1.29.1))': dependencies: '@vitest/runner': 1.5.3 '@vitest/snapshot': 1.5.3 @@ -5279,7 +6358,7 @@ snapshots: esbuild: 0.17.19 miniflare: 3.20240909.0 semver: 7.7.1 - vitest: 1.5.3(@types/node@22.13.1) + vitest: 1.5.3(@types/node@22.13.1)(lightningcss@1.29.1) wrangler: 3.77.0(@cloudflare/workers-types@4.20250129.0) zod: 3.24.1 transitivePeerDependencies: @@ -5330,9 +6409,9 @@ snapshots: '@colors/colors@1.6.0': {} - '@commander-js/extra-typings@12.1.0(commander@12.1.0)': + '@commander-js/extra-typings@13.1.0(commander@13.1.0)': dependencies: - commander: 12.1.0 + commander: 13.1.0 '@cspotcode/source-map-support@0.8.1': dependencies: @@ -5346,6 +6425,11 @@ snapshots: '@drizzle-team/brocli@0.10.2': {} + '@emnapi/runtime@1.3.1': + dependencies: + tslib: 2.8.1 + optional: true + '@envelop/core@5.0.3': dependencies: '@envelop/types': 5.0.0 @@ -5719,44 +6803,59 @@ snapshots: '@fastify/busboy@2.1.1': {} - '@gqty/cli@4.2.2(@babel/core@7.26.7)(typescript@5.7.3)': + '@gqty/cli@4.2.5(@babel/core@7.26.7)(@types/node@22.13.1)(typescript@5.7.3)': dependencies: - '@commander-js/extra-typings': 12.1.0(commander@12.1.0) + '@commander-js/extra-typings': 13.1.0(commander@13.1.0) '@graphql-codegen/core': 4.0.2(graphql@16.10.0) '@graphql-codegen/typescript': 4.1.3(@babel/core@7.26.7)(graphql@16.10.0) - '@graphql-tools/delegate': 10.2.11(graphql@16.10.0) - '@graphql-tools/utils': 10.7.2(graphql@16.10.0) + '@graphql-tools/utils': 10.8.1(graphql@16.10.0) '@graphql-tools/wrap': 10.0.29(graphql@16.10.0) - '@inquirer/prompts': 5.5.0 - chalk: 4.1.2 - commander: 12.1.0 + '@inquirer/prompts': 7.3.1(@types/node@22.13.1) + chalk: 5.4.1 + commander: 13.1.0 cosmiconfig: 9.0.0(typescript@5.7.3) cross-fetch: 4.1.0 fast-glob: 3.3.3 - gqty: 3.3.0(graphql@16.10.0) + gqty: 3.4.1(graphql@16.10.0) graphql: 16.10.0 lodash-es: 4.17.21 micromatch: 4.0.8 prettier: 2.8.8 transitivePeerDependencies: - '@babel/core' + - '@types/node' - encoding - graphql-sse - graphql-ws - supports-color - typescript + '@gqty/react@3.1.0(gqty@3.4.0-canary-20250207102900.644ad9fdeafa6318516627577b1d4d754d5c5a98(graphql@16.10.0))(graphql@16.10.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@react-hookz/web': 23.1.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + gqty: 3.4.0-canary-20250207102900.644ad9fdeafa6318516627577b1d4d754d5c5a98(graphql@16.10.0) + graphql: 16.10.0 + multidict: 1.0.9 + p-debounce: 4.0.0 + p-defer: 3.0.0 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-ssr-prepass: 1.6.0(react@19.0.0) + use-sync-external-store: 1.4.0(react@19.0.0) + transitivePeerDependencies: + - js-cookie + '@graphql-codegen/core@4.0.2(graphql@16.10.0)': dependencies: '@graphql-codegen/plugin-helpers': 5.1.0(graphql@16.10.0) '@graphql-tools/schema': 10.0.16(graphql@16.10.0) - '@graphql-tools/utils': 10.7.2(graphql@16.10.0) + '@graphql-tools/utils': 10.8.1(graphql@16.10.0) graphql: 16.10.0 tslib: 2.6.3 '@graphql-codegen/plugin-helpers@5.1.0(graphql@16.10.0)': dependencies: - '@graphql-tools/utils': 10.7.2(graphql@16.10.0) + '@graphql-tools/utils': 10.8.1(graphql@16.10.0) change-case-all: 1.0.15 common-tags: 1.8.2 graphql: 16.10.0 @@ -5767,7 +6866,7 @@ snapshots: '@graphql-codegen/schema-ast@4.1.0(graphql@16.10.0)': dependencies: '@graphql-codegen/plugin-helpers': 5.1.0(graphql@16.10.0) - '@graphql-tools/utils': 10.7.2(graphql@16.10.0) + '@graphql-tools/utils': 10.8.1(graphql@16.10.0) graphql: 16.10.0 tslib: 2.6.3 @@ -5789,7 +6888,7 @@ snapshots: '@graphql-codegen/plugin-helpers': 5.1.0(graphql@16.10.0) '@graphql-tools/optimize': 2.0.0(graphql@16.10.0) '@graphql-tools/relay-operation-optimizer': 7.0.12(@babel/core@7.26.7)(graphql@16.10.0) - '@graphql-tools/utils': 10.7.2(graphql@16.10.0) + '@graphql-tools/utils': 10.8.1(graphql@16.10.0) auto-bind: 4.0.0 change-case-all: 1.0.15 dependency-graph: 0.11.0 @@ -5804,7 +6903,7 @@ snapshots: '@graphql-tools/batch-execute@9.0.11(graphql@16.10.0)': dependencies: - '@graphql-tools/utils': 10.7.2(graphql@16.10.0) + '@graphql-tools/utils': 10.8.1(graphql@16.10.0) dataloader: 2.2.3 graphql: 16.10.0 tslib: 2.8.1 @@ -5814,7 +6913,7 @@ snapshots: '@graphql-tools/batch-execute': 9.0.11(graphql@16.10.0) '@graphql-tools/executor': 1.3.12(graphql@16.10.0) '@graphql-tools/schema': 10.0.16(graphql@16.10.0) - '@graphql-tools/utils': 10.7.2(graphql@16.10.0) + '@graphql-tools/utils': 10.8.1(graphql@16.10.0) '@repeaterjs/repeater': 3.0.6 dataloader: 2.2.3 dset: 3.1.4 @@ -5845,7 +6944,7 @@ snapshots: '@graphql-tools/relay-operation-optimizer@7.0.12(@babel/core@7.26.7)(graphql@16.10.0)': dependencies: '@ardatan/relay-compiler': 12.0.1(@babel/core@7.26.7)(graphql@16.10.0) - '@graphql-tools/utils': 10.7.2(graphql@16.10.0) + '@graphql-tools/utils': 10.8.1(graphql@16.10.0) graphql: 16.10.0 tslib: 2.8.1 transitivePeerDependencies: @@ -5869,11 +6968,19 @@ snapshots: graphql: 16.10.0 tslib: 2.8.1 + '@graphql-tools/utils@10.8.1(graphql@16.10.0)': + dependencies: + '@graphql-typed-document-node/core': 3.2.0(graphql@16.10.0) + cross-inspect: 1.0.1 + dset: 3.1.4 + graphql: 16.10.0 + tslib: 2.8.1 + '@graphql-tools/wrap@10.0.29(graphql@16.10.0)': dependencies: '@graphql-tools/delegate': 10.2.11(graphql@16.10.0) '@graphql-tools/schema': 10.0.16(graphql@16.10.0) - '@graphql-tools/utils': 10.7.2(graphql@16.10.0) + '@graphql-tools/utils': 10.8.1(graphql@16.10.0) graphql: 16.10.0 tslib: 2.8.1 @@ -5906,6 +7013,81 @@ snapshots: hono: 4.6.20 toucan-js: 4.1.0 + '@img/sharp-darwin-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.0.4 + optional: true + + '@img/sharp-darwin-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.0.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.0.5': + optional: true + + '@img/sharp-libvips-linux-s390x@1.0.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + optional: true + + '@img/sharp-linux-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.0.4 + optional: true + + '@img/sharp-linux-arm@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.0.5 + optional: true + + '@img/sharp-linux-s390x@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.0.4 + optional: true + + '@img/sharp-linux-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + optional: true + + '@img/sharp-wasm32@0.33.5': + dependencies: + '@emnapi/runtime': 1.3.1 + optional: true + + '@img/sharp-win32-ia32@0.33.5': + optional: true + + '@img/sharp-win32-x64@0.33.5': + optional: true + '@inquirer/checkbox@2.5.0': dependencies: '@inquirer/core': 9.2.1 @@ -5914,11 +7096,41 @@ snapshots: ansi-escapes: 4.3.2 yoctocolors-cjs: 2.1.2 + '@inquirer/checkbox@4.1.1(@types/node@22.13.1)': + dependencies: + '@inquirer/core': 10.1.6(@types/node@22.13.1) + '@inquirer/figures': 1.0.10 + '@inquirer/type': 3.0.4(@types/node@22.13.1) + ansi-escapes: 4.3.2 + yoctocolors-cjs: 2.1.2 + optionalDependencies: + '@types/node': 22.13.1 + '@inquirer/confirm@3.2.0': dependencies: '@inquirer/core': 9.2.1 '@inquirer/type': 1.5.5 + '@inquirer/confirm@5.1.5(@types/node@22.13.1)': + dependencies: + '@inquirer/core': 10.1.6(@types/node@22.13.1) + '@inquirer/type': 3.0.4(@types/node@22.13.1) + optionalDependencies: + '@types/node': 22.13.1 + + '@inquirer/core@10.1.6(@types/node@22.13.1)': + dependencies: + '@inquirer/figures': 1.0.10 + '@inquirer/type': 3.0.4(@types/node@22.13.1) + 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 + optionalDependencies: + '@types/node': 22.13.1 + '@inquirer/core@9.2.1': dependencies: '@inquirer/figures': 1.0.10 @@ -5940,12 +7152,28 @@ snapshots: '@inquirer/type': 1.5.5 external-editor: 3.1.0 + '@inquirer/editor@4.2.6(@types/node@22.13.1)': + dependencies: + '@inquirer/core': 10.1.6(@types/node@22.13.1) + '@inquirer/type': 3.0.4(@types/node@22.13.1) + external-editor: 3.1.0 + optionalDependencies: + '@types/node': 22.13.1 + '@inquirer/expand@2.3.0': dependencies: '@inquirer/core': 9.2.1 '@inquirer/type': 1.5.5 yoctocolors-cjs: 2.1.2 + '@inquirer/expand@4.0.8(@types/node@22.13.1)': + dependencies: + '@inquirer/core': 10.1.6(@types/node@22.13.1) + '@inquirer/type': 3.0.4(@types/node@22.13.1) + yoctocolors-cjs: 2.1.2 + optionalDependencies: + '@types/node': 22.13.1 + '@inquirer/figures@1.0.10': {} '@inquirer/input@2.3.0': @@ -5953,17 +7181,39 @@ snapshots: '@inquirer/core': 9.2.1 '@inquirer/type': 1.5.5 + '@inquirer/input@4.1.5(@types/node@22.13.1)': + dependencies: + '@inquirer/core': 10.1.6(@types/node@22.13.1) + '@inquirer/type': 3.0.4(@types/node@22.13.1) + optionalDependencies: + '@types/node': 22.13.1 + '@inquirer/number@1.1.0': dependencies: '@inquirer/core': 9.2.1 '@inquirer/type': 1.5.5 + '@inquirer/number@3.0.8(@types/node@22.13.1)': + dependencies: + '@inquirer/core': 10.1.6(@types/node@22.13.1) + '@inquirer/type': 3.0.4(@types/node@22.13.1) + optionalDependencies: + '@types/node': 22.13.1 + '@inquirer/password@2.2.0': dependencies: '@inquirer/core': 9.2.1 '@inquirer/type': 1.5.5 ansi-escapes: 4.3.2 + '@inquirer/password@4.0.8(@types/node@22.13.1)': + dependencies: + '@inquirer/core': 10.1.6(@types/node@22.13.1) + '@inquirer/type': 3.0.4(@types/node@22.13.1) + ansi-escapes: 4.3.2 + optionalDependencies: + '@types/node': 22.13.1 + '@inquirer/prompts@5.5.0': dependencies: '@inquirer/checkbox': 2.5.0 @@ -5977,12 +7227,35 @@ snapshots: '@inquirer/search': 1.1.0 '@inquirer/select': 2.5.0 + '@inquirer/prompts@7.3.1(@types/node@22.13.1)': + dependencies: + '@inquirer/checkbox': 4.1.1(@types/node@22.13.1) + '@inquirer/confirm': 5.1.5(@types/node@22.13.1) + '@inquirer/editor': 4.2.6(@types/node@22.13.1) + '@inquirer/expand': 4.0.8(@types/node@22.13.1) + '@inquirer/input': 4.1.5(@types/node@22.13.1) + '@inquirer/number': 3.0.8(@types/node@22.13.1) + '@inquirer/password': 4.0.8(@types/node@22.13.1) + '@inquirer/rawlist': 4.0.8(@types/node@22.13.1) + '@inquirer/search': 3.0.8(@types/node@22.13.1) + '@inquirer/select': 4.0.8(@types/node@22.13.1) + optionalDependencies: + '@types/node': 22.13.1 + '@inquirer/rawlist@2.3.0': dependencies: '@inquirer/core': 9.2.1 '@inquirer/type': 1.5.5 yoctocolors-cjs: 2.1.2 + '@inquirer/rawlist@4.0.8(@types/node@22.13.1)': + dependencies: + '@inquirer/core': 10.1.6(@types/node@22.13.1) + '@inquirer/type': 3.0.4(@types/node@22.13.1) + yoctocolors-cjs: 2.1.2 + optionalDependencies: + '@types/node': 22.13.1 + '@inquirer/search@1.1.0': dependencies: '@inquirer/core': 9.2.1 @@ -5990,6 +7263,15 @@ snapshots: '@inquirer/type': 1.5.5 yoctocolors-cjs: 2.1.2 + '@inquirer/search@3.0.8(@types/node@22.13.1)': + dependencies: + '@inquirer/core': 10.1.6(@types/node@22.13.1) + '@inquirer/figures': 1.0.10 + '@inquirer/type': 3.0.4(@types/node@22.13.1) + yoctocolors-cjs: 2.1.2 + optionalDependencies: + '@types/node': 22.13.1 + '@inquirer/select@2.5.0': dependencies: '@inquirer/core': 9.2.1 @@ -5998,6 +7280,16 @@ snapshots: ansi-escapes: 4.3.2 yoctocolors-cjs: 2.1.2 + '@inquirer/select@4.0.8(@types/node@22.13.1)': + dependencies: + '@inquirer/core': 10.1.6(@types/node@22.13.1) + '@inquirer/figures': 1.0.10 + '@inquirer/type': 3.0.4(@types/node@22.13.1) + ansi-escapes: 4.3.2 + yoctocolors-cjs: 2.1.2 + optionalDependencies: + '@types/node': 22.13.1 + '@inquirer/type@1.5.5': dependencies: mute-stream: 1.0.0 @@ -6006,6 +7298,10 @@ snapshots: dependencies: mute-stream: 1.0.0 + '@inquirer/type@3.0.4(@types/node@22.13.1)': + optionalDependencies: + '@types/node': 22.13.1 + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -6403,6 +7699,57 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@pm2/agent@2.0.4': + dependencies: + async: 3.2.6 + chalk: 3.0.0 + dayjs: 1.8.36 + debug: 4.3.7 + eventemitter2: 5.0.1 + fast-json-patch: 3.1.1 + fclone: 1.0.11 + nssocket: 0.6.0 + pm2-axon: 4.0.1 + pm2-axon-rpc: 0.7.1 + proxy-agent: 6.3.1 + semver: 7.5.4 + ws: 7.5.10 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@pm2/io@6.0.1': + dependencies: + async: 2.6.4 + debug: 4.3.7 + eventemitter2: 6.4.9 + require-in-the-middle: 5.2.0 + semver: 7.5.4 + shimmer: 1.2.1 + signal-exit: 3.0.7 + tslib: 1.9.3 + transitivePeerDependencies: + - supports-color + + '@pm2/js-api@0.8.0': + dependencies: + async: 2.6.4 + debug: 4.3.7 + eventemitter2: 6.4.9 + extrareqp2: 1.0.0(debug@4.3.7) + ws: 7.5.10 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@pm2/pm2-version-check@1.0.4': + dependencies: + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + '@pnpm/config.env-replace@1.1.0': {} '@pnpm/network.ca-file@1.0.2': @@ -6423,6 +7770,27 @@ snapshots: transitivePeerDependencies: - supports-color + '@radix-ui/react-compose-refs@1.1.1(@types/react@19.0.8)(react@19.0.0)': + dependencies: + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.8 + + '@radix-ui/react-slot@1.1.2(@types/react@19.0.8)(react@19.0.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.8)(react@19.0.0) + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.8 + + '@react-hookz/deep-equal@1.0.4': {} + + '@react-hookz/web@23.1.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@react-hookz/deep-equal': 1.0.4 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + '@repeaterjs/repeater@3.0.6': {} '@rollup/rollup-android-arm-eabi@4.34.2': @@ -6654,10 +8022,138 @@ snapshots: '@sindresorhus/merge-streams@4.0.0': {} + '@tailwindcss/node@4.0.4': + dependencies: + enhanced-resolve: 5.18.1 + jiti: 2.4.2 + tailwindcss: 4.0.4 + + '@tailwindcss/node@4.0.6': + dependencies: + enhanced-resolve: 5.18.1 + jiti: 2.4.2 + tailwindcss: 4.0.6 + + '@tailwindcss/oxide-android-arm64@4.0.4': + optional: true + + '@tailwindcss/oxide-android-arm64@4.0.6': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.0.4': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.0.6': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.0.4': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.0.6': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.0.4': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.0.6': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.4': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.6': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.0.4': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.0.6': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.0.4': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.0.6': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.0.4': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.0.6': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.0.4': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.0.6': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.0.4': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.0.6': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.0.4': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.0.6': + optional: true + + '@tailwindcss/oxide@4.0.4': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.0.4 + '@tailwindcss/oxide-darwin-arm64': 4.0.4 + '@tailwindcss/oxide-darwin-x64': 4.0.4 + '@tailwindcss/oxide-freebsd-x64': 4.0.4 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.0.4 + '@tailwindcss/oxide-linux-arm64-gnu': 4.0.4 + '@tailwindcss/oxide-linux-arm64-musl': 4.0.4 + '@tailwindcss/oxide-linux-x64-gnu': 4.0.4 + '@tailwindcss/oxide-linux-x64-musl': 4.0.4 + '@tailwindcss/oxide-win32-arm64-msvc': 4.0.4 + '@tailwindcss/oxide-win32-x64-msvc': 4.0.4 + + '@tailwindcss/oxide@4.0.6': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.0.6 + '@tailwindcss/oxide-darwin-arm64': 4.0.6 + '@tailwindcss/oxide-darwin-x64': 4.0.6 + '@tailwindcss/oxide-freebsd-x64': 4.0.6 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.0.6 + '@tailwindcss/oxide-linux-arm64-gnu': 4.0.6 + '@tailwindcss/oxide-linux-arm64-musl': 4.0.6 + '@tailwindcss/oxide-linux-x64-gnu': 4.0.6 + '@tailwindcss/oxide-linux-x64-musl': 4.0.6 + '@tailwindcss/oxide-win32-arm64-msvc': 4.0.6 + '@tailwindcss/oxide-win32-x64-msvc': 4.0.6 + + '@tailwindcss/postcss@4.0.4': + dependencies: + '@alloc/quick-lru': 5.2.0 + '@tailwindcss/node': 4.0.4 + '@tailwindcss/oxide': 4.0.4 + lightningcss: 1.29.1 + postcss: 8.5.1 + tailwindcss: 4.0.4 + + '@tailwindcss/postcss@4.0.6': + dependencies: + '@alloc/quick-lru': 5.2.0 + '@tailwindcss/node': 4.0.6 + '@tailwindcss/oxide': 4.0.6 + lightningcss: 1.29.1 + postcss: 8.5.1 + tailwindcss: 4.0.6 + + '@tootallnate/quickjs-emscripten@0.23.0': {} + '@types/connect@3.4.36': dependencies: '@types/node': 18.19.75 + '@types/cookie@0.6.0': {} + '@types/estree@1.0.6': {} '@types/mute-stream@0.0.4': @@ -6694,6 +8190,12 @@ snapshots: pg-protocol: 1.7.0 pg-types: 2.2.0 + '@types/ps-tree@1.1.6': {} + + '@types/react@19.0.8': + dependencies: + csstype: 3.1.3 + '@types/semver@7.5.8': {} '@types/shimmer@1.2.0': {} @@ -6786,6 +8288,12 @@ snapshots: clean-stack: 5.2.0 indent-string: 5.0.0 + amp-message@0.1.2: + dependencies: + amp: 0.3.1 + + amp@0.3.1: {} + ansi-colors@4.1.3: {} ansi-escapes@4.3.2: @@ -6839,10 +8347,28 @@ snapshots: assertion-error@1.1.0: {} + ast-types@0.13.4: + dependencies: + tslib: 2.8.1 + + async@2.6.4: + dependencies: + lodash: 4.17.21 + async@3.2.6: {} auto-bind@4.0.0: {} + autoprefixer@10.4.20(postcss@8.5.1): + dependencies: + browserslist: 4.24.4 + caniuse-lite: 1.0.30001697 + fraction.js: 4.3.7 + normalize-range: 0.1.2 + picocolors: 1.1.1 + postcss: 8.5.1 + postcss-value-parser: 4.2.0 + babel-plugin-syntax-trailing-function-commas@7.0.0-beta.0: {} babel-preset-fbjs@3.4.0(@babel/core@7.26.7): @@ -6880,6 +8406,8 @@ snapshots: balanced-match@1.0.2: {} + basic-ftp@5.0.5: {} + before-after-hook@3.0.2: {} better-path-resolve@1.0.0: @@ -6892,6 +8420,10 @@ snapshots: blake3-wasm@2.1.5: {} + blessed@0.1.81: {} + + bodec@0.1.0: {} + bottleneck@2.19.5: {} brace-expansion@1.1.11: @@ -6971,6 +8503,11 @@ snapshots: escape-string-regexp: 1.0.5 supports-color: 5.5.0 + chalk@3.0.0: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -7010,6 +8547,8 @@ snapshots: chardet@0.7.0: {} + charm@0.1.2: {} + check-error@1.0.3: dependencies: get-func-name: 2.0.2 @@ -7026,10 +8565,18 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chokidar@4.0.3: + dependencies: + readdirp: 4.1.1 + ci-info@3.9.0: {} cjs-module-lexer@1.4.3: {} + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + clean-stack@2.2.0: {} clean-stack@5.2.0: @@ -7051,6 +8598,10 @@ snapshots: optionalDependencies: '@colors/colors': 1.5.0 + cli-tableau@2.0.1: + dependencies: + chalk: 3.0.0 + cli-width@4.1.0: {} cliui@7.0.4: @@ -7065,6 +8616,8 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + clsx@2.1.1: {} + color-convert@1.9.3: dependencies: color-name: 1.1.3 @@ -7082,9 +8635,14 @@ snapshots: color-name: 1.1.4 simple-swizzle: 0.2.2 - color@3.2.1: + color@3.2.1: + dependencies: + color-convert: 1.9.3 + color-string: 1.9.1 + + color@4.2.3: dependencies: - color-convert: 1.9.3 + color-convert: 2.0.1 color-string: 1.9.1 colorspace@1.1.4: @@ -7094,6 +8652,10 @@ snapshots: commander@12.1.0: {} + commander@13.1.0: {} + + commander@2.15.1: {} + common-tags@1.8.2: {} compare-func@2.0.0: @@ -7142,6 +8704,8 @@ snapshots: cookie@0.7.2: {} + cookie@1.0.2: {} + core-util-is@1.0.3: {} cosmiconfig@9.0.0(typescript@5.7.3): @@ -7153,6 +8717,8 @@ snapshots: optionalDependencies: typescript: 5.7.3 + croner@4.1.97: {} + cross-fetch@3.2.0: dependencies: node-fetch: 2.7.0 @@ -7181,16 +8747,34 @@ snapshots: dependencies: type-fest: 1.4.0 + csstype@3.1.3: {} + + culvert@0.1.2: {} + data-uri-to-buffer@2.0.2: {} + data-uri-to-buffer@6.0.2: {} + dataloader@1.4.0: {} dataloader@2.2.3: {} date-fns@3.6.0: {} + dayjs@1.11.13: {} + + dayjs@1.8.36: {} + debounce-microtasks@0.1.8: {} + debug@3.2.7: + dependencies: + ms: 2.1.3 + + debug@4.3.7: + dependencies: + ms: 2.1.3 + debug@4.4.0: dependencies: ms: 2.1.3 @@ -7203,6 +8787,12 @@ snapshots: defu@6.1.4: {} + degenerator@5.0.1: + dependencies: + ast-types: 0.13.4 + escodegen: 2.1.0 + esprima: 4.0.1 + del@6.1.1: dependencies: globby: 11.1.0 @@ -7218,6 +8808,10 @@ snapshots: detect-indent@6.1.0: {} + detect-libc@1.0.3: {} + + detect-libc@2.0.3: {} + devalue@4.3.3: {} diff-sequences@29.6.3: {} @@ -7248,12 +8842,14 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.33.0(@cloudflare/workers-types@4.20250129.0)(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(bun-types@1.2.2): + drizzle-orm@0.33.0(@cloudflare/workers-types@4.20250129.0)(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(@types/react@19.0.8)(bun-types@1.2.2)(react@19.0.0): optionalDependencies: '@cloudflare/workers-types': 4.20250129.0 '@opentelemetry/api': 1.9.0 '@types/pg': 8.6.1 + '@types/react': 19.0.8 bun-types: 1.2.2 + react: 19.0.0 dset@3.1.4: {} @@ -7277,6 +8873,15 @@ snapshots: enabled@2.0.0: {} + enhanced-resolve@5.18.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.1 + + enquirer@2.3.6: + dependencies: + ansi-colors: 4.1.3 + enquirer@2.4.1: dependencies: ansi-colors: 4.1.3 @@ -7444,14 +9049,32 @@ snapshots: escape-string-regexp@5.0.0: {} + escodegen@2.1.0: + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + esprima@4.0.1: {} + estraverse@5.3.0: {} + estree-walker@0.6.1: {} estree-walker@3.0.3: dependencies: '@types/estree': 1.0.6 + esutils@2.0.3: {} + + eventemitter2@0.4.14: {} + + eventemitter2@5.0.1: {} + + eventemitter2@6.4.9: {} + execa@5.1.1: dependencies: cross-spawn: 7.0.6 @@ -7501,6 +9124,12 @@ snapshots: iconv-lite: 0.4.24 tmp: 0.0.33 + extrareqp2@1.0.0(debug@4.3.7): + dependencies: + follow-redirects: 1.15.9(debug@4.3.7) + transitivePeerDependencies: + - debug + fast-content-type-parse@2.0.1: {} fast-glob@3.3.3: @@ -7511,6 +9140,8 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 + fast-json-patch@3.1.1: {} + fastq@1.19.0: dependencies: reusify: 1.0.4 @@ -7533,6 +9164,8 @@ snapshots: transitivePeerDependencies: - encoding + fclone@1.0.11: {} + fecha@4.2.3: {} figures@2.0.0: @@ -7573,6 +9206,10 @@ snapshots: fn.name@1.1.0: {} + follow-redirects@1.15.9(debug@4.3.7): + optionalDependencies: + debug: 4.3.7 + foreground-child@3.3.0: dependencies: cross-spawn: 7.0.6 @@ -7580,6 +9217,8 @@ snapshots: forwarded-parse@2.1.2: {} + fraction.js@4.3.7: {} + frail-map@1.0.10: {} from2@2.3.0: @@ -7646,6 +9285,14 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + get-uri@6.0.4: + dependencies: + basic-ftp: 5.0.5 + data-uri-to-buffer: 6.0.2 + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + git-log-parser@1.2.1: dependencies: argv-formatter: 1.0.0 @@ -7655,6 +9302,12 @@ snapshots: through2: 2.0.5 traverse: 0.6.8 + git-node-fs@1.0.0(js-git@0.7.8): + optionalDependencies: + js-git: 0.7.8 + + git-sha1@0.1.2: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -7681,6 +9334,8 @@ snapshots: globals@11.12.0: {} + globalyzer@0.1.0: {} + globby@11.1.0: dependencies: array-union: 2.1.0 @@ -7699,7 +9354,24 @@ snapshots: slash: 5.1.0 unicorn-magic: 0.1.0 - gqty@3.3.0(graphql@16.10.0): + globrex@0.1.2: {} + + gqty@3.4.0-canary-20250207102900.644ad9fdeafa6318516627577b1d4d754d5c5a98(graphql@16.10.0): + dependencies: + debounce-microtasks: 0.1.8 + flatted: 3.3.2 + frail-map: 1.0.10 + just-extend: 6.2.0 + just-has: 2.3.0 + just-memoize: 2.2.0 + just-safe-get: 4.2.0 + just-safe-set: 4.2.1 + multidict: 1.0.9 + p-defer: 3.0.0 + optionalDependencies: + graphql: 16.10.0 + + gqty@3.4.1(graphql@16.10.0): dependencies: debounce-microtasks: 0.1.8 flatted: 3.3.2 @@ -7710,7 +9382,7 @@ snapshots: just-safe-get: 4.2.0 just-safe-set: 4.2.1 multidict: 1.0.9 - object-hash: 3.0.0 + ohash: 1.1.4 p-defer: 3.0.0 optionalDependencies: graphql: 16.10.0 @@ -7869,6 +9541,11 @@ snapshots: dependencies: loose-envify: 1.4.0 + ip-address@9.0.5: + dependencies: + jsbn: 1.1.0 + sprintf-js: 1.1.3 + is-absolute@1.0.0: dependencies: is-relative: 1.0.0 @@ -7952,7 +9629,16 @@ snapshots: java-properties@1.0.2: {} - jose@4.15.9: {} + jiti@2.4.2: {} + + jose@5.9.6: {} + + js-git@0.7.8: + dependencies: + bodec: 0.1.0 + culvert: 0.1.2 + git-sha1: 0.1.2 + pako: 0.2.9 js-tokens@4.0.0: {} @@ -7967,12 +9653,17 @@ snapshots: dependencies: argparse: 2.0.1 + jsbn@1.1.0: {} + jsesc@3.1.0: {} json-parse-better-errors@1.0.2: {} json-parse-even-better-errors@2.3.1: {} + json-stringify-safe@5.0.1: + optional: true + json5@2.2.3: {} jsonfile@4.0.0: @@ -8021,6 +9712,55 @@ snapshots: kuler@2.0.0: {} + lazy@1.0.11: {} + + lightningcss-darwin-arm64@1.29.1: + optional: true + + lightningcss-darwin-x64@1.29.1: + optional: true + + lightningcss-freebsd-x64@1.29.1: + optional: true + + lightningcss-linux-arm-gnueabihf@1.29.1: + optional: true + + lightningcss-linux-arm64-gnu@1.29.1: + optional: true + + lightningcss-linux-arm64-musl@1.29.1: + optional: true + + lightningcss-linux-x64-gnu@1.29.1: + optional: true + + lightningcss-linux-x64-musl@1.29.1: + optional: true + + lightningcss-win32-arm64-msvc@1.29.1: + optional: true + + lightningcss-win32-x64-msvc@1.29.1: + optional: true + + lightningcss@1.29.1: + dependencies: + detect-libc: 1.0.3 + optionalDependencies: + lightningcss-darwin-arm64: 1.29.1 + lightningcss-darwin-x64: 1.29.1 + lightningcss-freebsd-x64: 1.29.1 + lightningcss-linux-arm-gnueabihf: 1.29.1 + lightningcss-linux-arm64-gnu: 1.29.1 + lightningcss-linux-arm64-musl: 1.29.1 + lightningcss-linux-x64-gnu: 1.29.1 + lightningcss-linux-x64-musl: 1.29.1 + lightningcss-win32-arm64-msvc: 1.29.1 + lightningcss-win32-x64-msvc: 1.29.1 + + lilconfig@3.1.3: {} + lines-and-columns@1.2.4: {} load-json-file@4.0.0: @@ -8112,6 +9852,12 @@ snapshots: dependencies: yallist: 4.0.0 + lru-cache@7.18.3: {} + + lucide-react@0.474.0(react@19.0.0): + dependencies: + react: 19.0.0 + magic-string@0.25.9: dependencies: sourcemap-codec: 1.4.8 @@ -8202,6 +9948,8 @@ snapshots: minipass@7.1.2: {} + mkdirp@1.0.4: {} + mlly@1.7.4: dependencies: acorn: 8.14.0 @@ -8219,8 +9967,12 @@ snapshots: mustache@4.2.0: {} + mute-stream@0.0.8: {} + mute-stream@1.0.0: {} + mute-stream@2.0.0: {} + mz@2.7.0: dependencies: any-promise: 1.3.0 @@ -8229,10 +9981,20 @@ snapshots: nanoid@3.3.8: {} + needle@2.4.0: + dependencies: + debug: 3.2.7 + iconv-lite: 0.4.24 + sax: 1.4.1 + transitivePeerDependencies: + - supports-color + neo-async@2.6.2: {} nerf-dart@1.0.0: {} + netmask@2.0.2: {} + no-case@3.0.4: dependencies: lower-case: 2.0.2 @@ -8270,6 +10032,8 @@ snapshots: normalize-path@3.0.0: {} + normalize-range@0.1.2: {} + normalize-url@8.0.1: {} npm-run-path@4.0.1: @@ -8287,18 +10051,19 @@ snapshots: npm@10.9.2: {} - nullthrows@1.1.1: {} + nssocket@0.6.0: + dependencies: + eventemitter2: 0.4.14 + lazy: 1.0.11 - object-assign@4.1.1: {} + nullthrows@1.1.1: {} - object-hash@2.2.0: {} + oauth4webapi@3.1.4: {} - object-hash@3.0.0: {} + object-assign@4.1.1: {} ohash@1.1.4: {} - oidc-token-hash@5.0.3: {} - once@1.4.0: dependencies: wrappy: 1.0.2 @@ -8315,17 +10080,17 @@ snapshots: dependencies: mimic-fn: 4.0.0 - openid-client@5.7.1: + openid-client@6.1.7: dependencies: - jose: 4.15.9 - lru-cache: 6.0.0 - object-hash: 2.2.0 - oidc-token-hash: 5.0.3 + jose: 5.9.6 + oauth4webapi: 3.1.4 os-tmpdir@1.0.2: {} outdent@0.5.0: {} + p-debounce@4.0.0: {} + p-defer@3.0.0: {} p-each-series@2.2.0: {} @@ -8384,10 +10149,30 @@ snapshots: p-try@2.2.0: {} + pac-proxy-agent@7.1.0: + dependencies: + '@tootallnate/quickjs-emscripten': 0.23.0 + agent-base: 7.1.3 + debug: 4.4.0 + get-uri: 6.0.4 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + pac-resolver: 7.0.1 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + pac-resolver@7.0.1: + dependencies: + degenerator: 5.0.1 + netmask: 2.0.2 + package-json-from-dist@1.0.1: {} package-manager-detector@0.2.9: {} + pako@0.2.9: {} + param-case@3.0.4: dependencies: dot-case: 3.0.4 @@ -8492,6 +10277,15 @@ snapshots: picomatch@2.3.1: {} + pidusage@2.0.21: + dependencies: + safe-buffer: 5.2.1 + optional: true + + pidusage@3.0.2: + dependencies: + safe-buffer: 5.2.1 + pify@3.0.0: {} pify@4.0.1: {} @@ -8511,6 +10305,88 @@ snapshots: dependencies: find-up: 3.0.0 + pm2-axon-rpc@0.7.1: + dependencies: + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + + pm2-axon@4.0.1: + dependencies: + amp: 0.3.1 + amp-message: 0.1.2 + debug: 4.4.0 + escape-string-regexp: 4.0.0 + transitivePeerDependencies: + - supports-color + + pm2-deploy@1.0.2: + dependencies: + run-series: 1.1.9 + tv4: 1.3.0 + + pm2-multimeter@0.1.2: + dependencies: + charm: 0.1.2 + + pm2-sysmonit@1.2.8: + dependencies: + async: 3.2.6 + debug: 4.4.0 + pidusage: 2.0.21 + systeminformation: 5.25.11 + tx2: 1.0.5 + transitivePeerDependencies: + - supports-color + optional: true + + pm2@5.4.3: + dependencies: + '@pm2/agent': 2.0.4 + '@pm2/io': 6.0.1 + '@pm2/js-api': 0.8.0 + '@pm2/pm2-version-check': 1.0.4 + async: 3.2.6 + blessed: 0.1.81 + chalk: 3.0.0 + chokidar: 3.6.0 + cli-tableau: 2.0.1 + commander: 2.15.1 + croner: 4.1.97 + dayjs: 1.11.13 + debug: 4.4.0 + enquirer: 2.3.6 + eventemitter2: 5.0.1 + fclone: 1.0.11 + js-yaml: 4.1.0 + mkdirp: 1.0.4 + needle: 2.4.0 + pidusage: 3.0.2 + pm2-axon: 4.0.1 + pm2-axon-rpc: 0.7.1 + pm2-deploy: 1.0.2 + pm2-multimeter: 0.1.2 + promptly: 2.2.0 + semver: 7.7.1 + source-map-support: 0.5.21 + sprintf-js: 1.1.2 + vizion: 2.2.1 + optionalDependencies: + pm2-sysmonit: 1.2.8 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + postcss-load-config@6.0.1(jiti@2.4.2)(postcss@8.5.1): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + jiti: 2.4.2 + postcss: 8.5.1 + + postcss-value-parser@4.2.0: {} + postcss@8.5.1: dependencies: nanoid: 3.3.8 @@ -8547,8 +10423,27 @@ snapshots: dependencies: asap: 2.0.6 + promptly@2.2.0: + dependencies: + read: 1.0.7 + proto-list@1.2.4: {} + proxy-agent@6.3.1: + dependencies: + agent-base: 7.1.3 + debug: 4.4.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + lru-cache: 7.18.3 + pac-proxy-agent: 7.1.0 + proxy-from-env: 1.1.0 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + proxy-from-env@1.1.0: {} + queue-microtask@1.2.3: {} ramda@0.27.2: {} @@ -8560,8 +10455,29 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 + react-dom@19.0.0(react@19.0.0): + dependencies: + react: 19.0.0 + scheduler: 0.25.0 + react-is@18.3.1: {} + react-router@7.1.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@types/cookie': 0.6.0 + cookie: 1.0.2 + react: 19.0.0 + set-cookie-parser: 2.7.1 + turbo-stream: 2.4.0 + optionalDependencies: + react-dom: 19.0.0(react@19.0.0) + + react-ssr-prepass@1.6.0(react@19.0.0): + dependencies: + react: 19.0.0 + + react@19.0.0: {} + read-package-up@11.0.0: dependencies: find-up-simple: 1.0.0 @@ -8590,6 +10506,10 @@ snapshots: pify: 4.0.1 strip-bom: 3.0.0 + read@1.0.7: + dependencies: + mute-stream: 0.0.8 + readable-stream@2.3.8: dependencies: core-util-is: 1.0.3 @@ -8610,6 +10530,8 @@ snapshots: dependencies: picomatch: 2.3.1 + readdirp@4.1.1: {} + regenerator-runtime@0.14.1: {} registry-auth-token@5.0.3: @@ -8626,6 +10548,14 @@ snapshots: require-directory@2.1.1: {} + require-in-the-middle@5.2.0: + dependencies: + debug: 4.4.0 + module-details-from-path: 1.0.3 + resolve: 1.22.10 + transitivePeerDependencies: + - supports-color + require-in-the-middle@7.5.0: dependencies: debug: 4.4.0 @@ -8702,12 +10632,20 @@ snapshots: dependencies: queue-microtask: 1.2.3 + run-series@1.1.9: {} + safe-buffer@5.1.2: {} + safe-buffer@5.2.1: {} + safe-stable-stringify@2.5.0: {} safer-buffer@2.1.2: {} + sax@1.4.1: {} + + scheduler@0.25.0: {} + selfsigned@2.4.1: dependencies: '@types/node-forge': 1.3.11 @@ -8781,6 +10719,10 @@ snapshots: semver@6.3.1: {} + semver@7.5.4: + dependencies: + lru-cache: 6.0.0 + semver@7.7.1: {} sentence-case@3.0.4: @@ -8789,8 +10731,36 @@ snapshots: tslib: 2.8.1 upper-case-first: 2.0.2 + set-cookie-parser@2.7.1: {} + setimmediate@1.0.5: {} + sharp@0.33.5: + dependencies: + color: 4.2.3 + detect-libc: 2.0.3 + semver: 7.7.1 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.33.5 + '@img/sharp-darwin-x64': 0.33.5 + '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-linux-arm': 1.0.5 + '@img/sharp-libvips-linux-arm64': 1.0.4 + '@img/sharp-libvips-linux-s390x': 1.0.4 + '@img/sharp-libvips-linux-x64': 1.0.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@img/sharp-linux-arm': 0.33.5 + '@img/sharp-linux-arm64': 0.33.5 + '@img/sharp-linux-s390x': 0.33.5 + '@img/sharp-linux-x64': 0.33.5 + '@img/sharp-linuxmusl-arm64': 0.33.5 + '@img/sharp-linuxmusl-x64': 0.33.5 + '@img/sharp-wasm32': 0.33.5 + '@img/sharp-win32-ia32': 0.33.5 + '@img/sharp-win32-x64': 0.33.5 + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -8825,11 +10795,26 @@ snapshots: slash@5.1.0: {} + smart-buffer@4.2.0: {} + snake-case@3.0.4: dependencies: dot-case: 3.0.4 tslib: 2.8.1 + socks-proxy-agent@8.0.5: + dependencies: + agent-base: 7.1.3 + debug: 4.4.0 + socks: 2.8.4 + transitivePeerDependencies: + - supports-color + + socks@2.8.4: + dependencies: + ip-address: 9.0.5 + smart-buffer: 4.2.0 + source-map-js@1.2.1: {} source-map-support@0.5.21: @@ -8872,6 +10857,10 @@ snapshots: sprintf-js@1.0.3: {} + sprintf-js@1.1.2: {} + + sprintf-js@1.1.3: {} + stack-trace@0.0.10: {} stackback@0.0.2: {} @@ -8956,6 +10945,21 @@ snapshots: dependencies: tslib: 2.8.1 + systeminformation@5.25.11: + optional: true + + tailwind-merge@3.0.1: {} + + tailwindcss-animate@1.0.7(tailwindcss@4.0.4): + dependencies: + tailwindcss: 4.0.4 + + tailwindcss@4.0.4: {} + + tailwindcss@4.0.6: {} + + tapable@2.2.1: {} + temp-dir@2.0.0: {} temp-dir@3.0.0: {} @@ -8996,6 +11000,11 @@ snapshots: dependencies: convert-hrtime: 5.0.0 + tiny-glob@0.2.9: + dependencies: + globalyzer: 0.1.0 + globrex: 0.1.2 + tinybench@2.9.0: {} tinypool@0.8.4: {} @@ -9024,14 +11033,23 @@ snapshots: traverse@0.6.8: {} - treekill@1.0.0: {} - triple-beam@1.4.1: {} + tslib@1.9.3: {} + tslib@2.6.3: {} tslib@2.8.1: {} + turbo-stream@2.4.0: {} + + tv4@1.3.0: {} + + tx2@1.0.5: + dependencies: + json-stringify-safe: 5.0.1 + optional: true + type-detect@4.1.0: {} type-fest@0.16.0: {} @@ -9118,6 +11136,10 @@ snapshots: urlpattern-polyfill@10.0.0: {} + use-sync-external-store@1.4.0(react@19.0.0): + dependencies: + react: 19.0.0 + util-deprecate@1.0.2: {} validate-npm-package-license@3.0.4: @@ -9127,13 +11149,13 @@ snapshots: value-or-promise@1.0.12: {} - vite-node@1.5.3(@types/node@22.13.1): + vite-node@1.5.3(@types/node@22.13.1)(lightningcss@1.29.1): dependencies: cac: 6.7.14 debug: 4.4.0 pathe: 1.1.2 picocolors: 1.1.1 - vite: 5.4.14(@types/node@22.13.1) + vite: 5.4.14(@types/node@22.13.1)(lightningcss@1.29.1) transitivePeerDependencies: - '@types/node' - less @@ -9145,7 +11167,7 @@ snapshots: - supports-color - terser - vite@5.4.14(@types/node@22.13.1): + vite@5.4.14(@types/node@22.13.1)(lightningcss@1.29.1): dependencies: esbuild: 0.21.5 postcss: 8.5.1 @@ -9153,8 +11175,9 @@ snapshots: optionalDependencies: '@types/node': 22.13.1 fsevents: 2.3.3 + lightningcss: 1.29.1 - vitest@1.5.3(@types/node@22.13.1): + vitest@1.5.3(@types/node@22.13.1)(lightningcss@1.29.1): dependencies: '@vitest/expect': 1.5.3 '@vitest/runner': 1.5.3 @@ -9173,8 +11196,8 @@ snapshots: strip-literal: 2.1.1 tinybench: 2.9.0 tinypool: 0.8.4 - vite: 5.4.14(@types/node@22.13.1) - vite-node: 1.5.3(@types/node@22.13.1) + vite: 5.4.14(@types/node@22.13.1)(lightningcss@1.29.1) + vite-node: 1.5.3(@types/node@22.13.1)(lightningcss@1.29.1) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 22.13.1 @@ -9188,6 +11211,13 @@ snapshots: - supports-color - terser + vizion@2.2.1: + dependencies: + async: 2.6.4 + git-node-fs: 1.0.0(js-git@0.7.8) + ini: 1.3.8 + js-git: 0.7.8 + webidl-conversions@3.0.1: {} whatwg-url@5.0.0: @@ -9308,6 +11338,8 @@ snapshots: wrappy@1.0.2: {} + ws@7.5.10: {} + ws@8.18.0: {} xtend@4.0.2: {}