@yunarch/config-web
Version:
Shared configurations for web projects.
146 lines (139 loc) ⢠7.36 kB
JavaScript
import{n as e,r as t,t as n}from"../utils-B-9rP3qT.mjs";import{mkdir as r,readFile as i,writeFile as a}from"node:fs/promises";import{styleText as o}from"node:util";import s from"@inquirer/confirm";import{existsSync as c}from"node:fs";import l from"node:path";async function u(e,t){await n(`npx`,[`openapi-typescript-codegen`,`--input`,e,`--output`,t,`--client`,`fetch`],{shell:!0})}async function d(e){await a(`${e}/openapi-msw-http.ts`,`
import {
http as mswHttp,
type DefaultBodyType,
type HttpHandler,
type HttpResponseResolver,
type PathParams,
type RequestHandlerOptions,
} from 'msw';
import type { paths as ImportedPaths } from './schema';
// Type definitions
type Paths = ImportedPaths;
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,
get: mswHttp.get,
post: mswHttp.post,
put: mswHttp.put,
delete: mswHttp.delete,
patch: mswHttp.patch,
options: mswHttp.options,
} as const;
if (typeof method !== 'string' || !Object.hasOwn(handlers, method)) {
throw new Error('Unsupported Http Method');
}
return handlers[method as keyof typeof handlers](uri, resolver, options);
}
`)}async function f(e,t){await n(`npx`,[`openapi-typescript`,e,`-o`,t],{shell:!0}),await a(t,`/* eslint-disable -- Autogenerated file */\n${await i(t,`utf8`)}`)}async function p(e){if(l.extname(e)!==``)throw Error(`Output must be a directory.`);let n=process.cwd(),i=l.resolve(e),a=i.startsWith(n)?i:l.resolve(n,l.relative(l.parse(e).root,e));return c(a)||await t({name:`Generating output directory`,command:async()=>{await r(a,{recursive:!0})}}),a}async function m(e){if(!e.endsWith(`.json`))throw Error(`Input file must be a JSON file: ${e}`);if(e.startsWith(`http`))try{let t=await fetch(e);if(!t.ok)throw Error(`HTTP ${String(t.status)}`);return await t.text()}catch(t){throw Error(`Failed to fetch remote OpenAPI file: ${e}`,{cause:t})}if(!c(e))throw Error(`Input file does not exist: ${e}`);return await i(e,`utf8`)}async function h(e){if(!e.endsWith(`.json`))throw Error(`Output file must be a JSON file: ${e}`);return c(e)?await i(e,`utf8`):!1}async function g(e,n){return await t({name:`Reading OpenAPI schemas`,command:async()=>{let[t,r]=await Promise.all([m(e),h(n)]);return[JSON.stringify(JSON.parse(t)),r?JSON.stringify(JSON.parse(r)):!1]}})}e().name(`openapi-gen`).description(`A CLI tool to convert OpenAPI 3.0/3.1 schemas to TypeScript type-safe model interfaces and web service clients.`).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(`-y, --yes`,`Skip confirmation prompts and proceed with defaults.`).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.`).option(`--verify-openapi-gen`,`Verifies that the generated output is up to date with the input (e.g., in CI) to catch outdated or mismatched output without making changes.`).addHelpText(`after`,`
Example usage:
${o(`dim`,`$`)} \
${o(`cyan`,`openapi-gen`)} \
${o(`green`,`-i`)} ${o(`yellow`,`./openapi.json`)} \
${o(`green`,`-o`)} ${o(`yellow`,`./src/api/gen`)} \
${o(`green`,`--include-msw-utils`)}
`).action(async({input:e,output:r,yes:i,forceGen:c,verifyOpenapiGen:l,includeMswUtils:m,postScript:h})=>{try{console.log(o(`magenta`,`
đ openapi-gen
`));let _=await p(r),v=`${_}/openapi.json`,y=`${_}/schema.d.ts`,[b,x]=await g(e,v),S=b!==x;l&&(console.log(S?o(`yellow`,`
â ď¸ Local and remote schemas does not match!
`):o(`green`,`
â
Local and remote schemas match!
`)),process.exit(+!!S)),x===!1&&await t({name:`Creating local schema`,command:a(v,b)}),!S&&!c?(console.log(o(`blue`,`
No updates required.
`)),process.exit(0)):S&&(console.log(o(`yellow`,`
â ď¸ Local and remote schemas does not match!
`)),i||await s({message:`Do you want to use the remote schema? (y/n)?`})?await t({name:`Replacing local schema with input schema`,command:a(v,b)}):(console.log(o(`yellow`,`
â ď¸ Sync remote schemas skipped.
`)),c||process.exit(0))),await t({name:`Generating code from OpenAPI schema`,command:async()=>{let e=[f(v,y),u(v,_)];m&&e.push(d(_)),await Promise.all(e)}}),h&&await t({name:`Running post script`,command:async()=>{try{await(typeof Bun<`u`?n(`bun`,[`run`,h]):n(`node`,[`--run`,h]))}catch{await n(`npm`,[`run`,h],{shell:!0})}}}),console.log(o(`green`,`
â
openapi-gen process completed!
`))}catch(e){console.error(e),process.exit(1)}}).parseAsync(process.argv);export{};