diff --git a/api/package.json b/api/package.json index a21a46b9b..6c0e5bdb7 100644 --- a/api/package.json +++ b/api/package.json @@ -1,5 +1,5 @@ { - "version": "0.12.2", + "version": "0.12.4", "name": "@fiberplane/studio", "description": "Local development debugging interface for Hono apps", "author": "Fiberplane", diff --git a/packages/source-analysis/package.json b/packages/source-analysis/package.json index 5a90d9652..e7461c57b 100644 --- a/packages/source-analysis/package.json +++ b/packages/source-analysis/package.json @@ -1,6 +1,6 @@ { "name": "@fiberplane/source-analysis", - "version": "0.1.0", + "version": "0.3.0", "type": "module", "types": "./dist/index.d.ts", "author": "Fiberplane", @@ -22,6 +22,7 @@ "devDependencies": { "@cloudflare/workers-types": "^4.20241011.0", "@fiberplane/hono-otel": "^0.3.1", + "@hono/zod-openapi": "^0.18.0", "@types/node": "^22.8.7", "@types/resolve": "^1.20.6", "vitest": "^2.1.3" diff --git a/packages/source-analysis/src/RoutesMonitor.ts b/packages/source-analysis/src/RoutesMonitor.ts index 097f9a575..2ccfc67ad 100644 --- a/packages/source-analysis/src/RoutesMonitor.ts +++ b/packages/source-analysis/src/RoutesMonitor.ts @@ -1,4 +1,5 @@ import { EventEmitter } from "node:events"; +import { statSync } from "node:fs"; import path from "node:path"; import { type FileRemovedEvent, @@ -13,9 +14,10 @@ import { getParsedTsConfig, getTsLib, startServer } from "./service"; import type { TsISnapShot, TsLanguageService, + TsPackageType, TsProgram, - TsType, } from "./types"; +import { debounce } from "./utils"; type AnalysisStarted = { type: "analysisStarted"; @@ -68,7 +70,7 @@ export class RoutesMonitor extends EventEmitter { /** * Reference to the users' typescript instance */ - private _ts: TsType; + private _ts: TsPackageType; /** * The language service used to analyze the routes @@ -109,7 +111,32 @@ export class RoutesMonitor extends EventEmitter { constructor(projectRoot: string) { super(); - this.projectRoot = projectRoot; + const folder = path.isAbsolute(projectRoot) + ? projectRoot + : path.resolve(projectRoot); + + // check if folder exists + try { + const isDirectory = statSync(folder).isDirectory(); + if (!isDirectory) { + throw new Error("Not a directory"); + } + } catch (e: unknown) { + if (e instanceof Error) { + logger.error(`Folder ${folder} does not exist. Error: ${e.message}`); + } else { + logger.error("Unknown error while checking folder", { + folder: projectRoot, + error: e, + }); + } + + logger.warn( + "This is likely to cause issues with the file watcher & source analysis", + ); + } + + this.projectRoot = folder; this._ts = getTsLib(this.projectRoot); // Use the tsconfig include option to determine which files/locations to watch @@ -185,7 +212,6 @@ export class RoutesMonitor extends EventEmitter { return; } - logger.debug("Starting to monitor", this.projectRoot); this.addEventListenersToFileWatcher(); await this.fileWatcher.start(); @@ -199,12 +225,52 @@ export class RoutesMonitor extends EventEmitter { readFile: this.readFile.bind(this), ts: this._ts, }); - this._program = this._service.getProgram() ?? null; + + await this.isCompilerReady(); + this._isRunning = true; } + private async isCompilerReady(): Promise { + if (!this._service) { + throw new Error("Service not initialized"); + } + + // Sometimes the test suite failed (finding 0 routes) so now we check if all files + // we are watching are in the program before we continue + let retryCount = 0; + let valid = true; + do { + valid = true; + this._program = this._service.getProgram() ?? null; + const sourceFiles = this._program?.getSourceFiles(); + const sourceFileNames = sourceFiles?.map((sf) => sf.fileName); + for (const watchedFilePath of this.fileWatcher.knownFileNamesArray) { + if (sourceFileNames?.includes(watchedFilePath) !== true) { + const retryDelay = 100 + retryCount * 100; + logger.warn( + `File ${watchedFilePath} not (yet) available in the Typescript program, retrying...`, + { retryCount, retryDelay }, + ); + + valid = false; + await new Promise((resolve) => setTimeout(resolve, retryDelay)); + break; + } + } + } while (!valid && retryCount++ < 5); + + if (retryCount >= 5) { + logger.error( + `Failed to monitor all files after ${retryCount} retries. Code analysis might be incomplete.`, + ); + } + + return valid; + } + public updateRoutesResult() { - if (!this.isRunning) { + if (!this._isRunning) { throw new Error("Monitor not running"); } @@ -255,7 +321,6 @@ export class RoutesMonitor extends EventEmitter { return this._ts.sys.readFile(fileName); } - private getScriptSnapshot(fileName: string): TsISnapShot | undefined { if (this._aggressiveCaching) { const info = this.getFileInfo(fileName); @@ -314,7 +379,6 @@ export class RoutesMonitor extends EventEmitter { } }); } - public findHonoRoutes() { this.fileExistsCache = {}; @@ -367,20 +431,3 @@ export class RoutesMonitor extends EventEmitter { return Object.keys(this.fileMap); } } - -function debounce) => void | Promise>( - func: T, - wait: number, -): (...args: Parameters) => void { - let timeout: ReturnType | null = null; - - return (...args: Parameters) => { - if (timeout !== null) { - clearTimeout(timeout); - } - - timeout = setTimeout(() => { - func(...args); - }, wait); - }; -} diff --git a/packages/source-analysis/src/RoutesResult/RoutesResult.test.ts b/packages/source-analysis/src/RoutesResult/RoutesResult.test.ts index 9f6a5deeb..3eb83e3c1 100644 --- a/packages/source-analysis/src/RoutesResult/RoutesResult.test.ts +++ b/packages/source-analysis/src/RoutesResult/RoutesResult.test.ts @@ -126,3 +126,32 @@ test("multiple", async () => { monitor.stop(); } }); + +test("zod-openapi", async () => { + const absolutePath = path.join(__dirname, "../../test-cases/zod-openapi"); + const monitor = createRoutesMonitor(absolutePath); + monitor.autoCreateResult = false; + try { + await monitor.start(); + const factory = monitor.updateRoutesResult(); + assert(factory.rootId); + + let request = new Request("http://localhost/users", { method: "POST" }); + let response = await factory.currentApp.fetch(request); + expect(await response.text()).toEqual("Ok"); + expect(factory.getHistoryLength()).toBe(2); + expect(factory.hasVisited(factory.rootId)).toBeTruthy(); + expect(factory.getFilesForHistory()).toMatchSnapshot(); + + factory.resetHistory(); + // Try to visit another route + request = new Request("http://localhost/users", { method: "GET" }); + response = await factory.currentApp.fetch(request); + expect(await response.text()).toEqual("Ok"); + expect(factory.getHistoryLength()).toBe(2); + expect(factory.hasVisited(factory.rootId)).toBeTruthy(); + expect(factory.getFilesForHistory()).toMatchSnapshot(); + } finally { + monitor.stop(); + } +}); diff --git a/packages/source-analysis/src/RoutesResult/__snapshots__/RoutesResult.test.ts.snap b/packages/source-analysis/src/RoutesResult/__snapshots__/RoutesResult.test.ts.snap index 6b7a9d5b3..be08d2634 100644 --- a/packages/source-analysis/src/RoutesResult/__snapshots__/RoutesResult.test.ts.snap +++ b/packages/source-analysis/src/RoutesResult/__snapshots__/RoutesResult.test.ts.snap @@ -168,3 +168,111 @@ const app = new Hono(); app.post("/hello-world", (c) => c.json({ hello: "world" }) /* EOF: other.ts */" `; + +exports[`zod-openapi 1`] = ` +"/* index.ts */ +import { createRoute,z,z } from "@hono/zod-openapi"; +import { basicAuth } from "hono/basic-auth"; +const app = new OpenAPIHono(); + +const NewUserSchema = z + .object({ + name: z.string().openapi({ + example: "John Doe", + description: "The name of the user", + }), + age: z.number().openapi({ + example: 42, + }), + }) + .openapi("NewUser") + +const UserSchema = z + .object({ + id: z.number().openapi({ + example: 123, + }), + name: z.string().openapi({ + example: "John Doe", + }), + age: z.number().openapi({ + example: 42, + }), + }) + .openapi("User") + +app.post("/users", const createUserRoute = createRoute({ + method: "post", + path: "/users", + middleware: [ + basicAuth({ + username: "goose", + password: "honkhonk", + }), + ] as const, // Use \`as const\` to ensure TypeScript infers the middleware's Context correctly + request: { + body: { + content: { + "application/json": { + schema: NewUserSchema, + }, + }, + }, + }, + responses: { + 201: { + content: { + "application/json": { + schema: UserSchema, + }, + }, + description: "Retrieve the user", + }, + }, +}) +/* EOF: index.ts */" +`; + +exports[`zod-openapi 2`] = ` +"/* index.ts */ +import { createRoute,z,z } from "@hono/zod-openapi"; +import { basicAuth } from "hono/basic-auth"; +const app = new OpenAPIHono(); + +const UserSchema = z + .object({ + id: z.number().openapi({ + example: 123, + }), + name: z.string().openapi({ + example: "John Doe", + }), + age: z.number().openapi({ + example: 42, + }), + }) + .openapi("User") + +app.get("/users", createRoute({ + method: "get", + path: "/users", + middleware: [ + basicAuth({ + username: "goose", + password: "honkhonk", + }), + ] as const, // Use \`as const\` to ensure TypeScript infers the middleware's Context correctly + request: {}, + responses: { + 20: { + content: { + "application/json": { + schema: z.Array(UserSchema), + }, + }, + description: "Retrieve all users", + }, + }, + }) +/* EOF: index.ts */" +`; diff --git a/packages/source-analysis/src/RoutesResult/generate.ts b/packages/source-analysis/src/RoutesResult/generate.ts index 938da1dcf..937f75f24 100644 --- a/packages/source-analysis/src/RoutesResult/generate.ts +++ b/packages/source-analysis/src/RoutesResult/generate.ts @@ -283,7 +283,7 @@ function extractRouteTreeContent(includeIds: boolean, resource: RouteTree) { ? `// id:${resource.id} ` : "" - }const ${resource.name} = new Hono();`; + }const ${resource.name} = new ${resource.library === "hono" ? "Hono" : "OpenAPIHono"}();`; if (resource.baseUrl) { content += `\n${resource.name}.baseUrl = "${resource.baseUrl}";`; } diff --git a/packages/source-analysis/src/routeTrees/__snapshots__/extractRouteTree.test.ts.snap b/packages/source-analysis/src/routeTrees/__snapshots__/extractRouteTree.test.ts.snap index 5487b1d9b..f59c94b36 100644 --- a/packages/source-analysis/src/routeTrees/__snapshots__/extractRouteTree.test.ts.snap +++ b/packages/source-analysis/src/routeTrees/__snapshots__/extractRouteTree.test.ts.snap @@ -77,6 +77,7 @@ exports[`run test 'barrel files' with location '../../test-cases/barrel-files' 1 ], "fileName": "index.ts", "id": "ROUTE_TREE:index.ts@102", + "library": "hono", "modules": Set {}, "name": "app", "position": 102, @@ -312,6 +313,7 @@ exports[`run test 'hono factory' with location '../../test-cases/hono-factory' 1 ], "fileName": "bye.ts", "id": "ROUTE_TREE:bye.ts@36", + "library": "hono", "modules": Set {}, "name": "bye", "position": 36, @@ -328,6 +330,7 @@ exports[`run test 'hono factory' with location '../../test-cases/hono-factory' 1 ], "fileName": "factory.ts", "id": "ROUTE_TREE:factory.ts@248", + "library": "hono", "modules": Set {}, "name": "app", "position": 248, @@ -342,6 +345,7 @@ exports[`run test 'hono factory' with location '../../test-cases/hono-factory' 1 ], "fileName": "index.ts", "id": "ROUTE_TREE:index.ts@101", + "library": "hono", "modules": Set {}, "name": "subHello", "position": 101, @@ -356,6 +360,7 @@ exports[`run test 'hono factory' with location '../../test-cases/hono-factory' 1 ], "fileName": "index.ts", "id": "ROUTE_TREE:index.ts@75", + "library": "hono", "modules": Set {}, "name": "app", "position": 75, @@ -369,6 +374,7 @@ exports[`run test 'hono factory' with location '../../test-cases/hono-factory' 1 ], "fileName": "panic.ts", "id": "ROUTE_TREE:panic.ts@36", + "library": "hono", "modules": Set {}, "name": "app", "position": 36, @@ -382,6 +388,7 @@ exports[`run test 'hono factory' with location '../../test-cases/hono-factory' 1 ], "fileName": "silence.ts", "id": "ROUTE_TREE:silence.ts@36", + "library": "hono", "modules": Set {}, "name": "silence", "position": 36, @@ -583,6 +590,7 @@ exports[`run test 'import as' with location '../../test-cases/import-as' 1`] = ` ], "fileName": "index.ts", "id": "ROUTE_TREE:index.ts@151", + "library": "hono", "modules": Set {}, "name": "app", "position": 151, @@ -798,6 +806,7 @@ exports[`run test 'module imports' with location '../../test-cases/module-import ], "fileName": "index.ts", "id": "ROUTE_TREE:index.ts@114", + "library": "hono", "modules": Set {}, "name": "app", "position": 114, @@ -1000,6 +1009,7 @@ exports[`run test 'multiple files' with location '../../test-cases/multiple' 1`] ], "fileName": "index.ts", "id": "ROUTE_TREE:index.ts@36", + "library": "hono", "modules": Set {}, "name": "app", "position": 36, @@ -1014,6 +1024,7 @@ exports[`run test 'multiple files' with location '../../test-cases/multiple' 1`] ], "fileName": "other.ts", "id": "ROUTE_TREE:other.ts@35", + "library": "hono", "modules": Set {}, "name": "app", "position": 35, @@ -1060,39 +1071,89 @@ exports[`run test 'multiple files' with location '../../test-cases/multiple' 1`] exports[`run test 'single file' with location '../../test-cases/single' 1`] = ` { - "ROUTE_ENTRY:index.ts@55": { + "ROUTE_ENTRY:index.ts@126": { "fileName": "index.ts", - "id": "ROUTE_ENTRY:index.ts@55", + "id": "ROUTE_ENTRY:index.ts@126", + "method": "post", + "modules": Set {}, + "path": "/single-quote", + "position": 126, + "sources": Set { + "SOURCE_REFERENCE:index.ts@152", + }, + "type": "ROUTE_ENTRY", + }, + "ROUTE_ENTRY:index.ts@186": { + "fileName": "index.ts", + "id": "ROUTE_ENTRY:index.ts@186", + "method": "put", + "modules": Set {}, + "path": "/literal-template-literal-stuff", + "position": 186, + "sources": Set { + "SOURCE_REFERENCE:index.ts@235", + }, + "type": "ROUTE_ENTRY", + }, + "ROUTE_ENTRY:index.ts@81": { + "fileName": "index.ts", + "id": "ROUTE_ENTRY:index.ts@81", "method": "get", "modules": Set {}, "path": "/", - "position": 55, + "position": 81, "sources": Set { - "SOURCE_REFERENCE:index.ts@68", + "SOURCE_REFERENCE:index.ts@94", }, "type": "ROUTE_ENTRY", }, "ROUTE_TREE:index.ts@36": { "baseUrl": "", "entries": [ - "ROUTE_ENTRY:index.ts@55", + "ROUTE_ENTRY:index.ts@81", + "ROUTE_ENTRY:index.ts@126", + "ROUTE_ENTRY:index.ts@186", ], "fileName": "index.ts", "id": "ROUTE_TREE:index.ts@36", + "library": "hono", "modules": Set {}, "name": "app", "position": 36, "sources": Set {}, "type": "ROUTE_TREE", }, - "SOURCE_REFERENCE:index.ts@68": { + "SOURCE_REFERENCE:index.ts@152": { + "character": 26, + "content": "(c) => c.text("Hello, 'Hono'!")", + "fileName": "index.ts", + "id": "SOURCE_REFERENCE:index.ts@152", + "line": 5, + "modules": Set {}, + "position": 152, + "references": Set {}, + "type": "SOURCE_REFERENCE", + }, + "SOURCE_REFERENCE:index.ts@235": { + "character": 49, + "content": "(c) => + c.text("Hello, \`\${Hono}\`!")", + "fileName": "index.ts", + "id": "SOURCE_REFERENCE:index.ts@235", + "line": 6, + "modules": Set {}, + "position": 235, + "references": Set {}, + "type": "SOURCE_REFERENCE", + }, + "SOURCE_REFERENCE:index.ts@94": { "character": 13, "content": "(c) => c.text("Hello, Hono!")", "fileName": "index.ts", - "id": "SOURCE_REFERENCE:index.ts@68", + "id": "SOURCE_REFERENCE:index.ts@94", "line": 4, "modules": Set {}, - "position": 68, + "position": 94, "references": Set {}, "type": "SOURCE_REFERENCE", }, @@ -1201,6 +1262,7 @@ exports[`run test 'split routes' with location '../../test-cases/split-routes' 1 ], "fileName": "index.ts", "id": "ROUTE_TREE:index.ts@103", + "library": "hono", "modules": Set {}, "name": "projects", "position": 103, @@ -1216,6 +1278,7 @@ exports[`run test 'split routes' with location '../../test-cases/split-routes' 1 ], "fileName": "index.ts", "id": "ROUTE_TREE:index.ts@601", + "library": "hono", "modules": Set {}, "name": "app", "position": 601, @@ -1230,6 +1293,7 @@ exports[`run test 'split routes' with location '../../test-cases/split-routes' 1 ], "fileName": "projects.ts", "id": "ROUTE_TREE:projects.ts@36", + "library": "hono", "modules": Set {}, "name": "projects", "position": 36, @@ -1244,6 +1308,7 @@ exports[`run test 'split routes' with location '../../test-cases/split-routes' 1 ], "fileName": "users.ts", "id": "ROUTE_TREE:users.ts@36", + "library": "hono", "modules": Set {}, "name": "users", "position": 36, @@ -1445,3 +1510,230 @@ exports[`run test 'split routes' with location '../../test-cases/split-routes' 1 }, } `; + +exports[`run test 'zod-openapi' with location '../../test-cases/zod-openapi' 1`] = ` +{ + "MODULE_REFERENCE:%40hono%2Fzod-openapi@createRoute": { + "id": "MODULE_REFERENCE:%40hono%2Fzod-openapi@createRoute", + "import": "createRoute", + "importPath": "@hono/zod-openapi", + "name": "@hono/zod-openapi", + "pathId": "@hono/zod-openapi", + "type": "MODULE_REFERENCE", + "version": "0.18.0", + }, + "MODULE_REFERENCE:%40hono%2Fzod-openapi@z": { + "id": "MODULE_REFERENCE:%40hono%2Fzod-openapi@z", + "import": "z", + "importPath": "@hono/zod-openapi", + "name": "@hono/zod-openapi", + "pathId": "@hono/zod-openapi", + "type": "MODULE_REFERENCE", + "version": "0.18.0", + }, + "MODULE_REFERENCE:hono%2Fbasic-auth@basicAuth": { + "id": "MODULE_REFERENCE:hono%2Fbasic-auth@basicAuth", + "import": "basicAuth", + "importPath": "hono/basic-auth", + "name": "hono", + "pathId": "hono/basic-auth", + "type": "MODULE_REFERENCE", + "version": "4.6.9", + }, + "ROUTE_ENTRY:index.ts@1714": { + "fileName": "index.ts", + "id": "ROUTE_ENTRY:index.ts@1714", + "method": "post", + "modules": Set {}, + "path": "/users", + "position": 1714, + "sources": Set { + "SOURCE_REFERENCE:index.ts@949", + }, + "type": "ROUTE_ENTRY", + }, + "ROUTE_ENTRY:index.ts@1972": { + "fileName": "index.ts", + "id": "ROUTE_ENTRY:index.ts@1972", + "method": "get", + "modules": Set {}, + "path": "/users", + "position": 1972, + "sources": Set { + "SOURCE_REFERENCE:index.ts@1987", + }, + "type": "ROUTE_ENTRY", + }, + "ROUTE_ENTRY:index.ts@2980": { + "fileName": "index.ts", + "id": "ROUTE_ENTRY:index.ts@2980", + "method": "get", + "modules": Set {}, + "path": "/", + "position": 2980, + "sources": Set { + "SOURCE_REFERENCE:index.ts@2993", + }, + "type": "ROUTE_ENTRY", + }, + "ROUTE_TREE:index.ts@169": { + "baseUrl": "", + "entries": [ + "ROUTE_ENTRY:index.ts@1714", + "ROUTE_ENTRY:index.ts@1972", + "ROUTE_ENTRY:index.ts@2980", + ], + "fileName": "index.ts", + "id": "ROUTE_TREE:index.ts@169", + "library": "zod-openapi", + "modules": Set {}, + "name": "app", + "position": 169, + "sources": Set {}, + "type": "ROUTE_TREE", + }, + "SOURCE_REFERENCE:index.ts@1987": { + "character": 2, + "content": "createRoute({ + method: "get", + path: "/users", + middleware: [ + basicAuth({ + username: "goose", + password: "honkhonk", + }), + ] as const, // Use \`as const\` to ensure TypeScript infers the middleware's Context correctly + request: {}, + responses: { + 20: { + content: { + "application/json": { + schema: z.Array(UserSchema), + }, + }, + description: "Retrieve all users", + }, + }, + })", + "fileName": "index.ts", + "id": "SOURCE_REFERENCE:index.ts@1987", + "line": 83, + "modules": Set { + "MODULE_REFERENCE:%40hono%2Fzod-openapi@createRoute", + "MODULE_REFERENCE:hono%2Fbasic-auth@basicAuth", + "MODULE_REFERENCE:%40hono%2Fzod-openapi@z", + }, + "position": 1987, + "references": Set { + "SOURCE_REFERENCE:index.ts@621", + }, + "type": "SOURCE_REFERENCE", + }, + "SOURCE_REFERENCE:index.ts@267": { + "character": 6, + "content": "const NewUserSchema = z + .object({ + name: z.string().openapi({ + example: "John Doe", + description: "The name of the user", + }), + age: z.number().openapi({ + example: 42, + }), + }) + .openapi("NewUser")", + "fileName": "index.ts", + "id": "SOURCE_REFERENCE:index.ts@267", + "line": 7, + "modules": Set { + "MODULE_REFERENCE:%40hono%2Fzod-openapi@z", + }, + "position": 267, + "references": Set {}, + "type": "SOURCE_REFERENCE", + }, + "SOURCE_REFERENCE:index.ts@2993": { + "character": 13, + "content": "(c) => { + return c.text("Hello Hono OpenAPI!"); +}", + "fileName": "index.ts", + "id": "SOURCE_REFERENCE:index.ts@2993", + "line": 124, + "modules": Set {}, + "position": 2993, + "references": Set {}, + "type": "SOURCE_REFERENCE", + }, + "SOURCE_REFERENCE:index.ts@621": { + "character": 6, + "content": "const UserSchema = z + .object({ + id: z.number().openapi({ + example: 123, + }), + name: z.string().openapi({ + example: "John Doe", + }), + age: z.number().openapi({ + example: 42, + }), + }) + .openapi("User")", + "fileName": "index.ts", + "id": "SOURCE_REFERENCE:index.ts@621", + "line": 21, + "modules": Set { + "MODULE_REFERENCE:%40hono%2Fzod-openapi@z", + }, + "position": 621, + "references": Set {}, + "type": "SOURCE_REFERENCE", + }, + "SOURCE_REFERENCE:index.ts@949": { + "character": 24, + "content": "const createUserRoute = createRoute({ + method: "post", + path: "/users", + middleware: [ + basicAuth({ + username: "goose", + password: "honkhonk", + }), + ] as const, // Use \`as const\` to ensure TypeScript infers the middleware's Context correctly + request: { + body: { + content: { + "application/json": { + schema: NewUserSchema, + }, + }, + }, + }, + responses: { + 201: { + content: { + "application/json": { + schema: UserSchema, + }, + }, + description: "Retrieve the user", + }, + }, +})", + "fileName": "index.ts", + "id": "SOURCE_REFERENCE:index.ts@949", + "line": 36, + "modules": Set { + "MODULE_REFERENCE:%40hono%2Fzod-openapi@createRoute", + "MODULE_REFERENCE:hono%2Fbasic-auth@basicAuth", + }, + "position": 949, + "references": Set { + "SOURCE_REFERENCE:index.ts@267", + "SOURCE_REFERENCE:index.ts@621", + }, + "type": "SOURCE_REFERENCE", + }, +} +`; diff --git a/packages/source-analysis/src/routeTrees/extractRouteTree.test.ts b/packages/source-analysis/src/routeTrees/extractRouteTree.test.ts index 03bafef82..d0dc0db79 100644 --- a/packages/source-analysis/src/routeTrees/extractRouteTree.test.ts +++ b/packages/source-analysis/src/routeTrees/extractRouteTree.test.ts @@ -6,18 +6,22 @@ test.each([ { name: "single file", location: "../../test-cases/single", + expectedErrorCount: 0, }, { name: "multiple files", location: "../../test-cases/multiple", + expectedErrorCount: 0, }, { name: "module imports", location: "../../test-cases/module-imports", + expectedErrorCount: 0, }, { name: "barrel files", location: "../../test-cases/barrel-files", + expectedErrorCount: 0, }, // { // name: "bindings", @@ -26,6 +30,7 @@ test.each([ { name: "split routes", location: "../../test-cases/split-routes", + expectedErrorCount: 0, }, // { // name: "empty", @@ -34,10 +39,20 @@ test.each([ { name: "hono factory", location: "../../test-cases/hono-factory", + expectedErrorCount: 0, }, { name: "import as", location: "../../test-cases/import-as", + expectedErrorCount: 0, + }, + { + name: "zod-openapi", + location: "../../test-cases/zod-openapi", + // There are two typescript errors when analyzing the test-case + // What's triggering them is `.openapi("SOMETHING")` + // Typescript error is: `No symbol found for type` + expectedErrorCount: 2, }, // The projects below are larger projects which aren't @@ -53,7 +68,7 @@ test.each([ // }, ])( "run test $name with location $location", - async ({ location, name }) => { + async ({ location, name, expectedErrorCount }) => { // Get the exact location const absolutePath = path.join(__dirname, location); // Create a monitor for the routes @@ -65,7 +80,6 @@ test.each([ try { // Start monitoring the filesystem await monitor.start(); - const start = performance.now(); const result = monitor.findHonoRoutes(); console.log( @@ -73,7 +87,7 @@ test.each([ ); // Expect no errors - expect(result.errorCount).toBe(0); + expect(result.errorCount).toBe(expectedErrorCount); // Get all found resources const resources = result.resourceManager.getResources(); diff --git a/packages/source-analysis/src/routeTrees/extractRouteTrees.ts b/packages/source-analysis/src/routeTrees/extractRouteTrees.ts index dcb14a452..5b28dde40 100644 --- a/packages/source-analysis/src/routeTrees/extractRouteTrees.ts +++ b/packages/source-analysis/src/routeTrees/extractRouteTrees.ts @@ -8,6 +8,7 @@ import { type RouteTree, type RouteTreeReference, type SearchContext, + type SourceReference, type SourceReferenceId, type TsArrowFunction, type TsCallExpression, @@ -17,6 +18,7 @@ import { type TsLanguageService, type TsNode, type TsNodeArray, + type TsPackageType, type TsProgram, type TsReferenceEntry, type TsReturnStatement, @@ -32,19 +34,12 @@ import { findNodeAtPosition } from "./utils"; export function extractRouteTrees( service: TsLanguageService, program: TsProgram, - ts: TsType, + ts: TsPackageType, projectRoot: string, ): { errorCount?: number; resourceManager: ResourceManager; } { - // const now = performance.now(); - // const program = service.getProgram(); - // if (!program) { - // throw new Error("Program not found"); - // } - // logger.log('program', performance.now() - now) - const resourceManager = new ResourceManager(projectRoot); const checker = program.getTypeChecker(); @@ -69,7 +64,7 @@ export function extractRouteTrees( for (const sourceFile of files) { if (!sourceFile.isDeclarationFile) { ts.forEachChild(sourceFile, (node: TsNode) => { - visit(node, sourceFile.fileName, context); + visitToFindRouteTree(node, sourceFile.fileName, context); }); } } @@ -80,8 +75,32 @@ export function extractRouteTrees( }; } -function visit(node: TsNode, fileName: string, context: SearchContext) { - const { ts, checker, resourceManager, service } = context; +function getTypeErrorDetails(type: TsType): string { + const symbol = type.getSymbol(); + if (!symbol) { + return "No symbol found for type"; + } + const declarations = symbol.getDeclarations(); + if (!declarations || declarations.length === 0) { + return "No declarations found for symbol"; + } + return declarations + .map((declaration) => { + const sourceFile = declaration.getSourceFile(); + const { line, character } = sourceFile.getLineAndCharacterOfPosition( + declaration.getStart(), + ); + return `Error in ${sourceFile.fileName} at ${line + 1}:${character + 1}`; + }) + .join("\n"); +} + +function visitToFindRouteTree( + node: TsNode, + fileName: string, + context: SearchContext, +) { + const { ts, checker } = context; if (!ts.isVariableStatement(node)) { return; } @@ -94,68 +113,117 @@ function visit(node: TsNode, fileName: string, context: SearchContext) { if ("intrinsicName" in type && type.intrinsicName === "error") { context.errorCount++; - logger.error("Error in type check"); - logger.error("In: ", node.getSourceFile().fileName, node.kind); - logger.error("Node text:", node.getFullText()); - logger.error("type information", type.getSymbol()); + logger.info("Error in type check"); + const prettyLocation = node + .getSourceFile() + .getLineAndCharacterOfPosition(node.getStart()); + logger.info( + `Location: ${node.getSourceFile().fileName}:${prettyLocation.line + 1}:${prettyLocation.character + 1}`, + ); + logger.debug("Syntax kind:", ts.SyntaxKind[node.kind]); + logger.debug("Type error details:", getTypeErrorDetails(type)); } const typeName = checker.typeToString(type); if (typeName.startsWith("Hono<")) { - // TODO: use the type information to get the name of the hono instance - // TODO: (edge case) handle reassignments of the same variable. It's possible reuse a variable for different hono instances - const honoInstanceName = declaration.name.getText(); - const position = declaration.name.getStart(); - const params = { - type: "ROUTE_TREE" as const, - baseUrl: "", - name: honoInstanceName, - fileName: resourceManager.asRelativePath(node.getSourceFile().fileName), - position, - entries: [], - modules: new Set(), - sources: new Set(), - }; + handleHonoAppInstance(declaration, node, fileName, context); + } else if (typeName.startsWith("OpenAPIHono<")) { + handleOpenApiHonoInstance(declaration, node, fileName, context); + } + } +} - const current = resourceManager.createRouteTree(params); - - // TODO: add support for late initialization of the hono instance - // What if people do something like: - // - // ``` ts - // let app: Hono; - // app = new Hono(); - // ``` - // - // Or have some other kind of initialization: - // - // ``` ts - // let app: Hono; - // app = createApp(); - // ``` - - if ( - declaration.initializer && - ts.isCallExpression(declaration.initializer) - ) { - handleInitializerCallExpression( - declaration.initializer, - current, - context, - ); - } +function handleOpenApiHonoInstance( + declaration: TsVariableDeclaration, + node: TsNode, + fileName: string, + context: SearchContext, +) { + const { ts, resourceManager, service } = context; + + const honoInstanceName = declaration.name.getText(); + const position = declaration.name.getStart(); + const params = { + type: "ROUTE_TREE" as const, + baseUrl: "", + name: honoInstanceName, + fileName: resourceManager.asRelativePath(node.getSourceFile().fileName), + position, + entries: [], + library: "zod-openapi" as const, + modules: new Set(), + sources: new Set(), + }; - const references = ( - service.getReferencesAtPosition(fileName, position) ?? [] - ).filter( - (reference) => - reference.fileName === fileName && - reference.textSpan.start !== position, - ); - for (const entry of references) { - followReference(current, entry, context); - } - } + const current = resourceManager.createRouteTree(params); + if (declaration.initializer && ts.isCallExpression(declaration.initializer)) { + handleInitializerCallExpression(declaration.initializer, current, context); + } + + const references = ( + service.getReferencesAtPosition(fileName, position) ?? [] + ).filter( + (reference) => + reference.fileName === fileName && reference.textSpan.start !== position, + ); + + for (const entry of references) { + followReference(current, entry, context, handleOpenApiHonoMethodCall); + } +} + +function handleHonoAppInstance( + declaration: TsVariableDeclaration, + node: TsNode, + fileName: string, + context: SearchContext, +) { + const { ts, resourceManager, service } = context; + // TODO: use the type information to get the name of the hono instance + // TODO: (edge case) handle reassignments of the same variable. It's possible reuse a variable for different hono instances + const honoInstanceName = declaration.name.getText(); + const position = declaration.name.getStart(); + const params = { + type: "ROUTE_TREE" as const, + baseUrl: "", + name: honoInstanceName, + fileName: resourceManager.asRelativePath(node.getSourceFile().fileName), + position, + entries: [], + library: "hono" as const, + modules: new Set(), + sources: new Set(), + }; + + const current = resourceManager.createRouteTree(params); + + // TODO: add support for late initialization of the hono instance + // What if people do something like: + // + // ``` ts + // let app: Hono; + // app = new Hono(); + // ``` + // + // Or have some other kind of initialization: + // + // ``` ts + // let app: Hono; + // app = createApp(); + // ``` + + if (declaration.initializer && ts.isCallExpression(declaration.initializer)) { + handleInitializerCallExpression(declaration.initializer, current, context); + } + + const references = ( + service.getReferencesAtPosition(fileName, position) ?? [] + ).filter( + (reference) => + reference.fileName === fileName && reference.textSpan.start !== position, + ); + for (const entry of references) { + followReference(current, entry, context, handleHonoMethodCall); } } @@ -194,7 +262,7 @@ function handleInitializerCallExpression( ) { const functionBody = functionNode.parent.body; functionBody.forEachChild((child) => { - visit(child, declarationFileName, context); + visitToFindRouteTree(child, declarationFileName, context); }); // Now find the return statements in the function body to construct the route tree reference @@ -232,6 +300,12 @@ function followReference( routeTree: RouteTree, reference: TsReferenceEntry, context: SearchContext, + handleMethodCall: ( + callExpression: TsCallExpression, + methodName: string, + routeTree: RouteTree, + context: SearchContext, + ) => void, ) { const { getFile, ts } = context; @@ -256,6 +330,92 @@ function followReference( ts.isCallExpression(callExpression) ) { const methodName = accessExpression.name.text; + handleMethodCall(callExpression, methodName, routeTree, context); + } +} +// function followHonoAppReference( +// routeTree: RouteTree, +// reference: TsReferenceEntry, +// context: SearchContext, +// ) { +// followReference(routeTree, reference, context, handleHonoMethodCall); +// } + +function handleOpenApiHonoMethodCall( + callExpression: TsCallExpression, + methodName: string, + routeTree: RouteTree, + context: SearchContext, +) { + const { ts, resourceManager } = context; + if (methodName === "openapi") { + const [firstArgument] = callExpression.arguments; + if (!firstArgument) { + return; + } + + if (ts.isIdentifier(firstArgument)) { + const symbol = context.checker.getSymbolAtLocation(firstArgument); + const declaration = symbol?.valueDeclaration; + if (declaration && ts.isVariableDeclaration(declaration)) { + const initializer = declaration.initializer; + if (!initializer || !ts.isCallExpression(initializer)) { + return; + } + + const { + method, + path, + sourceReferencesIds = new Set(), + } = getOpenApiRouteDetailsFromCallExpression(initializer, context); + if (path) { + const params: Omit = { + type: "ROUTE_ENTRY", + fileName: callExpression.getSourceFile().fileName, + position: callExpression.getStart(), + method: method && isHonoMethod(method) ? method : undefined, + path, + modules: new Set(), + sources: sourceReferencesIds, + }; + + const entry = resourceManager.createRouteEntry(params); + + // Add the tree node to the list of entries + // Later the entry will be filled with source references + routeTree.entries.push(entry.id); + } + } + } else if (ts.isCallExpression(firstArgument)) { + const { + method, + path, + sourceReferencesIds = new Set(), + } = getOpenApiRouteDetailsFromCallExpression(firstArgument, context); + if (path) { + const params: Omit = { + type: "ROUTE_ENTRY", + fileName: callExpression.getSourceFile().fileName, + position: callExpression.getStart(), + method: method && isHonoMethod(method) ? method : undefined, + path, + modules: new Set(), + sources: sourceReferencesIds, + }; + + const entry = resourceManager.createRouteEntry(params); + + // Add the tree node to the list of entries + // Later the entry will be filled with source references + routeTree.entries.push(entry.id); + } + } else { + logger.warn( + "Unsupported firstArgument", + ts.SyntaxKind[firstArgument.kind], + ); + } + } else { handleHonoMethodCall(callExpression, methodName, routeTree, context); } } @@ -293,13 +453,20 @@ function handleHonoMethodCall( } const method = isHonoMethod(methodName) ? methodName : undefined; + let path = ""; + + if (ts.isStringLiteral(firstArgument)) { + path = firstArgument.text; + } else if (ts.isTemplateLiteral(firstArgument)) { + path = expandTemplateLiteral(firstArgument, context); + } const params: Omit = { type: "ROUTE_ENTRY", fileName: callExpression.getSourceFile().fileName, position: callExpression.getStart(), method, - path: JSON.parse(firstArgument.getText()), + path, modules: new Set(), sources: new Set(), }; @@ -321,6 +488,71 @@ function handleHonoMethodCall( } } +function expandTemplateLiteral( + template: TsNode, + context: SearchContext, +): string { + const { ts } = context; + let result = ""; + + template.forEachChild((child) => { + if ( + ts.isTemplateHead(child) || + ts.isTemplateMiddle(child) || + ts.isTemplateTail(child) + ) { + result += child.text; + } else if (ts.isTemplateSpan(child)) { + const expression = child.expression; + const value = getExpressionValue(expression, context); + result += value; + result += child.literal.text; + } else if (ts.isExpression(child)) { + const value = getExpressionValue(child, context); + result += value; + } + }); + + return result; +} + +function getExpressionValue( + expression: TsNode, + context: SearchContext, +): string { + const { checker, ts } = context; + let constantValue: string | number | undefined; + if ( + ts.isPropertyAccessExpression(expression) || + ts.isElementAccessExpression(expression) || + ts.isEnumMember(expression) + ) { + constantValue = checker.getConstantValue(expression); + } + + if (constantValue !== undefined) { + return String(constantValue); + } + + if (ts.isIdentifier(expression)) { + const symbol = checker.getSymbolAtLocation(expression); + if (symbol) { + const declaration = symbol.valueDeclaration; + if (declaration && ts.isVariableDeclaration(declaration)) { + const initializer = declaration.initializer; + if (initializer) { + return getExpressionValue(initializer, context); + } + } + } + } + if (ts.isStringLiteral(expression)) { + return expression.text; + } + + return expression.getText(); +} + function handleRoute( callExpression: TsCallExpression, routeTree: RouteTree, @@ -493,7 +725,6 @@ function analyzeReturnStatement( if (ts.isIdentifier(node)) { const symbol = checker.getSymbolAtLocation(node); const declaration = symbol?.declarations?.[0]; - // logger.log('declaration', declaration && ts.SyntaxKind[declaration.kind]) if (declaration && ts.isVariableDeclaration(declaration)) { variables.push(declaration); } @@ -511,3 +742,66 @@ function analyzeReturnStatement( function isAlias(symbol: TsSymbol, context: SearchContext) { return symbol.flags === context.ts.SymbolFlags.Alias; } + +function getOpenApiRouteDetailsFromCallExpression( + callExpression: TsCallExpression, + context: SearchContext, +): { + method?: string | undefined; + path?: string | undefined; + sourceReferencesIds?: Set; +} { + const { ts } = context; + + let rootSourceReference: null | SourceReference = null; + + if ( + !ts.isIdentifier(callExpression.expression) || + callExpression.expression.text !== "createRoute" + ) { + logger.warn( + "Unsupported call expression for open api route declaration", + callExpression.getText(), + ); + return {}; + } + + const [routeConfig] = callExpression.arguments; + if (!routeConfig || !ts.isObjectLiteralExpression(routeConfig)) { + logger.warn( + "Unsupported route config parameter for open api route declaration", + routeConfig?.getText(), + ); + return {}; + } + + let method: string | undefined; + let path: string | undefined; + + for (const property of routeConfig.properties) { + if (!ts.isPropertyAssignment(property) || !ts.isIdentifier(property.name)) { + continue; + } + + const name = property.name.text; + if (name === "method" && ts.isStringLiteral(property.initializer)) { + method = property.initializer.text; + } else if (name === "path" && ts.isStringLiteral(property.initializer)) { + path = property.initializer.text; + } + } + rootSourceReference = createSourceReferenceForNode(callExpression, context); + + let sourceReferencesIds: Set; + if (rootSourceReference) { + sourceReferencesIds = new Set([rootSourceReference.id]); + } else { + sourceReferencesIds = new Set(); + } + + return { + method, + path, + sourceReferencesIds, + }; +} diff --git a/packages/source-analysis/src/routeTrees/utils.ts b/packages/source-analysis/src/routeTrees/utils.ts index 53a116658..c433acb3d 100644 --- a/packages/source-analysis/src/routeTrees/utils.ts +++ b/packages/source-analysis/src/routeTrees/utils.ts @@ -3,12 +3,12 @@ import type { ModuleReference, TsModuleResolutionHost, TsNode, + TsPackageType, TsSourceFile, - TsType, } from "../types"; export function findNodeAtPosition( - ts: TsType, + ts: TsPackageType, sourceFile: TsSourceFile, position: number, ): TsNode | undefined { diff --git a/packages/source-analysis/src/service.ts b/packages/source-analysis/src/service.ts index 219eb745c..1aed57a03 100644 --- a/packages/source-analysis/src/service.ts +++ b/packages/source-analysis/src/service.ts @@ -7,13 +7,14 @@ import type { TsCompilerOptions, TsISnapShot, TsLanguageServiceHost, - TsType, + TsPackageType, } from "./types"; + const relativeResolve = relative.sync; export function getParsedTsConfig( location: string, - ts: TsType, + ts: TsPackageType, ): ConfigFileContent { const configPath = ts.findConfigFile( location, @@ -38,7 +39,10 @@ export function getParsedTsConfig( }; } -export function getOptions(location: string, ts: TsType): TsCompilerOptions { +export function getOptions( + location: string, + ts: TsPackageType, +): TsCompilerOptions { const { options } = getParsedTsConfig(location, ts); if (!options.baseUrl) { options.baseUrl = location; @@ -81,7 +85,7 @@ export function startServer(params: { getScriptSnapshot: (fileName: string) => TsISnapShot | undefined; location: string; readFile: (fileName: string) => string | undefined; - ts: TsType; + ts: TsPackageType; }) { const { directoryExists, diff --git a/packages/source-analysis/src/types.ts b/packages/source-analysis/src/types.ts index 86c159ee7..15b7d6bf5 100644 --- a/packages/source-analysis/src/types.ts +++ b/packages/source-analysis/src/types.ts @@ -7,7 +7,7 @@ export const bundledTypescript = bundledTs; export const relativeResolve = relative.sync; // Alias some exported typescript types -export type TsType = typeof bundledTs; +export type TsPackageType = typeof bundledTs; export type TsArrowFunction = bundledTs.ArrowFunction; export type TsCallExpression = bundledTs.CallExpression; export type TsCompilerOptions = bundledTs.CompilerOptions; @@ -40,6 +40,7 @@ export type ConfigFileContent = Pick< > & { configPath?: string; }; +export type TsType = bundledTs.Type; export type RouteTreeId = Tagged; export type RouteTreeReferenceId = Tagged; @@ -76,6 +77,7 @@ export type RouteTree = { sources: Set; modules: Set; + library: "hono" | "zod-openapi"; } & FileReference; export type RouteTreeReference = { @@ -211,7 +213,7 @@ export type LocalFileResourceId = LocalFileResource["id"]; export type SearchContext = { resourceManager: ResourceManager; service: TsLanguageService; - ts: TsType; + ts: TsPackageType; errorCount: number; program: TsProgram; checker: TsTypeChecker; diff --git a/packages/source-analysis/src/utils.ts b/packages/source-analysis/src/utils.ts index fca1330d4..b8d7722bf 100644 --- a/packages/source-analysis/src/utils.ts +++ b/packages/source-analysis/src/utils.ts @@ -1,6 +1,6 @@ import path from "node:path"; import { logger } from "./logger"; -import type { TsNode, TsType, TsTypeChecker } from "./types"; +import type { TsNode, TsPackageType, TsTypeChecker } from "./types"; /** * Gets the file uri to be used in the typescript language server @@ -39,7 +39,7 @@ export function isSubpath(parentPath: string, subPath: string): boolean { export function debugSymbolAtLocation( node: TsNode, checker: TsTypeChecker, - ts: TsType, + ts: TsPackageType, ) { function logSymbolInfo(node: TsNode, depth: number) { const symbol = checker.getSymbolAtLocation(node); @@ -56,3 +56,19 @@ export function debugSymbolAtLocation( logSymbolInfo(node, 8); // Adjust depth as needed } + +export function debounce< + T extends (...args: Array) => void | Promise, +>(func: T, wait: number): (...args: Parameters) => void { + let timeout: ReturnType | null = null; + + return (...args: Parameters) => { + if (timeout !== null) { + clearTimeout(timeout); + } + + timeout = setTimeout(() => { + func(...args); + }, wait); + }; +} diff --git a/packages/source-analysis/test-cases/single/index.ts b/packages/source-analysis/test-cases/single/index.ts index d35e2f3e0..6abe869b5 100644 --- a/packages/source-analysis/test-cases/single/index.ts +++ b/packages/source-analysis/test-cases/single/index.ts @@ -1,7 +1,11 @@ import { Hono } from "hono"; const app = new Hono(); - +const LITERAL = "literal"; app.get("/", (c) => c.text("Hello, Hono!")); +app.post("/single-quote", (c) => c.text("Hello, 'Hono'!")); +app.put(`/${LITERAL}-template-${LITERAL}-stuff`, (c) => + c.text("Hello, `${Hono}`!"), +); export default app; diff --git a/packages/source-analysis/test-cases/zod-openapi/index.ts b/packages/source-analysis/test-cases/zod-openapi/index.ts new file mode 100644 index 000000000..35de21730 --- /dev/null +++ b/packages/source-analysis/test-cases/zod-openapi/index.ts @@ -0,0 +1,129 @@ +import { instrument } from "@fiberplane/hono-otel"; +import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi"; +import { basicAuth } from "hono/basic-auth"; + +const app = new OpenAPIHono(); + +// Schema that defines the body of a request to create a new user +const NewUserSchema = z + .object({ + name: z.string().openapi({ + example: "John Doe", + description: "The name of the user", + }), + age: z.number().openapi({ + example: 42, + }), + }) + .openapi("NewUser"); + +// Schema that defines the response of a request to get a user +// TODO - Figure out how to extend the NewUserSchema object +const UserSchema = z + .object({ + id: z.number().openapi({ + example: 123, + }), + name: z.string().openapi({ + example: "John Doe", + }), + age: z.number().openapi({ + example: 42, + }), + }) + .openapi("User"); + +// Define the request/response schema for a route to create a new user +const createUserRoute = createRoute({ + method: "post", + path: "/users", + middleware: [ + basicAuth({ + username: "goose", + password: "honkhonk", + }), + ] as const, // Use `as const` to ensure TypeScript infers the middleware's Context correctly + request: { + body: { + content: { + "application/json": { + schema: NewUserSchema, + }, + }, + }, + }, + responses: { + 201: { + content: { + "application/json": { + schema: UserSchema, + }, + }, + description: "Retrieve the user", + }, + }, +}); + +// Register the basic auth security scheme +app.openAPIRegistry.registerComponent("securitySchemes", "basicAuth", { + type: "http", + scheme: "basic", +}); + +// Define the handler for a route to create a new user +app.openapi(createUserRoute, async (c) => { + const { name, age } = c.req.valid("json"); + // Store the name and age in the database + // but for now, just return the input + const result = { name, age }; + return c.json(result, 201); +}); + +// List all users +app.openapi( + createRoute({ + method: "get", + path: "/users", + middleware: [ + basicAuth({ + username: "goose", + password: "honkhonk", + }), + ] as const, // Use `as const` to ensure TypeScript infers the middleware's Context correctly + request: {}, + responses: { + 20: { + content: { + "application/json": { + schema: z.Array(UserSchema), + }, + }, + description: "Retrieve all users", + }, + }, + }), + async (c) => { + const { name, age } = c.req.valid("json"); + // Store the name and age in the database + // but for now, just return the input + const result = { name, age }; + return c.json([result], 200); + }, +); + +// Mount the api documentation +// The OpenAPI documentation will be available at /doc +app.doc("/doc", { + openapi: "3.0.0", + info: { + version: "1.0.0", + title: "Simple Hono OpenAPI API", + }, +}); + +// Define a simple route to test the API (this is not part of the OpenAPI spec) +app.get("/", (c) => { + return c.text("Hello Hono OpenAPI!"); +}); + +export default instrument(app); diff --git a/packages/source-analysis/test-cases/zod-openapi/tsconfig.json b/packages/source-analysis/test-cases/zod-openapi/tsconfig.json new file mode 100644 index 000000000..1dbd49784 --- /dev/null +++ b/packages/source-analysis/test-cases/zod-openapi/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "skipLibCheck": true, + "lib": ["ESNext"], + "esModuleInterop": true, + "outDir": "./dist", + "rootDir": "./src", + "declaration": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "noPropertyAccessFromIndexSignature": true + }, +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 647c8e531..e9d551c70 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,7 +22,7 @@ importers: version: 5.5.4 wrangler: specifier: ^3.83.0 - version: 3.91.0(@cloudflare/workers-types@4.20241112.0) + version: 3.91.0(@cloudflare/workers-types@4.20241127.0) api: dependencies: @@ -243,10 +243,10 @@ importers: version: 16.4.5 drizzle-orm: specifier: ^0.35.3 - version: 0.35.3(@cloudflare/workers-types@4.20241112.0)(@libsql/client-wasm@0.14.0)(@libsql/client@0.14.0)(@neondatabase/serverless@0.10.1)(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.3)(react@18.3.1) + version: 0.35.3(@cloudflare/workers-types@4.20241127.0)(@libsql/client-wasm@0.14.0)(@libsql/client@0.14.0)(@neondatabase/serverless@0.10.1)(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.3)(react@18.3.1) drizzle-zod: specifier: ^0.5.1 - version: 0.5.1(drizzle-orm@0.35.3(@cloudflare/workers-types@4.20241112.0)(@libsql/client-wasm@0.14.0)(@libsql/client@0.14.0)(@neondatabase/serverless@0.10.1)(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.3)(react@18.3.1))(zod@3.23.8) + version: 0.5.1(drizzle-orm@0.35.3(@cloudflare/workers-types@4.20241127.0)(@libsql/client-wasm@0.14.0)(@libsql/client@0.14.0)(@neondatabase/serverless@0.10.1)(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.3)(react@18.3.1))(zod@3.23.8) hono: specifier: ^4.6.5 version: 4.6.9 @@ -256,13 +256,13 @@ importers: devDependencies: '@cloudflare/workers-types': specifier: ^4.20241112.0 - version: 4.20241112.0 + version: 4.20241127.0 drizzle-kit: specifier: ^0.26.2 version: 0.26.2 wrangler: specifier: ^3.87.0 - version: 3.88.0(@cloudflare/workers-types@4.20241112.0) + version: 3.88.0(@cloudflare/workers-types@4.20241127.0) examples/server-side-events: dependencies: @@ -290,14 +290,14 @@ importers: version: 16.4.5 drizzle-orm: specifier: ^0.36.0 - version: 0.36.4(@cloudflare/workers-types@4.20241112.0)(@libsql/client-wasm@0.14.0)(@libsql/client@0.14.0)(@neondatabase/serverless@0.10.1)(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.3)(react@18.3.1) + version: 0.36.4(@cloudflare/workers-types@4.20241127.0)(@libsql/client-wasm@0.14.0)(@libsql/client@0.14.0)(@neondatabase/serverless@0.10.1)(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.3)(react@18.3.1) hono: specifier: ^4.6.7 version: 4.6.9 devDependencies: '@cloudflare/workers-types': specifier: ^4.20241106.0 - version: 4.20241112.0 + version: 4.20241127.0 '@fiberplane/hono-otel': specifier: workspace:* version: link:../../../packages/client-library-otel @@ -306,7 +306,7 @@ importers: version: 4.19.2 wrangler: specifier: ^3.87.0 - version: 3.90.0(@cloudflare/workers-types@4.20241112.0) + version: 3.91.0(@cloudflare/workers-types@4.20241127.0) examples/service-bindings/woof: dependencies: @@ -315,14 +315,14 @@ importers: version: 16.4.5 drizzle-orm: specifier: ^0.36.0 - version: 0.36.4(@cloudflare/workers-types@4.20241112.0)(@libsql/client-wasm@0.14.0)(@libsql/client@0.14.0)(@neondatabase/serverless@0.10.1)(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.3)(react@18.3.1) + version: 0.36.4(@cloudflare/workers-types@4.20241127.0)(@libsql/client-wasm@0.14.0)(@libsql/client@0.14.0)(@neondatabase/serverless@0.10.1)(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.3)(react@18.3.1) devDependencies: '@cloudflare/workers-types': specifier: ^4.20241106.0 - version: 4.20241112.0 + version: 4.20241127.0 wrangler: specifier: ^3.87.0 - version: 3.90.0(@cloudflare/workers-types@4.20241112.0) + version: 3.91.0(@cloudflare/workers-types@4.20241127.0) examples/test-static-analysis: dependencies: @@ -368,7 +368,7 @@ importers: version: 0.14.0 ai: specifier: ^3.4.10 - version: 3.4.20(openai@4.68.4(encoding@0.1.13)(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@4.2.19))(svelte@4.2.19)(vue@3.5.12(typescript@5.6.3))(zod@3.23.8) + version: 3.4.20(openai@4.68.4(encoding@0.1.13)(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@4.2.19))(svelte@4.2.19)(vue@3.5.12(typescript@5.7.2))(zod@3.23.8) dotenv: specifier: ^16.4.5 version: 16.4.5 @@ -574,6 +574,9 @@ importers: '@fiberplane/hono-otel': specifier: ^0.3.1 version: 0.3.1 + '@hono/zod-openapi': + specifier: ^0.18.0 + version: 0.18.0(hono@4.6.9)(zod@3.23.8) '@types/node': specifier: ^22.8.7 version: 22.8.7 @@ -773,7 +776,7 @@ importers: devDependencies: '@darkobits/vite-plugin-favicons': specifier: ^0.3.2 - version: 0.3.2(typescript@5.6.3)(vite@5.4.10(@types/node@20.14.15)(lightningcss@1.28.1)) + version: 0.3.2(typescript@5.7.2)(vite@5.4.10(@types/node@20.14.15)(lightningcss@1.28.1)) '@iconify/react': specifier: ^5.0.2 version: 5.0.2(react@18.3.1) @@ -803,10 +806,10 @@ importers: version: 5.4.10(@types/node@20.14.15)(lightningcss@1.28.1) vite-plugin-svgr: specifier: ^4.2.0 - version: 4.2.0(rollup@4.24.0)(typescript@5.6.3)(vite@5.4.10(@types/node@20.14.15)(lightningcss@1.28.1)) + version: 4.2.0(rollup@4.24.0)(typescript@5.7.2)(vite@5.4.10(@types/node@20.14.15)(lightningcss@1.28.1)) vite-tsconfig-paths: specifier: ^4.3.2 - version: 4.3.2(typescript@5.6.3)(vite@5.4.10(@types/node@20.14.15)(lightningcss@1.28.1)) + version: 4.3.2(typescript@5.7.2)(vite@5.4.10(@types/node@20.14.15)(lightningcss@1.28.1)) vitest: specifier: ^1.6.0 version: 1.6.0(@types/node@20.14.15)(lightningcss@1.28.1) @@ -828,13 +831,13 @@ importers: devDependencies: '@cloudflare/workers-types': specifier: ^4.20241004.0 - version: 4.20241022.0 + version: 4.20241127.0 typescript: specifier: ^5.5.3 version: 5.5.4 wrangler: specifier: ^3.88.0 - version: 3.91.0(@cloudflare/workers-types@4.20241022.0) + version: 3.91.0(@cloudflare/workers-types@4.20241127.0) www: dependencies: @@ -892,7 +895,7 @@ importers: version: 0.14.1 wrangler: specifier: ^3.78.5 - version: 3.78.5(@cloudflare/workers-types@4.20241112.0) + version: 3.78.5(@cloudflare/workers-types@4.20241127.0) packages: @@ -1272,34 +1275,18 @@ packages: resolution: {integrity: sha512-gVPW8YLz92ZeCibQH2QUw96odJoiM3k/ZPH3f2HxptozmH6+OnyyvKXo/Egg39HAM230akarQKHf0W74UHlh0Q==} engines: {node: '>=16'} - '@babel/code-frame@7.24.7': - resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} - engines: {node: '>=6.9.0'} - '@babel/code-frame@7.26.0': resolution: {integrity: sha512-INCKxTtbXtcNbUZ3YXutwMpEleqttcswhAdee7dhuoVrD2cnuc3PqtERBtxkX5nziX9vnBL8WXmSGwv8CuPV6g==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.25.2': - resolution: {integrity: sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==} - engines: {node: '>=6.9.0'} - '@babel/compat-data@7.26.0': resolution: {integrity: sha512-qETICbZSLe7uXv9VE8T/RWOdIE5qqyTucOt4zLYMafj2MRO271VGgLd4RACJMeBO37UPWhXiKMBk7YlJ0fOzQA==} engines: {node: '>=6.9.0'} - '@babel/core@7.25.2': - resolution: {integrity: sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==} - engines: {node: '>=6.9.0'} - '@babel/core@7.26.0': resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} engines: {node: '>=6.9.0'} - '@babel/generator@7.25.0': - resolution: {integrity: sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==} - engines: {node: '>=6.9.0'} - '@babel/generator@7.26.0': resolution: {integrity: sha512-/AIkAmInnWwgEAJGQr9vY0c66Mj6kjkE2ZPB1PurTRaRAh3U+J45sAQMjQDJdh4WbR3l0x5xkimXBKyBXXAu2w==} engines: {node: '>=6.9.0'} @@ -1308,28 +1295,14 @@ packages: resolution: {integrity: sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.25.2': - resolution: {integrity: sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==} - engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.25.9': resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.24.7': - resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} - engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.25.9': resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.25.2': - resolution: {integrity: sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@babel/helper-module-transforms@7.26.0': resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} engines: {node: '>=6.9.0'} @@ -1340,51 +1313,22 @@ packages: resolution: {integrity: sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==} engines: {node: '>=6.9.0'} - '@babel/helper-simple-access@7.24.7': - resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.24.8': - resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==} - engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.25.9': resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.24.7': - resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} - engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.25.9': resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.24.8': - resolution: {integrity: sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==} - engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.25.9': resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.25.0': - resolution: {integrity: sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==} - engines: {node: '>=6.9.0'} - '@babel/helpers@7.26.0': resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} engines: {node: '>=6.9.0'} - '@babel/highlight@7.24.7': - resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.25.3': - resolution: {integrity: sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==} - engines: {node: '>=6.0.0'} - hasBin: true - '@babel/parser@7.25.6': resolution: {integrity: sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==} engines: {node: '>=6.0.0'} @@ -1411,30 +1355,14 @@ packages: resolution: {integrity: sha512-DSgLeL/FNcpXuzav5wfYvHCGvynXkJbn3Zvc3823AEe9nPwW9IK4UoCSS5yGymmQzN0pCPvivtgS6/8U2kkm1w==} engines: {node: '>=6.9.0'} - '@babel/template@7.25.0': - resolution: {integrity: sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==} - engines: {node: '>=6.9.0'} - '@babel/template@7.25.9': resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.25.3': - resolution: {integrity: sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==} - engines: {node: '>=6.9.0'} - '@babel/traverse@7.25.9': resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==} engines: {node: '>=6.9.0'} - '@babel/types@7.25.2': - resolution: {integrity: sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.25.6': - resolution: {integrity: sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==} - engines: {node: '>=6.9.0'} - '@babel/types@7.26.0': resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} engines: {node: '>=6.9.0'} @@ -1603,10 +1531,6 @@ packages: resolution: {integrity: sha512-46cP5FCrl3TrvHeoHLb5SRuiDMKH5kc9Yvo36SAfzt8dqJI/qJRoY1GP3ioHn/gP7v2QIoUOTAzIl7Ml7MnfrA==} engines: {node: '>=16.7.0'} - '@cloudflare/workers-shared@0.8.0': - resolution: {integrity: sha512-1OvFkNtslaMZAJsaocTmbACApgmWv55uLpNj50Pn2MGcxdAjpqykXJFQw5tKc+lGV9TDZh9oO3Rsk17IEQDzIg==} - engines: {node: '>=16.7.0'} - '@cloudflare/workers-shared@0.9.0': resolution: {integrity: sha512-eP6Ir45uPbKnpADVzUCtkRUYxYxjB1Ew6n/whTJvHu8H4m93USHAceCMm736VBZdlxuhXXUjEP3fCUxKPn+cfw==} engines: {node: '>=16.7.0'} @@ -1620,8 +1544,8 @@ packages: '@cloudflare/workers-types@4.20241022.0': resolution: {integrity: sha512-1zOAw5QIDKItzGatzCrEpfLOB1AuMTwVqKmbw9B9eBfCUGRFNfJYMrJxIwcse9EmKahsQt2GruqU00pY/GyXgg==} - '@cloudflare/workers-types@4.20241112.0': - resolution: {integrity: sha512-Q4p9bAWZrX14bSCKY9to19xl0KMU7nsO5sJ2cTVspHoypsjPUMeQCsjHjmsO2C4Myo8/LPeDvmqFmkyNAPPYZw==} + '@cloudflare/workers-types@4.20241127.0': + resolution: {integrity: sha512-UqlvtqV8eI0CdPR7nxlbVlE52+lcjHvGdbYXEPwisy23+39RsFV7OOy0da0moJAhqnL2OhDmWTOaKdsVcPHiJQ==} '@codemirror/autocomplete@6.18.3': resolution: {integrity: sha512-1dNIOmiM0z4BIBwxmxEfA1yoxh1MF/6KPBbh20a5vphGV0ictKlgQsbJs6D6SkR6iJpGbpwRsa6PFMNlg9T9pQ==} @@ -3996,15 +3920,6 @@ packages: resolution: {integrity: sha512-S45oynt/WH19bHbIXjtli6QmwNYvaz+vtnubvNpNDvUOoA/OWh6j1OikIP3G+v5GHdxyC6EXoChG3HgYGEUfcg==} engines: {node: '>=14.0.0'} - '@rollup/pluginutils@5.1.0': - resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - '@rollup/pluginutils@5.1.3': resolution: {integrity: sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==} engines: {node: '>=14.0.0'} @@ -4552,9 +4467,6 @@ packages: '@types/estree-jsx@1.0.5': resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} - '@types/estree@1.0.5': - resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} @@ -7036,11 +6948,6 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true - jsesc@2.5.2: - resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} - engines: {node: '>=4'} - hasBin: true - jsesc@3.0.2: resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} engines: {node: '>=6'} @@ -9234,10 +9141,6 @@ packages: to-arraybuffer@1.0.1: resolution: {integrity: sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==} - to-fast-properties@2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} - engines: {node: '>=4'} - to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -9296,16 +9199,6 @@ packages: resolution: {integrity: sha512-Ibv4KAWfFkFdKJxnWfVtdOmB0Zi1RJVxcbPGiCDsFpCQSsmpWyuzHG3rQyI5YkobWwxFPEyQfu1hdo4qLG2zPw==} hasBin: true - tsconfck@3.1.1: - resolution: {integrity: sha512-00eoI6WY57SvZEVjm13stEVE90VkEdJAFGgpFLTsZbJyW/LwFQ7uQxJHWpZ2hzSWgCPKc9AnBnNP+0X7o3hAmQ==} - engines: {node: ^18 || >=20} - hasBin: true - peerDependencies: - typescript: ^5.0.0 - peerDependenciesMeta: - typescript: - optional: true - tsconfck@3.1.4: resolution: {integrity: sha512-kdqWFGVJqe+KGYvlSO9NIaWn9jT1Ny4oKVzAJsKii5eoE9snzTJzL4+MMVOMn+fikWGFmKEylcXL710V/kIPJQ==} engines: {node: ^18 || >=20} @@ -9408,6 +9301,11 @@ packages: engines: {node: '>=14.17'} hasBin: true + typescript@5.7.2: + resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} + engines: {node: '>=14.17'} + hasBin: true + ufo@1.5.4: resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} @@ -9998,16 +9896,6 @@ packages: '@cloudflare/workers-types': optional: true - wrangler@3.90.0: - resolution: {integrity: sha512-E/6E9ORAl987+3kP8wDiE3L1lj9r4vQ32/dl5toIxIkSMssmPRQVdxqwgMxbxJrytbFNo8Eo6swgjd4y4nUaLg==} - engines: {node: '>=16.17.0'} - hasBin: true - peerDependencies: - '@cloudflare/workers-types': ^4.20241106.0 - peerDependenciesMeta: - '@cloudflare/workers-types': - optional: true - wrangler@3.91.0: resolution: {integrity: sha512-Hdzn6wbY9cz5kL85ZUvWLwLIH7nPaEVRblfms40jhRf4qQO/Zf74aFlku8rQFbe8/2aVZFaxJVfBd6JQMeMSBQ==} engines: {node: '>=16.17.0'} @@ -10246,13 +10134,13 @@ snapshots: transitivePeerDependencies: - zod - '@ai-sdk/vue@0.0.57(vue@3.5.12(typescript@5.6.3))(zod@3.23.8)': + '@ai-sdk/vue@0.0.57(vue@3.5.12(typescript@5.7.2))(zod@3.23.8)': dependencies: '@ai-sdk/provider-utils': 1.0.22(zod@3.23.8) '@ai-sdk/ui-utils': 0.0.48(zod@3.23.8) - swrv: 1.0.4(vue@3.5.12(typescript@5.6.3)) + swrv: 1.0.4(vue@3.5.12(typescript@5.7.2)) optionalDependencies: - vue: 3.5.12(typescript@5.6.3) + vue: 3.5.12(typescript@5.7.2) transitivePeerDependencies: - zod @@ -11069,41 +10957,14 @@ snapshots: jsonwebtoken: 9.0.2 uuid: 8.3.2 - '@babel/code-frame@7.24.7': - dependencies: - '@babel/highlight': 7.24.7 - picocolors: 1.1.0 - '@babel/code-frame@7.26.0': dependencies: '@babel/helper-validator-identifier': 7.25.9 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.25.2': {} - '@babel/compat-data@7.26.0': {} - '@babel/core@7.25.2': - dependencies: - '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.24.7 - '@babel/generator': 7.25.0 - '@babel/helper-compilation-targets': 7.25.2 - '@babel/helper-module-transforms': 7.25.2(@babel/core@7.25.2) - '@babel/helpers': 7.25.0 - '@babel/parser': 7.25.3 - '@babel/template': 7.25.0 - '@babel/traverse': 7.25.3 - '@babel/types': 7.25.6 - convert-source-map: 2.0.0 - debug: 4.3.7(supports-color@5.5.0) - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - '@babel/core@7.26.0': dependencies: '@ampproject/remapping': 2.3.0 @@ -11124,13 +10985,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.25.0': - dependencies: - '@babel/types': 7.25.6 - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - jsesc: 2.5.2 - '@babel/generator@7.26.0': dependencies: '@babel/parser': 7.26.0 @@ -11143,14 +10997,6 @@ snapshots: dependencies: '@babel/types': 7.26.0 - '@babel/helper-compilation-targets@7.25.2': - dependencies: - '@babel/compat-data': 7.25.2 - '@babel/helper-validator-option': 7.24.8 - browserslist: 4.24.2 - lru-cache: 5.1.1 - semver: 6.3.1 - '@babel/helper-compilation-targets@7.25.9': dependencies: '@babel/compat-data': 7.26.0 @@ -11159,13 +11005,6 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-module-imports@7.24.7': - dependencies: - '@babel/traverse': 7.25.3 - '@babel/types': 7.25.6 - transitivePeerDependencies: - - supports-color - '@babel/helper-module-imports@7.25.9': dependencies: '@babel/traverse': 7.25.9 @@ -11173,16 +11012,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.25.2(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-module-imports': 7.24.7 - '@babel/helper-simple-access': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 - '@babel/traverse': 7.25.3 - transitivePeerDependencies: - - supports-color - '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 @@ -11194,46 +11023,17 @@ snapshots: '@babel/helper-plugin-utils@7.25.9': {} - '@babel/helper-simple-access@7.24.7': - dependencies: - '@babel/traverse': 7.25.3 - '@babel/types': 7.25.6 - transitivePeerDependencies: - - supports-color - - '@babel/helper-string-parser@7.24.8': {} - '@babel/helper-string-parser@7.25.9': {} - '@babel/helper-validator-identifier@7.24.7': {} - '@babel/helper-validator-identifier@7.25.9': {} - '@babel/helper-validator-option@7.24.8': {} - '@babel/helper-validator-option@7.25.9': {} - '@babel/helpers@7.25.0': - dependencies: - '@babel/template': 7.25.0 - '@babel/types': 7.25.6 - '@babel/helpers@7.26.0': dependencies: '@babel/template': 7.25.9 '@babel/types': 7.26.0 - '@babel/highlight@7.24.7': - dependencies: - '@babel/helper-validator-identifier': 7.24.7 - chalk: 2.4.2 - js-tokens: 4.0.0 - picocolors: 1.1.0 - - '@babel/parser@7.25.3': - dependencies: - '@babel/types': 7.25.6 - '@babel/parser@7.25.6': dependencies: '@babel/types': 7.26.0 @@ -11262,30 +11062,12 @@ snapshots: dependencies: regenerator-runtime: 0.14.1 - '@babel/template@7.25.0': - dependencies: - '@babel/code-frame': 7.24.7 - '@babel/parser': 7.25.3 - '@babel/types': 7.25.6 - '@babel/template@7.25.9': dependencies: '@babel/code-frame': 7.26.0 '@babel/parser': 7.26.0 '@babel/types': 7.26.0 - '@babel/traverse@7.25.3': - dependencies: - '@babel/code-frame': 7.24.7 - '@babel/generator': 7.25.0 - '@babel/parser': 7.25.3 - '@babel/template': 7.25.0 - '@babel/types': 7.25.6 - debug: 4.3.7(supports-color@5.5.0) - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - '@babel/traverse@7.25.9': dependencies: '@babel/code-frame': 7.26.0 @@ -11298,18 +11080,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/types@7.25.2': - dependencies: - '@babel/helper-string-parser': 7.24.8 - '@babel/helper-validator-identifier': 7.24.7 - to-fast-properties: 2.0.0 - - '@babel/types@7.25.6': - dependencies: - '@babel/helper-string-parser': 7.24.8 - '@babel/helper-validator-identifier': 7.24.7 - to-fast-properties: 2.0.0 - '@babel/types@7.26.0': dependencies: '@babel/helper-string-parser': 7.25.9 @@ -11416,11 +11186,6 @@ snapshots: mime: 3.0.0 zod: 3.23.8 - '@cloudflare/workers-shared@0.8.0': - dependencies: - mime: 3.0.0 - zod: 3.23.8 - '@cloudflare/workers-shared@0.9.0': dependencies: mime: 3.0.0 @@ -11432,7 +11197,7 @@ snapshots: '@cloudflare/workers-types@4.20241022.0': {} - '@cloudflare/workers-types@4.20241112.0': {} + '@cloudflare/workers-types@4.20241127.0': {} '@codemirror/autocomplete@6.18.3(@codemirror/language@6.10.2)(@codemirror/state@6.4.1)(@codemirror/view@6.33.0)(@lezer/common@1.2.1)': dependencies: @@ -11553,21 +11318,21 @@ snapshots: dependencies: ms: 2.1.3 - '@darkobits/valida@0.1.6(typescript@5.6.3)': + '@darkobits/valida@0.1.6(typescript@5.7.2)': dependencies: deepmerge: 4.3.1 fastest-levenshtein: 1.0.16 is-plain-object: 5.0.0 ow: 0.28.1 ramda: 0.28.0 - ts-essentials: 9.4.2(typescript@5.6.3) + ts-essentials: 9.4.2(typescript@5.7.2) transitivePeerDependencies: - typescript - '@darkobits/vite-plugin-favicons@0.3.2(typescript@5.6.3)(vite@5.4.10(@types/node@20.14.15)(lightningcss@1.28.1))': + '@darkobits/vite-plugin-favicons@0.3.2(typescript@5.7.2)(vite@5.4.10(@types/node@20.14.15)(lightningcss@1.28.1))': dependencies: '@darkobits/log': 2.0.0-beta.16 - '@darkobits/valida': 0.1.6(typescript@5.6.3) + '@darkobits/valida': 0.1.6(typescript@5.7.2) cacache: 18.0.4 fast-json-stable-stringify: 2.1.0 favicons: 7.2.0 @@ -11575,7 +11340,7 @@ snapshots: fs-extra: 11.2.0 parse5: 7.1.2 ssri: 10.0.6 - ts-essentials: 9.4.2(typescript@5.6.3) + ts-essentials: 9.4.2(typescript@5.7.2) vite: 5.4.10(@types/node@20.14.15)(lightningcss@1.28.1) transitivePeerDependencies: - typescript @@ -12242,7 +12007,7 @@ snapshots: '@antfu/install-pkg': 0.4.1 '@antfu/utils': 0.7.10 '@iconify/types': 2.0.0 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7(supports-color@5.5.0) kolorist: 1.8.0 local-pkg: 0.5.0 mlly: 1.7.1 @@ -13730,14 +13495,6 @@ snapshots: '@remix-run/router@1.19.1': {} - '@rollup/pluginutils@5.1.0(rollup@4.24.0)': - dependencies: - '@types/estree': 1.0.5 - estree-walker: 2.0.2 - picomatch: 2.3.1 - optionalDependencies: - rollup: 4.24.0 - '@rollup/pluginutils@5.1.3(rollup@4.24.0)': dependencies: '@types/estree': 1.0.6 @@ -14201,56 +13958,56 @@ snapshots: '@smithy/types': 3.6.0 tslib: 2.6.3 - '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.25.2)': + '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.26.0 - '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.25.2)': + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.26.0 - '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.25.2)': + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.26.0 - '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.25.2)': + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.26.0 - '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.25.2)': + '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.26.0 - '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.25.2)': + '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.26.0 - '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.25.2)': + '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.26.0 - '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.25.2)': + '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.26.0 - '@svgr/babel-preset@8.1.0(@babel/core@7.25.2)': + '@svgr/babel-preset@8.1.0(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.2 - '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.25.2) - '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.25.2) - '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.25.2) - '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.25.2) - '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.25.2) - '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.25.2) - '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.25.2) - '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.25.2) + '@babel/core': 7.26.0 + '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.26.0) + '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.26.0) + '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.26.0) + '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.26.0) + '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.26.0) + '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.26.0) + '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.26.0) + '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.26.0) - '@svgr/core@8.1.0(typescript@5.6.3)': + '@svgr/core@8.1.0(typescript@5.7.2)': dependencies: - '@babel/core': 7.25.2 - '@svgr/babel-preset': 8.1.0(@babel/core@7.25.2) + '@babel/core': 7.26.0 + '@svgr/babel-preset': 8.1.0(@babel/core@7.26.0) camelcase: 6.3.0 - cosmiconfig: 8.3.6(typescript@5.6.3) + cosmiconfig: 8.3.6(typescript@5.7.2) snake-case: 3.0.4 transitivePeerDependencies: - supports-color @@ -14258,14 +14015,14 @@ snapshots: '@svgr/hast-util-to-babel-ast@8.0.0': dependencies: - '@babel/types': 7.25.2 + '@babel/types': 7.26.0 entities: 4.5.0 - '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.6.3))': + '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.7.2))': dependencies: - '@babel/core': 7.25.2 - '@svgr/babel-preset': 8.1.0(@babel/core@7.25.2) - '@svgr/core': 8.1.0(typescript@5.6.3) + '@babel/core': 7.26.0 + '@svgr/babel-preset': 8.1.0(@babel/core@7.26.0) + '@svgr/core': 8.1.0(typescript@5.7.2) '@svgr/hast-util-to-babel-ast': 8.0.0 svg-parser: 2.0.4 transitivePeerDependencies: @@ -14383,8 +14140,6 @@ snapshots: dependencies: '@types/estree': 1.0.6 - '@types/estree@1.0.5': {} - '@types/estree@1.0.6': {} '@types/figlet@1.5.8': {} @@ -14743,11 +14498,11 @@ snapshots: '@vue/shared': 3.5.12 vue: 3.5.12(typescript@5.6.2) - '@vue/server-renderer@3.5.12(vue@3.5.12(typescript@5.6.3))': + '@vue/server-renderer@3.5.12(vue@3.5.12(typescript@5.7.2))': dependencies: '@vue/compiler-ssr': 3.5.12 '@vue/shared': 3.5.12 - vue: 3.5.12(typescript@5.6.3) + vue: 3.5.12(typescript@5.7.2) '@vue/shared@3.5.12': {} @@ -14865,7 +14620,7 @@ snapshots: - solid-js - vue - ai@3.4.20(openai@4.68.4(encoding@0.1.13)(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@4.2.19))(svelte@4.2.19)(vue@3.5.12(typescript@5.6.3))(zod@3.23.8): + ai@3.4.20(openai@4.68.4(encoding@0.1.13)(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@4.2.19))(svelte@4.2.19)(vue@3.5.12(typescript@5.7.2))(zod@3.23.8): dependencies: '@ai-sdk/provider': 0.0.26 '@ai-sdk/provider-utils': 1.0.22(zod@3.23.8) @@ -14873,7 +14628,7 @@ snapshots: '@ai-sdk/solid': 0.0.52(zod@3.23.8) '@ai-sdk/svelte': 0.0.54(svelte@4.2.19)(zod@3.23.8) '@ai-sdk/ui-utils': 0.0.48(zod@3.23.8) - '@ai-sdk/vue': 0.0.57(vue@3.5.12(typescript@5.6.3))(zod@3.23.8) + '@ai-sdk/vue': 0.0.57(vue@3.5.12(typescript@5.7.2))(zod@3.23.8) '@opentelemetry/api': 1.9.0 eventsource-parser: 1.1.2 json-schema: 0.4.0 @@ -15366,7 +15121,7 @@ snapshots: parse5: 7.1.2 parse5-htmlparser2-tree-adapter: 7.0.0 parse5-parser-stream: 7.1.2 - undici: 6.19.7 + undici: 6.19.8 whatwg-mimetype: 4.0.0 chokidar@3.6.0: @@ -15592,14 +15347,14 @@ snapshots: core-util-is@1.0.3: {} - cosmiconfig@8.3.6(typescript@5.6.3): + cosmiconfig@8.3.6(typescript@5.7.2): dependencies: import-fresh: 3.3.0 js-yaml: 4.1.0 parse-json: 5.2.0 path-type: 4.0.0 optionalDependencies: - typescript: 5.6.3 + typescript: 5.7.2 crelt@1.0.6: {} @@ -15859,11 +15614,11 @@ snapshots: '@types/react': 18.3.3 react: 18.3.1 - drizzle-orm@0.35.3(@cloudflare/workers-types@4.20241112.0)(@libsql/client-wasm@0.14.0)(@libsql/client@0.14.0)(@neondatabase/serverless@0.10.1)(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.3)(react@18.3.1): + drizzle-orm@0.35.3(@cloudflare/workers-types@4.20241127.0)(@libsql/client-wasm@0.14.0)(@libsql/client@0.14.0)(@neondatabase/serverless@0.10.1)(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.3)(react@18.3.1): dependencies: '@libsql/client-wasm': 0.14.0 optionalDependencies: - '@cloudflare/workers-types': 4.20241112.0 + '@cloudflare/workers-types': 4.20241127.0 '@libsql/client': 0.14.0 '@neondatabase/serverless': 0.10.1 '@opentelemetry/api': 1.9.0 @@ -15871,9 +15626,9 @@ snapshots: '@types/react': 18.3.3 react: 18.3.1 - drizzle-orm@0.36.4(@cloudflare/workers-types@4.20241112.0)(@libsql/client-wasm@0.14.0)(@libsql/client@0.14.0)(@neondatabase/serverless@0.10.1)(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.3)(react@18.3.1): + drizzle-orm@0.36.4(@cloudflare/workers-types@4.20241127.0)(@libsql/client-wasm@0.14.0)(@libsql/client@0.14.0)(@neondatabase/serverless@0.10.1)(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.3)(react@18.3.1): optionalDependencies: - '@cloudflare/workers-types': 4.20241112.0 + '@cloudflare/workers-types': 4.20241127.0 '@libsql/client': 0.14.0 '@libsql/client-wasm': 0.14.0 '@neondatabase/serverless': 0.10.1 @@ -15892,9 +15647,9 @@ snapshots: drizzle-orm: 0.33.0(@cloudflare/workers-types@4.20241018.0)(@libsql/client@0.14.0)(@neondatabase/serverless@0.10.1)(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.3)(react@18.3.1) zod: 3.23.8 - drizzle-zod@0.5.1(drizzle-orm@0.35.3(@cloudflare/workers-types@4.20241112.0)(@libsql/client-wasm@0.14.0)(@libsql/client@0.14.0)(@neondatabase/serverless@0.10.1)(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.3)(react@18.3.1))(zod@3.23.8): + drizzle-zod@0.5.1(drizzle-orm@0.35.3(@cloudflare/workers-types@4.20241127.0)(@libsql/client-wasm@0.14.0)(@libsql/client@0.14.0)(@neondatabase/serverless@0.10.1)(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.3)(react@18.3.1))(zod@3.23.8): dependencies: - drizzle-orm: 0.35.3(@cloudflare/workers-types@4.20241112.0)(@libsql/client-wasm@0.14.0)(@libsql/client@0.14.0)(@neondatabase/serverless@0.10.1)(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.3)(react@18.3.1) + drizzle-orm: 0.35.3(@cloudflare/workers-types@4.20241127.0)(@libsql/client-wasm@0.14.0)(@libsql/client@0.14.0)(@neondatabase/serverless@0.10.1)(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.3)(react@18.3.1) zod: 3.23.8 dset@3.1.4: {} @@ -17102,8 +16857,6 @@ snapshots: dependencies: argparse: 2.0.1 - jsesc@2.5.2: {} - jsesc@3.0.2: {} json-bigint@1.0.0: @@ -18050,7 +17803,7 @@ snapshots: miniflare@3.20240909.3: dependencies: '@cspotcode/source-map-support': 0.8.1 - acorn: 8.12.1 + acorn: 8.13.0 acorn-walk: 8.3.3 capnp-ts: 0.7.0 exit-hook: 2.2.1 @@ -18573,7 +18326,7 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.26.0 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -19797,9 +19550,9 @@ snapshots: dependencies: vue: 3.5.12(typescript@5.6.2) - swrv@1.0.4(vue@3.5.12(typescript@5.6.3)): + swrv@1.0.4(vue@3.5.12(typescript@5.7.2)): dependencies: - vue: 3.5.12(typescript@5.6.3) + vue: 3.5.12(typescript@5.7.2) tailwind-merge@2.5.2: {} @@ -19964,8 +19717,6 @@ snapshots: to-arraybuffer@1.0.1: {} - to-fast-properties@2.0.0: {} - to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -19999,9 +19750,9 @@ snapshots: ts-error@1.0.6: {} - ts-essentials@9.4.2(typescript@5.6.3): + ts-essentials@9.4.2(typescript@5.7.2): optionalDependencies: - typescript: 5.6.3 + typescript: 5.7.2 ts-interface-checker@0.1.13: {} @@ -20035,14 +19786,14 @@ snapshots: normalize-path: 3.0.0 plimit-lit: 1.6.1 - tsconfck@3.1.1(typescript@5.6.3): - optionalDependencies: - typescript: 5.6.3 - tsconfck@3.1.4(typescript@5.6.2): optionalDependencies: typescript: 5.6.2 + tsconfck@3.1.4(typescript@5.7.2): + optionalDependencies: + typescript: 5.7.2 + tslib@1.14.1: {} tslib@2.6.3: {} @@ -20126,6 +19877,9 @@ snapshots: typescript@5.6.3: {} + typescript@5.7.2: + optional: true + ufo@1.5.4: {} undefsafe@2.0.5: {} @@ -20386,22 +20140,22 @@ snapshots: - supports-color - terser - vite-plugin-svgr@4.2.0(rollup@4.24.0)(typescript@5.6.3)(vite@5.4.10(@types/node@20.14.15)(lightningcss@1.28.1)): + vite-plugin-svgr@4.2.0(rollup@4.24.0)(typescript@5.7.2)(vite@5.4.10(@types/node@20.14.15)(lightningcss@1.28.1)): dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.24.0) - '@svgr/core': 8.1.0(typescript@5.6.3) - '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.6.3)) + '@rollup/pluginutils': 5.1.3(rollup@4.24.0) + '@svgr/core': 8.1.0(typescript@5.7.2) + '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.7.2)) vite: 5.4.10(@types/node@20.14.15)(lightningcss@1.28.1) transitivePeerDependencies: - rollup - supports-color - typescript - vite-tsconfig-paths@4.3.2(typescript@5.6.3)(vite@5.4.10(@types/node@20.14.15)(lightningcss@1.28.1)): + vite-tsconfig-paths@4.3.2(typescript@5.7.2)(vite@5.4.10(@types/node@20.14.15)(lightningcss@1.28.1)): dependencies: - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7(supports-color@5.5.0) globrex: 0.1.2 - tsconfck: 3.1.1(typescript@5.6.3) + tsconfck: 3.1.4(typescript@5.7.2) optionalDependencies: vite: 5.4.10(@types/node@20.14.15)(lightningcss@1.28.1) transitivePeerDependencies: @@ -20676,15 +20430,15 @@ snapshots: optionalDependencies: typescript: 5.6.2 - vue@3.5.12(typescript@5.6.3): + vue@3.5.12(typescript@5.7.2): dependencies: '@vue/compiler-dom': 3.5.12 '@vue/compiler-sfc': 3.5.12 '@vue/runtime-dom': 3.5.12 - '@vue/server-renderer': 3.5.12(vue@3.5.12(typescript@5.6.3)) + '@vue/server-renderer': 3.5.12(vue@3.5.12(typescript@5.7.2)) '@vue/shared': 3.5.12 optionalDependencies: - typescript: 5.6.3 + typescript: 5.7.2 w3c-keyname@2.2.8: {} @@ -20822,7 +20576,7 @@ snapshots: '@cloudflare/workerd-linux-arm64': 1.20241106.1 '@cloudflare/workerd-windows-64': 1.20241106.1 - wrangler@3.78.5(@cloudflare/workers-types@4.20241112.0): + wrangler@3.78.5(@cloudflare/workers-types@4.20241127.0): dependencies: '@cloudflare/kv-asset-handler': 0.3.4 '@cloudflare/workers-shared': 0.5.3 @@ -20843,7 +20597,7 @@ snapshots: workerd: 1.20240909.0 xxhash-wasm: 1.0.2 optionalDependencies: - '@cloudflare/workers-types': 4.20241112.0 + '@cloudflare/workers-types': 4.20241127.0 fsevents: 2.3.3 transitivePeerDependencies: - bufferutil @@ -20937,7 +20691,7 @@ snapshots: - supports-color - utf-8-validate - wrangler@3.88.0(@cloudflare/workers-types@4.20241112.0): + wrangler@3.88.0(@cloudflare/workers-types@4.20241127.0): dependencies: '@cloudflare/kv-asset-handler': 0.3.4 '@cloudflare/workers-shared': 0.7.1 @@ -20959,72 +20713,14 @@ snapshots: workerd: 1.20241106.1 xxhash-wasm: 1.0.2 optionalDependencies: - '@cloudflare/workers-types': 4.20241112.0 - fsevents: 2.3.3 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - wrangler@3.90.0(@cloudflare/workers-types@4.20241112.0): - dependencies: - '@cloudflare/kv-asset-handler': 0.3.4 - '@cloudflare/workers-shared': 0.8.0 - '@esbuild-plugins/node-globals-polyfill': 0.2.3(esbuild@0.17.19) - '@esbuild-plugins/node-modules-polyfill': 0.2.2(esbuild@0.17.19) - blake3-wasm: 2.1.5 - chokidar: 4.0.1 - date-fns: 4.1.0 - esbuild: 0.17.19 - itty-time: 1.0.6 - miniflare: 3.20241106.1 - nanoid: 3.3.7 - path-to-regexp: 6.3.0 - resolve: 1.22.8 - resolve.exports: 2.0.2 - selfsigned: 2.4.1 - source-map: 0.6.1 - unenv: unenv-nightly@2.0.0-20241111-080453-894aa31 - workerd: 1.20241106.1 - xxhash-wasm: 1.0.2 - optionalDependencies: - '@cloudflare/workers-types': 4.20241112.0 - fsevents: 2.3.3 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - wrangler@3.91.0(@cloudflare/workers-types@4.20241022.0): - dependencies: - '@cloudflare/kv-asset-handler': 0.3.4 - '@cloudflare/workers-shared': 0.9.0 - '@esbuild-plugins/node-globals-polyfill': 0.2.3(esbuild@0.17.19) - '@esbuild-plugins/node-modules-polyfill': 0.2.2(esbuild@0.17.19) - blake3-wasm: 2.1.5 - chokidar: 4.0.1 - date-fns: 4.1.0 - esbuild: 0.17.19 - itty-time: 1.0.6 - miniflare: 3.20241106.1 - nanoid: 3.3.7 - path-to-regexp: 6.3.0 - resolve: 1.22.8 - resolve.exports: 2.0.2 - selfsigned: 2.4.1 - source-map: 0.6.1 - unenv: unenv-nightly@2.0.0-20241121-161142-806b5c0 - workerd: 1.20241106.1 - xxhash-wasm: 1.0.2 - optionalDependencies: - '@cloudflare/workers-types': 4.20241022.0 + '@cloudflare/workers-types': 4.20241127.0 fsevents: 2.3.3 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - wrangler@3.91.0(@cloudflare/workers-types@4.20241112.0): + wrangler@3.91.0(@cloudflare/workers-types@4.20241127.0): dependencies: '@cloudflare/kv-asset-handler': 0.3.4 '@cloudflare/workers-shared': 0.9.0 @@ -21046,7 +20742,7 @@ snapshots: workerd: 1.20241106.1 xxhash-wasm: 1.0.2 optionalDependencies: - '@cloudflare/workers-types': 4.20241112.0 + '@cloudflare/workers-types': 4.20241127.0 fsevents: 2.3.3 transitivePeerDependencies: - bufferutil diff --git a/studio/src/Layout/SidePanel/SidePanelTrigger.tsx b/studio/src/Layout/SidePanel/SidePanelTrigger.tsx index 7df5126bc..9e19d2604 100644 --- a/studio/src/Layout/SidePanel/SidePanelTrigger.tsx +++ b/studio/src/Layout/SidePanel/SidePanelTrigger.tsx @@ -1,5 +1,12 @@ +import { KeyboardShortcutKey } from "@/components/KeyboardShortcut"; import { Button } from "@/components/ui/button"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip"; import { useRequestorStore } from "@/pages/RequestorPage/store"; +import { isMac } from "@/utils"; import { Icon } from "@iconify/react/dist/iconify.js"; import { useHotkeys } from "react-hotkeys-hook"; @@ -14,15 +21,29 @@ export function SidePanelTrigger() { }); return ( - + + + + + + Open Side Panel +
+ {isMac ? "⌘" : "Ctrl"}{" "} + B +
+
+
); } diff --git a/studio/src/pages/RequestorPage/store/request-body.ts b/studio/src/pages/RequestorPage/store/request-body.ts index e44e80192..6ba391871 100644 --- a/studio/src/pages/RequestorPage/store/request-body.ts +++ b/studio/src/pages/RequestorPage/store/request-body.ts @@ -13,7 +13,7 @@ const RequestorBodyTypeSchema = z.union([ RequestorBodyFileTypeSchema, ]); -type RequestorBodyType = z.infer; +export type RequestorBodyType = z.infer; export const isRequestorBodyType = ( bodyType: unknown, diff --git a/studio/src/pages/RequestorPage/store/set-body-type.ts b/studio/src/pages/RequestorPage/store/set-body-type.ts index bb1498d99..42901258d 100644 --- a/studio/src/pages/RequestorPage/store/set-body-type.ts +++ b/studio/src/pages/RequestorPage/store/set-body-type.ts @@ -20,6 +20,7 @@ export function setBodyTypeInState( ): void { const oldBodyValue = state.body.value; const oldBodyType = state.body.type; + // Handle the case where the body type is the same, but the multipart flag is different if (oldBodyType === newBodyType) { // HACK - Refactor @@ -58,6 +59,5 @@ export function setBodyTypeInState( const isNonTextOldBody = Array.isArray(oldBodyValue) || oldBodyValue instanceof File; const newBodyValue = isNonTextOldBody ? "" : oldBodyValue; - state.body = { type: newBodyType, value: newBodyValue }; //, } diff --git a/studio/src/pages/RequestorPage/useRequestorHistory.ts b/studio/src/pages/RequestorPage/useRequestorHistory.ts index e97cbd530..056f2c974 100644 --- a/studio/src/pages/RequestorPage/useRequestorHistory.ts +++ b/studio/src/pages/RequestorPage/useRequestorHistory.ts @@ -3,13 +3,17 @@ import { removeQueryParams } from "@/utils"; import type { TraceListResponse } from "@fiberplane/fpx-types"; import { useHandler } from "@fiberplane/hooks"; import { useMemo } from "react"; -import { createKeyValueParameters } from "./KeyValueForm"; +import { + type KeyValueParameter, + createKeyValueParameters, +} from "./KeyValueForm"; import { type ProxiedRequestResponse, useFetchRequestorRequests, } from "./queries"; import { findMatchedRoute } from "./routes"; -import { useRequestorStore } from "./store"; +import { type RequestorBody, useRequestorStore } from "./store"; +import type { RequestorBodyType } from "./store/request-body"; import { isRequestMethod, isWsRequest } from "./types"; import { sortProxiedRequestResponsesDescending, @@ -17,6 +21,7 @@ import { } from "./utils"; const EMPTY_TRACES: TraceListResponse = []; + export function useRequestorHistory() { const { routes, @@ -146,7 +151,9 @@ export function useRequestorHistory() { } else { const safeBody = typeof body !== "string" ? JSON.stringify(body) : body; - setBody(safeBody); + const bodyType = determineBodyType(headers); + const transformedBody = transformBodyValue(bodyType, safeBody); + setBody(transformedBody); } } else { // HACK - move this logic into the reducer @@ -197,3 +204,148 @@ export function useRequestorHistory() { loadHistoricalRequest, }; } + +type BodyType = { + type: RequestorBodyType; + isMultipart?: boolean; +}; + +/** + * Transforms the body value based on the body type into something that can be displayed in the UI + * + * @NOTE - This is a temporary solution. Currently does not work for form data. + */ +function transformBodyValue( + bodyType: BodyType, + bodyValue: string | undefined | null, +): RequestorBody { + switch (bodyType.type) { + case "json": { + try { + const parsed = JSON.parse(bodyValue || ""); + return { type: "json", value: JSON.stringify(parsed, null, 2) }; // Pretty-print JSON + } catch { + if (!bodyValue) { + return { type: "json", value: undefined }; + } + if (typeof bodyValue === "string") { + return { type: "json", value: bodyValue }; + } + return { type: "json", value: JSON.stringify(bodyValue) }; + } + } + case "text": { + return { type: "text", value: bodyValue ?? undefined }; + } + + case "form-data": { + /** + * NOTE - Handling form bodies is tricky because of how the middleware might serialize them + * E.g., this is a multipart form data request body as the trace shows it + * + * ``` + * {"avatar":{"name":"IMG_5635.png","type":"image/png","size":3141659}} + * ``` + * + * E.g., this is a urlencoded form data request body as the trace shows it + * + * ``` + * {"name":"Samwise the Brave"} + * ``` + */ + if (bodyType.isMultipart) { + // Handle multipart form-data + // const formattedValue = parseUrlEncodedFormBody(bodyValue ?? ""); + return { + type: "form-data", + isMultipart: true, + value: [], + }; + } + // Handle urlencoded form-data + const formattedValue = parseUrlEncodedFormBody(bodyValue ?? ""); + return { + type: "form-data", + isMultipart: false, + value: formattedValue.map((param) => ({ + ...param, + value: { + value: param.value, + type: "text", + }, + })), + }; + } + case "file": + return { type: "file", value: undefined }; + + default: + return { type: "text", value: bodyValue ?? undefined }; + } +} + +function determineBodyType(headers: Record): BodyType { + const contentType = headers["Content-Type"] || headers["content-type"]; + if (!contentType) { + return { type: "text" }; + } + + if (contentType.includes("application/json")) { + return { type: "json" }; + } + if ( + contentType.includes("application/xml") || + contentType.includes("text/xml") + ) { + return { type: "text" }; + } + if (contentType.includes("text/plain")) { + return { type: "text" }; + } + if (contentType.includes("application/x-www-form-urlencoded")) { + return { type: "form-data", isMultipart: false }; + } + if (contentType.includes("multipart/form-data")) { + return { type: "form-data", isMultipart: true }; + } + if (contentType.includes("application/octet-stream")) { + return { type: "file" }; + } + + return { type: "text" }; +} + +function parseUrlEncodedFormBody(body: string): KeyValueParameter[] { + if (isStringifiedRecordWithKeys(body)) { + return createKeyValueParameters( + Object.entries(JSON.parse(body)).map(([key, value]) => ({ + key, + value: String(value), + enabled: true, + })), + ); + } + + // Split the body by '&' to get key-value pairs + const pairs = body.split("&"); + + // Map each pair to a KeyValueParameter + const keyValueParameters = pairs.map((pair) => { + const [key, value] = pair.split("=").map(decodeURIComponent); + return { key, value }; + }); + + // Use createKeyValueParameters to generate the final structure + return createKeyValueParameters(keyValueParameters); +} + +function isStringifiedRecordWithKeys( + obj: unknown, +): obj is Record { + try { + const parsed = JSON.parse(obj as string); + return typeof parsed === "object" && parsed !== null; + } catch { + return false; + } +} diff --git a/www/src/assets/blog/2024-12-06-client-side-guide-hydrated.mov b/www/src/assets/blog/2024-12-06-client-side-guide-hydrated.mov new file mode 100644 index 000000000..763595b3e Binary files /dev/null and b/www/src/assets/blog/2024-12-06-client-side-guide-hydrated.mov differ diff --git a/www/src/assets/blog/2024-12-06-client-side-guide-static.mov b/www/src/assets/blog/2024-12-06-client-side-guide-static.mov new file mode 100644 index 000000000..56a98db1f Binary files /dev/null and b/www/src/assets/blog/2024-12-06-client-side-guide-static.mov differ diff --git a/www/src/content/changelog/!canary.mdx b/www/src/content/changelog/!canary.mdx index 09d75e47b..1a7a156c3 100644 --- a/www/src/content/changelog/!canary.mdx +++ b/www/src/content/changelog/!canary.mdx @@ -1,14 +1,9 @@ --- -date: 2024-10-19 +date: 2024-12-09 version: canary draft: true --- ### Features -- Improved code analysis. Through the new @fiberplane/source-analysis package a more flexbile and thorough source code implementation is included -- OpenAPI integration. You can now fetch the OpenAPI spec for your api, and Studio will use it to map spec definitions to your api routes. Expected query parameters, headers, and body fields are then surfaced in Studio. -- Basic Command bar. You can now toggle the timeline or logs with the command bar (`cmd+k`), as well as switch between different tabs (Body, Headers, Params, Docs) in the request fields. -- Natural language request generation. You can now generate requests with an instruction prompt. Press `cmd+shift+g` to open the prompt input. - ### Bug fixes diff --git a/www/src/content/changelog/2024-10-25-v0_10_0.mdx b/www/src/content/changelog/2024-10-25-v0_10_0.mdx new file mode 100644 index 000000000..18fd1f944 --- /dev/null +++ b/www/src/content/changelog/2024-10-25-v0_10_0.mdx @@ -0,0 +1,9 @@ +--- +date: 2024-10-25 +version: 0.10.0 +draft: false +--- + +### Features + +- Fiberplane Services. Log in with GitHub to get access to 100 free AI-generated requests every day! diff --git a/www/src/content/changelog/2024-12-02-v0_12_1.mdx b/www/src/content/changelog/2024-12-02-v0_12_1.mdx new file mode 100644 index 000000000..a77002f22 --- /dev/null +++ b/www/src/content/changelog/2024-12-02-v0_12_1.mdx @@ -0,0 +1,11 @@ +--- +date: 2024-12-02 +version: 0.12.1 +draft: false +--- + +### Features +* Improved code analysis. Through the new @fiberplane/source-analysis package a more flexible and thorough source code implementation is included + +### Bug fixes +* Fixed an issue where the request body type would switch to "text" even when you selected "JSON" diff --git a/www/src/content/changelog/2024-12-04-v0_12_3.mdx b/www/src/content/changelog/2024-12-04-v0_12_3.mdx new file mode 100644 index 000000000..8dd61cd2a --- /dev/null +++ b/www/src/content/changelog/2024-12-04-v0_12_3.mdx @@ -0,0 +1,8 @@ +--- +date: 2024-12-04 +version: 0.12.3 +draft: false +--- + +### Features +* Routes file tree view. You can now view your routes as they're organized in your file system. diff --git a/www/src/content/changelog/2024-12-05-v0_12_4.mdx b/www/src/content/changelog/2024-12-05-v0_12_4.mdx new file mode 100644 index 000000000..5011e18f3 --- /dev/null +++ b/www/src/content/changelog/2024-12-05-v0_12_4.mdx @@ -0,0 +1,14 @@ +--- +date: 2024-12-05 +version: 0.12.4 +draft: false +--- + +### Features +* OpenAPI integration. Studio will use the OpenAPI spec from Hono-Zod-OpenAPI apps to map spec definitions to your api routes. Docs for a route are shown in a new "Docs" tab. +* Basic Command bar. You can now toggle the timeline or logs with the command bar (`cmd+k`), as well as switch between different tabs (Body, Headers, Params, Docs) in the request fields. +* Natural language request generation. You can now generate requests with an instruction prompt. Press `cmd+shift+g` to open the prompt input. + +### Bug fixes +* Added a tooltip to the sidebar panel toggle to make it clearer what the keyboard shortcut is (`cmd+b`) +* Fixed an issue where the route side bar would not highlight the currently selected route after you switched the method manually