UNPKG

@yunarch/config-web

Version:

Shared configurations for web projects.

136 lines (128 loc) 6.88 kB
#!/usr/bin/env node import{a as l,b as o,c as y}from"../chunk-PWSW557X.js";import{writeFile as O}from"fs/promises";import{styleText as f}from"util";import S from"@inquirer/confirm";async function g(e,t){await o({name:"Generating models",command:`npx openapi-typescript-codegen --input ${e} --output ${t} --client fetch`})}import{writeFile as R}from"fs/promises";var w=` import { http as mswHttp, type DefaultBodyType, type HttpHandler, type HttpResponseResolver, type PathParams, type RequestHandlerOptions, } from 'msw'; import type { paths } from './schema'; // Type definitions type HttpMethod = | 'get' | 'put' | 'post' | 'delete' | 'options' | 'head' | 'patch' | 'trace'; /** * Type guard to get the http methods available for a given path. */ type Methods<Path extends keyof paths> = { [M in keyof paths[Path]]: M extends HttpMethod ? paths[Path][M] extends undefined ? never : M : never; }[keyof paths[Path]]; /** * Type guard to get the content type 'application/json' or 'multipart/form-data' of a type. */ type ExtractContent<T> = T extends { content?: infer C } ? undefined extends C ? DefaultBodyType : 'application/json' extends keyof C ? C['application/json'] : 'multipart/form-data' extends keyof C ? C['multipart/form-data'] : DefaultBodyType : DefaultBodyType; /** * Type guard to get the parameters of a path. */ export type OpenapiPathParams< P extends keyof paths, M extends keyof paths[P], > = 'parameters' extends keyof paths[P][M] ? 'path' extends keyof paths[P][M]['parameters'] ? PathParams<keyof paths[P][M]['parameters']['path']> : PathParams : PathParams; /** * Type guard to get the request body of a path. */ export type OpenapiPathRequestBody< P extends keyof paths, M extends keyof paths[P], > = paths[P][M] extends { requestBody?: infer RB } ? undefined extends RB ? DefaultBodyType : ExtractContent<RB> : DefaultBodyType; /** * Type guard to get the response body of a path. */ export type OpenapiPathResponseBody< P extends keyof paths, M extends keyof paths[P], > = paths[P][M] extends { responses?: infer R } ? undefined extends R ? DefaultBodyType : 200 extends keyof R ? ExtractContent<R[200]> : 201 extends keyof R ? ExtractContent<R[201]> : DefaultBodyType : DefaultBodyType; /** * Wrapper around MSW http function so we can have "typesafe" handlers against an openapi schema. * * @param path - The path to use from the openapi definition. * @param method - The method to use on the handler. * @param resolver - The MSW resolver function. * @param options - The MSW http request handler options. * @returns a typesafe wrapper for MSW http function. * * @throws Error if the method is not supported. */ export function http<P extends keyof paths, M extends Methods<P>>( path: P, method: M, resolver: HttpResponseResolver< OpenapiPathParams<P, M>, OpenapiPathRequestBody<P, M>, OpenapiPathResponseBody<P, M> >, options?: RequestHandlerOptions ): HttpHandler { const uri = \`*\${path.toString().replaceAll(/{(?<temp1>[^}]+)}/g, ':$1')}\`; const handlers = { head: mswHttp.head(uri, resolver, options), get: mswHttp.get(uri, resolver, options), post: mswHttp.post(uri, resolver, options), put: mswHttp.put(uri, resolver, options), delete: mswHttp.delete(uri, resolver, options), patch: mswHttp.patch(uri, resolver, options), options: mswHttp.options(uri, resolver, options), } as const; if (typeof method !== 'string' || !Object.hasOwn(handlers, method)) { throw new Error('Unsupported Http Method'); } return handlers[method as keyof typeof handlers]; } `;async function x(e){await o({name:"Generating openapi MSW utils",command:async()=>{await R(`${e}/openapi-msw-http.ts`,w)}})}import{readFile as H,writeFile as B}from"fs/promises";async function P(e,t){await o({name:"Generating schema types",command:async()=>{await l(`npx openapi-typescript ${e} -o ${t}`);let n=await H(t,"utf8");await B(t,`/* eslint-disable -- Autogenerated file */ ${n}`)}})}import{existsSync as u}from"fs";import{mkdir as E,readFile as T}from"fs/promises";import m from"path";async function M(e){if(m.extname(e)!=="")throw new Error("Output must be a directory.");let t=process.cwd(),n=m.resolve(e),a=n.startsWith(t)?n:m.resolve(t,m.relative(m.parse(e).root,e));return u(a)||await o({name:"Generating output directory",command:async()=>{await E(a,{recursive:!0})}}),a}async function k(e,t){let[n,s]=await Promise.all([o({name:"Reading input openapi schema",command:async()=>{if(!e.endsWith(".json"))throw new Error(`Input file must be a JSON file: ${e}`);if(e.startsWith("http"))try{let{stdout:a}=await l(`curl -s ${e} --fail`);return a}catch{throw new Error(`Failed to fetch remote OpenAPI file: ${e}`)}if(!u(e))throw new Error(`Input file does not exist: ${e}`);return await T(e,"utf8")}}),o({name:"Reading output openapi schema",command:async()=>{if(!t.endsWith(".json"))throw new Error(`Output file must be a JSON file: ${t}`);return u(t)?await T(t,"utf8"):!1}})]);return[JSON.stringify(JSON.parse(n)),s?JSON.stringify(JSON.parse(s)):!1]}y().name("openapi-sync").description("A CLI tool to convert OpenAPI 3.0/3.1 schemas to TypeScript types and create type-safe fetching based on a openapi file and keep them in sync.").requiredOption("-i, --input <path>","The input (local or remote) openapi schema (JSON).").requiredOption("-o, --output <folder>","The output folder to save the generated models and openapi schema and type definitions.").option("-f, --force-gen","Force generation of typescript schemas and fetching code even if the input and output schemas are identical.").option("--include-msw-utils","Include MSW mocking utilities based on the generated typescript types.").option("--post-script <script>","A package.json script to run after the code generation.").action(async({input:e,output:t,forceGen:n,includeMswUtils:s,postScript:a})=>{try{console.log(f("magenta",` \u{1F680} openapi-sync `));let r=await M(t),p=`${r}/openapi.json`,v=`${r}/schema.d.ts`,[h,i]=await k(e,p);i&&h===i&&!n?(console.log(f("blue",` No updates required. `)),process.exit(0)):i?i&&h!==i&&(console.log(f("yellow",` \u26A0\uFE0F Local and remote schemas does not match! `)),await S({message:"Do you want to use the remote schema? (y/n)?"})?await o({name:"Replacing local schema with input schema",command:O(p,h)}):(console.log(f("yellow",` \u26A0\uFE0F Sync remote schemas skipped. `)),n||process.exit(0))):await o({name:"Creating local schema",command:O(p,h)}),await Promise.all([P(p,v),g(p,r)]),s&&await x(r),a&&await o({name:"Running post script",command:`node --run ${a}`}),console.log(f("green",` \u2705 openapi-sync process completed! `))}catch(r){console.error(r),process.exit(1)}}).parseAsync(process.argv);