From cb6a814918e3e2cd7139ec103a0a8d62a6627a99 Mon Sep 17 00:00:00 2001 From: AlexSserb Date: Mon, 28 Jul 2025 17:42:25 +0400 Subject: [PATCH] feat: openapi client generation --- eslint.config.mjs | 9 + package.json | 143 +++++----- src/client/core/ApiError.ts | 29 +++ src/client/core/ApiRequestOptions.ts | 24 ++ src/client/core/ApiResult.ts | 11 + src/client/core/CancelablePromise.ts | 130 ++++++++++ src/client/core/OpenAPI.ts | 32 +++ src/client/core/request.ts | 372 +++++++++++++++++++++++++++ src/client/index.ts | 8 + tsconfig.json | 2 +- yarn.lock | 112 +++++++- 11 files changed, 798 insertions(+), 74 deletions(-) create mode 100644 src/client/core/ApiError.ts create mode 100644 src/client/core/ApiRequestOptions.ts create mode 100644 src/client/core/ApiResult.ts create mode 100644 src/client/core/CancelablePromise.ts create mode 100644 src/client/core/OpenAPI.ts create mode 100644 src/client/core/request.ts create mode 100644 src/client/index.ts diff --git a/eslint.config.mjs b/eslint.config.mjs index 60abc58..d95f1df 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -17,5 +17,14 @@ export default tseslint.config( "react/jsx-curly-brace-presence": "off", "curly": "off", }, + }, + { + files: ["src/client/**/*.{ts,tsx}"], + rules: { + "import/no-useless-path-segments": "off", + }, + linterOptions: { + reportUnusedDisableDirectives: false, + }, } ); diff --git a/package.json b/package.json index 5195654..20883fe 100644 --- a/package.json +++ b/package.json @@ -1,72 +1,75 @@ { - "name": "crm-frontend", - "version": "0.1.0", - "private": true, - "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start", - "lint": "next lint" - }, - "dependencies": { - "@mantine/core": "8.1.2", - "@mantine/form": "^8.1.3", - "@mantine/hooks": "8.1.2", - "@mantine/modals": "^8.2.1", - "@mantine/notifications": "^8.2.1", - "@next/bundle-analyzer": "^15.3.3", - "@reduxjs/toolkit": "^2.8.2", - "@tabler/icons-react": "^3.34.0", - "@tailwindcss/postcss": "^4.1.11", - "axios": "^1.11.0", - "classnames": "^2.5.1", - "framer-motion": "^12.23.7", - "i18n-iso-countries": "^7.14.0", - "libphonenumber-js": "^1.12.10", - "next": "15.3.3", - "react": "19.1.0", - "react-dom": "19.1.0", - "react-imask": "^7.6.1", - "react-redux": "^9.2.0", - "redux-persist": "^6.0.0", - "sharp": "^0.34.3" - }, - "devDependencies": { - "@babel/core": "^7.27.4", - "@eslint/js": "^9.29.0", - "@ianvs/prettier-plugin-sort-imports": "^4.4.2", - "@storybook/nextjs": "^8.6.8", - "@storybook/react": "^8.6.8", - "@testing-library/dom": "^10.4.0", - "@testing-library/jest-dom": "^6.6.3", - "@testing-library/react": "^16.3.0", - "@testing-library/user-event": "^14.6.1", - "@types/eslint-plugin-jsx-a11y": "^6", - "@types/jest": "^29.5.14", - "@types/node": "^22.13.11", - "@types/react": "19.1.8", - "@types/react-redux": "^7.1.34", - "@types/redux-persist": "^4.3.1", - "autoprefixer": "^10.4.21", - "babel-loader": "^10.0.0", - "eslint": "^9.29.0", - "eslint-config-mantine": "^4.0.3", - "eslint-plugin-jsx-a11y": "^6.10.2", - "eslint-plugin-react": "^7.37.5", - "jest": "^30.0.0", - "jest-environment-jsdom": "^30.0.0", - "postcss": "^8.5.6", - "postcss-preset-mantine": "1.17.0", - "postcss-simple-vars": "^7.0.1", - "prettier": "^3.5.3", - "storybook": "^8.6.8", - "storybook-dark-mode": "^4.0.2", - "stylelint": "^16.20.0", - "stylelint-config-standard-scss": "^15.0.1", - "tailwindcss": "^4.1.11", - "ts-jest": "^29.4.0", - "typescript": "5.8.3", - "typescript-eslint": "^8.34.0" - }, - "packageManager": "yarn@4.9.2" + "name": "crm-frontend", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint", + "generate-client": "openapi --input http://127.0.0.1:8000/openapi.json --output ./src/client --client axios --useOptions --useUnionTypes --exportSchemas true && prettier --write ./src/client/**/*.ts" + }, + "dependencies": { + "@mantine/core": "8.1.2", + "@mantine/form": "^8.1.3", + "@mantine/hooks": "8.1.2", + "@mantine/modals": "^8.2.1", + "@mantine/notifications": "^8.2.1", + "@next/bundle-analyzer": "^15.3.3", + "@reduxjs/toolkit": "^2.8.2", + "@tabler/icons-react": "^3.34.0", + "@tailwindcss/postcss": "^4.1.11", + "axios": "^1.11.0", + "classnames": "^2.5.1", + "framer-motion": "^12.23.7", + "i18n-iso-countries": "^7.14.0", + "libphonenumber-js": "^1.12.10", + "next": "15.3.3", + "openapi-typescript-codegen": "^0.29.0", + "react": "19.1.0", + "react-dom": "19.1.0", + "react-imask": "^7.6.1", + "react-redux": "^9.2.0", + "redux-persist": "^6.0.0", + "sharp": "^0.34.3" + }, + "devDependencies": { + "@babel/core": "^7.27.4", + "@eslint/js": "^9.29.0", + "@ianvs/prettier-plugin-sort-imports": "^4.4.2", + "@storybook/nextjs": "^8.6.8", + "@storybook/react": "^8.6.8", + "@testing-library/dom": "^10.4.0", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", + "@types/eslint-plugin-jsx-a11y": "^6", + "@types/jest": "^29.5.14", + "@types/node": "^22.13.11", + "@types/react": "19.1.8", + "@types/react-redux": "^7.1.34", + "@types/redux-persist": "^4.3.1", + "autoprefixer": "^10.4.21", + "babel-loader": "^10.0.0", + "eslint": "^9.29.0", + "eslint-config-mantine": "^4.0.3", + "eslint-plugin-eslint-comments": "^3.2.0", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-react": "^7.37.5", + "jest": "^30.0.0", + "jest-environment-jsdom": "^30.0.0", + "postcss": "^8.5.6", + "postcss-preset-mantine": "1.17.0", + "postcss-simple-vars": "^7.0.1", + "prettier": "^3.5.3", + "storybook": "^8.6.8", + "storybook-dark-mode": "^4.0.2", + "stylelint": "^16.20.0", + "stylelint-config-standard-scss": "^15.0.1", + "tailwindcss": "^4.1.11", + "ts-jest": "^29.4.0", + "typescript": "5.8.3", + "typescript-eslint": "^8.34.0" + }, + "packageManager": "yarn@4.9.2" } diff --git a/src/client/core/ApiError.ts b/src/client/core/ApiError.ts new file mode 100644 index 0000000..f25aa06 --- /dev/null +++ b/src/client/core/ApiError.ts @@ -0,0 +1,29 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ApiRequestOptions } from "./ApiRequestOptions"; +import type { ApiResult } from "./ApiResult"; + +export class ApiError extends Error { + public readonly url: string; + public readonly status: number; + public readonly statusText: string; + public readonly body: any; + public readonly request: ApiRequestOptions; + + constructor( + request: ApiRequestOptions, + response: ApiResult, + message: string + ) { + super(message); + + this.name = "ApiError"; + this.url = response.url; + this.status = response.status; + this.statusText = response.statusText; + this.body = response.body; + this.request = request; + } +} diff --git a/src/client/core/ApiRequestOptions.ts b/src/client/core/ApiRequestOptions.ts new file mode 100644 index 0000000..cff730e --- /dev/null +++ b/src/client/core/ApiRequestOptions.ts @@ -0,0 +1,24 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type ApiRequestOptions = { + readonly method: + | "GET" + | "PUT" + | "POST" + | "DELETE" + | "OPTIONS" + | "HEAD" + | "PATCH"; + readonly url: string; + readonly path?: Record; + readonly cookies?: Record; + readonly headers?: Record; + readonly query?: Record; + readonly formData?: Record; + readonly body?: any; + readonly mediaType?: string; + readonly responseHeader?: string; + readonly errors?: Record; +}; diff --git a/src/client/core/ApiResult.ts b/src/client/core/ApiResult.ts new file mode 100644 index 0000000..ee1126e --- /dev/null +++ b/src/client/core/ApiResult.ts @@ -0,0 +1,11 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type ApiResult = { + readonly url: string; + readonly ok: boolean; + readonly status: number; + readonly statusText: string; + readonly body: any; +}; diff --git a/src/client/core/CancelablePromise.ts b/src/client/core/CancelablePromise.ts new file mode 100644 index 0000000..183e413 --- /dev/null +++ b/src/client/core/CancelablePromise.ts @@ -0,0 +1,130 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export class CancelError extends Error { + constructor(message: string) { + super(message); + this.name = "CancelError"; + } + + public get isCancelled(): boolean { + return true; + } +} + +export interface OnCancel { + readonly isResolved: boolean; + readonly isRejected: boolean; + readonly isCancelled: boolean; + + (cancelHandler: () => void): void; +} + +export class CancelablePromise implements Promise { + #isResolved: boolean; + #isRejected: boolean; + #isCancelled: boolean; + readonly #cancelHandlers: (() => void)[]; + readonly #promise: Promise; + #resolve?: (value: T | PromiseLike) => void; + #reject?: (reason?: any) => void; + + constructor( + executor: ( + resolve: (value: T | PromiseLike) => void, + reject: (reason?: any) => void, + onCancel: OnCancel + ) => void + ) { + this.#isResolved = false; + this.#isRejected = false; + this.#isCancelled = false; + this.#cancelHandlers = []; + this.#promise = new Promise((resolve, reject) => { + this.#resolve = resolve; + this.#reject = reject; + + const onResolve = (value: T | PromiseLike): void => { + if (this.#isResolved || this.#isRejected || this.#isCancelled) { + return; + } + this.#isResolved = true; + if (this.#resolve) this.#resolve(value); + }; + + const onReject = (reason?: any): void => { + if (this.#isResolved || this.#isRejected || this.#isCancelled) { + return; + } + this.#isRejected = true; + if (this.#reject) this.#reject(reason); + }; + + const onCancel = (cancelHandler: () => void): void => { + if (this.#isResolved || this.#isRejected || this.#isCancelled) { + return; + } + this.#cancelHandlers.push(cancelHandler); + }; + + Object.defineProperty(onCancel, "isResolved", { + get: (): boolean => this.#isResolved, + }); + + Object.defineProperty(onCancel, "isRejected", { + get: (): boolean => this.#isRejected, + }); + + Object.defineProperty(onCancel, "isCancelled", { + get: (): boolean => this.#isCancelled, + }); + + return executor(onResolve, onReject, onCancel as OnCancel); + }); + } + + get [Symbol.toStringTag]() { + return "Cancellable Promise"; + } + + public then( + onFulfilled?: ((value: T) => TResult1 | PromiseLike) | null, + onRejected?: ((reason: any) => TResult2 | PromiseLike) | null + ): Promise { + return this.#promise.then(onFulfilled, onRejected); + } + + public catch( + onRejected?: ((reason: any) => TResult | PromiseLike) | null + ): Promise { + return this.#promise.catch(onRejected); + } + + public finally(onFinally?: (() => void) | null): Promise { + return this.#promise.finally(onFinally); + } + + public cancel(): void { + if (this.#isResolved || this.#isRejected || this.#isCancelled) { + return; + } + this.#isCancelled = true; + if (this.#cancelHandlers.length) { + try { + for (const cancelHandler of this.#cancelHandlers) { + cancelHandler(); + } + } catch (error) { + console.warn("Cancellation threw an error", error); + return; + } + } + this.#cancelHandlers.length = 0; + if (this.#reject) this.#reject(new CancelError("Request aborted")); + } + + public get isCancelled(): boolean { + return this.#isCancelled; + } +} diff --git a/src/client/core/OpenAPI.ts b/src/client/core/OpenAPI.ts new file mode 100644 index 0000000..af29cb1 --- /dev/null +++ b/src/client/core/OpenAPI.ts @@ -0,0 +1,32 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ApiRequestOptions } from "./ApiRequestOptions"; + +type Resolver = (options: ApiRequestOptions) => Promise; +type Headers = Record; + +export type OpenAPIConfig = { + BASE: string; + VERSION: string; + WITH_CREDENTIALS: boolean; + CREDENTIALS: "include" | "omit" | "same-origin"; + TOKEN?: string | Resolver | undefined; + USERNAME?: string | Resolver | undefined; + PASSWORD?: string | Resolver | undefined; + HEADERS?: Headers | Resolver | undefined; + ENCODE_PATH?: ((path: string) => string) | undefined; +}; + +export const OpenAPI: OpenAPIConfig = { + BASE: "", + VERSION: "0.1.0", + WITH_CREDENTIALS: false, + CREDENTIALS: "include", + TOKEN: undefined, + USERNAME: undefined, + PASSWORD: undefined, + HEADERS: undefined, + ENCODE_PATH: undefined, +}; diff --git a/src/client/core/request.ts b/src/client/core/request.ts new file mode 100644 index 0000000..66d3344 --- /dev/null +++ b/src/client/core/request.ts @@ -0,0 +1,372 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import axios from "axios"; +import type { + AxiosError, + AxiosInstance, + AxiosRequestConfig, + AxiosResponse, +} from "axios"; +import FormData from "form-data"; +import { ApiError } from "./ApiError"; +import type { ApiRequestOptions } from "./ApiRequestOptions"; +import type { ApiResult } from "./ApiResult"; +import { CancelablePromise } from "./CancelablePromise"; +import type { OnCancel } from "./CancelablePromise"; +import type { OpenAPIConfig } from "./OpenAPI"; + +export const isDefined = ( + value: T | null | undefined +): value is Exclude => { + return value !== undefined && value !== null; +}; + +export const isString = (value: any): value is string => { + return typeof value === "string"; +}; + +export const isStringWithValue = (value: any): value is string => { + return isString(value) && value !== ""; +}; + +export const isBlob = (value: any): value is Blob => { + return ( + typeof value === "object" && + typeof value.type === "string" && + typeof value.stream === "function" && + typeof value.arrayBuffer === "function" && + typeof value.constructor === "function" && + typeof value.constructor.name === "string" && + /^(Blob|File)$/.test(value.constructor.name) && + /^(Blob|File)$/.test(value[Symbol.toStringTag]) + ); +}; + +export const isFormData = (value: any): value is FormData => { + return value instanceof FormData; +}; + +export const isSuccess = (status: number): boolean => { + return status >= 200 && status < 300; +}; + +export const base64 = (str: string): string => { + try { + return btoa(str); + } catch (err) { + // @ts-ignore + return Buffer.from(str).toString("base64"); + } +}; + +export const getQueryString = (params: Record): string => { + const qs: string[] = []; + + const append = (key: string, value: any) => { + qs.push( + `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}` + ); + }; + + const process = (key: string, value: any) => { + if (isDefined(value)) { + if (Array.isArray(value)) { + value.forEach(v => { + process(key, v); + }); + } else if (typeof value === "object") { + Object.entries(value).forEach(([k, v]) => { + process(`${key}[${k}]`, v); + }); + } else { + append(key, value); + } + } + }; + + Object.entries(params).forEach(([key, value]) => { + process(key, value); + }); + + if (qs.length > 0) { + return `?${qs.join("&")}`; + } + + return ""; +}; + +const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => { + const encoder = config.ENCODE_PATH || encodeURI; + + const path = options.url + .replace("{api-version}", config.VERSION) + .replace(/{(.*?)}/g, (substring: string, group: string) => { + if (options.path?.hasOwnProperty(group)) { + return encoder(String(options.path[group])); + } + return substring; + }); + + const url = `${config.BASE}${path}`; + if (options.query) { + return `${url}${getQueryString(options.query)}`; + } + return url; +}; + +export const getFormData = ( + options: ApiRequestOptions +): FormData | undefined => { + if (options.formData) { + const formData = new FormData(); + + const process = (key: string, value: any) => { + if (isString(value) || isBlob(value)) { + formData.append(key, value); + } else { + formData.append(key, JSON.stringify(value)); + } + }; + + Object.entries(options.formData) + .filter(([_, value]) => isDefined(value)) + .forEach(([key, value]) => { + if (Array.isArray(value)) { + value.forEach(v => process(key, v)); + } else { + process(key, value); + } + }); + + return formData; + } + return undefined; +}; + +type Resolver = (options: ApiRequestOptions) => Promise; + +export const resolve = async ( + options: ApiRequestOptions, + resolver?: T | Resolver +): Promise => { + if (typeof resolver === "function") { + return (resolver as Resolver)(options); + } + return resolver; +}; + +export const getHeaders = async ( + config: OpenAPIConfig, + options: ApiRequestOptions, + formData?: FormData +): Promise> => { + const [token, username, password, additionalHeaders] = await Promise.all([ + resolve(options, config.TOKEN), + resolve(options, config.USERNAME), + resolve(options, config.PASSWORD), + resolve(options, config.HEADERS), + ]); + + const formHeaders = + (typeof formData?.getHeaders === "function" && + formData?.getHeaders()) || + {}; + + const headers = Object.entries({ + Accept: "application/json", + ...additionalHeaders, + ...options.headers, + ...formHeaders, + }) + .filter(([_, value]) => isDefined(value)) + .reduce( + (headers, [key, value]) => ({ + ...headers, + [key]: String(value), + }), + {} as Record + ); + + if (isStringWithValue(token)) { + headers["Authorization"] = `Bearer ${token}`; + } + + if (isStringWithValue(username) && isStringWithValue(password)) { + const credentials = base64(`${username}:${password}`); + headers["Authorization"] = `Basic ${credentials}`; + } + + if (options.body !== undefined) { + if (options.mediaType) { + headers["Content-Type"] = options.mediaType; + } else if (isBlob(options.body)) { + headers["Content-Type"] = + options.body.type || "application/octet-stream"; + } else if (isString(options.body)) { + headers["Content-Type"] = "text/plain"; + } else if (!isFormData(options.body)) { + headers["Content-Type"] = "application/json"; + } + } + + return headers; +}; + +export const getRequestBody = (options: ApiRequestOptions): any => { + if (options.body) { + return options.body; + } + return undefined; +}; + +export const sendRequest = async ( + config: OpenAPIConfig, + options: ApiRequestOptions, + url: string, + body: any, + formData: FormData | undefined, + headers: Record, + onCancel: OnCancel, + axiosClient: AxiosInstance +): Promise> => { + const source = axios.CancelToken.source(); + + const requestConfig: AxiosRequestConfig = { + url, + headers, + data: body ?? formData, + method: options.method, + withCredentials: config.WITH_CREDENTIALS, + withXSRFToken: + config.CREDENTIALS === "include" ? config.WITH_CREDENTIALS : false, + cancelToken: source.token, + }; + + onCancel(() => source.cancel("The user aborted a request.")); + + try { + return await axiosClient.request(requestConfig); + } catch (error) { + const axiosError = error as AxiosError; + if (axiosError.response) { + return axiosError.response; + } + throw error; + } +}; + +export const getResponseHeader = ( + response: AxiosResponse, + responseHeader?: string +): string | undefined => { + if (responseHeader) { + const content = response.headers[responseHeader]; + if (isString(content)) { + return content; + } + } + return undefined; +}; + +export const getResponseBody = (response: AxiosResponse): any => { + if (response.status !== 204) { + return response.data; + } + return undefined; +}; + +export const catchErrorCodes = ( + options: ApiRequestOptions, + result: ApiResult +): void => { + const errors: Record = { + 400: "Bad Request", + 401: "Unauthorized", + 403: "Forbidden", + 404: "Not Found", + 500: "Internal Server Error", + 502: "Bad Gateway", + 503: "Service Unavailable", + ...options.errors, + }; + + const error = errors[result.status]; + if (error) { + throw new ApiError(options, result, error); + } + + if (!result.ok) { + const errorStatus = result.status ?? "unknown"; + const errorStatusText = result.statusText ?? "unknown"; + const errorBody = (() => { + try { + return JSON.stringify(result.body, null, 2); + } catch (e) { + return undefined; + } + })(); + + throw new ApiError( + options, + result, + `Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}` + ); + } +}; + +/** + * Request method + * @param config The OpenAPI configuration object + * @param options The request options from the service + * @param axiosClient The axios client instance to use + * @returns CancelablePromise + * @throws ApiError + */ +export const request = ( + config: OpenAPIConfig, + options: ApiRequestOptions, + axiosClient: AxiosInstance = axios +): CancelablePromise => { + return new CancelablePromise(async (resolve, reject, onCancel) => { + try { + const url = getUrl(config, options); + const formData = getFormData(options); + const body = getRequestBody(options); + const headers = await getHeaders(config, options, formData); + + if (!onCancel.isCancelled) { + const response = await sendRequest( + config, + options, + url, + body, + formData, + headers, + onCancel, + axiosClient + ); + const responseBody = getResponseBody(response); + const responseHeader = getResponseHeader( + response, + options.responseHeader + ); + + const result: ApiResult = { + url, + ok: isSuccess(response.status), + status: response.status, + statusText: response.statusText, + body: responseHeader ?? responseBody, + }; + + catchErrorCodes(options, result); + + resolve(result.body); + } + } catch (error) { + reject(error); + } + }); +}; diff --git a/src/client/index.ts b/src/client/index.ts new file mode 100644 index 0000000..8f7ea7f --- /dev/null +++ b/src/client/index.ts @@ -0,0 +1,8 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export { ApiError } from "./core/ApiError"; +export { CancelablePromise, CancelError } from "./core/CancelablePromise"; +export { OpenAPI } from "./core/OpenAPI"; +export type { OpenAPIConfig } from "./core/OpenAPI"; diff --git a/tsconfig.json b/tsconfig.json index b0612c9..702f67b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "types": ["node", "jest", "@testing-library/jest-dom"], - "target": "es5", + "target": "ES2020", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, diff --git a/yarn.lock b/yarn.lock index c3859b8..0a420e1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29,6 +29,17 @@ __metadata: languageName: node linkType: hard +"@apidevtools/json-schema-ref-parser@npm:^11.5.4": + version: 11.9.3 + resolution: "@apidevtools/json-schema-ref-parser@npm:11.9.3" + dependencies: + "@jsdevtools/ono": "npm:^7.1.3" + "@types/json-schema": "npm:^7.0.15" + js-yaml: "npm:^4.1.0" + checksum: 10c0/5745813b3d964279f387677b7a903ba6634cdeaf879ff3a331a694392cbc923763f398506df190be114f2574b8b570baab3e367c2194bb35f50147ff6cf27d7a + languageName: node + linkType: hard + "@asamuzakjp/css-color@npm:^3.2.0": version: 3.2.0 resolution: "@asamuzakjp/css-color@npm:3.2.0" @@ -2730,6 +2741,13 @@ __metadata: languageName: node linkType: hard +"@jsdevtools/ono@npm:^7.1.3": + version: 7.1.3 + resolution: "@jsdevtools/ono@npm:7.1.3" + checksum: 10c0/a9f7e3e8e3bc315a34959934a5e2f874c423cf4eae64377d3fc9de0400ed9f36cb5fd5ebce3300d2e8f4085f557c4a8b591427a583729a87841fda46e6c216b9 + languageName: node + linkType: hard + "@keyv/serialize@npm:^1.1.0": version: 1.1.0 resolution: "@keyv/serialize@npm:1.1.0" @@ -5647,6 +5665,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^12.0.0": + version: 12.1.0 + resolution: "commander@npm:12.1.0" + checksum: 10c0/6e1996680c083b3b897bfc1cfe1c58dfbcd9842fd43e1aaf8a795fbc237f65efcc860a3ef457b318e73f29a4f4a28f6403c3d653d021d960e4632dd45bde54a9 + languageName: node + linkType: hard + "commander@npm:^2.20.0": version: 2.20.3 resolution: "commander@npm:2.20.3" @@ -5853,6 +5878,7 @@ __metadata: classnames: "npm:^2.5.1" eslint: "npm:^9.29.0" eslint-config-mantine: "npm:^4.0.3" + eslint-plugin-eslint-comments: "npm:^3.2.0" eslint-plugin-jsx-a11y: "npm:^6.10.2" eslint-plugin-react: "npm:^7.37.5" framer-motion: "npm:^12.23.7" @@ -5861,6 +5887,7 @@ __metadata: jest-environment-jsdom: "npm:^30.0.0" libphonenumber-js: "npm:^1.12.10" next: "npm:15.3.3" + openapi-typescript-codegen: "npm:^0.29.0" postcss: "npm:^8.5.6" postcss-preset-mantine: "npm:1.17.0" postcss-simple-vars: "npm:^7.0.1" @@ -6754,6 +6781,13 @@ __metadata: languageName: node linkType: hard +"escape-string-regexp@npm:^1.0.5": + version: 1.0.5 + resolution: "escape-string-regexp@npm:1.0.5" + checksum: 10c0/a968ad453dd0c2724e14a4f20e177aaf32bb384ab41b674a8454afe9a41c5e6fe8903323e0a1052f56289d04bd600f81278edf140b0fcc02f5cac98d0f5b5371 + languageName: node + linkType: hard + "escape-string-regexp@npm:^2.0.0": version: 2.0.0 resolution: "escape-string-regexp@npm:2.0.0" @@ -6781,6 +6815,18 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-eslint-comments@npm:^3.2.0": + version: 3.2.0 + resolution: "eslint-plugin-eslint-comments@npm:3.2.0" + dependencies: + escape-string-regexp: "npm:^1.0.5" + ignore: "npm:^5.0.5" + peerDependencies: + eslint: ">=4.19.1" + checksum: 10c0/c71db824592dc8ea498021572a0bd33d763ef26126bdb3b84a027ca75a1adbe0894ec95024f7de39ef12308560e62cbf8af0d06ffe472be5ba8bd9169c928e96 + languageName: node + linkType: hard + "eslint-plugin-jsx-a11y@npm:^6.10.2": version: 6.10.2 resolution: "eslint-plugin-jsx-a11y@npm:6.10.2" @@ -7393,6 +7439,17 @@ __metadata: languageName: node linkType: hard +"fs-extra@npm:^11.2.0": + version: 11.3.0 + resolution: "fs-extra@npm:11.3.0" + dependencies: + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^6.0.1" + universalify: "npm:^2.0.0" + checksum: 10c0/5f95e996186ff45463059feb115a22fb048bdaf7e487ecee8a8646c78ed8fdca63630e3077d4c16ce677051f5e60d3355a06f3cd61f3ca43f48cc58822a44d0a + languageName: node + linkType: hard + "fs-minipass@npm:^3.0.0": version: 3.0.3 resolution: "fs-minipass@npm:3.0.3" @@ -7680,6 +7737,24 @@ __metadata: languageName: node linkType: hard +"handlebars@npm:^4.7.8": + version: 4.7.8 + resolution: "handlebars@npm:4.7.8" + dependencies: + minimist: "npm:^1.2.5" + neo-async: "npm:^2.6.2" + source-map: "npm:^0.6.1" + uglify-js: "npm:^3.1.4" + wordwrap: "npm:^1.0.0" + dependenciesMeta: + uglify-js: + optional: true + bin: + handlebars: bin/handlebars + checksum: 10c0/7aff423ea38a14bb379316f3857fe0df3c5d66119270944247f155ba1f08e07a92b340c58edaa00cfe985c21508870ee5183e0634dcb53dd405f35c93ef7f10d + languageName: node + linkType: hard + "has-bigints@npm:^1.0.2": version: 1.1.0 resolution: "has-bigints@npm:1.1.0" @@ -7968,7 +8043,7 @@ __metadata: languageName: node linkType: hard -"ignore@npm:^5.2.0": +"ignore@npm:^5.0.5, ignore@npm:^5.2.0": version: 5.3.2 resolution: "ignore@npm:5.3.2" checksum: 10c0/f9f652c957983634ded1e7f02da3b559a0d4cc210fca3792cb67f1b153623c9c42efdc1c4121af171e295444459fc4a9201101fb041b1104a3c000bccb188337 @@ -9832,7 +9907,7 @@ __metadata: languageName: node linkType: hard -"minimist@npm:^1.2.6": +"minimist@npm:^1.2.5, minimist@npm:^1.2.6": version: 1.2.8 resolution: "minimist@npm:1.2.8" checksum: 10c0/19d3fcdca050087b84c2029841a093691a91259a47def2f18222f41e7645a0b7c44ef4b40e88a1e58a40c84d2ef0ee6047c55594d298146d0eb3f6b737c20ce6 @@ -10307,6 +10382,21 @@ __metadata: languageName: node linkType: hard +"openapi-typescript-codegen@npm:^0.29.0": + version: 0.29.0 + resolution: "openapi-typescript-codegen@npm:0.29.0" + dependencies: + "@apidevtools/json-schema-ref-parser": "npm:^11.5.4" + camelcase: "npm:^6.3.0" + commander: "npm:^12.0.0" + fs-extra: "npm:^11.2.0" + handlebars: "npm:^4.7.8" + bin: + openapi: bin/index.js + checksum: 10c0/df4a7e6bb6e8044906249b1adfeaa46b3ed0bb887e782914195d9de579dbda9a3494356f4174517dd3cd2f93f58dce7b6b246ae54d06e0fad7f224a011e0aefb + languageName: node + linkType: hard + "opener@npm:^1.5.2": version: 1.5.2 resolution: "opener@npm:1.5.2" @@ -12134,7 +12224,7 @@ __metadata: languageName: node linkType: hard -"source-map@npm:0.6.1, source-map@npm:^0.6.0, source-map@npm:~0.6.0, source-map@npm:~0.6.1": +"source-map@npm:0.6.1, source-map@npm:^0.6.0, source-map@npm:^0.6.1, source-map@npm:~0.6.0, source-map@npm:~0.6.1": version: 0.6.1 resolution: "source-map@npm:0.6.1" checksum: 10c0/ab55398007c5e5532957cb0beee2368529618ac0ab372d789806f5718123cc4367d57de3904b4e6a4170eb5a0b0f41373066d02ca0735a0c4d75c7d328d3e011 @@ -13114,6 +13204,15 @@ __metadata: languageName: node linkType: hard +"uglify-js@npm:^3.1.4": + version: 3.19.3 + resolution: "uglify-js@npm:3.19.3" + bin: + uglifyjs: bin/uglifyjs + checksum: 10c0/83b0a90eca35f778e07cad9622b80c448b6aad457c9ff8e568afed978212b42930a95f9e1be943a1ffa4258a3340fbb899f41461131c05bb1d0a9c303aed8479 + languageName: node + linkType: hard + "unbox-primitive@npm:^1.1.0": version: 1.1.0 resolution: "unbox-primitive@npm:1.1.0" @@ -13685,6 +13784,13 @@ __metadata: languageName: node linkType: hard +"wordwrap@npm:^1.0.0": + version: 1.0.0 + resolution: "wordwrap@npm:1.0.0" + checksum: 10c0/7ed2e44f3c33c5c3e3771134d2b0aee4314c9e49c749e37f464bf69f2bcdf0cbf9419ca638098e2717cff4875c47f56a007532f6111c3319f557a2ca91278e92 + languageName: node + linkType: hard + "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": version: 7.0.0 resolution: "wrap-ansi@npm:7.0.0"