diff --git a/apps/example/package.json b/apps/example/package.json index 8d62e72..885e8be 100644 --- a/apps/example/package.json +++ b/apps/example/package.json @@ -6,18 +6,23 @@ "dev": "pnpm prebuild && next dev", "build": "pnpm prebuild && next build", "start": "next start", - "generate": "pnpm prebuild && next-rest-framework generate", - "validate": "pnpm prebuild && next-rest-framework validate", + "generate": "pnpm prebuild && NODE_OPTIONS='--import=tsx' next-rest-framework generate", + "validate": "pnpm prebuild && NODE_OPTIONS='--import=tsx' next-rest-framework validate", + "custom-generate-openapi": "pnpm prebuild && tsx ./src/scripts/custom-generate-openapi.ts", + "custom-validate-openapi": "pnpm prebuild && tsx ./src/scripts/custom-validate-openapi.ts", "lint": "tsc && next lint" }, "dependencies": { + "jsdom": "24.0.0", "next-rest-framework": "workspace:*", + "tsx": "4.7.2", "zod-form-data": "2.0.2" }, "devDependencies": { + "@types/jsdom": "^21.1.6", "autoprefixer": "10.0.1", + "eslint-config-next": "14.0.4", "postcss": "8.4.33", - "tailwindcss": "3.3.0", - "eslint-config-next": "14.0.4" + "tailwindcss": "3.3.0" } } diff --git a/apps/example/public/openapi.json b/apps/example/public/openapi.json index 5d69114..5ce4b16 100644 --- a/apps/example/public/openapi.json +++ b/apps/example/public/openapi.json @@ -76,6 +76,31 @@ } } }, + "/api/v1/route-with-external-dep": { + "get": { + "operationId": "routeWithExternalDep", + "responses": { + "200": { + "description": "Response for status 200", + "content": { + "text/html": { + "schema": { + "$ref": "#/components/schemas/RouteWithExternalDep200ResponseBody" + } + } + } + }, + "500": { + "description": "An unknown error occurred, trying again might help.", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/UnexpectedError" } + } + } + } + } + } + }, "/api/v1/route-with-params/{slug}": { "get": { "operationId": "getParams", @@ -526,6 +551,31 @@ } } }, + "/api/v2/route-with-external-dep": { + "get": { + "operationId": "routeWithExternalDep", + "responses": { + "200": { + "description": "Response for status 200", + "content": { + "text/html": { + "schema": { + "$ref": "#/components/schemas/RouteWithExternalDep200ResponseBody" + } + } + } + }, + "500": { + "description": "An unknown error occurred, trying again might help.", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/UnexpectedError" } + } + } + } + } + } + }, "/api/v2/route-with-params/{slug}": { "get": { "operationId": "getPathParams", @@ -1076,6 +1126,7 @@ "file": { "type": "string", "format": "binary" } } }, + "RouteWithExternalDep200ResponseBody": { "type": "string" }, "UnexpectedError": { "type": "object", "properties": { "message": { "type": "string" } }, diff --git a/apps/example/src/app/api/v2/route-with-external-dep/route.ts b/apps/example/src/app/api/v2/route-with-external-dep/route.ts new file mode 100644 index 0000000..5a77ccb --- /dev/null +++ b/apps/example/src/app/api/v2/route-with-external-dep/route.ts @@ -0,0 +1,23 @@ +import { TypedNextResponse, route, routeOperation } from 'next-rest-framework'; +import { JSDOM } from 'jsdom'; +import { z } from 'zod'; + +export const { GET } = route({ + routeWithExternalDep: routeOperation({ + method: 'GET' + }) + .outputs([ + { + contentType: 'text/html', + status: 200, + body: z.string() + } + ]) + .handler(() => { + const dom = new JSDOM('

Hello world

'); + + return new TypedNextResponse(dom.serialize(), { + headers: { 'Content-Type': 'text/html' } + }); + }) +}); diff --git a/apps/example/src/pages/api/v1/route-with-external-dep/index.ts b/apps/example/src/pages/api/v1/route-with-external-dep/index.ts new file mode 100644 index 0000000..b45ab7e --- /dev/null +++ b/apps/example/src/pages/api/v1/route-with-external-dep/index.ts @@ -0,0 +1,21 @@ +import { apiRoute, apiRouteOperation } from 'next-rest-framework'; +import { JSDOM } from 'jsdom'; +import { z } from 'zod'; + +export default apiRoute({ + routeWithExternalDep: apiRouteOperation({ + method: 'GET' + }) + .outputs([ + { + contentType: 'text/html', + status: 200, + body: z.string() + } + ]) + .handler((_req, res) => { + const dom = new JSDOM('

Hello world

'); + res.setHeader('Content-Type', 'text/html'); + res.send(dom.serialize()); + }) +}); diff --git a/apps/example/src/scripts/custom-generate-openapi.ts b/apps/example/src/scripts/custom-generate-openapi.ts new file mode 100644 index 0000000..a369ce1 --- /dev/null +++ b/apps/example/src/scripts/custom-generate-openapi.ts @@ -0,0 +1,7 @@ +import { generate } from 'next-rest-framework/dist/cli/generate'; + +generate({ configPath: '/api/v2' }) + .then(() => { + console.log('Completed building OpenAPI schema from custom script.'); + }) + .catch(console.error); diff --git a/apps/example/src/scripts/custom-validate-openapi.ts b/apps/example/src/scripts/custom-validate-openapi.ts new file mode 100644 index 0000000..c7ba680 --- /dev/null +++ b/apps/example/src/scripts/custom-validate-openapi.ts @@ -0,0 +1,7 @@ +import { validate } from 'next-rest-framework/dist/cli/validate'; + +validate({ configPath: '/api/v2' }) + .then(() => { + console.log('Completed validating OpenAPI schema from custom script.'); + }) + .catch(console.error); diff --git a/docs/docs/api-reference.md b/docs/docs/api-reference.md index 15eb742..8305fb4 100644 --- a/docs/docs/api-reference.md +++ b/docs/docs/api-reference.md @@ -207,10 +207,11 @@ The RPC operation handler function is a strongly-typed function to implement the ## [CLI](#cli) -The Next REST Framework CLI supports generating and validating the `openapi.json` file: +The CLI commands will parse your Next.js APIs and generate/validate the `openapi.json` file. +If using TypeScript, you will need to install `tsx` and use it as the Node.js loader for the CLI commands below: `npm install --save-dev tsx` -- `npx next-rest-framework generate` to generate the `openapi.json` file. -- `npx next-rest-framework validate` to validate that the `openapi.json` file is up-to-date. +- `NODE_OPTIONS='--import=tsx' npx next-rest-framework generate` to generate the `openapi.json` file. +- `NODE_OPTIONS='--import=tsx' npx next-rest-framework validate` to validate that the `openapi.json` file is up-to-date. The `next-rest-framework validate` command is useful to have as part of the static checks in your CI/CD pipeline. Both commands support the following options: @@ -222,7 +223,7 @@ A good practice is to set these in your `package.json` as both commands are need ```json "scripts": { - "generate": "next-rest-framework generate", - "validate": "next-rest-framework validate", + "generate": "NODE_OPTIONS='--import=tsx' next-rest-framework generate", + "validate": "NODE_OPTIONS='--import=tsx' next-rest-framework validate", } ``` diff --git a/docs/docs/getting-started.md b/docs/docs/getting-started.md index d15ea0f..7fa18b7 100644 --- a/docs/docs/getting-started.md +++ b/docs/docs/getting-started.md @@ -11,9 +11,10 @@ sidebar_position: 2 You also need the following dependencies installed in you Next.js project: - [Next.js](https://github.com/vercel/next.js) >= v12 -- [Zod](https://github.com/colinhacks/zod) >= v3 -- [TypeScript](https://www.typescriptlang.org/) >= v3 -- Optional, needed if working with forms: [zod-form-data](https://www.npmjs.com/package/zod-form-data) >= v2 +- Optional (needed for validating input): [Zod](https://github.com/colinhacks/zod) >= v3 +- Optional: [TypeScript](https://www.typescriptlang.org/) >= v3 +- Optional (needed when using the CLI commands and using TypeScript): [tsx](https://github.com/privatenumber/tsx) >= v4 +- Optional (needed if working with forms): [zod-form-data](https://www.npmjs.com/package/zod-form-data) >= v2 ## [Installation](#installation) diff --git a/docs/docs/intro.md b/docs/docs/intro.md index d13d9b5..5fa1be4 100644 --- a/docs/docs/intro.md +++ b/docs/docs/intro.md @@ -30,9 +30,10 @@ Next REST Framework is an open-source, opinionated, lightweight, easy-to-use set You also need the following dependencies installed in you Next.js project: - [Next.js](https://github.com/vercel/next.js) >= v12 -- [Zod](https://github.com/colinhacks/zod) >= v3 -- [TypeScript](https://www.typescriptlang.org/) >= v3 -- Optional, needed if working with forms: [zod-form-data](https://www.npmjs.com/package/zod-form-data) >= v2 +- Optional (needed for validating input): [Zod](https://github.com/colinhacks/zod) >= v3 +- Optional: [TypeScript](https://www.typescriptlang.org/) >= v3 +- Optional (needed when using the CLI commands and using TypeScript): [tsx](https://github.com/privatenumber/tsx) >= v4 +- Optional (needed if working with forms): [zod-form-data](https://www.npmjs.com/package/zod-form-data) >= v2 ### [Installation](#installation) diff --git a/packages/next-rest-framework/README.md b/packages/next-rest-framework/README.md index 5c0d730..a2244b1 100644 --- a/packages/next-rest-framework/README.md +++ b/packages/next-rest-framework/README.md @@ -101,9 +101,10 @@ This is a monorepo containing the following packages / projects: You also need the following dependencies installed in you Next.js project: - [Next.js](https://github.com/vercel/next.js) >= v12 -- [Zod](https://github.com/colinhacks/zod) >= v3 -- [TypeScript](https://www.typescriptlang.org/) >= v3 -- Optional, needed if working with forms: [zod-form-data](https://www.npmjs.com/package/zod-form-data) >= v2 +- Optional (needed for validating input): [Zod](https://github.com/colinhacks/zod) >= v3 +- Optional: [TypeScript](https://www.typescriptlang.org/) >= v3 +- Optional (needed when using the CLI commands and using TypeScript): [tsx](https://github.com/privatenumber/tsx) >= v4 +- Optional (needed if working with forms): [zod-form-data](https://www.npmjs.com/package/zod-form-data) >= v2 ## [Installation](#installation) @@ -886,10 +887,11 @@ The RPC operation handler function is a strongly-typed function to implement the ## [CLI](#cli) -The Next REST Framework CLI supports generating and validating the `openapi.json` file: +The CLI commands will parse your Next.js APIs and generate/validate the `openapi.json` file. +If using TypeScript, you will need to install [tsx](https://github.com/privatenumber/tsx) and use it as the Node.js loader for the CLI commands below: `npm install --save-dev tsx` -- `npx next-rest-framework generate` to generate the `openapi.json` file. -- `npx next-rest-framework validate` to validate that the `openapi.json` file is up-to-date. +- `NODE_OPTIONS='--import=tsx' npx next-rest-framework generate` to generate the `openapi.json` file. +- `NODE_OPTIONS='--import=tsx' npx next-rest-framework validate` to validate that the `openapi.json` file is up-to-date. The `next-rest-framework validate` command is useful to have as part of the static checks in your CI/CD pipeline. Both commands support the following options: @@ -901,8 +903,8 @@ A good practice is to set these in your `package.json` as both commands are need ```json "scripts": { - "generate": "next-rest-framework generate", - "validate": "next-rest-framework validate", + "generate": "NODE_OPTIONS='--import=tsx' next-rest-framework generate", + "validate": "NODE_OPTIONS='--import=tsx' next-rest-framework validate", } ``` diff --git a/packages/next-rest-framework/package.json b/packages/next-rest-framework/package.json index de00849..77582ca 100644 --- a/packages/next-rest-framework/package.json +++ b/packages/next-rest-framework/package.json @@ -38,8 +38,6 @@ "dependencies": { "chalk": "4.1.2", "commander": "10.0.1", - "esbuild": "0.19.11", - "fast-glob": "3.3.2", "formidable": "^3.5.1", "lodash": "4.17.21", "prettier": "3.0.2", @@ -51,6 +49,7 @@ "@types/jest": "29.5.4", "@types/lodash": "4.14.197", "@types/qs": "6.9.11", + "esbuild": "0.19.11", "jest": "29.6.4", "next": "*", "node-mocks-http": "1.13.0", diff --git a/packages/next-rest-framework/src/cli/constants.ts b/packages/next-rest-framework/src/cli/constants.ts index f7d7a9a..fee9683 100644 --- a/packages/next-rest-framework/src/cli/constants.ts +++ b/packages/next-rest-framework/src/cli/constants.ts @@ -1,2 +1 @@ export const OPEN_API_VERSION = '3.0.1'; -export const NEXT_REST_FRAMEWORK_TEMP_FOLDER_NAME = '.next-rest-framework'; diff --git a/packages/next-rest-framework/src/cli/generate.ts b/packages/next-rest-framework/src/cli/generate.ts index b90d0ce..268857f 100644 --- a/packages/next-rest-framework/src/cli/generate.ts +++ b/packages/next-rest-framework/src/cli/generate.ts @@ -3,7 +3,6 @@ import { existsSync, readFileSync, writeFileSync } from 'fs'; import { join } from 'path'; import * as prettier from 'prettier'; import { findConfig, generateOpenApiSpec } from './utils'; -import { isEqualWith } from 'lodash'; const writeOpenApiSpec = async ({ path, @@ -35,11 +34,7 @@ const writeOpenApiSpec = async ({ }; // Regenerate the OpenAPI spec if it has changed. -export const syncOpenApiSpecFromBuild = async ({ - configPath -}: { - configPath?: string; -}) => { +export const generate = async ({ configPath }: { configPath?: string }) => { const config = await findConfig({ configPath }); if (!config) { @@ -53,7 +48,7 @@ export const syncOpenApiSpecFromBuild = async ({ const data = readFileSync(path); const openApiSpec = JSON.parse(data.toString()); - if (!isEqualWith(openApiSpec, spec)) { + if (!(JSON.stringify(openApiSpec) === JSON.stringify(spec))) { console.info( chalk.yellowBright( 'OpenAPI spec changed, regenerating `openapi.json`...' diff --git a/packages/next-rest-framework/src/cli/index.ts b/packages/next-rest-framework/src/cli/index.ts index 4d76d43..6657a5f 100644 --- a/packages/next-rest-framework/src/cli/index.ts +++ b/packages/next-rest-framework/src/cli/index.ts @@ -2,9 +2,8 @@ import { Command } from 'commander'; import chalk from 'chalk'; -import { clearTmpFolder, compileEndpoints } from './utils'; -import { validateOpenApiSpecFromBuild } from './validate'; -import { syncOpenApiSpecFromBuild } from './generate'; +import { generate } from './generate'; +import { validate } from './validate'; const program = new Command(); @@ -19,18 +18,15 @@ program const configPath: string = options.configPath ?? ''; try { - await compileEndpoints(); console.info(chalk.yellowBright('Generating OpenAPI spec...')); - await syncOpenApiSpecFromBuild({ + await generate({ configPath }); } catch (e) { console.error(e); process.exit(1); } - - await clearTmpFolder(); }); program @@ -44,10 +40,9 @@ program const configPath: string = options.configPath ?? ''; try { - await compileEndpoints(); console.info(chalk.yellowBright('Validating OpenAPI spec...')); - const valid = await validateOpenApiSpecFromBuild({ + const valid = await validate({ configPath }); @@ -58,8 +53,6 @@ program console.error(e); process.exit(1); } - - await clearTmpFolder(); }); program.parse(process.argv); diff --git a/packages/next-rest-framework/src/cli/utils.ts b/packages/next-rest-framework/src/cli/utils.ts index 8b3e3bd..b06dd1b 100644 --- a/packages/next-rest-framework/src/cli/utils.ts +++ b/packages/next-rest-framework/src/cli/utils.ts @@ -1,7 +1,4 @@ -import { rm } from 'fs/promises'; import chalk from 'chalk'; -import { globSync } from 'fast-glob'; -import { build } from 'esbuild'; import { type NrfOasData } from '../shared/paths'; import { type NextRestFrameworkConfig } from '../types'; import { existsSync, readdirSync } from 'fs'; @@ -9,36 +6,7 @@ import { type OpenAPIV3_1 } from 'openapi-types'; import { join } from 'path'; import { isValidMethod } from '../shared'; import { merge } from 'lodash'; -import { - NEXT_REST_FRAMEWORK_TEMP_FOLDER_NAME, - OPEN_API_VERSION -} from './constants'; - -export const clearTmpFolder = async () => { - try { - await rm(join(process.cwd(), NEXT_REST_FRAMEWORK_TEMP_FOLDER_NAME), { - recursive: true, - force: true - }); - } catch {} -}; - -// Compile the Next.js routes and API routes to CJS modules with esbuild. -export const compileEndpoints = async () => { - await clearTmpFolder(); - console.info(chalk.yellowBright('Compiling endpoints...')); - const entryPoints = globSync(['./**/*.ts', '!**/node_modules/**']); - - // Bundle to CJS modules and use an explicit .cjs extension to make the file imports work in other parts of the CLI. - await build({ - entryPoints, - bundle: true, - format: 'cjs', - platform: 'node', - outdir: NEXT_REST_FRAMEWORK_TEMP_FOLDER_NAME, - outExtension: { '.js': '.cjs' } - }); -}; +import { OPEN_API_VERSION } from './constants'; // Traverse the base path and find all nested files. const getNestedFiles = (basePath: string, dir: string): string[] => { @@ -55,7 +23,8 @@ const getNestedFiles = (basePath: string, dir: string): string[] => { // Convert file path of a route to a route name. const getRouteName = (file: string) => `/${file}` - .replace('/route.cjs', '') + .replace('/route.ts', '') + .replace('/route.js', '') .replace(/\\/g, '/') .replaceAll('[', '{') .replaceAll(']', '}'); @@ -67,7 +36,8 @@ const getApiRouteName = (file: string) => .replace(/\\/g, '/') .replaceAll('[', '{') .replaceAll(']', '}') - .replace('.cjs', ''); + .replace('.ts', '') + .replace('.js', ''); // Find the Next REST Framework config from one of the docs route handlers. export const findConfig = async ({ configPath }: { configPath?: string }) => { @@ -78,11 +48,7 @@ export const findConfig = async ({ configPath }: { configPath?: string }) => { // Scan `app` or `src/app` folders for docs route handlers. const findAppRouterConfig = async (path: string) => { - const appRouterPath = join( - process.cwd(), - NEXT_REST_FRAMEWORK_TEMP_FOLDER_NAME, - path - ); + const appRouterPath = join(process.cwd(), path); if (existsSync(appRouterPath)) { const filteredRoutes = getNestedFiles(appRouterPath, '').filter( @@ -91,19 +57,14 @@ export const findConfig = async ({ configPath }: { configPath?: string }) => { return configPath === getRouteName(file); } - return true; + return file.endsWith('route.ts') || file.endsWith('route.js'); } ); await Promise.all( filteredRoutes.map(async (route) => { try { - const filePathToRoute = join( - process.cwd(), - NEXT_REST_FRAMEWORK_TEMP_FOLDER_NAME, - path, - route - ); + const filePathToRoute = join(process.cwd(), path, route); const url = new URL(`file://${filePathToRoute}`).toString(); const res = await import(url).then((mod) => mod.default); @@ -122,7 +83,7 @@ export const findConfig = async ({ configPath }: { configPath?: string }) => { }); } } - } catch (e) { + } catch { // Route was not a docs handler. } }) @@ -138,11 +99,7 @@ export const findConfig = async ({ configPath }: { configPath?: string }) => { // Scan `pages/api` or `src/pages/api` folders for docs API route handlers. const findPagesRouterConfig = async (path: string) => { - const pagesRouterPath = join( - process.cwd(), - NEXT_REST_FRAMEWORK_TEMP_FOLDER_NAME, - path - ); + const pagesRouterPath = join(process.cwd(), path); if (existsSync(pagesRouterPath)) { const filteredApiRoutes = getNestedFiles(pagesRouterPath, '').filter( @@ -158,12 +115,7 @@ export const findConfig = async ({ configPath }: { configPath?: string }) => { await Promise.all( filteredApiRoutes.map(async (route) => { try { - const filePathToRoute = join( - process.cwd(), - NEXT_REST_FRAMEWORK_TEMP_FOLDER_NAME, - path, - route - ); + const filePathToRoute = join(process.cwd(), path, route); const url = new URL(`file://${filePathToRoute}`).toString(); const res = await import(url).then((mod) => mod.default); @@ -281,11 +233,14 @@ export const generatePathsFromBuild = async ({ * - Filter disallowed paths. */ const getCleanedRoutes = (files: string[]) => - files - .filter((file) => file.endsWith('route.cjs')) - .filter((file) => !file.includes('[...')) - .filter((file) => !file.endsWith('rpc/[operationId]/route.cjs')) - .filter((file) => isAllowedRoute(getRouteName(file))); + files.filter( + (file) => + (file.endsWith('route.ts') || file.endsWith('route.js')) && + !file.includes('[...') && + !file.endsWith('rpc/[operationId]/route.ts') && + !file.endsWith('rpc/[operationId]/route.js') && + isAllowedRoute(getRouteName(file)) + ); /* * Filter RPC routes to include: @@ -293,21 +248,29 @@ export const generatePathsFromBuild = async ({ * - Filter disallowed paths. */ const getCleanedRpcRoutes = (files: string[]) => - files - .filter((file) => file.endsWith('rpc/[operationId]/route.cjs')) - .filter((file) => isAllowedRoute(getRouteName(file))); + files.filter( + (file) => + (file.endsWith('rpc/[operationId]/route.ts') || + file.endsWith('rpc/[operationId]/route.js')) && + isAllowedRoute(getRouteName(file)) + ); /* * Filter the API routes to include: + * - Remove non-TS/JS files. * - Remove catch-all API routes. * - Filter RPC API routes. * - Filter disallowed paths. */ const getCleanedApiRoutes = (files: string[]) => - files - .filter((file) => !file.includes('[...')) - .filter((file) => !file.endsWith('rpc/[operationId].cjs')) - .filter((file) => isAllowedRoute(getApiRouteName(file))); + files.filter( + (file) => + (file.endsWith('.ts') || file.endsWith('.js')) && + !file.includes('[...') && + !file.endsWith('rpc/[operationId].ts') && + !file.endsWith('rpc/[operationId].js') && + isAllowedRoute(getApiRouteName(file)) + ); /* * Filter RPC API routes to include: @@ -315,9 +278,12 @@ export const generatePathsFromBuild = async ({ * - Filter disallowed paths. */ const getCleanedRpcApiRoutes = (files: string[]) => - files - .filter((file) => file.endsWith('rpc/[operationId].cjs')) - .filter((file) => isAllowedRoute(getApiRouteName(file))); + files.filter( + (file) => + (file.endsWith('rpc/[operationId].ts') || + file.endsWith('rpc/[operationId].js')) && + isAllowedRoute(getApiRouteName(file)) + ); const isNrfOasData = (x: unknown): x is NrfOasData => { if (typeof x !== 'object' || x === null) { @@ -331,11 +297,7 @@ export const generatePathsFromBuild = async ({ // Scan `app` or `src/app` folders for route handlers and get the OpenAPI paths. const collectAppRouterPaths = async (path: string) => { - const appRouterPath = join( - process.cwd(), - NEXT_REST_FRAMEWORK_TEMP_FOLDER_NAME, - path - ); + const appRouterPath = join(process.cwd(), path); if (existsSync(appRouterPath)) { const files = getNestedFiles(appRouterPath, ''); @@ -345,12 +307,7 @@ export const generatePathsFromBuild = async ({ await Promise.all( [...routes, ...rpcRoutes].map(async (route) => { try { - const filePathToRoute = join( - process.cwd(), - NEXT_REST_FRAMEWORK_TEMP_FOLDER_NAME, - path, - route - ); + const filePathToRoute = join(process.cwd(), path, route); const url = new URL(`file://${filePathToRoute}`).toString(); const res = await import(url).then((mod) => mod.default); @@ -383,11 +340,7 @@ export const generatePathsFromBuild = async ({ // Scan `pages/api` or `src/pages/api` folders for API route handlers and get the OpenAPI paths. const collectPagesRouterPaths = async (path: string) => { - const pagesRouterPath = join( - process.cwd(), - NEXT_REST_FRAMEWORK_TEMP_FOLDER_NAME, - path - ); + const pagesRouterPath = join(process.cwd(), path); if (existsSync(pagesRouterPath)) { const files = getNestedFiles(pagesRouterPath, ''); @@ -397,12 +350,7 @@ export const generatePathsFromBuild = async ({ await Promise.all( [...apiRoutes, ...rpcApiRoutes].map(async (apiRoute) => { try { - const filePathToRoute = join( - process.cwd(), - NEXT_REST_FRAMEWORK_TEMP_FOLDER_NAME, - path, - apiRoute - ); + const filePathToRoute = join(process.cwd(), path, apiRoute); const url = new URL(`file://${filePathToRoute}`).toString(); const res = await import(url).then((mod) => mod.default); diff --git a/packages/next-rest-framework/src/cli/validate.ts b/packages/next-rest-framework/src/cli/validate.ts index 52a8acf..b270cfa 100644 --- a/packages/next-rest-framework/src/cli/validate.ts +++ b/packages/next-rest-framework/src/cli/validate.ts @@ -1,15 +1,10 @@ import { join } from 'path'; import { findConfig, generateOpenApiSpec } from './utils'; import { readFileSync } from 'fs'; -import { isEqualWith } from 'lodash'; import chalk from 'chalk'; // Check if the OpenAPI spec is up-to-date. -export const validateOpenApiSpecFromBuild = async ({ - configPath -}: { - configPath?: string; -}) => { +export const validate = async ({ configPath }: { configPath?: string }) => { const config = await findConfig({ configPath }); if (!config) { @@ -23,7 +18,7 @@ export const validateOpenApiSpecFromBuild = async ({ const data = readFileSync(path); const openApiSpec = JSON.parse(data.toString()); - if (!isEqualWith(openApiSpec, spec)) { + if (!(JSON.stringify(openApiSpec) === JSON.stringify(spec))) { console.error( chalk.red( 'API spec changed is not up-to-date. Run `next-rest-framework generate` to update it.' diff --git a/packages/next-rest-framework/tsup.config.ts b/packages/next-rest-framework/tsup.config.ts index 6bd9935..f98a7f2 100644 --- a/packages/next-rest-framework/tsup.config.ts +++ b/packages/next-rest-framework/tsup.config.ts @@ -25,7 +25,9 @@ export default defineConfig({ 'src/index.ts', 'src/constants.ts', 'src/client/index.ts', - 'src/cli/index.ts' + 'src/cli/index.ts', + 'src/cli/generate.ts', + 'src/cli/validate.ts' ], bundle: true, esbuildPlugins: [uaParserDirnamePlugin()], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 99502e7..e921d4b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -63,13 +63,22 @@ importers: apps/example: dependencies: + jsdom: + specifier: 24.0.0 + version: 24.0.0 next-rest-framework: specifier: workspace:* version: link:../../packages/next-rest-framework + tsx: + specifier: 4.7.2 + version: 4.7.2 zod-form-data: specifier: 2.0.2 version: 2.0.2(zod@3.22.2) devDependencies: + '@types/jsdom': + specifier: ^21.1.6 + version: 21.1.6 autoprefixer: specifier: 10.0.1 version: 10.0.1(postcss@8.4.33) @@ -122,12 +131,6 @@ importers: commander: specifier: 10.0.1 version: 10.0.1 - esbuild: - specifier: 0.19.11 - version: 0.19.11 - fast-glob: - specifier: 3.3.2 - version: 3.3.2 formidable: specifier: ^3.5.1 version: 3.5.1 @@ -156,6 +159,9 @@ importers: '@types/qs': specifier: 6.9.11 version: 6.9.11 + esbuild: + specifier: 0.19.11 + version: 0.19.11 jest: specifier: 29.6.4 version: 29.6.4(@types/node@20.5.4)(ts-node@10.9.1) @@ -1706,7 +1712,6 @@ packages: engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.14.0 - dev: true /@babel/template@7.22.5: resolution: {integrity: sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==} @@ -3685,6 +3690,14 @@ packages: pretty-format: 29.6.3 dev: true + /@types/jsdom@21.1.6: + resolution: {integrity: sha512-/7kkMsC+/kMs7gAYmmBR9P0vGTnOoLhQhyhQJSlXGI5bzTHp6xdo0TtKWQAsz6pmSAeVqKSbqeyP6hytqr9FDw==} + dependencies: + '@types/node': 20.5.4 + '@types/tough-cookie': 4.0.5 + parse5: 7.1.2 + dev: true + /@types/json-schema@7.0.12: resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==} @@ -3828,6 +3841,10 @@ packages: resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} dev: true + /@types/tough-cookie@4.0.5: + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + dev: true + /@types/unist@2.0.7: resolution: {integrity: sha512-cputDpIbFgLUaGQn6Vqg3/YsJwxUwHLO13v3i5ouxT4lat0khip9AEWxtERujXV9wxIB1EyF97BSJFt6vpdI8g==} dev: false @@ -4109,6 +4126,15 @@ packages: engines: {node: '>= 10.0.0'} dev: false + /agent-base@7.1.1: + resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} + engines: {node: '>= 14'} + dependencies: + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: false + /aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} @@ -4397,6 +4423,10 @@ packages: has-symbols: 1.0.3 dev: true + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: false + /at-least-node@1.0.0: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} @@ -5036,6 +5066,13 @@ packages: engines: {node: '>=10'} dev: false + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: false + /comma-separated-tokens@1.0.8: resolution: {integrity: sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==} dev: false @@ -5426,6 +5463,13 @@ packages: css-tree: 1.1.3 dev: false + /cssstyle@4.0.1: + resolution: {integrity: sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==} + engines: {node: '>=18'} + dependencies: + rrweb-cssom: 0.6.0 + dev: false + /csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} @@ -5433,6 +5477,14 @@ packages: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} dev: true + /data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.0.0 + dev: false + /debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -5471,6 +5523,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /decimal.js@10.4.3: + resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} + dev: false + /decompress-response@3.3.0: resolution: {integrity: sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==} engines: {node: '>=4'} @@ -5554,6 +5610,11 @@ packages: slash: 3.0.0 dev: false + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: false + /depd@1.1.2: resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} engines: {node: '>= 0.6'} @@ -5797,7 +5858,6 @@ packages: /entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} - dev: false /error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} @@ -6660,6 +6720,15 @@ packages: webpack: 5.88.2 dev: false + /form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: false + /formidable@3.5.1: resolution: {integrity: sha512-WJWKelbRHN41m5dumb0/k8TeAx7Id/y3a+Z7QfhxP/htI9Js5zYaEDtG8uMgG0vM0lOlqnmjE99/kfpOYi/0Og==} dependencies: @@ -6790,7 +6859,6 @@ packages: resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==} dependencies: resolve-pkg-maps: 1.0.0 - dev: true /github-slugger@1.5.0: resolution: {integrity: sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==} @@ -7083,7 +7151,7 @@ packages: /history@4.10.1: resolution: {integrity: sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==} dependencies: - '@babel/runtime': 7.22.11 + '@babel/runtime': 7.23.8 loose-envify: 1.4.0 resolve-pathname: 3.0.0 tiny-invariant: 1.3.1 @@ -7106,6 +7174,13 @@ packages: wbuf: 1.7.3 dev: false + /html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + dependencies: + whatwg-encoding: 3.1.1 + dev: false + /html-entities@2.4.0: resolution: {integrity: sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==} dev: false @@ -7202,6 +7277,16 @@ packages: resolution: {integrity: sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==} dev: false + /http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + dependencies: + agent-base: 7.1.1 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: false + /http-proxy-middleware@2.0.6(@types/express@4.17.17): resolution: {integrity: sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==} engines: {node: '>=12.0.0'} @@ -7232,6 +7317,16 @@ packages: - debug dev: false + /https-proxy-agent@7.0.4: + resolution: {integrity: sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==} + engines: {node: '>= 14'} + dependencies: + agent-base: 7.1.1 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: false + /human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -7243,6 +7338,13 @@ packages: safer-buffer: 2.1.2 dev: false + /iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + dev: false + /icss-utils@5.1.0(postcss@8.4.33): resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} engines: {node: ^10 || ^12 || >= 14} @@ -7555,6 +7657,10 @@ packages: dependencies: isobject: 3.0.1 + /is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + dev: false + /is-regex@1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} @@ -8197,6 +8303,42 @@ packages: dependencies: argparse: 2.0.1 + /jsdom@24.0.0: + resolution: {integrity: sha512-UDS2NayCvmXSXVP6mpTj+73JnNQadZlr9N68189xib2tx5Mls7swlTNao26IoHv46BZJFvXygyRtyXd1feAk1A==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^2.11.2 + peerDependenciesMeta: + canvas: + optional: true + dependencies: + cssstyle: 4.0.1 + data-urls: 5.0.0 + decimal.js: 10.4.3 + form-data: 4.0.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.4 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.7 + parse5: 7.1.2 + rrweb-cssom: 0.6.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 4.1.3 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.0.0 + ws: 8.16.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: false + /jsesc@0.5.0: resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} hasBin: true @@ -8769,6 +8911,10 @@ packages: resolution: {integrity: sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg==} dev: true + /nwsapi@2.2.7: + resolution: {integrity: sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==} + dev: false + /object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -9019,7 +9165,6 @@ packages: resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} dependencies: entities: 4.5.0 - dev: false /parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} @@ -9678,6 +9823,10 @@ packages: ipaddr.js: 1.9.1 dev: false + /psl@1.9.0: + resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} + dev: false + /pump@3.0.0: resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} dependencies: @@ -9693,6 +9842,11 @@ packages: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} + /punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + dev: false + /pupa@2.1.1: resolution: {integrity: sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==} engines: {node: '>=8'} @@ -9722,6 +9876,10 @@ packages: side-channel: 1.0.4 dev: false + /querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + dev: false + /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -10213,7 +10371,6 @@ packages: /resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - dev: true /resolve.exports@2.0.2: resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} @@ -10281,6 +10438,10 @@ packages: fsevents: 2.3.3 dev: true + /rrweb-cssom@0.6.0: + resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==} + dev: false + /rtl-detect@1.0.4: resolution: {integrity: sha512-EBR4I2VDSSYr7PkBmFy04uhycIpDKp+21p/jARYXlCSjQksTBQcJ0HFUPOO79EPPH5JS6VAhiIQbycf0O3JAxQ==} dev: false @@ -10349,6 +10510,13 @@ packages: resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} dev: false + /saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + dependencies: + xmlchars: 2.2.0 + dev: false + /scheduler@0.20.2: resolution: {integrity: sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==} dependencies: @@ -10941,6 +11109,10 @@ packages: stable: 0.1.8 dev: false + /symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + dev: false + /tailwindcss@3.3.0(postcss@8.4.33): resolution: {integrity: sha512-hOXlFx+YcklJ8kXiCAfk/FMyr4Pm9ck477G0m/us2344Vuj355IpoEDB5UmGAsSpTBmr+4ZhjzW04JuFXkb/fw==} engines: {node: '>=12.13.0'} @@ -11084,6 +11256,16 @@ packages: engines: {node: '>=6'} dev: false + /tough-cookie@4.1.3: + resolution: {integrity: sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==} + engines: {node: '>=6'} + dependencies: + psl: 1.9.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + dev: false + /tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} dev: false @@ -11094,6 +11276,13 @@ packages: punycode: 2.3.0 dev: true + /tr46@5.0.0: + resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==} + engines: {node: '>=18'} + dependencies: + punycode: 2.3.1 + dev: false + /tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -11241,6 +11430,17 @@ packages: - ts-node dev: true + /tsx@4.7.2: + resolution: {integrity: sha512-BCNd4kz6fz12fyrgCTEdZHGJ9fWTGeUzXmQysh0RVocDY3h4frk05ZNCXSy4kIenF7y/QnrdiVpTsyNRn6vlAw==} + engines: {node: '>=18.0.0'} + hasBin: true + dependencies: + esbuild: 0.19.11 + get-tsconfig: 4.7.2 + optionalDependencies: + fsevents: 2.3.3 + dev: false + /type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -11445,6 +11645,11 @@ packages: unist-util-visit-parents: 3.1.1 dev: false + /universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + dev: false + /universalify@2.0.0: resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} engines: {node: '>= 10.0.0'} @@ -11514,6 +11719,13 @@ packages: prepend-http: 2.0.0 dev: false + /url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + dev: false + /use-composed-ref@1.3.0(react@17.0.2): resolution: {integrity: sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==} peerDependencies: @@ -11612,6 +11824,13 @@ packages: vfile-message: 2.0.4 dev: false + /w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + dependencies: + xml-name-validator: 5.0.0 + dev: false + /wait-on@6.0.1: resolution: {integrity: sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw==} engines: {node: '>=10.0.0'} @@ -11657,6 +11876,11 @@ packages: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} dev: true + /webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + dev: false + /webpack-bundle-analyzer@4.9.0: resolution: {integrity: sha512-+bXGmO1LyiNx0i9enBu3H8mv42sj/BJWhZNFwjz92tVnBa9J3JMGo2an2IXlEleoDOPn/Hofl5hr/xCpObUDtw==} engines: {node: '>= 10.13.0'} @@ -11819,6 +12043,26 @@ packages: engines: {node: '>=0.8.0'} dev: false + /whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + dependencies: + iconv-lite: 0.6.3 + dev: false + + /whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + dev: false + + /whatwg-url@14.0.0: + resolution: {integrity: sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==} + engines: {node: '>=18'} + dependencies: + tr46: 5.0.0 + webidl-conversions: 7.0.0 + dev: false + /whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} dependencies: @@ -11988,6 +12232,19 @@ packages: optional: true dev: false + /ws@8.16.0: + resolution: {integrity: sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: false + /xdg-basedir@4.0.0: resolution: {integrity: sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==} engines: {node: '>=8'} @@ -12000,6 +12257,15 @@ packages: sax: 1.2.4 dev: false + /xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + dev: false + + /xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + dev: false + /xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'}