From d7c43273f27b98984aa8e3d6480ad8f56917b09a Mon Sep 17 00:00:00 2001 From: Nico Schett <52858351+schettn@users.noreply.github.com> Date: Wed, 5 Feb 2025 22:22:28 +0000 Subject: [PATCH 01/27] chore: integrate builder in dev server for simplified maintenance --- .changeset/breezy-socks-deliver.md | 6 +++ packages/pylon-builder/CHANGELOG.md | 7 ---- packages/pylon-builder/package.json | 33 ----------------- .../pylon-builder/src/load-package-json.ts | 15 -------- packages/pylon-builder/tsconfig.json | 5 --- packages/pylon-dev/package.json | 7 +++- .../src/builder}/bundler/bundler.ts | 0 .../src/builder}/bundler/index.ts | 0 .../src => pylon-dev/src/builder}/index.ts | 0 .../src/builder}/schema/builder.ts | 0 .../src/builder}/schema/schema-parser.ts | 0 .../schema/type-definition-builder.ts | 0 .../src/builder}/schema/types-helper.ts | 0 packages/pylon-dev/src/index.ts | 2 +- pnpm-lock.yaml | 37 +++++++------------ 15 files changed, 26 insertions(+), 86 deletions(-) create mode 100644 .changeset/breezy-socks-deliver.md delete mode 100644 packages/pylon-builder/CHANGELOG.md delete mode 100644 packages/pylon-builder/package.json delete mode 100644 packages/pylon-builder/src/load-package-json.ts delete mode 100644 packages/pylon-builder/tsconfig.json rename packages/{pylon-builder/src => pylon-dev/src/builder}/bundler/bundler.ts (100%) rename packages/{pylon-builder/src => pylon-dev/src/builder}/bundler/index.ts (100%) rename packages/{pylon-builder/src => pylon-dev/src/builder}/index.ts (100%) rename packages/{pylon-builder/src => pylon-dev/src/builder}/schema/builder.ts (100%) rename packages/{pylon-builder/src => pylon-dev/src/builder}/schema/schema-parser.ts (100%) rename packages/{pylon-builder/src => pylon-dev/src/builder}/schema/type-definition-builder.ts (100%) rename packages/{pylon-builder/src => pylon-dev/src/builder}/schema/types-helper.ts (100%) 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/packages/pylon-builder/CHANGELOG.md b/packages/pylon-builder/CHANGELOG.md deleted file mode 100644 index 1b727fe..0000000 --- a/packages/pylon-builder/CHANGELOG.md +++ /dev/null @@ -1,7 +0,0 @@ -# @getcronit/pylon-builder - -## 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 bc62035..0000000 --- a/packages/pylon-builder/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "@getcronit/pylon-builder", - "version": "2.6.6", - "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/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 5ee763c..9e4df67 100644 --- a/packages/pylon-dev/package.json +++ b/packages/pylon-dev/package.json @@ -21,7 +21,6 @@ }, "homepage": "https://pylon.cronit.io", "dependencies": { - "@getcronit/pylon-builder": "workspace:^", "@getcronit/pylon-telemetry": "workspace:^", "@gqty/cli": "^4.2.0", "commander": "^12.1.0", @@ -34,5 +33,11 @@ }, "engines": { "node": ">=18.0.0" + }, + "devDependencies": { + "chokidar": "^3.5.3", + "esbuild": "^0.23.1", + "esbuild-plugin-tsc": "^0.4.0", + "typescript": "^5.7.3" } } diff --git a/packages/pylon-builder/src/bundler/bundler.ts b/packages/pylon-dev/src/builder/bundler/bundler.ts similarity index 100% rename from packages/pylon-builder/src/bundler/bundler.ts rename to packages/pylon-dev/src/builder/bundler/bundler.ts 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-builder/src/index.ts b/packages/pylon-dev/src/builder/index.ts similarity index 100% rename from packages/pylon-builder/src/index.ts rename to packages/pylon-dev/src/builder/index.ts 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/index.ts b/packages/pylon-dev/src/index.ts index 9346c95..fa5329c 100644 --- a/packages/pylon-dev/src/index.ts +++ b/packages/pylon-dev/src/index.ts @@ -1,6 +1,5 @@ #!/usr/bin/env node -import {build} from '@getcronit/pylon-builder' import {fetchSchema, generateClient} from '@gqty/cli' import {program, type Command} from 'commander' import consola from 'consola' @@ -10,6 +9,7 @@ import {ChildProcess, spawn} from 'child_process' import kill from 'treekill' import * as telemetry from '@getcronit/pylon-telemetry' import dotenv from 'dotenv' +import {build} from './builder' dotenv.config() diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 057acff..c0b9201 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -213,32 +213,8 @@ 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-telemetry': specifier: workspace:^ version: link:../pylon-telemetry @@ -257,6 +233,19 @@ importers: treekill: specifier: ^1.0.0 version: 1.0.0 + devDependencies: + chokidar: + specifier: ^3.5.3 + version: 3.6.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) + typescript: + specifier: ^5.7.3 + version: 5.7.3 packages/pylon-telemetry: dependencies: From eee120e4e8aff66f402f906014d3a2143895d5a2 Mon Sep 17 00:00:00 2001 From: Nico Schett <52858351+schettn@users.noreply.github.com> Date: Thu, 6 Feb 2025 00:06:12 +0000 Subject: [PATCH 02/27] perf(pylon-dev): implement esbuild watch mode and notify plugin for improved build process --- .changeset/real-fans-jog.md | 5 + packages/pylon-dev/package.json | 1 - .../pylon-dev/src/builder/bundler/bundler.ts | 220 +++--------------- .../bundler/plugins/inject-code-plugin.ts | 106 +++++++++ .../builder/bundler/plugins/notify-plugin.ts | 91 ++++++++ packages/pylon-dev/src/builder/index.ts | 6 +- packages/pylon-dev/src/index.ts | 135 +++++------ 7 files changed, 294 insertions(+), 270 deletions(-) create mode 100644 .changeset/real-fans-jog.md create mode 100644 packages/pylon-dev/src/builder/bundler/plugins/inject-code-plugin.ts create mode 100644 packages/pylon-dev/src/builder/bundler/plugins/notify-plugin.ts 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/packages/pylon-dev/package.json b/packages/pylon-dev/package.json index 9e4df67..90c8e4f 100644 --- a/packages/pylon-dev/package.json +++ b/packages/pylon-dev/package.json @@ -35,7 +35,6 @@ "node": ">=18.0.0" }, "devDependencies": { - "chokidar": "^3.5.3", "esbuild": "^0.23.1", "esbuild-plugin-tsc": "^0.4.0", "typescript": "^5.7.3" diff --git a/packages/pylon-dev/src/builder/bundler/bundler.ts b/packages/pylon-dev/src/builder/bundler/bundler.ts index 7f41e63..b8412a3 100644 --- a/packages/pylon-dev/src/builder/bundler/bundler.ts +++ b/packages/pylon-dev/src/builder/bundler/bundler.ts @@ -1,203 +1,57 @@ // bundler.ts -import fs from 'fs' -import chokidar from 'chokidar' -import {Plugin, build} from 'esbuild' +import {context} from 'esbuild' import esbuildPluginTsc from 'esbuild-plugin-tsc' import path from 'path' -import consola from 'consola' +import { + InjectCodePluginOptions, + injectCodePlugin +} from './plugins/inject-code-plugin' +import {NotifyPluginOptions, notifyPlugin} from './plugins/notify-plugin' 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 + getBuildDefs: InjectCodePluginOptions['getBuildDefs'] + onBuild?: NotifyPluginOptions['onBuild'] } 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() + const inputPath = path.join(process.cwd(), this.sfiFilePath) + const dir = path.join(process.cwd(), this.outputDir) + + const ctx = await context({ + logLevel: 'silent', + metafile: true, + entryPoints: [inputPath], + outdir: dir, + bundle: true, + format: 'esm', + sourcemap: 'inline', + packages: 'external', + + plugins: [ + notifyPlugin({ + dir, + onBuild: options.onBuild + }), + injectCodePlugin({ + getBuildDefs: options.getBuildDefs, + outputDir: this.outputDir + }), + esbuildPluginTsc({ + tsconfigPath: path.join(process.cwd(), 'tsconfig.json') + }) + ] + }) + + return ctx } } - -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/inject-code-plugin.ts b/packages/pylon-dev/src/builder/bundler/plugins/inject-code-plugin.ts new file mode 100644 index 0000000..c930d91 --- /dev/null +++ b/packages/pylon-dev/src/builder/bundler/plugins/inject-code-plugin.ts @@ -0,0 +1,106 @@ +import {Plugin} from 'esbuild' +import path from 'path' +import fs from 'fs/promises' + +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 fs.writeFile(typeDefsPath, typeDefs) + + // Write base resolvers to a file + + const resolversPath = path.join( + process.cwd(), + outputDir, + 'resolvers.js' + ) + + await fs.writeFile( + 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..74c8744 --- /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 + }) => 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) { + onBuild({ + totalFiles, + totalSize, + schemaChanged: true, + duration + }) + } + }) + } +}) diff --git a/packages/pylon-dev/src/builder/index.ts b/packages/pylon-dev/src/builder/index.ts index df5e4d1..1b9a72b 100644 --- a/packages/pylon-dev/src/builder/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 @@ -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-dev/src/index.ts b/packages/pylon-dev/src/index.ts index fa5329c..0ba5a3a 100644 --- a/packages/pylon-dev/src/index.ts +++ b/packages/pylon-dev/src/index.ts @@ -19,21 +19,21 @@ 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' - }) - - await telemetry.sendBuildEvent({ - duration: duration, - totalFiles, - totalSize, - isDevelopment: false + outputFilePath: './.pylon', + onBuild: async ({totalFiles, totalSize, duration}) => { + await telemetry.sendBuildEvent({ + duration, + totalFiles, + totalSize, + isDevelopment: false + }) + } }) - consola.success('[Pylon]: Schema built') + await ctx.rebuild() + await ctx.dispose() }) program @@ -69,6 +69,46 @@ const start = Date.now() async function main(options: ArgOptions, command: Command) { consola.log(`[Pylon]: ${command.name()} version ${command.version()}`) + const ctx = await build({ + sfiFilePath: './src/index.ts', + outputFilePath: `./.pylon`, + onBuild: 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.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) { + await telemetry.sendBuildEvent({ + duration, + totalFiles, + totalSize, + isDevelopment: true + }) + } + } + }) + let currentProc: ChildProcess | null = null let serve = async (shouldGenerateClient: boolean = false) => { @@ -165,77 +205,8 @@ async function main(options: ArgOptions, command: Command) { } } - consola.start('[Pylon]: Building schema') - try { - const {duration, totalFiles, totalSize} = 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 - }) - } - } - }) - - 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') - - 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! - `) + await ctx.watch() } catch (e) { consola.error("[Pylon]: Couldn't build schema", e) From b7f8b4b8bdd8bacff6633c5d24d59c1edc8f13c8 Mon Sep 17 00:00:00 2001 From: Nico Schett <52858351+schettn@users.noreply.github.com> Date: Thu, 6 Feb 2025 01:07:41 +0000 Subject: [PATCH 03/27] fix(dependencies): replace treekill with ps-tree for process management --- .changeset/six-vans-care.md | 5 ++ packages/pylon-dev/package.json | 3 +- packages/pylon-dev/src/index.ts | 44 ++++++++++------- pnpm-lock.yaml | 87 ++++++++++++++++++++++++++++----- 4 files changed, 106 insertions(+), 33 deletions(-) create mode 100644 .changeset/six-vans-care.md 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/packages/pylon-dev/package.json b/packages/pylon-dev/package.json index 90c8e4f..42cb2c8 100644 --- a/packages/pylon-dev/package.json +++ b/packages/pylon-dev/package.json @@ -26,7 +26,7 @@ "commander": "^12.1.0", "consola": "^3.2.3", "dotenv": "^16.4.5", - "treekill": "^1.0.0" + "ps-tree": "^1.2.0" }, "publishConfig": { "access": "public" @@ -35,6 +35,7 @@ "node": ">=18.0.0" }, "devDependencies": { + "@types/ps-tree": "^1.1.6", "esbuild": "^0.23.1", "esbuild-plugin-tsc": "^0.4.0", "typescript": "^5.7.3" diff --git a/packages/pylon-dev/src/index.ts b/packages/pylon-dev/src/index.ts index 0ba5a3a..f8fafbb 100644 --- a/packages/pylon-dev/src/index.ts +++ b/packages/pylon-dev/src/index.ts @@ -6,7 +6,7 @@ import consola from 'consola' import path from 'path' import {version} from '../package.json' import {ChildProcess, spawn} from 'child_process' -import kill from 'treekill' +import psTree from 'ps-tree' import * as telemetry from '@getcronit/pylon-telemetry' import dotenv from 'dotenv' import {build} from './builder' @@ -109,18 +109,32 @@ async function main(options: ArgOptions, command: Command) { } }) + async function killProcessAndChildren(pid: number) { + await ctx.cancel() + psTree(pid, (err, children) => { + if (err) { + console.error('Error fetching child processes:', err) + return + } + + // Kill the parent process and all its children + const allPids = children.map(child => parseInt(child.PID)).concat(pid) + allPids.forEach(childPid => { + try { + process.kill(childPid, 'SIGINT') + } catch (error) {} + }) + }) + } + let currentProc: ChildProcess | null = null let serve = async (shouldGenerateClient: boolean = false) => { - if (currentProc) { + if (currentProc?.pid) { // Remove all listeners to prevent the pylon dev server from crashing currentProc.removeAllListeners() - kill(currentProc.pid, 'SIGINT', err => { - if (err) { - consola.error(err) - } - }) + await killProcessAndChildren(currentProc.pid) } const [commandName, ...args] = options.command.split(' ') @@ -212,27 +226,19 @@ async function main(options: ArgOptions, command: Command) { // Kill the server if it's running const proc = currentProc as ChildProcess | null - if (proc) { + if (proc?.pid) { proc.removeAllListeners() - kill(proc.pid, 'SIGINT', err => { - if (err) { - consola.error(err) - } - }) + await killProcessAndChildren(proc.pid) } } process.on('SIGINT', async code => { try { - if (currentProc) { + if (currentProc?.pid) { currentProc.removeAllListeners() - kill(currentProc.pid, 'SIGINT', err => { - if (err) { - consola.error(err) - } - }) + await killProcessAndChildren(currentProc.pid) } } catch { // Ignore diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c0b9201..5f995a2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -230,13 +230,13 @@ importers: dotenv: specifier: ^16.4.5 version: 16.4.7 - treekill: - specifier: ^1.0.0 - version: 1.0.0 + ps-tree: + specifier: ^1.2.0 + version: 1.2.0 devDependencies: - chokidar: - specifier: ^3.5.3 - version: 3.6.0 + '@types/ps-tree': + specifier: ^1.1.6 + version: 1.1.6 esbuild: specifier: ^0.23.1 version: 0.23.1 @@ -2121,6 +2121,9 @@ packages: '@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/semver@7.5.8': resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} @@ -2713,6 +2716,9 @@ packages: duplexer2@0.1.4: resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} + duplexer@0.1.2: + resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -2815,6 +2821,9 @@ packages: estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + event-stream@3.3.4: + resolution: {integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==} + execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -2915,6 +2924,9 @@ packages: from2@2.3.0: resolution: {integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==} + from@0.1.7: + resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==} + fs-extra@10.1.0: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} @@ -3459,6 +3471,9 @@ packages: resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==} engines: {node: '>=0.10.0'} + map-stream@0.1.0: + resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==} + marked-terminal@7.3.0: resolution: {integrity: sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==} engines: {node: '>=16.0.0'} @@ -3917,6 +3932,9 @@ packages: pathval@1.1.1: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + pause-stream@0.0.11: + resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} + pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} @@ -3999,6 +4017,11 @@ packages: proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + ps-tree@1.2.0: + resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==} + engines: {node: '>= 0.10'} + hasBin: true + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -4247,6 +4270,9 @@ packages: split2@1.0.0: resolution: {integrity: sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==} + split@0.3.3: + resolution: {integrity: sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==} + sponge-case@1.0.1: resolution: {integrity: sha512-dblb9Et4DAtiZ5YSUZHLl4XhH4uK80GhAZrVXdN4O2P4gQ40Wa5UIOPUHlA/nFd2PLblBZWUioLMMAVrgpoYcA==} @@ -4272,6 +4298,9 @@ packages: stream-combiner2@1.1.1: resolution: {integrity: sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==} + stream-combiner@0.0.4: + resolution: {integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==} + streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -4378,6 +4407,9 @@ packages: through2@2.0.5: resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + time-span@5.1.0: resolution: {integrity: sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==} engines: {node: '>=12'} @@ -4414,11 +4446,6 @@ 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'} @@ -6683,6 +6710,8 @@ snapshots: pg-protocol: 1.7.0 pg-types: 2.2.0 + '@types/ps-tree@1.1.6': {} + '@types/semver@7.5.8': {} '@types/shimmer@1.2.0': {} @@ -7250,6 +7279,8 @@ snapshots: dependencies: readable-stream: 2.3.8 + duplexer@0.1.2: {} + eastasianwidth@0.2.0: {} ecdsa-sig-formatter@1.0.11: @@ -7441,6 +7472,16 @@ snapshots: dependencies: '@types/estree': 1.0.6 + event-stream@3.3.4: + dependencies: + duplexer: 0.1.2 + from: 0.1.7 + map-stream: 0.1.0 + pause-stream: 0.0.11 + split: 0.3.3 + stream-combiner: 0.0.4 + through: 2.3.8 + execa@5.1.1: dependencies: cross-spawn: 7.0.6 @@ -7576,6 +7617,8 @@ snapshots: inherits: 2.0.4 readable-stream: 2.3.8 + from@0.1.7: {} + fs-extra@10.1.0: dependencies: graceful-fs: 4.2.11 @@ -8111,6 +8154,8 @@ snapshots: map-cache@0.2.2: {} + map-stream@0.1.0: {} + marked-terminal@7.3.0(marked@12.0.2): dependencies: ansi-escapes: 7.0.0 @@ -8465,6 +8510,10 @@ snapshots: pathval@1.1.1: {} + pause-stream@0.0.11: + dependencies: + through: 2.3.8 + pg-int8@1.0.1: {} pg-protocol@1.7.0: {} @@ -8538,6 +8587,10 @@ snapshots: proto-list@1.2.4: {} + ps-tree@1.2.0: + dependencies: + event-stream: 3.3.4 + queue-microtask@1.2.3: {} ramda@0.27.2: {} @@ -8855,6 +8908,10 @@ snapshots: dependencies: through2: 2.0.5 + split@0.3.3: + dependencies: + through: 2.3.8 + sponge-case@1.0.1: dependencies: tslib: 2.8.1 @@ -8879,6 +8936,10 @@ snapshots: duplexer2: 0.1.4 readable-stream: 2.3.8 + stream-combiner@0.0.4: + dependencies: + duplexer: 0.1.2 + streamsearch@1.1.0: {} string-width@4.2.3: @@ -8981,6 +9042,8 @@ snapshots: readable-stream: 2.3.8 xtend: 4.0.2 + through@2.3.8: {} + time-span@5.1.0: dependencies: convert-hrtime: 5.0.0 @@ -9013,8 +9076,6 @@ snapshots: traverse@0.6.8: {} - treekill@1.0.0: {} - triple-beam@1.4.1: {} tslib@2.6.3: {} From dfb5c770543f441ae8e4ddf0f1d5c9d0ab403590 Mon Sep 17 00:00:00 2001 From: Nico Schett <52858351+schettn@users.noreply.github.com> Date: Thu, 6 Feb 2025 02:00:18 +0000 Subject: [PATCH 04/27] feat(pylon): extend plugin system with middleware and app setup support --- .changeset/tall-spies-joke.md | 32 ++++++++ .../src/app/handler/graphql-viewer-handler.ts | 64 ---------------- packages/pylon/src/app/index.ts | 19 ++++- .../src/app/{handler => }/pylon-handler.ts | 48 +++++++++--- packages/pylon/src/index.ts | 24 ++++-- .../{app/envelop => plugins}/use-sentry.ts | 4 +- packages/pylon/src/plugins/use-viewer.ts | 76 +++++++++++++++++++ 7 files changed, 184 insertions(+), 83 deletions(-) create mode 100644 .changeset/tall-spies-joke.md delete mode 100644 packages/pylon/src/app/handler/graphql-viewer-handler.ts rename packages/pylon/src/app/{handler => }/pylon-handler.ts (75%) rename packages/pylon/src/{app/envelop => plugins}/use-sentry.ts (99%) create mode 100644 packages/pylon/src/plugins/use-viewer.ts 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/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..54f465f 100644 --- a/packages/pylon/src/app/index.ts +++ b/packages/pylon/src/app/index.ts @@ -1,9 +1,8 @@ -import {Hono} from 'hono' +import {Hono, MiddlewareHandler} from 'hono' import {logger} from 'hono/logger' import {sentry} from '@hono/sentry' import {asyncContext, Env} from '../context' -import {graphqlViewerHandler} from './handler/graphql-viewer-handler' export const app = new Hono() @@ -29,4 +28,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 75% rename from packages/pylon/src/app/handler/pylon-handler.ts rename to packages/pylon/src/app/pylon-handler.ts index fa019c6..d04cf9c 100644 --- a/packages/pylon/src/app/handler/pylon-handler.ts +++ b/packages/pylon/src/app/pylon-handler.ts @@ -7,12 +7,14 @@ 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' interface PylonHandlerOptions { graphql: { @@ -23,12 +25,40 @@ 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 || [])] + + loadPluginsMiddleware(plugins) if (!typeDefs) { // Try to read the schema from the default location @@ -113,7 +143,7 @@ export const handler = (options: PylonHandlerOptions) => { }, graphqlEndpoint: '/graphql', ...config, - plugins: [useSentry(), ...(config?.plugins || [])], + plugins, schema }) diff --git a/packages/pylon/src/index.ts b/packages/pylon/src/index.ts index 3a1980d..21d04e3 100644 --- a/packages/pylon/src/index.ts +++ b/packages/pylon/src/index.ts @@ -1,5 +1,4 @@ -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' @@ -12,13 +11,28 @@ 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'> +import type {Plugin as YogaPlugin} from 'graphql-yoga' +import {MiddlewareHandler} from 'hono' + +export type Plugin< + PluginContext extends Record = {}, + TServerContext extends Record = {}, + TUserContext = {} +> = YogaPlugin & { + middleware?: MiddlewareHandler + setup?: (app: typeof pylonApp) => void +} + +export type PylonConfig = { + plugins?: Plugin[] +} export type ID = string & {readonly brand?: unique symbol} export type Int = number & {readonly brand?: unique symbol} 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-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...
+ + + + ` + ) + ) + } + } + } +} From 65bc196a8ee75fd329b1c16d0b87f3ed1fd8e875 Mon Sep 17 00:00:00 2001 From: Nico Schett <52858351+schettn@users.noreply.github.com> Date: Thu, 6 Feb 2025 02:27:43 +0000 Subject: [PATCH 05/27] feat(pylon): add support for plugin build lifecycle --- .changeset/ninety-toys-shout.md | 24 +++++++++++++++++++ packages/pylon-dev/package.json | 3 +++ .../pylon-dev/src/builder/bundler/bundler.ts | 24 +++++++++++++++++++ packages/pylon/src/index.ts | 1 + pnpm-lock.yaml | 3 +++ 5 files changed, 55 insertions(+) create mode 100644 .changeset/ninety-toys-shout.md 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/packages/pylon-dev/package.json b/packages/pylon-dev/package.json index 42cb2c8..2774976 100644 --- a/packages/pylon-dev/package.json +++ b/packages/pylon-dev/package.json @@ -39,5 +39,8 @@ "esbuild": "^0.23.1", "esbuild-plugin-tsc": "^0.4.0", "typescript": "^5.7.3" + }, + "peerDependencies": { + "@getcronit/pylon": "workspace:^2.0.0" } } diff --git a/packages/pylon-dev/src/builder/bundler/bundler.ts b/packages/pylon-dev/src/builder/bundler/bundler.ts index b8412a3..770e3f9 100644 --- a/packages/pylon-dev/src/builder/bundler/bundler.ts +++ b/packages/pylon-dev/src/builder/bundler/bundler.ts @@ -1,6 +1,7 @@ // bundler.ts import {context} from 'esbuild' import esbuildPluginTsc from 'esbuild-plugin-tsc' +import type {PylonConfig} from '@getcronit/pylon' import path from 'path' import { @@ -23,6 +24,27 @@ export class Bundler { this.outputDir = outputDir } + private async loadAndExexConfigPluginsBuild() { + 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 plugins = config?.plugins || [] + + for (const plugin of plugins) { + if (plugin.build) { + await plugin.build() + } + } + } + public async build(options: BundlerBuildOptions) { const inputPath = path.join(process.cwd(), this.sfiFilePath) const dir = path.join(process.cwd(), this.outputDir) @@ -52,6 +74,8 @@ export class Bundler { ] }) + await this.loadAndExexConfigPluginsBuild() + return ctx } } diff --git a/packages/pylon/src/index.ts b/packages/pylon/src/index.ts index 21d04e3..c231f90 100644 --- a/packages/pylon/src/index.ts +++ b/packages/pylon/src/index.ts @@ -28,6 +28,7 @@ export type Plugin< > = YogaPlugin & { middleware?: MiddlewareHandler setup?: (app: typeof pylonApp) => void + build?: () => Promise } export type PylonConfig = { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5f995a2..61056e0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -215,6 +215,9 @@ importers: packages/pylon-dev: dependencies: + '@getcronit/pylon': + specifier: workspace:^2.0.0 + version: link:../pylon '@getcronit/pylon-telemetry': specifier: workspace:^ version: link:../pylon-telemetry From 0b965bfde9f8e1db5b2728aeb8259def3c857325 Mon Sep 17 00:00:00 2001 From: Nico Schett <52858351+schettn@users.noreply.github.com> Date: Thu, 6 Feb 2025 02:42:29 +0000 Subject: [PATCH 06/27] feat(pylon): add landing page support and unhandled route plugin --- .changeset/curly-tools-itch.md | 13 ++ packages/pylon/src/app/pylon-handler.ts | 9 + packages/pylon/src/index.ts | 1 + .../pylon/src/plugins/use-unhandled-route.ts | 186 ++++++++++++++++++ 4 files changed, 209 insertions(+) create mode 100644 .changeset/curly-tools-itch.md create mode 100644 packages/pylon/src/plugins/use-unhandled-route.ts 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/packages/pylon/src/app/pylon-handler.ts b/packages/pylon/src/app/pylon-handler.ts index d04cf9c..21d38ae 100644 --- a/packages/pylon/src/app/pylon-handler.ts +++ b/packages/pylon/src/app/pylon-handler.ts @@ -15,6 +15,7 @@ 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: { @@ -58,6 +59,14 @@ export const handler = (options: PylonHandlerOptions) => { const plugins = [useSentry(), useViewer(), ...(config?.plugins || [])] + if (config?.landingPage ?? true) { + plugins.push( + useUnhandledRoute({ + graphqlEndpoint: '/graphql' + }) + ) + } + loadPluginsMiddleware(plugins) if (!typeDefs) { diff --git a/packages/pylon/src/index.ts b/packages/pylon/src/index.ts index c231f90..dfe38a1 100644 --- a/packages/pylon/src/index.ts +++ b/packages/pylon/src/index.ts @@ -32,6 +32,7 @@ export type Plugin< } export type PylonConfig = { + landingPage?: boolean plugins?: Plugin[] } 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() + }) + } + } +} From f228add1e2e04c8af5d91db6063583bd317de463 Mon Sep 17 00:00:00 2001 From: Nico Schett <52858351+schettn@users.noreply.github.com> Date: Thu, 6 Feb 2025 12:52:39 +0000 Subject: [PATCH 07/27] feat(auth): overhaul authentication system with new useAuth plugin and role-based access control --- .changeset/stale-lobsters-flash.md | 36 +++ ...lt-in-authentication-and-authorization.mdx | 128 ++++---- packages/pylon/package.json | 2 +- .../pylon/src/auth/decorators/requireAuth.ts | 52 --- packages/pylon/src/auth/index.ts | 306 ------------------ packages/pylon/src/context.ts | 4 +- packages/pylon/src/define-pylon.ts | 13 +- packages/pylon/src/index.ts | 2 +- .../src/plugins/use-auth/auth-require.ts | 95 ++++++ .../plugins/use-auth/import-private-key.ts | 61 ++++ packages/pylon/src/plugins/use-auth/index.ts | 3 + packages/pylon/src/plugins/use-auth/types.ts | 9 + .../pylon/src/plugins/use-auth/use-auth.ts | 253 +++++++++++++++ pnpm-lock.yaml | 52 +-- 14 files changed, 552 insertions(+), 464 deletions(-) create mode 100644 .changeset/stale-lobsters-flash.md delete mode 100644 packages/pylon/src/auth/decorators/requireAuth.ts delete mode 100644 packages/pylon/src/auth/index.ts create mode 100644 packages/pylon/src/plugins/use-auth/auth-require.ts create mode 100644 packages/pylon/src/plugins/use-auth/import-private-key.ts create mode 100644 packages/pylon/src/plugins/use-auth/index.ts create mode 100644 packages/pylon/src/plugins/use-auth/types.ts create mode 100644 packages/pylon/src/plugins/use-auth/use-auth.ts 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/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/packages/pylon/package.json b/packages/pylon/package.json index 32f9dc6..c5b8560 100644 --- a/packages/pylon/package.json +++ b/packages/pylon/package.json @@ -32,7 +32,7 @@ "graphql-yoga": "^5.6.2", "hono": "^4.0.8", "jsonwebtoken": "^9.0.2", - "openid-client": "^5.6.4", + "openid-client": "^6.1.7", "toucan-js": "^4.1.0", "winston": "^3.8.2" }, 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/index.ts b/packages/pylon/src/index.ts index dfe38a1..2735e31 100644 --- a/packages/pylon/src/index.ts +++ b/packages/pylon/src/index.ts @@ -1,7 +1,7 @@ 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, 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/pnpm-lock.yaml b/pnpm-lock.yaml index 61056e0..f215855 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -200,8 +200,8 @@ 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 toucan-js: specifier: ^4.1.0 version: 4.1.0 @@ -3304,8 +3304,8 @@ packages: resolution: {integrity: sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==} engines: {node: '>= 0.6.0'} - jose@4.15.9: - resolution: {integrity: sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==} + jose@5.9.6: + resolution: {integrity: sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==} js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -3460,10 +3460,6 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} - magic-string@0.25.9: resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} @@ -3713,14 +3709,13 @@ packages: 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'} @@ -3728,10 +3723,6 @@ packages: 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==} @@ -3746,8 +3737,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==} @@ -4743,9 +4734,6 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - yargs-parser@20.2.9: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} engines: {node: '>=10'} @@ -7987,7 +7975,7 @@ snapshots: java-properties@1.0.2: {} - jose@4.15.9: {} + jose@5.9.6: {} js-tokens@4.0.0: {} @@ -8143,10 +8131,6 @@ snapshots: dependencies: yallist: 3.1.1 - lru-cache@6.0.0: - dependencies: - yallist: 4.0.0 - magic-string@0.25.9: dependencies: sourcemap-codec: 1.4.8 @@ -8326,16 +8310,14 @@ snapshots: nullthrows@1.1.1: {} - object-assign@4.1.1: {} + oauth4webapi@3.1.4: {} - object-hash@2.2.0: {} + object-assign@4.1.1: {} object-hash@3.0.0: {} ohash@1.1.4: {} - oidc-token-hash@5.0.3: {} - once@1.4.0: dependencies: wrappy: 1.0.2 @@ -8352,12 +8334,10 @@ 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: {} @@ -9371,8 +9351,6 @@ snapshots: yallist@3.1.1: {} - yallist@4.0.0: {} - yargs-parser@20.2.9: {} yargs-parser@21.1.1: {} From 6fd91c79026a523a976bf22f5a2858cb3aaba083 Mon Sep 17 00:00:00 2001 From: Nico Schett <52858351+schettn@users.noreply.github.com> Date: Tue, 11 Feb 2025 15:21:28 +0000 Subject: [PATCH 08/27] feat(pylon): add pages plugin and use pm2 for dev server --- .changeset/proud-trees-end.md | 7 + examples/pages/.dockerignore | 15 + examples/pages/.gitignore | 175 ++ examples/pages/components.json | 21 + examples/pages/components/ui/button.tsx | 58 + examples/pages/globals.css | 192 ++ examples/pages/lib/utils.ts | 6 + examples/pages/package.json | 33 + examples/pages/pages/foo2/page.tsx | 13 + examples/pages/pages/layout.tsx | 9 + examples/pages/pages/page.tsx | 13 + examples/pages/pages/test2/page.tsx | 13 + examples/pages/postcss.config.js | 5 + examples/pages/pylon.d.ts | 10 + examples/pages/src/index.ts | 22 + examples/pages/tsconfig.json | 21 + packages/create-pylon/src/index.ts | 93 +- .../templates/bun/pages/components.json | 21 + .../bun/pages/components/ui/button.tsx | 57 + .../templates/bun/pages/globals.css | 179 ++ .../templates/bun/pages/lib/utils.ts | 6 + .../templates/bun/pages/package.json | 33 + .../templates/bun/pages/pages/layout.tsx | 9 + .../templates/bun/pages/pages/page.tsx | 13 + .../templates/bun/pages/postcss.config.js | 5 + .../templates/bun/pages/pylon.d.ts | 10 + .../templates/bun/pages/src/index.ts | 16 + .../templates/bun/pages/tsconfig.json | 20 + packages/pylon-dev/package.json | 7 +- .../pylon-dev/src/builder/build-client.ts | 156 ++ .../pylon-dev/src/builder/bundler/bundler.ts | 89 +- .../bundler/plugins/inject-code-plugin.ts | 5 +- .../builder/bundler/plugins/notify-plugin.ts | 6 +- packages/pylon-dev/src/builder/index.ts | 8 +- .../src/builder/update-file-if-changed.ts | 15 + packages/pylon-dev/src/index.ts | 256 +-- packages/pylon/package.json | 29 +- packages/pylon/src/app/index.ts | 3 +- packages/pylon/src/get-env.ts | 6 +- packages/pylon/src/index.ts | 5 +- .../src/plugins/use-pages/build/app-utils.ts | 144 ++ .../src/plugins/use-pages/build/index.ts | 152 ++ .../use-pages/build/plugins/image-plugin.ts | 85 + .../build/plugins/inject-app-hydration.ts | 72 + .../use-pages/build/plugins/postcss-plugin.ts | 28 + packages/pylon/src/plugins/use-pages/index.ts | 12 + .../plugins/use-pages/setup/app-loader.tsx | 33 + .../src/plugins/use-pages/setup/index.tsx | 344 ++++ packages/pylon/tsconfig.json | 56 +- pnpm-lock.yaml | 1745 ++++++++++++++++- 50 files changed, 3925 insertions(+), 406 deletions(-) create mode 100644 .changeset/proud-trees-end.md create mode 100644 examples/pages/.dockerignore create mode 100644 examples/pages/.gitignore create mode 100644 examples/pages/components.json create mode 100644 examples/pages/components/ui/button.tsx create mode 100644 examples/pages/globals.css create mode 100644 examples/pages/lib/utils.ts create mode 100644 examples/pages/package.json create mode 100644 examples/pages/pages/foo2/page.tsx create mode 100644 examples/pages/pages/layout.tsx create mode 100644 examples/pages/pages/page.tsx create mode 100644 examples/pages/pages/test2/page.tsx create mode 100644 examples/pages/postcss.config.js create mode 100644 examples/pages/pylon.d.ts create mode 100644 examples/pages/src/index.ts create mode 100644 examples/pages/tsconfig.json create mode 100644 packages/create-pylon/templates/bun/pages/components.json create mode 100644 packages/create-pylon/templates/bun/pages/components/ui/button.tsx create mode 100644 packages/create-pylon/templates/bun/pages/globals.css create mode 100644 packages/create-pylon/templates/bun/pages/lib/utils.ts create mode 100644 packages/create-pylon/templates/bun/pages/package.json create mode 100644 packages/create-pylon/templates/bun/pages/pages/layout.tsx create mode 100644 packages/create-pylon/templates/bun/pages/pages/page.tsx create mode 100644 packages/create-pylon/templates/bun/pages/postcss.config.js create mode 100644 packages/create-pylon/templates/bun/pages/pylon.d.ts create mode 100644 packages/create-pylon/templates/bun/pages/src/index.ts create mode 100644 packages/create-pylon/templates/bun/pages/tsconfig.json create mode 100644 packages/pylon-dev/src/builder/build-client.ts create mode 100644 packages/pylon-dev/src/builder/update-file-if-changed.ts create mode 100644 packages/pylon/src/plugins/use-pages/build/app-utils.ts create mode 100644 packages/pylon/src/plugins/use-pages/build/index.ts create mode 100644 packages/pylon/src/plugins/use-pages/build/plugins/image-plugin.ts create mode 100644 packages/pylon/src/plugins/use-pages/build/plugins/inject-app-hydration.ts create mode 100644 packages/pylon/src/plugins/use-pages/build/plugins/postcss-plugin.ts create mode 100644 packages/pylon/src/plugins/use-pages/index.ts create mode 100644 packages/pylon/src/plugins/use-pages/setup/app-loader.tsx create mode 100644 packages/pylon/src/plugins/use-pages/setup/index.tsx 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/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..0f972f8 --- /dev/null +++ b/examples/pages/package.json @@ -0,0 +1,33 @@ +{ + "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:^", + "@radix-ui/react-slot": "^1.1.2", + "bun-types": "^1.1.18", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.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": "workspace:^", + "@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..4de7c3d --- /dev/null +++ b/examples/pages/pages/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/test2/page.tsx b/examples/pages/pages/test2/page.tsx new file mode 100644 index 0000000..83825b0 --- /dev/null +++ b/examples/pages/pages/test2/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/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..f5cb32b --- /dev/null +++ b/examples/pages/pylon.d.ts @@ -0,0 +1,10 @@ +import '@getcronit/pylon' +import {useQuery} from './.pylon/client' + +declare module '@getcronit/pylon' { + interface Bindings {} + + interface Variables {} + + 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..ff491e7 --- /dev/null +++ b/examples/pages/tsconfig.json @@ -0,0 +1,21 @@ +{ + "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/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..ccb92fd --- /dev/null +++ b/packages/create-pylon/templates/bun/pages/package.json @@ -0,0 +1,33 @@ +{ + "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": "^3.0.0", + "@radix-ui/react-slot": "^1.1.2", + "bun-types": "^1.1.18", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.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": "^2.0.0", + "@types/react": "^19.0.8" + }, + "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..57c4226 --- /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' + +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..f5cb32b --- /dev/null +++ b/packages/create-pylon/templates/bun/pages/pylon.d.ts @@ -0,0 +1,10 @@ +import '@getcronit/pylon' +import {useQuery} from './.pylon/client' + +declare module '@getcronit/pylon' { + interface Bindings {} + + interface Variables {} + + 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-dev/package.json b/packages/pylon-dev/package.json index 2774976..e48b421 100644 --- a/packages/pylon-dev/package.json +++ b/packages/pylon-dev/package.json @@ -25,8 +25,7 @@ "@gqty/cli": "^4.2.0", "commander": "^12.1.0", "consola": "^3.2.3", - "dotenv": "^16.4.5", - "ps-tree": "^1.2.0" + "dotenv": "^16.4.5" }, "publishConfig": { "access": "public" @@ -38,9 +37,11 @@ "@types/ps-tree": "^1.1.6", "esbuild": "^0.23.1", "esbuild-plugin-tsc": "^0.4.0", + "pm2": "^5.4.3", "typescript": "^5.7.3" }, "peerDependencies": { - "@getcronit/pylon": "workspace:^2.0.0" + "@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 index 770e3f9..1dfa0a2 100644 --- a/packages/pylon-dev/src/builder/bundler/bundler.ts +++ b/packages/pylon-dev/src/builder/bundler/bundler.ts @@ -1,14 +1,16 @@ // bundler.ts -import {context} from 'esbuild' +import esbuild, {context} from 'esbuild' import esbuildPluginTsc from 'esbuild-plugin-tsc' -import type {PylonConfig} from '@getcronit/pylon' +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'] @@ -24,7 +26,7 @@ export class Bundler { this.outputDir = outputDir } - private async loadAndExexConfigPluginsBuild() { + private async initBuildPlugins(args: {onBuild: () => void}) { const configPath = path.join(process.cwd(), this.outputDir, 'index.js') let config: PylonConfig | undefined @@ -36,20 +38,42 @@ export class Bundler { console.error('Error loading config', e) } + const buildContexts: ReturnType>[] = [] + const plugins = config?.plugins || [] for (const plugin of plugins) { if (plugin.build) { - await 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 => { + result.outputFiles?.forEach(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], @@ -62,7 +86,9 @@ export class Bundler { plugins: [ notifyPlugin({ dir, - onBuild: options.onBuild + onBuild: async output => { + await options.onBuild?.(output) + } }), injectCodePlugin({ getBuildDefs: options.getBuildDefs, @@ -70,12 +96,59 @@ export class Bundler { }), esbuildPluginTsc({ tsconfigPath: path.join(process.cwd(), 'tsconfig.json') - }) + }), + writeOnEndPlugin ] }) - await this.loadAndExexConfigPluginsBuild() + const pluginCtxs = await this.initBuildPlugins({ + onBuild: () => { + options.onBuild?.({ + totalFiles: 0, + totalSize: 0, + schemaChanged: false, + duration: 0 + }) + } + }) + + 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 - return 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-dev/src/builder/bundler/plugins/inject-code-plugin.ts b/packages/pylon-dev/src/builder/bundler/plugins/inject-code-plugin.ts index c930d91..add9ad7 100644 --- a/packages/pylon-dev/src/builder/bundler/plugins/inject-code-plugin.ts +++ b/packages/pylon-dev/src/builder/bundler/plugins/inject-code-plugin.ts @@ -1,6 +1,7 @@ import {Plugin} from 'esbuild' import path from 'path' import fs from 'fs/promises' +import {updateFileIfChanged} from '../../update-file-if-changed' export interface InjectCodePluginOptions { getBuildDefs: () => { @@ -44,7 +45,7 @@ export const injectCodePlugin = ({ 'schema.graphql' ) - await fs.writeFile(typeDefsPath, typeDefs) + await updateFileIfChanged(typeDefsPath, typeDefs) // Write base resolvers to a file @@ -54,7 +55,7 @@ export const injectCodePlugin = ({ 'resolvers.js' ) - await fs.writeFile( + await updateFileIfChanged( resolversPath, `export const resolvers = ${preparedResolvers}` ) diff --git a/packages/pylon-dev/src/builder/bundler/plugins/notify-plugin.ts b/packages/pylon-dev/src/builder/bundler/plugins/notify-plugin.ts index 74c8744..c9b51ea 100644 --- a/packages/pylon-dev/src/builder/bundler/plugins/notify-plugin.ts +++ b/packages/pylon-dev/src/builder/bundler/plugins/notify-plugin.ts @@ -9,7 +9,7 @@ export interface NotifyPluginOptions { totalSize: number schemaChanged: boolean duration: number - }) => void + }) => Promise | void dir: string } @@ -79,10 +79,10 @@ ${error.detail ? error.detail : ''}`) } if (onBuild) { - onBuild({ + await onBuild({ totalFiles, totalSize, - schemaChanged: true, + schemaChanged, duration }) } diff --git a/packages/pylon-dev/src/builder/index.ts b/packages/pylon-dev/src/builder/index.ts index 1b9a72b..ad0a69a 100644 --- a/packages/pylon-dev/src/builder/index.ts +++ b/packages/pylon-dev/src/builder/index.ts @@ -18,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 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 f8fafbb..973fb1e 100644 --- a/packages/pylon-dev/src/index.ts +++ b/packages/pylon-dev/src/index.ts @@ -1,15 +1,14 @@ #!/usr/bin/env node -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 psTree from 'ps-tree' -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() @@ -22,13 +21,15 @@ program const ctx = await build({ sfiFilePath: './src/index.ts', outputFilePath: './.pylon', - onBuild: async ({totalFiles, totalSize, duration}) => { + onBuild: async ({totalFiles, totalSize, duration, schemaChanged}) => { await telemetry.sendBuildEvent({ duration, totalFiles, totalSize, isDevelopment: false }) + + await buildClient({schemaChanged}) } }) @@ -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,193 +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()}`) - - const ctx = await build({ - sfiFilePath: './src/index.ts', - outputFilePath: `./.pylon`, - onBuild: 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.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 + pm2.connect(async function (err) { + if (err) { + consola.error(err) + process.exit(1) + } - We value your feedback—help us make Pylon even better! - `) + const ctx = await build({ + sfiFilePath: './src/index.ts', + outputFilePath: `./.pylon`, + onBuild: async ({schemaChanged, totalFiles, totalSize, duration}) => { + await buildClient({schemaChanged}) + // Restart the server + // pm2.restart('pylon-dev', function (err) { + // if (err) throw err + // }) } + }) - if (schemaChanged) { - await telemetry.sendBuildEvent({ - duration, - totalFiles, - totalSize, - isDevelopment: true - }) - } - } - }) + await ctx.watch() - async function killProcessAndChildren(pid: number) { - await ctx.cancel() - psTree(pid, (err, children) => { + pm2.launchBus((err, bus) => { if (err) { - console.error('Error fetching child processes:', err) + consola.error(err) return } - // Kill the parent process and all its children - const allPids = children.map(child => parseInt(child.PID)).concat(pid) - allPids.forEach(childPid => { - try { - process.kill(childPid, 'SIGINT') - } catch (error) {} + bus.on('log:out', data => { + consola.log(data.data) }) - }) - } - - let currentProc: ChildProcess | null = null - - let serve = async (shouldGenerateClient: boolean = false) => { - if (currentProc?.pid) { - // Remove all listeners to prevent the pylon dev server from crashing - currentProc.removeAllListeners() - - await killProcessAndChildren(currentProc.pid) - } - - 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` - ) + bus.on('log:err', data => { + consola.error(data.data) + }) }) - 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) - } + 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' } - } - - generateWithRetry() - } - } - - try { - await ctx.watch() - } 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?.pid) { - proc.removeAllListeners() + } as any, + function (err, apps) { + // Check if it is a duplicate start + if (err) throw err - await killProcessAndChildren(proc.pid) - } - } - - process.on('SIGINT', async code => { - try { - if (currentProc?.pid) { - currentProc.removeAllListeners() - - await killProcessAndChildren(currentProc.pid) + 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 c5b8560..21e6a0f 100644 --- a/packages/pylon/package.json +++ b/packages/pylon/package.json @@ -23,16 +23,21 @@ "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": "^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" }, @@ -40,6 +45,28 @@ "node": ">=18.0.0" }, "devDependencies": { - "@sentry/types": "^8.54.0" + "@sentry/types": "^8.54.0", + "postcss-load-config": "^6.0.1" + }, + "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/index.ts b/packages/pylon/src/app/index.ts index 54f465f..d507eca 100644 --- a/packages/pylon/src/app/index.ts +++ b/packages/pylon/src/app/index.ts @@ -1,6 +1,7 @@ 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' @@ -20,7 +21,7 @@ app.use('*', async (c, next) => { }) }) -app.use('*', logger()) +app.use('*', except(['/__pylon/static/*'], logger())) app.use((c, next) => { // @ts-ignore 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 2735e31..0b9a8fc 100644 --- a/packages/pylon/src/index.ts +++ b/packages/pylon/src/index.ts @@ -18,8 +18,11 @@ export {getEnv} from './get-env.js' export {createDecorator} from './create-decorator.js' export {createPubSub as experimentalCreatePubSub} from 'graphql-yoga' +export {usePages, type PageProps, PageData} 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 = {}, @@ -28,7 +31,7 @@ export type Plugin< > = YogaPlugin & { middleware?: MiddlewareHandler setup?: (app: typeof pylonApp) => void - build?: () => Promise + build?: (args: {onBuild: () => void}) => Promise, 'serve'>> } export type PylonConfig = { 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..fd36569 --- /dev/null +++ b/packages/pylon/src/plugins/use-pages/build/app-utils.ts @@ -0,0 +1,144 @@ +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 { Routes, Route } from 'react-router'; + ${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..adfc8d1 --- /dev/null +++ b/packages/pylon/src/plugins/use-pages/build/index.ts @@ -0,0 +1,152 @@ +import path from 'path' +import {Plugin} from '../../..' +import {generateAppFile, getPageRoutes} from './app-utils' +import chokidar 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) + + // 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) { + console.log('Updated app file') + } + } + + const writeOnEndPlugin: esbuild.Plugin = { + name: 'write-on-end', + setup(build) { + build.onEnd(async result => { + result.outputFiles?.forEach(async file => { + await fs.mkdir(path.dirname(file.path), {recursive: true}) + await updateFileIfChanged(file.path, file.text) + }) + }) + } + } + + let pagesWatcher: chokidar.FSWatcher | null = null + + const clientCtx = await esbuild.context({ + write: false, + metafile: true, + 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: false, + 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(), + plugins: [imagePlugin, postcssPlugin, writeOnEndPlugin], + publicPath: '/__pylon/static', + assetNames: 'assets/[name]-[hash]', + chunkNames: 'chunks/[name]-[hash]', + format: 'esm', + platform: 'node', + entryPoints: ['.pylon/app.tsx'], + outdir: DIST_PAGES_DIR, + bundle: true, + packages: 'external', + splitting: false, + minify: false, + loader: { + // Map file extensions to the file loader + + '.svg': 'file', + '.woff': 'file', + '.woff2': 'file' + } + }) + + return { + watch: async () => { + console.log('Watching pages directory...') + pagesWatcher = chokidar.watch('pages', {ignoreInitial: false}) + + pagesWatcher.on('all', (event, path) => { + if (['add', 'change', 'unlink'].includes(event)) { + buildAppFile() + } + }) + + await Promise.all([clientCtx.watch(), serverCtx.watch()]) + }, + dispose: async () => { + console.log('Disposing pages') + + if (pagesWatcher) { + pagesWatcher.close() + } + + Promise.all([clientCtx.dispose(), serverCtx.dispose()]) + }, + rebuild: async () => { + console.log('Rebuilding pages') + await buildAppFile() + + 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..7bf4990 --- /dev/null +++ b/packages/pylon/src/plugins/use-pages/build/plugins/inject-app-hydration.ts @@ -0,0 +1,72 @@ +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 {BrowserRouter} from 'react-router' + import React, {useMemo} from 'react' + + const pylonData = window.__PYLON_DATA__ + + console.log('pylonData', pylonData) + + 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..159cfd1 --- /dev/null +++ b/packages/pylon/src/plugins/use-pages/setup/index.tsx @@ -0,0 +1,344 @@ +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) => { + if (c.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) + } + ) + + 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)) { + throw new Error('File not found') + } + + 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 => { + console.log('image optimization route') + 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 { + console.log('imagePath', imagePath) + 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) + + console.log('quality', quality) + + // Optimize the image using sharp + const image = await sharp(imagePath) + .resize(finalWidth, finalHeight) + .toFormat(imageFormat, { + quality + }) + .toFile(cachePath) + + console.log('image', image) + + 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/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/pnpm-lock.yaml b/pnpm-lock.yaml index f215855..7420c3c 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,49 @@ importers: specifier: workspace:^ version: link:../../packages/pylon-dev + examples/pages: + dependencies: + '@getcronit/pylon': + specifier: workspace:^ + version: link:../../packages/pylon + '@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 + 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 + '@types/react': + specifier: ^19.0.8 + version: 19.0.8 + packages/create-pylon: dependencies: '@getcronit/pylon-telemetry': @@ -172,6 +215,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 +227,18 @@ 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) 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 @@ -202,6 +257,24 @@ importers: openid-client: specifier: ^6.1.7 version: 6.1.7 + postcss: + specifier: ^8.5.1 + version: 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 @@ -212,6 +285,9 @@ importers: '@sentry/types': specifier: ^8.54.0 version: 8.54.0 + postcss-load-config: + specifier: ^6.0.1 + version: 6.0.1(jiti@2.4.2)(postcss@8.5.1) packages/pylon-dev: dependencies: @@ -233,9 +309,9 @@ importers: dotenv: specifier: ^16.4.5 version: 16.4.7 - ps-tree: - specifier: ^1.2.0 - version: 1.2.0 + graphql: + specifier: ^16.9.0 + version: 16.10.0 devDependencies: '@types/ps-tree': specifier: ^1.1.6 @@ -246,6 +322,9 @@ importers: esbuild-plugin-tsc: specifier: ^0.4.0 version: 0.4.0(typescript@5.7.3) + pm2: + specifier: ^5.4.3 + version: 5.4.3 typescript: specifier: ^5.7.3 version: 5.7.3 @@ -258,6 +337,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'} @@ -685,6 +768,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'} @@ -1408,6 +1494,22 @@ packages: 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: @@ -1518,6 +1620,111 @@ 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'} @@ -1883,6 +2090,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'} @@ -1898,6 +2119,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==} @@ -2091,9 +2343,91 @@ packages: resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} + '@tailwindcss/node@4.0.4': + resolution: {integrity: sha512-VLFq80IyoV1hsHPcCm1mmlyPyUT6NlovQLoO2y7PGm84mW94ZrNJ7ax5H6K4M7Aj/fdMfem5IX7Ka+LXWZpDGg==} + + '@tailwindcss/oxide-android-arm64@4.0.4': + resolution: {integrity: sha512-hiGUA8d15ynH/LdurQNObnuTjri7i4ApAzhesusNxoz4br7vhZ6QO5CFgniYAYNZvf8Q8wCTBg0nj61RalBeVQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.0.4': + resolution: {integrity: sha512-vTca+ysNl8BYmYJTni9pLC+L3S4bvrj0ai1eUV3yYXYa5Cpugr5Fni6ylV0gcTZOyETm2RCCJ/0azU6MgqE6HA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.0.4': + resolution: {integrity: sha512-rxPWb5AQJ/aAM/5UDCjaQaMYIcrZHe/Dr9xZu9+P9nJf3WAweNsGi+e+SW9EYGRiF3hkBtP2dvxVNAkTiEbNQQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.0.4': + resolution: {integrity: sha512-UOnRHzlS5V5cxaMgBo6rk1E92tTDUtO/falc9vOpNiRdWhNcofYNN9zvZP63Wuo5FC6/XCyAnJo6OXUm18TwrQ==} + 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-arm64-gnu@4.0.4': + resolution: {integrity: sha512-5a7WD30nVdI7Rl1ohZ0Ojj9t5yRnZkJBizvh3uIW52h9UeNpon8TfoknF6rU/TwD32dQ0Cjo5CcCHtQ2wW9PCA==} + 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-x64-gnu@4.0.4': + resolution: {integrity: sha512-K5dBjGHzby9eyUBwy9YHFhKY+5i8fzIBZM1NBWp6L2xpM7OzW9WJDgNcgESkZami9g+EozkQLt3ZmMZHAieXkw==} + 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-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-x64-msvc@4.0.4': + resolution: {integrity: sha512-WzMA0aL/24/JyNrv2Yhr/Og24QGRPWJMjRyCJ4HRoGMs6/8svOQKrnnZ/9LUFwn56irAndFBjWWnlaqykH+g5Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.0.4': + resolution: {integrity: sha512-vPpu30KFLiGyPOoElkYt8WRvzGKVrrOz49KpfiGGtnQGmyUpL8VCbJzzEEcpKT5BpaaQidhFok+OXscf6hHjOQ==} + engines: {node: '>= 10'} + + '@tailwindcss/postcss@4.0.4': + resolution: {integrity: sha512-Up8fB+DUhy8qvDqlHgZAWaL5iVEbypcuOjzlW4K6EyU+aGEvXK0/wrcKBKOTvg3KKP5givJMexJ0aG1hDPOuRg==} + + '@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==} @@ -2127,6 +2461,9 @@ packages: '@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==} @@ -2206,6 +2543,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'} @@ -2274,6 +2617,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==} @@ -2281,6 +2631,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==} @@ -2292,6 +2649,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==} @@ -2309,6 +2670,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==} @@ -2371,6 +2740,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'} @@ -2392,6 +2765,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==} @@ -2406,6 +2782,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'} @@ -2423,6 +2802,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'} @@ -2434,6 +2817,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==} @@ -2453,6 +2840,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==} @@ -2460,6 +2851,9 @@ packages: resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} 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'} @@ -2512,6 +2906,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==} @@ -2524,6 +2922,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==} @@ -2546,9 +2947,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==} @@ -2558,9 +2969,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'} @@ -2581,6 +3015,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'} @@ -2593,6 +3031,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==} @@ -2719,9 +3166,6 @@ packages: duplexer2@0.1.4: resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} - duplexer@0.1.2: - resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} - eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -2743,6 +3187,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'} @@ -2813,19 +3265,38 @@ 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==} - event-stream@3.3.4: - resolution: {integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==} + 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==} @@ -2850,6 +3321,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==} @@ -2857,6 +3331,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==} @@ -2869,6 +3346,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==} @@ -2914,6 +3394,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'} @@ -2921,15 +3410,15 @@ 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==} from2@2.3.0: resolution: {integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==} - from@0.1.7: - resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==} - fs-extra@10.1.0: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} @@ -2994,9 +3483,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'} @@ -3017,6 +3521,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'} @@ -3025,6 +3532,9 @@ packages: resolution: {integrity: sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==} engines: {node: '>=18'} + globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + gqty@3.3.0: resolution: {integrity: sha512-5ky4L771GcW2LDPR/k/5lYxn8f440K3oiB4Gglf29mqTgVNjD7V+K+4eiaJRGvduR7gSiaBD6hkFMGoLaYshnw==} engines: {node: ^12.20.0 || >=14.13.0} @@ -3040,6 +3550,21 @@ packages: graphql-ws: optional: true + 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 + graceful-fs@4.2.10: resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} @@ -3198,6 +3723,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'} @@ -3304,9 +3833,16 @@ packages: resolution: {integrity: sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==} engines: {node: '>= 0.6.0'} + 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==} @@ -3321,6 +3857,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'} @@ -3332,6 +3871,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'} @@ -3371,12 +3913,84 @@ packages: kuler@2.0.0: resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + lazy@1.0.11: + resolution: {integrity: sha512-Y+CjUfLmIpoUCCRl0ub4smrYtGGr5AOa2AKOaWelGHOGz33X/Y/KizefGqbkwfz44+cnq/+9habclf8vOmu2LA==} + engines: {node: '>=0.2.0'} - load-json-file@4.0.0: - resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} - engines: {node: '>=4'} + 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==} + + load-json-file@4.0.0: + resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} + engines: {node: '>=4'} local-pkg@0.5.1: resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==} @@ -3460,6 +4074,19 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru-cache@6.0.0: + 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==} @@ -3470,9 +4097,6 @@ packages: resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==} engines: {node: '>=0.10.0'} - map-stream@0.1.0: - resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==} - marked-terminal@7.3.0: resolution: {integrity: sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==} engines: {node: '>=16.0.0'} @@ -3541,6 +4165,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==} @@ -3561,6 +4190,9 @@ 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} @@ -3573,12 +4205,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==} @@ -3616,6 +4257,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'} @@ -3706,6 +4351,10 @@ 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==} @@ -3747,6 +4396,10 @@ packages: outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} + 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'} @@ -3823,12 +4476,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==} @@ -3926,9 +4590,6 @@ packages: pathval@1.1.1: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} - pause-stream@0.0.11: - resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} - pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} @@ -3947,6 +4608,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'} @@ -3966,6 +4635,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} @@ -4008,13 +4721,18 @@ 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==} - ps-tree@1.2.0: - resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==} - engines: {node: '>= 0.10'} - hasBin: true + 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==} @@ -4026,9 +4744,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'} @@ -4045,6 +4787,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==} @@ -4070,6 +4816,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'} @@ -4126,9 +4876,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'} @@ -4136,6 +4892,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'} @@ -4171,6 +4933,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'} @@ -4179,9 +4946,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'} @@ -4225,9 +4999,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,15 +5050,18 @@ packages: split2@1.0.0: resolution: {integrity: sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==} - split@0.3.3: - resolution: {integrity: sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==} - sponge-case@1.0.1: resolution: {integrity: sha512-dblb9Et4DAtiZ5YSUZHLl4XhH4uK80GhAZrVXdN4O2P4gQ40Wa5UIOPUHlA/nFd2PLblBZWUioLMMAVrgpoYcA==} 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==} @@ -4292,9 +5081,6 @@ packages: stream-combiner2@1.1.1: resolution: {integrity: sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==} - stream-combiner@0.0.4: - resolution: {integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==} - streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -4368,6 +5154,27 @@ 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==} + + 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'} @@ -4401,13 +5208,13 @@ packages: through2@2.0.5: resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} - through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - time-span@5.1.0: 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==} @@ -4444,12 +5251,25 @@ packages: 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'} @@ -4565,6 +5385,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==} @@ -4636,6 +5461,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==} @@ -4708,6 +5537,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'} @@ -4734,6 +5575,9 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yargs-parser@20.2.9: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} engines: {node: '>=10'} @@ -4774,6 +5618,8 @@ packages: snapshots: + '@alloc/quick-lru@5.2.0': {} + '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.8 @@ -5276,7 +6122,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 @@ -5286,7 +6132,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: @@ -5353,6 +6199,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 @@ -5753,6 +6604,21 @@ snapshots: - 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) @@ -5913,6 +6779,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 @@ -6410,6 +7351,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': @@ -6430,6 +7422,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': @@ -6661,14 +7674,80 @@ snapshots: '@sindresorhus/merge-streams@4.0.0': {} - '@types/connect@3.4.36': + '@tailwindcss/node@4.0.4': dependencies: - '@types/node': 18.19.75 + enhanced-resolve: 5.18.1 + jiti: 2.4.2 + tailwindcss: 4.0.4 - '@types/estree@1.0.6': {} + '@tailwindcss/oxide-android-arm64@4.0.4': + optional: true - '@types/mute-stream@0.0.4': - dependencies: + '@tailwindcss/oxide-darwin-arm64@4.0.4': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.0.4': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.0.4': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.4': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.0.4': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.0.4': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.0.4': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.0.4': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.0.4': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.0.4': + 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/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 + + '@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': + dependencies: '@types/node': 18.19.75 '@types/mysql@2.15.26': @@ -6703,6 +7782,10 @@ snapshots: '@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': {} @@ -6795,6 +7878,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: @@ -6848,10 +7937,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): @@ -6889,6 +7996,8 @@ snapshots: balanced-match@1.0.2: {} + basic-ftp@5.0.5: {} + before-after-hook@3.0.2: {} better-path-resolve@1.0.0: @@ -6901,6 +8010,10 @@ snapshots: blake3-wasm@2.1.5: {} + blessed@0.1.81: {} + + bodec@0.1.0: {} + bottleneck@2.19.5: {} brace-expansion@1.1.11: @@ -6980,6 +8093,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 @@ -7019,6 +8137,8 @@ snapshots: chardet@0.7.0: {} + charm@0.1.2: {} + check-error@1.0.3: dependencies: get-func-name: 2.0.2 @@ -7039,6 +8159,10 @@ snapshots: 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: @@ -7060,6 +8184,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: @@ -7074,6 +8202,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 @@ -7096,6 +8226,11 @@ snapshots: color-convert: 1.9.3 color-string: 1.9.1 + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + colorspace@1.1.4: dependencies: color: 3.2.1 @@ -7103,6 +8238,8 @@ snapshots: commander@12.1.0: {} + commander@2.15.1: {} + common-tags@1.8.2: {} compare-func@2.0.0: @@ -7151,6 +8288,8 @@ snapshots: cookie@0.7.2: {} + cookie@1.0.2: {} + core-util-is@1.0.3: {} cosmiconfig@9.0.0(typescript@5.7.3): @@ -7162,6 +8301,8 @@ snapshots: optionalDependencies: typescript: 5.7.3 + croner@4.1.97: {} + cross-fetch@3.2.0: dependencies: node-fetch: 2.7.0 @@ -7190,16 +8331,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 @@ -7212,6 +8371,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 @@ -7227,6 +8392,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: {} @@ -7257,12 +8426,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: {} @@ -7270,8 +8441,6 @@ snapshots: dependencies: readable-stream: 2.3.8 - duplexer@0.1.2: {} - eastasianwidth@0.2.0: {} ecdsa-sig-formatter@1.0.11: @@ -7288,6 +8457,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 @@ -7455,23 +8633,31 @@ 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 - event-stream@3.3.4: - dependencies: - duplexer: 0.1.2 - from: 0.1.7 - map-stream: 0.1.0 - pause-stream: 0.0.11 - split: 0.3.3 - stream-combiner: 0.0.4 - through: 2.3.8 + esutils@2.0.3: {} + + eventemitter2@0.4.14: {} + + eventemitter2@5.0.1: {} + + eventemitter2@6.4.9: {} execa@5.1.1: dependencies: @@ -7522,6 +8708,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: @@ -7532,6 +8724,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 @@ -7554,6 +8748,8 @@ snapshots: transitivePeerDependencies: - encoding + fclone@1.0.11: {} + fecha@4.2.3: {} figures@2.0.0: @@ -7594,6 +8790,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 @@ -7601,6 +8801,8 @@ snapshots: forwarded-parse@2.1.2: {} + fraction.js@4.3.7: {} + frail-map@1.0.10: {} from2@2.3.0: @@ -7608,8 +8810,6 @@ snapshots: inherits: 2.0.4 readable-stream: 2.3.8 - from@0.1.7: {} - fs-extra@10.1.0: dependencies: graceful-fs: 4.2.11 @@ -7669,6 +8869,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 @@ -7678,6 +8886,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 @@ -7704,6 +8918,8 @@ snapshots: globals@11.12.0: {} + globalyzer@0.1.0: {} + globby@11.1.0: dependencies: array-union: 2.1.0 @@ -7722,6 +8938,8 @@ snapshots: slash: 5.1.0 unicorn-magic: 0.1.0 + globrex@0.1.2: {} + gqty@3.3.0(graphql@16.10.0): dependencies: debounce-microtasks: 0.1.8 @@ -7738,6 +8956,21 @@ snapshots: optionalDependencies: graphql: 16.10.0 + 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 + graceful-fs@4.2.10: {} graceful-fs@4.2.11: {} @@ -7892,6 +9125,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 @@ -7975,8 +9213,17 @@ snapshots: java-properties@1.0.2: {} + 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: {} js-tokens@9.0.1: {} @@ -7990,12 +9237,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: @@ -8044,6 +9296,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: @@ -8131,6 +9432,16 @@ snapshots: dependencies: yallist: 3.1.1 + lru-cache@6.0.0: + 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 @@ -8141,8 +9452,6 @@ snapshots: map-cache@0.2.2: {} - map-stream@0.1.0: {} - marked-terminal@7.3.0(marked@12.0.2): dependencies: ansi-escapes: 7.0.0 @@ -8223,6 +9532,8 @@ snapshots: minipass@7.1.2: {} + mkdirp@1.0.4: {} + mlly@1.7.4: dependencies: acorn: 8.14.0 @@ -8240,6 +9551,8 @@ snapshots: mustache@4.2.0: {} + mute-stream@0.0.8: {} + mute-stream@1.0.0: {} mz@2.7.0: @@ -8250,10 +9563,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 @@ -8291,6 +9614,8 @@ snapshots: normalize-path@3.0.0: {} + normalize-range@0.1.2: {} + normalize-url@8.0.1: {} npm-run-path@4.0.1: @@ -8308,6 +9633,11 @@ snapshots: npm@10.9.2: {} + nssocket@0.6.0: + dependencies: + eventemitter2: 0.4.14 + lazy: 1.0.11 + nullthrows@1.1.1: {} oauth4webapi@3.1.4: {} @@ -8343,6 +9673,8 @@ snapshots: outdent@0.5.0: {} + p-debounce@4.0.0: {} + p-defer@3.0.0: {} p-each-series@2.2.0: {} @@ -8401,10 +9733,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 @@ -8493,10 +9845,6 @@ snapshots: pathval@1.1.1: {} - pause-stream@0.0.11: - dependencies: - through: 2.3.8 - pg-int8@1.0.1: {} pg-protocol@1.7.0: {} @@ -8513,6 +9861,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: {} @@ -8532,6 +9889,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 @@ -8568,11 +10007,26 @@ snapshots: dependencies: asap: 2.0.6 + promptly@2.2.0: + dependencies: + read: 1.0.7 + proto-list@1.2.4: {} - ps-tree@1.2.0: + proxy-agent@6.3.1: dependencies: - event-stream: 3.3.4 + 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: {} @@ -8585,8 +10039,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 @@ -8615,6 +10090,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 @@ -8651,6 +10130,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 @@ -8727,12 +10214,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 @@ -8806,6 +10301,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: @@ -8814,8 +10313,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 @@ -8850,11 +10377,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: @@ -8891,16 +10433,16 @@ snapshots: dependencies: through2: 2.0.5 - split@0.3.3: - dependencies: - through: 2.3.8 - sponge-case@1.0.1: dependencies: tslib: 2.8.1 sprintf-js@1.0.3: {} + sprintf-js@1.1.2: {} + + sprintf-js@1.1.3: {} + stack-trace@0.0.10: {} stackback@0.0.2: {} @@ -8919,10 +10461,6 @@ snapshots: duplexer2: 0.1.4 readable-stream: 2.3.8 - stream-combiner@0.0.4: - dependencies: - duplexer: 0.1.2 - streamsearch@1.1.0: {} string-width@4.2.3: @@ -8989,6 +10527,19 @@ 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: {} + + tapable@2.2.1: {} + temp-dir@2.0.0: {} temp-dir@3.0.0: {} @@ -9025,12 +10576,15 @@ snapshots: readable-stream: 2.3.8 xtend: 4.0.2 - through@2.3.8: {} - time-span@5.1.0: 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: {} @@ -9061,10 +10615,21 @@ snapshots: 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: {} @@ -9151,6 +10716,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: @@ -9160,13 +10729,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 @@ -9178,7 +10747,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 @@ -9186,8 +10755,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 @@ -9206,8 +10776,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 @@ -9221,6 +10791,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: @@ -9341,6 +10918,8 @@ snapshots: wrappy@1.0.2: {} + ws@7.5.10: {} + ws@8.18.0: {} xtend@4.0.2: {} @@ -9351,6 +10930,8 @@ snapshots: yallist@3.1.1: {} + yallist@4.0.0: {} + yargs-parser@20.2.9: {} yargs-parser@21.1.1: {} From 90e08639048050fad4dcaf65b6e5937f746c6103 Mon Sep 17 00:00:00 2001 From: Nico Schett <52858351+schettn@users.noreply.github.com> Date: Tue, 11 Feb 2025 15:33:35 +0000 Subject: [PATCH 09/27] add chokidar as a dev dependency --- packages/pylon/package.json | 1 + pnpm-lock.yaml | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/packages/pylon/package.json b/packages/pylon/package.json index 9330e55..c5ee44d 100644 --- a/packages/pylon/package.json +++ b/packages/pylon/package.json @@ -46,6 +46,7 @@ }, "devDependencies": { "@sentry/types": "^8.54.0", + "chokidar": "^4.0.3", "postcss-load-config": "^6.0.1" }, "peerDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7420c3c..fe3ee49 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -285,6 +285,9 @@ importers: '@sentry/types': specifier: ^8.54.0 version: 8.54.0 + chokidar: + specifier: ^4.0.3 + version: 4.0.3 postcss-load-config: specifier: ^6.0.1 version: 6.0.1(jiti@2.4.2)(postcss@8.5.1) @@ -2775,6 +2778,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'} @@ -4802,6 +4809,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==} @@ -8155,6 +8166,10 @@ 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: {} @@ -10114,6 +10129,8 @@ snapshots: dependencies: picomatch: 2.3.1 + readdirp@4.1.1: {} + regenerator-runtime@0.14.1: {} registry-auth-token@5.0.3: From b2b63f4e67c55542413f7be6d62ce8139cfcbdbe Mon Sep 17 00:00:00 2001 From: Nico Schett <52858351+schettn@users.noreply.github.com> Date: Tue, 11 Feb 2025 15:37:31 +0000 Subject: [PATCH 10/27] update chokidar import to use FSWatcher type --- packages/pylon/src/plugins/use-pages/build/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/pylon/src/plugins/use-pages/build/index.ts b/packages/pylon/src/plugins/use-pages/build/index.ts index adfc8d1..a3a3c96 100644 --- a/packages/pylon/src/plugins/use-pages/build/index.ts +++ b/packages/pylon/src/plugins/use-pages/build/index.ts @@ -1,7 +1,7 @@ import path from 'path' import {Plugin} from '../../..' import {generateAppFile, getPageRoutes} from './app-utils' -import chokidar from 'chokidar' +import chokidar, {FSWatcher} from 'chokidar' import fs from 'fs/promises' import esbuild from 'esbuild' import {injectAppHydrationPlugin} from './plugins/inject-app-hydration' @@ -52,7 +52,7 @@ export const build: Plugin['build'] = async () => { } } - let pagesWatcher: chokidar.FSWatcher | null = null + let pagesWatcher: FSWatcher | null = null const clientCtx = await esbuild.context({ write: false, @@ -116,7 +116,7 @@ export const build: Plugin['build'] = async () => { console.log('Watching pages directory...') pagesWatcher = chokidar.watch('pages', {ignoreInitial: false}) - pagesWatcher.on('all', (event, path) => { + pagesWatcher!.on('all', (event, path) => { if (['add', 'change', 'unlink'].includes(event)) { buildAppFile() } From 6f2521705b2882fe77d53141a59749d7cbd96d5c Mon Sep 17 00:00:00 2001 From: Nico Schett <52858351+schettn@users.noreply.github.com> Date: Tue, 11 Feb 2025 15:42:12 +0000 Subject: [PATCH 11/27] chore: update pylon and pylon-dev dependencies to canary version --- packages/create-pylon/templates/bun/pages/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/create-pylon/templates/bun/pages/package.json b/packages/create-pylon/templates/bun/pages/package.json index ccb92fd..7304f91 100644 --- a/packages/create-pylon/templates/bun/pages/package.json +++ b/packages/create-pylon/templates/bun/pages/package.json @@ -9,7 +9,7 @@ "build": "pylon build" }, "dependencies": { - "@getcronit/pylon": "^3.0.0", + "@getcronit/pylon": "canary", "@radix-ui/react-slot": "^1.1.2", "bun-types": "^1.1.18", "class-variance-authority": "^0.7.1", @@ -22,7 +22,7 @@ "tailwindcss-animate": "^1.0.7" }, "devDependencies": { - "@getcronit/pylon-dev": "^2.0.0", + "@getcronit/pylon-dev": "canary", "@types/react": "^19.0.8" }, "repository": { From 3d13feb5a3ac3ab93f3fe59bd76200cba5e8df14 Mon Sep 17 00:00:00 2001 From: Nico Schett <52858351+schettn@users.noreply.github.com> Date: Tue, 11 Feb 2025 15:57:23 +0000 Subject: [PATCH 12/27] update @gqty/cli to 4.2.5 and add pm2, esbuild, and esbuild-plugin-tsc as dependencies --- packages/pylon-dev/package.json | 10 +- pnpm-lock.yaml | 343 +++++++++++++++++++++++++++----- 2 files changed, 302 insertions(+), 51 deletions(-) diff --git a/packages/pylon-dev/package.json b/packages/pylon-dev/package.json index d570bd7..94349ee 100644 --- a/packages/pylon-dev/package.json +++ b/packages/pylon-dev/package.json @@ -22,10 +22,13 @@ "homepage": "https://pylon.cronit.io", "dependencies": { "@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" + "dotenv": "^16.4.5", + "esbuild": "^0.23.1", + "esbuild-plugin-tsc": "^0.4.0", + "pm2": "^5.4.3" }, "publishConfig": { "access": "public" @@ -35,9 +38,6 @@ }, "devDependencies": { "@types/ps-tree": "^1.1.6", - "esbuild": "^0.23.1", - "esbuild-plugin-tsc": "^0.4.0", - "pm2": "^5.4.3", "typescript": "^5.7.3" }, "peerDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fe3ee49..626137e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -301,8 +301,8 @@ importers: 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 @@ -312,22 +312,22 @@ importers: dotenv: specifier: ^16.4.5 version: 16.4.7 - graphql: - specifier: ^16.9.0 - version: 16.10.0 - devDependencies: - '@types/ps-tree': - specifier: ^1.1.6 - version: 1.1.6 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 @@ -756,10 +756,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==} @@ -1488,11 +1488,11 @@ 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 @@ -1589,6 +1589,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'} @@ -1732,10 +1738,37 @@ packages: 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'} @@ -1744,10 +1777,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'} @@ -1756,30 +1807,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'} @@ -1788,6 +1902,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'} @@ -2858,6 +2981,10 @@ 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==} @@ -3542,8 +3669,8 @@ packages: globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} - gqty@3.3.0: - resolution: {integrity: sha512-5ky4L771GcW2LDPR/k/5lYxn8f440K3oiB4Gglf29mqTgVNjD7V+K+4eiaJRGvduR7gSiaBD6hkFMGoLaYshnw==} + 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 @@ -3557,8 +3684,8 @@ packages: graphql-ws: optional: true - gqty@3.4.0-canary-20250207102900.644ad9fdeafa6318516627577b1d4d754d5c5a98: - resolution: {integrity: sha512-s/50A1kLL4625nmQ6R8Esl6w6/1Oao0HSJu43wugY6XvFK9G6ag7HUrR5IEoTV7/Ow/Vi0lj3bEprcZT6n0QtA==} + gqty@3.4.1: + resolution: {integrity: sha512-rd/FdqykHaGBfbFnmXQwN02QFz7p/MygGh/QBdTeLm2fP4JzoEvpIQyL4zt7j8b1ZGWv2OMbNotI4udWBsKFvQ==} engines: {node: ^12.20.0 || >=14.13.0} peerDependencies: graphql: ^16.9.0 @@ -4204,6 +4331,10 @@ packages: 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==} @@ -4372,10 +4503,6 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - object-hash@3.0.0: - resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} - engines: {node: '>= 6'} - ohash@1.1.4: resolution: {integrity: sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==} @@ -6194,9 +6321,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: @@ -6588,27 +6715,27 @@ 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 @@ -6634,13 +6761,13 @@ snapshots: 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 @@ -6651,7 +6778,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 @@ -6673,7 +6800,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 @@ -6688,7 +6815,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 @@ -6698,7 +6825,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 @@ -6729,7 +6856,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: @@ -6753,11 +6880,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 @@ -6873,11 +7008,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 @@ -6899,12 +7064,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': @@ -6912,17 +7093,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 @@ -6936,12 +7139,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 @@ -6949,6 +7175,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 @@ -6957,6 +7192,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 @@ -6965,6 +7210,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 @@ -8253,6 +8502,8 @@ snapshots: commander@12.1.0: {} + commander@13.1.0: {} + commander@2.15.1: {} common-tags@1.8.2: {} @@ -8955,7 +9206,7 @@ snapshots: globrex@0.1.2: {} - gqty@3.3.0(graphql@16.10.0): + gqty@3.4.0-canary-20250207102900.644ad9fdeafa6318516627577b1d4d754d5c5a98(graphql@16.10.0): dependencies: debounce-microtasks: 0.1.8 flatted: 3.3.2 @@ -8966,12 +9217,11 @@ snapshots: just-safe-get: 4.2.0 just-safe-set: 4.2.1 multidict: 1.0.9 - object-hash: 3.0.0 p-defer: 3.0.0 optionalDependencies: graphql: 16.10.0 - gqty@3.4.0-canary-20250207102900.644ad9fdeafa6318516627577b1d4d754d5c5a98(graphql@16.10.0): + gqty@3.4.1(graphql@16.10.0): dependencies: debounce-microtasks: 0.1.8 flatted: 3.3.2 @@ -8982,6 +9232,7 @@ snapshots: just-safe-get: 4.2.0 just-safe-set: 4.2.1 multidict: 1.0.9 + ohash: 1.1.4 p-defer: 3.0.0 optionalDependencies: graphql: 16.10.0 @@ -9570,6 +9821,8 @@ snapshots: mute-stream@1.0.0: {} + mute-stream@2.0.0: {} + mz@2.7.0: dependencies: any-promise: 1.3.0 @@ -9659,8 +9912,6 @@ snapshots: object-assign@4.1.1: {} - object-hash@3.0.0: {} - ohash@1.1.4: {} once@1.4.0: From 8acee79391b2d8d34b99c11ddfec672494316bdf Mon Sep 17 00:00:00 2001 From: Nico Schett <52858351+schettn@users.noreply.github.com> Date: Tue, 11 Feb 2025 16:10:15 +0000 Subject: [PATCH 13/27] add chokidar and postcss-load-config as dependencies in package.json --- packages/pylon/package.json | 8 ++++---- pnpm-lock.yaml | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/pylon/package.json b/packages/pylon/package.json index c5ee44d..f6fd7b1 100644 --- a/packages/pylon/package.json +++ b/packages/pylon/package.json @@ -39,15 +39,15 @@ "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", - "chokidar": "^4.0.3", - "postcss-load-config": "^6.0.1" + "@sentry/types": "^8.54.0" }, "peerDependencies": { "@tailwindcss/postcss": "^4.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 626137e..b7a9277 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -233,6 +233,9 @@ importers: 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 @@ -260,6 +263,9 @@ importers: 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 @@ -285,12 +291,6 @@ importers: '@sentry/types': specifier: ^8.54.0 version: 8.54.0 - chokidar: - specifier: ^4.0.3 - version: 4.0.3 - postcss-load-config: - specifier: ^6.0.1 - version: 6.0.1(jiti@2.4.2)(postcss@8.5.1) packages/pylon-dev: dependencies: From 347fb156387c9a8c82abbe324756a7bf7d3177a6 Mon Sep 17 00:00:00 2001 From: Nico Schett <52858351+schettn@users.noreply.github.com> Date: Tue, 11 Feb 2025 16:16:29 +0000 Subject: [PATCH 14/27] update pages.json file with page routes if changed --- packages/pylon/src/plugins/use-pages/build/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/pylon/src/plugins/use-pages/build/index.ts b/packages/pylon/src/plugins/use-pages/build/index.ts index a3a3c96..beeb21e 100644 --- a/packages/pylon/src/plugins/use-pages/build/index.ts +++ b/packages/pylon/src/plugins/use-pages/build/index.ts @@ -30,6 +30,9 @@ export const build: Plugin['build'] = 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') From f2dce964eb04b9aa7df6adc7965195d8ef6d5bfb Mon Sep 17 00:00:00 2001 From: Nico Schett <52858351+schettn@users.noreply.github.com> Date: Tue, 11 Feb 2025 16:22:18 +0000 Subject: [PATCH 15/27] add @tailwindcss/postcss as a devDependency in package.json --- packages/create-pylon/templates/bun/pages/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/create-pylon/templates/bun/pages/package.json b/packages/create-pylon/templates/bun/pages/package.json index 7304f91..004b6eb 100644 --- a/packages/create-pylon/templates/bun/pages/package.json +++ b/packages/create-pylon/templates/bun/pages/package.json @@ -23,7 +23,8 @@ }, "devDependencies": { "@getcronit/pylon-dev": "canary", - "@types/react": "^19.0.8" + "@types/react": "^19.0.8", + "@tailwindcss/postcss": "^4.0.6" }, "repository": { "type": "git", From 725602adb48f54a94a4d6a2a1b5fa964ba5df9a6 Mon Sep 17 00:00:00 2001 From: Nico Schett <52858351+schettn@users.noreply.github.com> Date: Wed, 12 Feb 2025 12:32:58 +0000 Subject: [PATCH 16/27] use getEnv() to retrieve environment variables in disableCacheMiddleware --- packages/pylon/src/plugins/use-pages/setup/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/pylon/src/plugins/use-pages/setup/index.tsx b/packages/pylon/src/plugins/use-pages/setup/index.tsx index 159cfd1..63d8d10 100644 --- a/packages/pylon/src/plugins/use-pages/setup/index.tsx +++ b/packages/pylon/src/plugins/use-pages/setup/index.tsx @@ -20,7 +20,8 @@ export type PageProps = { } const disableCacheMiddleware: MiddlewareHandler = async (c, next) => { - if (c.env.NODE_ENV === 'development') { + const env = getEnv() + if (env.NODE_ENV === 'development') { c.header( 'Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate' From d18431f1875bc771e7004ad7eec8376475e39d94 Mon Sep 17 00:00:00 2001 From: Nico Schett <52858351+schettn@users.noreply.github.com> Date: Wed, 12 Feb 2025 12:33:14 +0000 Subject: [PATCH 17/27] optimize file handling in bundler and use Promise.all for concurrent processing --- .../pylon-dev/src/builder/bundler/bundler.ts | 18 ++++++++++++++---- .../src/plugins/use-pages/build/index.ts | 19 ++++++++++++++----- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/packages/pylon-dev/src/builder/bundler/bundler.ts b/packages/pylon-dev/src/builder/bundler/bundler.ts index 1dfa0a2..608f103 100644 --- a/packages/pylon-dev/src/builder/bundler/bundler.ts +++ b/packages/pylon-dev/src/builder/bundler/bundler.ts @@ -64,10 +64,12 @@ export class Bundler { name: 'write-on-end', setup(build) { build.onEnd(async result => { - result.outputFiles?.forEach(async file => { - await fs.mkdir(path.dirname(file.path), {recursive: true}) - await updateFileIfChanged(file.path, file.text) - }) + await Promise.all( + result.outputFiles!.map(async file => { + await fs.mkdir(path.dirname(file.path), {recursive: true}) + await updateFileIfChanged(file.path, file.text) + }) + ) }) } } @@ -101,6 +103,8 @@ export class Bundler { ] }) + await ctx.rebuild() + const pluginCtxs = await this.initBuildPlugins({ onBuild: () => { options.onBuild?.({ @@ -112,6 +116,12 @@ export class Bundler { } }) + await Promise.all( + pluginCtxs.map(async c => { + await (await c).rebuild() + }) + ) + return { watch: async () => { for (const ctx of pluginCtxs) { diff --git a/packages/pylon/src/plugins/use-pages/build/index.ts b/packages/pylon/src/plugins/use-pages/build/index.ts index beeb21e..a134041 100644 --- a/packages/pylon/src/plugins/use-pages/build/index.ts +++ b/packages/pylon/src/plugins/use-pages/build/index.ts @@ -47,19 +47,27 @@ export const build: Plugin['build'] = async () => { name: 'write-on-end', setup(build) { build.onEnd(async result => { - result.outputFiles?.forEach(async file => { - await fs.mkdir(path.dirname(file.path), {recursive: true}) - await updateFileIfChanged(file.path, file.text) - }) + 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, @@ -93,6 +101,7 @@ export const build: Plugin['build'] = async () => { const serverCtx = await esbuild.context({ write: false, absWorkingDir: process.cwd(), + nodePaths, plugins: [imagePlugin, postcssPlugin, writeOnEndPlugin], publicPath: '/__pylon/static', assetNames: 'assets/[name]-[hash]', @@ -100,9 +109,9 @@ export const build: Plugin['build'] = async () => { format: 'esm', platform: 'node', entryPoints: ['.pylon/app.tsx'], + packages: 'external', outdir: DIST_PAGES_DIR, bundle: true, - packages: 'external', splitting: false, minify: false, loader: { From fb4306496b44c1b45fc4451c30e34967f0f0e0b5 Mon Sep 17 00:00:00 2001 From: Nico Schett <52858351+schettn@users.noreply.github.com> Date: Wed, 12 Feb 2025 12:33:22 +0000 Subject: [PATCH 18/27] add @gqty/react and gqty as dependencies in package.json --- packages/create-pylon/templates/bun/pages/package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/create-pylon/templates/bun/pages/package.json b/packages/create-pylon/templates/bun/pages/package.json index 004b6eb..7883ee1 100644 --- a/packages/create-pylon/templates/bun/pages/package.json +++ b/packages/create-pylon/templates/bun/pages/package.json @@ -10,10 +10,12 @@ }, "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", From a67cee630ed97b15cf39e4b3ae0fb3f59995243c Mon Sep 17 00:00:00 2001 From: Nico Schett <52858351+schettn@users.noreply.github.com> Date: Wed, 12 Feb 2025 12:33:35 +0000 Subject: [PATCH 19/27] update pnpm-lock.yaml to include @gqty/react, gqty, and @tailwindcss/postcss at version 4.0.6 --- pnpm-lock.yaml | 152 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b7a9277..e14e1a9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -148,6 +148,9 @@ importers: '@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) @@ -160,6 +163,9 @@ importers: 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) @@ -182,6 +188,9 @@ importers: '@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 @@ -2472,79 +2481,155 @@ packages: '@tailwindcss/node@4.0.4': resolution: {integrity: sha512-VLFq80IyoV1hsHPcCm1mmlyPyUT6NlovQLoO2y7PGm84mW94ZrNJ7ax5H6K4M7Aj/fdMfem5IX7Ka+LXWZpDGg==} + '@tailwindcss/node@4.0.6': + resolution: {integrity: sha512-jb6E0WeSq7OQbVYcIJ6LxnZTeC4HjMvbzFBMCrQff4R50HBlo/obmYNk6V2GCUXDeqiXtvtrQgcIbT+/boB03Q==} + '@tailwindcss/oxide-android-arm64@4.0.4': resolution: {integrity: sha512-hiGUA8d15ynH/LdurQNObnuTjri7i4ApAzhesusNxoz4br7vhZ6QO5CFgniYAYNZvf8Q8wCTBg0nj61RalBeVQ==} engines: {node: '>= 10'} cpu: [arm64] os: [android] + '@tailwindcss/oxide-android-arm64@4.0.6': + resolution: {integrity: sha512-xDbym6bDPW3D2XqQqX3PjqW3CKGe1KXH7Fdkc60sX5ZLVUbzPkFeunQaoP+BuYlLc2cC1FoClrIRYnRzof9Sow==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + '@tailwindcss/oxide-darwin-arm64@4.0.4': resolution: {integrity: sha512-vTca+ysNl8BYmYJTni9pLC+L3S4bvrj0ai1eUV3yYXYa5Cpugr5Fni6ylV0gcTZOyETm2RCCJ/0azU6MgqE6HA==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] + '@tailwindcss/oxide-darwin-arm64@4.0.6': + resolution: {integrity: sha512-1f71/ju/tvyGl5c2bDkchZHy8p8EK/tDHCxlpYJ1hGNvsYihZNurxVpZ0DefpN7cNc9RTT8DjrRoV8xXZKKRjg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + '@tailwindcss/oxide-darwin-x64@4.0.4': resolution: {integrity: sha512-rxPWb5AQJ/aAM/5UDCjaQaMYIcrZHe/Dr9xZu9+P9nJf3WAweNsGi+e+SW9EYGRiF3hkBtP2dvxVNAkTiEbNQQ==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] + '@tailwindcss/oxide-darwin-x64@4.0.6': + resolution: {integrity: sha512-s/hg/ZPgxFIrGMb0kqyeaqZt505P891buUkSezmrDY6lxv2ixIELAlOcUVTkVh245SeaeEiUVUPiUN37cwoL2g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + '@tailwindcss/oxide-freebsd-x64@4.0.4': resolution: {integrity: sha512-UOnRHzlS5V5cxaMgBo6rk1E92tTDUtO/falc9vOpNiRdWhNcofYNN9zvZP63Wuo5FC6/XCyAnJo6OXUm18TwrQ==} engines: {node: '>= 10'} cpu: [x64] os: [freebsd] + '@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==} @@ -5309,6 +5394,9 @@ packages: 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'} @@ -7940,39 +8028,78 @@ snapshots: 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 @@ -7987,6 +8114,20 @@ snapshots: '@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 @@ -7996,6 +8137,15 @@ snapshots: 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': @@ -10806,6 +10956,8 @@ snapshots: tailwindcss@4.0.4: {} + tailwindcss@4.0.6: {} + tapable@2.2.1: {} temp-dir@2.0.0: {} From 4549283ca68d73a0596c4d7ce0d1a0509a8bdbc6 Mon Sep 17 00:00:00 2001 From: Nico Schett <52858351+schettn@users.noreply.github.com> Date: Wed, 12 Feb 2025 12:50:02 +0000 Subject: [PATCH 20/27] add @gqty/react and gqty as dependencies, and @tailwindcss/postcss as a devDependency in package.json --- examples/pages/package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/pages/package.json b/examples/pages/package.json index 0f972f8..bb09597 100644 --- a/examples/pages/package.json +++ b/examples/pages/package.json @@ -10,10 +10,12 @@ }, "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", @@ -23,6 +25,7 @@ }, "devDependencies": { "@getcronit/pylon-dev": "workspace:^", + "@tailwindcss/postcss": "^4.0.6", "@types/react": "^19.0.8" }, "repository": { From 0b12573af2c6261cfe516868abfb4d9cee3dd78e Mon Sep 17 00:00:00 2001 From: Nico Schett <52858351+schettn@users.noreply.github.com> Date: Wed, 12 Feb 2025 12:58:18 +0000 Subject: [PATCH 21/27] enable minification in build process for improved performance --- packages/pylon/src/plugins/use-pages/build/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/pylon/src/plugins/use-pages/build/index.ts b/packages/pylon/src/plugins/use-pages/build/index.ts index a134041..733ec16 100644 --- a/packages/pylon/src/plugins/use-pages/build/index.ts +++ b/packages/pylon/src/plugins/use-pages/build/index.ts @@ -84,7 +84,7 @@ export const build: Plugin['build'] = async () => { outdir: DIST_STATIC_DIR, bundle: true, splitting: true, - minify: false, + minify: true, loader: { // Map file extensions to the file loader @@ -113,7 +113,7 @@ export const build: Plugin['build'] = async () => { outdir: DIST_PAGES_DIR, bundle: true, splitting: false, - minify: false, + minify: true, loader: { // Map file extensions to the file loader From e920a4951f1801b7534212a343297c7606c530fa Mon Sep 17 00:00:00 2001 From: Nico Schett Date: Tue, 18 Feb 2025 17:12:19 +0100 Subject: [PATCH 22/27] fix: update imports and module resolution for improved organization and compatibility --- examples/pages/pages/page.tsx | 4 ++-- examples/pages/pages/test2/page.tsx | 4 ++-- examples/pages/pylon.d.ts | 2 ++ examples/pages/tsconfig.json | 1 + packages/pylon/package.json | 14 +++++++++++++- packages/pylon/src/index.ts | 6 ++++-- packages/pylon/src/pages/index.ts | 2 ++ .../pylon/src/plugins/use-pages/build/app-utils.ts | 3 ++- .../build/plugins/inject-app-hydration.ts | 3 ++- packages/pylon/tsconfig.pylon.json | 2 +- 10 files changed, 31 insertions(+), 10 deletions(-) create mode 100644 packages/pylon/src/pages/index.ts diff --git a/examples/pages/pages/page.tsx b/examples/pages/pages/page.tsx index 4de7c3d..78b4d4f 100644 --- a/examples/pages/pages/page.tsx +++ b/examples/pages/pages/page.tsx @@ -1,5 +1,5 @@ -import {Button} from '@/components/ui/button' -import {PageProps} from '@getcronit/pylon' +import { Button } from '@/components/ui/button' +import { PageProps } from '@getcronit/pylon/pages' const Page: React.FC = props => { return ( diff --git a/examples/pages/pages/test2/page.tsx b/examples/pages/pages/test2/page.tsx index 83825b0..a33ff7e 100644 --- a/examples/pages/pages/test2/page.tsx +++ b/examples/pages/pages/test2/page.tsx @@ -1,5 +1,5 @@ -import {Button} from '@/components/ui/button' -import {PageProps} from '@getcronit/pylon' +import { Button } from '@/components/ui/button' +import { PageProps } from '@getcronit/pylon/pages' const Page: React.FC = props => { return ( diff --git a/examples/pages/pylon.d.ts b/examples/pages/pylon.d.ts index f5cb32b..be56fdd 100644 --- a/examples/pages/pylon.d.ts +++ b/examples/pages/pylon.d.ts @@ -5,6 +5,8 @@ declare module '@getcronit/pylon' { interface Bindings {} interface Variables {} +} +declare module '@getcronit/pylon/pages' { interface PageData extends ReturnType {} } diff --git a/examples/pages/tsconfig.json b/examples/pages/tsconfig.json index ff491e7..27e0fe3 100644 --- a/examples/pages/tsconfig.json +++ b/examples/pages/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "@getcronit/pylon/tsconfig.pylon.json", "compilerOptions": { + "moduleResolution": "bundler", // add Bun type definitions "types": ["bun-types"], "baseUrl": ".", diff --git a/packages/pylon/package.json b/packages/pylon/package.json index f6fd7b1..d73f50c 100644 --- a/packages/pylon/package.json +++ b/packages/pylon/package.json @@ -4,8 +4,20 @@ "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" + } + }, "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": [ diff --git a/packages/pylon/src/index.ts b/packages/pylon/src/index.ts index 0b9a8fc..34c02eb 100644 --- a/packages/pylon/src/index.ts +++ b/packages/pylon/src/index.ts @@ -18,7 +18,7 @@ export {getEnv} from './get-env.js' export {createDecorator} from './create-decorator.js' export {createPubSub as experimentalCreatePubSub} from 'graphql-yoga' -export {usePages, type PageProps, PageData} from './plugins/use-pages/index' +export {usePages} from './plugins/use-pages/index' import type {Plugin as YogaPlugin} from 'graphql-yoga' import {MiddlewareHandler} from 'hono' @@ -31,7 +31,9 @@ export type Plugin< > = YogaPlugin & { middleware?: MiddlewareHandler setup?: (app: typeof pylonApp) => void - build?: (args: {onBuild: () => void}) => Promise, 'serve'>> + build?: (args: { + onBuild: () => void + }) => Promise, 'serve'>> } export type PylonConfig = { 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-pages/build/app-utils.ts b/packages/pylon/src/plugins/use-pages/build/app-utils.ts index fd36569..28ef10b 100644 --- a/packages/pylon/src/plugins/use-pages/build/app-utils.ts +++ b/packages/pylon/src/plugins/use-pages/build/app-utils.ts @@ -107,7 +107,8 @@ export const generateAppFile = (pageRoutes: PageRoute[]): string => { // Dynamically build the App component with React Router Routes const appComponent = `"use client"; import {lazy, Suspense} from 'react' - import { Routes, Route } from 'react-router'; + 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} 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 index 7bf4990..8075156 100644 --- 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 @@ -17,7 +17,8 @@ export const injectAppHydrationPlugin: Plugin = { contents += ` import {hydrateRoot} from 'react-dom/client' import * as client from './${pathToClient}' - import {BrowserRouter} from 'react-router' + 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__ 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, From add4312a240cd5c097a2385ffdc40e52b21c56e5 Mon Sep 17 00:00:00 2001 From: Nico Schett Date: Tue, 18 Feb 2025 18:10:53 +0100 Subject: [PATCH 23/27] update pages template --- packages/create-pylon/templates/bun/pages/pages/page.tsx | 6 +++--- packages/create-pylon/templates/bun/pages/pylon.d.ts | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/create-pylon/templates/bun/pages/pages/page.tsx b/packages/create-pylon/templates/bun/pages/pages/page.tsx index 57c4226..dc706ed 100644 --- a/packages/create-pylon/templates/bun/pages/pages/page.tsx +++ b/packages/create-pylon/templates/bun/pages/pages/page.tsx @@ -1,7 +1,7 @@ -import {Button} from '@/components/ui/button' -import {PageProps} from '@getcronit/pylon' +import { Button } from '@/components/ui/button' +import { PageProps } from '@getcronit/pylon/pages' -const Page: React.FC = ({data}) => { +const Page: React.FC = ({ data }) => { return (
{data.hello} diff --git a/packages/create-pylon/templates/bun/pages/pylon.d.ts b/packages/create-pylon/templates/bun/pages/pylon.d.ts index f5cb32b..be56fdd 100644 --- a/packages/create-pylon/templates/bun/pages/pylon.d.ts +++ b/packages/create-pylon/templates/bun/pages/pylon.d.ts @@ -5,6 +5,8 @@ declare module '@getcronit/pylon' { interface Bindings {} interface Variables {} +} +declare module '@getcronit/pylon/pages' { interface PageData extends ReturnType {} } From 9828f8db927b6a39c3063f63063e6ae2210416db Mon Sep 17 00:00:00 2001 From: Nico Schett Date: Tue, 18 Feb 2025 18:38:57 +0100 Subject: [PATCH 24/27] add tsconfig.pylon.json path to package.json --- packages/pylon/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/pylon/package.json b/packages/pylon/package.json index d73f50c..5a6c02f 100644 --- a/packages/pylon/package.json +++ b/packages/pylon/package.json @@ -12,7 +12,8 @@ "./pages": { "import": "./dist/pages/index.js", "types": "./dist/pages/index.d.ts" - } + }, + "./tsconfig.pylon.json": "./tsconfig.pylon.json" }, "scripts": { "build": "rimraf ./dist && pnpm run build:server && pnpm run build:pages", From dc6307c79f970b4f8709b2830437af5003c3fd8d Mon Sep 17 00:00:00 2001 From: Nico Schett Date: Tue, 18 Feb 2025 20:10:25 +0100 Subject: [PATCH 25/27] add public directory copying and serve static files in setup --- .../src/plugins/use-pages/build/index.ts | 30 ++++- .../src/plugins/use-pages/setup/index.tsx | 106 ++++++++++++++---- 2 files changed, 109 insertions(+), 27 deletions(-) diff --git a/packages/pylon/src/plugins/use-pages/build/index.ts b/packages/pylon/src/plugins/use-pages/build/index.ts index 733ec16..ff1266d 100644 --- a/packages/pylon/src/plugins/use-pages/build/index.ts +++ b/packages/pylon/src/plugins/use-pages/build/index.ts @@ -43,6 +43,27 @@ export const build: Plugin['build'] = async () => { } } + 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) { @@ -126,11 +147,12 @@ export const build: Plugin['build'] = async () => { return { watch: async () => { console.log('Watching pages directory...') - pagesWatcher = chokidar.watch('pages', {ignoreInitial: false}) + pagesWatcher = chokidar.watch('pages', {ignoreInitial: true}) - pagesWatcher!.on('all', (event, path) => { + pagesWatcher!.on('all', async (event, path) => { if (['add', 'change', 'unlink'].includes(event)) { - buildAppFile() + await buildAppFile() + await copyPublicDir() } }) @@ -149,6 +171,8 @@ export const build: Plugin['build'] = async () => { console.log('Rebuilding pages') await buildAppFile() + await copyPublicDir() + await Promise.all([clientCtx.rebuild(), serverCtx.rebuild()]) return {} as any diff --git a/packages/pylon/src/plugins/use-pages/setup/index.tsx b/packages/pylon/src/plugins/use-pages/setup/index.tsx index 63d8d10..f90a3e9 100644 --- a/packages/pylon/src/plugins/use-pages/setup/index.tsx +++ b/packages/pylon/src/plugins/use-pages/setup/index.tsx @@ -2,15 +2,15 @@ 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' +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 interface PageData { } export type PageProps = { data: PageData @@ -117,6 +117,64 @@ export const setup: Plugin['setup'] = app => { } ) + 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) + } + + console.log('publicFiles', publicFiles) + + app.on('GET', + publicFiles.map(file => `/${file}`), disableCacheMiddleware, async c => { + const publicFilePath = path.resolve( + process.cwd(), + '.pylon', + '__pylon', + 'public', + c.req.path.replace('/', '') + ) + + console.log('publicFilePath', publicFilePath) + + 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(), @@ -127,7 +185,7 @@ export const setup: Plugin['setup'] = app => { ) if (!fs.existsSync(filePath)) { - throw new Error('File not found') + return c.status(404) } if (filePath.endsWith('.js')) { @@ -161,14 +219,14 @@ export const setup: Plugin['setup'] = app => { app.get('/__pylon/image', async c => { console.log('image optimization route') try { - const {src, w, h, q = '75', format = 'webp'} = c.req.query() + 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) + return c.json({ error: 'Missing parameters.' }, 400) } let imagePath = path.join(process.cwd(), '.pylon', src) @@ -182,7 +240,7 @@ export const setup: Plugin['setup'] = app => { console.log('imagePath', imagePath) await fs.promises.access(imagePath) } catch { - return c.json({error: 'Image not found'}, 404) + return c.json({ error: 'Image not found' }, 404) } // Get image metadata (width and height) to calculate aspect ratio @@ -200,7 +258,7 @@ export const setup: Plugin['setup'] = app => { } // Calculate missing dimension - const {width: finalWidth, height: finalHeight} = calculateDimensions( + const { width: finalWidth, height: finalHeight } = calculateDimensions( metadata.width, metadata.height, w ? parseInt(w) : undefined, @@ -251,19 +309,19 @@ export const setup: Plugin['setup'] = app => { ) } catch (error) { console.error('Error processing the image:', error) - return c.json({error: 'Error processing the image'}, 500) + return c.json({ error: 'Error processing the image' }, 500) } }) } -import sharp, {FormatEnum} from 'sharp' -import {createHash} from 'crypto' +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}) +fs.promises.mkdir(IMAGE_CACHE_DIR, { recursive: true }) // Helper function to generate the cached image path const getCachedImagePath = ( @@ -287,7 +345,7 @@ const calculateDimensions = ( height?: number ) => { if (!width && !height) { - return {width: originalWidth, height: originalHeight} + return { width: originalWidth, height: originalHeight } } if (width && !height) { // Calculate height based on the aspect ratio @@ -296,7 +354,7 @@ const calculateDimensions = ( // Calculate width based on the aspect ratio width = Math.round((height * originalWidth) / originalHeight) } - return {width, height} + return { width, height } } function isSupportedFormat(format: string): format is keyof FormatEnum { @@ -323,11 +381,11 @@ const getContentType = (format: string) => { } } -import {tmpdir} from 'os' -import {promisify} from 'util' -import {pipeline} from 'stream/promises' -import {PageRoute} from '../build/app-utils' -import {MiddlewareHandler} from 'hono' +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) From aaa58469c713c31e5ac822416ad6088823eaebfb Mon Sep 17 00:00:00 2001 From: Nico Schett Date: Tue, 18 Feb 2025 21:57:58 +0100 Subject: [PATCH 26/27] trim log output to remove unnecessary whitespace --- packages/pylon-dev/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pylon-dev/src/index.ts b/packages/pylon-dev/src/index.ts index 973fb1e..661f693 100644 --- a/packages/pylon-dev/src/index.ts +++ b/packages/pylon-dev/src/index.ts @@ -81,7 +81,7 @@ async function main(options: ArgOptions, command: Command) { } bus.on('log:out', data => { - consola.log(data.data) + consola.log(data.data.trim()) }) bus.on('log:err', data => { From 5fd3a977d589c14cc224729b00d712f57339cb3d Mon Sep 17 00:00:00 2001 From: Nico Schett Date: Tue, 18 Feb 2025 22:03:07 +0100 Subject: [PATCH 27/27] remove unnecessary console.log statements for cleaner output --- packages/pylon/src/plugins/use-pages/build/index.ts | 5 ----- .../plugins/use-pages/build/plugins/inject-app-hydration.ts | 2 -- packages/pylon/src/plugins/use-pages/setup/index.tsx | 6 ------ 3 files changed, 13 deletions(-) diff --git a/packages/pylon/src/plugins/use-pages/build/index.ts b/packages/pylon/src/plugins/use-pages/build/index.ts index ff1266d..559a80c 100644 --- a/packages/pylon/src/plugins/use-pages/build/index.ts +++ b/packages/pylon/src/plugins/use-pages/build/index.ts @@ -39,7 +39,6 @@ export const build: Plugin['build'] = async () => { const state = await updateFileIfChanged(appFilePath, appContent) if (state) { - console.log('Updated app file') } } @@ -146,7 +145,6 @@ export const build: Plugin['build'] = async () => { return { watch: async () => { - console.log('Watching pages directory...') pagesWatcher = chokidar.watch('pages', {ignoreInitial: true}) pagesWatcher!.on('all', async (event, path) => { @@ -159,8 +157,6 @@ export const build: Plugin['build'] = async () => { await Promise.all([clientCtx.watch(), serverCtx.watch()]) }, dispose: async () => { - console.log('Disposing pages') - if (pagesWatcher) { pagesWatcher.close() } @@ -168,7 +164,6 @@ export const build: Plugin['build'] = async () => { Promise.all([clientCtx.dispose(), serverCtx.dispose()]) }, rebuild: async () => { - console.log('Rebuilding pages') await buildAppFile() await copyPublicDir() 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 index 8075156..49f6be6 100644 --- 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 @@ -23,8 +23,6 @@ export const injectAppHydrationPlugin: Plugin = { const pylonData = window.__PYLON_DATA__ - console.log('pylonData', pylonData) - const AppLoader = (props: { client: any pylonData: { diff --git a/packages/pylon/src/plugins/use-pages/setup/index.tsx b/packages/pylon/src/plugins/use-pages/setup/index.tsx index f90a3e9..380cb8f 100644 --- a/packages/pylon/src/plugins/use-pages/setup/index.tsx +++ b/packages/pylon/src/plugins/use-pages/setup/index.tsx @@ -126,7 +126,6 @@ export const setup: Plugin['setup'] = app => { console.error('Error reading public files', error) } - console.log('publicFiles', publicFiles) app.on('GET', publicFiles.map(file => `/${file}`), disableCacheMiddleware, async c => { @@ -138,7 +137,6 @@ export const setup: Plugin['setup'] = app => { c.req.path.replace('/', '') ) - console.log('publicFilePath', publicFilePath) try { await fs.promises.access(publicFilePath) @@ -217,7 +215,6 @@ export const setup: Plugin['setup'] = app => { // Image optimization route app.get('/__pylon/image', async c => { - console.log('image optimization route') try { const { src, w, h, q = '75', format = 'webp' } = c.req.query() @@ -237,7 +234,6 @@ export const setup: Plugin['setup'] = app => { // Check if the image exists asynchronously try { - console.log('imagePath', imagePath) await fs.promises.access(imagePath) } catch { return c.json({ error: 'Image not found' }, 404) @@ -289,7 +285,6 @@ export const setup: Plugin['setup'] = app => { const quality = parseInt(q) - console.log('quality', quality) // Optimize the image using sharp const image = await sharp(imagePath) @@ -299,7 +294,6 @@ export const setup: Plugin['setup'] = app => { }) .toFile(cachePath) - console.log('image', image) c.res.headers.set('Content-Type', getContentType(image.format))