From 78baab8c66fc62089ca8fb36a3f13361c729fb42 Mon Sep 17 00:00:00 2001 From: Coal Cooper Date: Sat, 13 Jan 2024 09:01:57 -0500 Subject: [PATCH 01/12] add codegen dist folder to gitignore update src/lib/api --- .gitignore | 5 +- scripts/openapi-codegen/src/app.ts | 371 ++++++++++++++ src/lib/api/endpoints.ts | 791 +++++++++++++++++++++++++++++ 3 files changed, 1166 insertions(+), 1 deletion(-) create mode 100644 scripts/openapi-codegen/src/app.ts create mode 100644 src/lib/api/endpoints.ts diff --git a/.gitignore b/.gitignore index 3004680..f23a2c9 100755 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,7 @@ Thumbs.db build build/* pbc-frontend -pbc-frontend/* \ No newline at end of file +pbc-frontend/* + +# Scripts +scripts/openapi-codegen/dist \ No newline at end of file diff --git a/scripts/openapi-codegen/src/app.ts b/scripts/openapi-codegen/src/app.ts new file mode 100644 index 0000000..3832add --- /dev/null +++ b/scripts/openapi-codegen/src/app.ts @@ -0,0 +1,371 @@ +import * as ts from 'typescript' +import * as fs from 'fs/promises' +import { join } from 'path' + +import apidocs from './openapi/openapi.json' + +type EndpointData = { + meta: { + methodName: string + controller: string + } + + method: 'get' | 'post' | 'put' | 'delete' + path: string + + query?: { [index: string | number | symbol]: string } + pathVariables?: { [index: string | number | symbol]: string } + body?: { + required: boolean + type: string + } + response?: string +} + +const apiBaseURL = apidocs.servers[0].url +const interfaceNames = Object.keys(apidocs.components.schemas) + +const outputDirectory = join('dist', 'api') +const outputModelsFile = 'models.ts' +const outputEndpointsFile = 'endpoints.ts' +const outputIndexFile = 'index.ts' + +const inputFilePath = 'src/openapi/openapi.ts' +const program = ts.createProgram([inputFilePath], {}) + +const source = program.getSourceFile(inputFilePath) +let outputBuffer = '' + +async function writeBufferToFile(file: string) { + try { + await fs.mkdir(outputDirectory, { recursive: true }) + } catch (_) { + /* Directory already exists */ + } + await fs.writeFile(join(outputDirectory, file), outputBuffer) + outputBuffer = '' +} + +function convertValue(type: string): string { + if (type === 'integer') return 'number' + return type +} + +function parseSchema(value: any): string { + // { type: "type or array or undefined", $ref: "undefined or points to schema", items: "present when type=array, presumably has type or ref" } + if (value.oneOf) + return value.oneOf.map(({ $ref }: { $ref: string }) => parse$Ref($ref)).join(' | ') + if (value.enum && value.type === 'string') + return value.enum.map((value: string) => `'${value}'`).join(' | ') + if (value.items && value.type !== 'array') return parseSchema(value.items) + if (value.type === 'array') { + if (value.items.type) return convertValue(value.items.type) + '[]' + return parseSchema(value.items).replaceAll(/(\w+)/g, '$1[]') + } + + return convertValue(value.type) ?? parse$Ref(value.$ref) +} + +function parse$Ref(value: string): string { + return value?.replace('#/components/schemas/', '') +} + +function bufferModels() { + outputBuffer = 'export type WithRequired = T & { [P in K]-?: T[P] }\n' + + const componentsInterface = source!.statements.find( + (statement) => + ts.isInterfaceDeclaration(statement) && + statement.name && + statement.name.text === 'components', + ) as ts.InterfaceDeclaration | undefined + if (componentsInterface) { + const schemasProperty = componentsInterface.members.find( + (statement) => + ts.isPropertySignature(statement) && statement.name?.getText(source) === 'schemas', + ) as ts.PropertySignature | undefined + if (schemasProperty) { + schemasProperty.forEachChild((node) => { + if (ts.isTypeLiteralNode(node)) { + node.forEachChild((node) => { + const components = node.getText(source).split(':') + const name = components.shift() + const declaration = components + .join(':') + .trim() + .replace(/components\["schemas"\]\["(\w+)"\]/gi, '$1') + .replaceAll(/ {2,}/g, ' ') + .replaceAll(/^ \}/gm, '}') + const type = `export type ${name} = ${declaration}`.replaceAll(';', '') + outputBuffer += `\n${type}\n` + }) + } + }) + } + } +} + +function bufferEndpoints() { + outputBuffer = `import type {\n` + interfaceNames.forEach((name) => (outputBuffer += ` ${name},\n`)) + outputBuffer += `} from './models'\n\n` + outputBuffer += `type Fetch = (input: URL | RequestInfo, init?: RequestInit) => Promise\n\n` + + outputBuffer += `export const BASE_URL = '${apiBaseURL}'\n\n` + + outputBuffer += `function resolveURL(url: URL, { query, path }: { query?: {[index: string]: any}, path?: {[index: string]: any}}): string {\n` + outputBuffer += ` let resolvedURL = url.toString()\n` + outputBuffer += ` \n` + outputBuffer += ` if (path) {\n` + outputBuffer += ` for (const [key, value] of Object.entries(path)) {\n` + outputBuffer += ` const variablePattern = new RegExp(\`{\s*\${key}\s*}\`, 'g')\n` + outputBuffer += ` resolvedURL = resolvedURL.replace(variablePattern, value)\n` + outputBuffer += ` }\n` + outputBuffer += ` }\n` + outputBuffer += `\n` + outputBuffer += ` if (query) {\n` + outputBuffer += ` const searchParams = new URLSearchParams(query)\n` + outputBuffer += ` const queryString = searchParams.toString()\n` + outputBuffer += `\n` + outputBuffer += ` if (queryString) {\n` + outputBuffer += ` resolvedURL += resolvedURL.includes('?') ? \`&\${queryString}\` : \`?\${queryString}\`\n` + outputBuffer += ` }\n` + outputBuffer += ` }\n` + outputBuffer += `\n` + outputBuffer += ` return resolvedURL\n` + outputBuffer += `}\n\n` + + const endpoints = getEndpoints() + const endpointMethods = endpoints.map(createEndpointMethod) + endpointMethods.forEach((method) => (outputBuffer += method + '\n\n')) +} + +function getEndpoints(): EndpointData[] { + const endpoints = [] + for (const path in apidocs.paths) { + // @ts-expect-error typescript for whatever reason does not recognize path as a valid key of apidocs.paths + const allOperations = apidocs.paths[path] + for (const httpMethod in allOperations) { + const details = allOperations[httpMethod] + const query = (details.parameters?.filter((parameter: any) => parameter.in === 'query') ?? + []) as any[] + const pathVariables = (details.parameters?.filter( + (parameter: any) => parameter.in === 'path', + ) ?? []) as any[] + const hasBody = !!details.requestBody + + const endpoint = { + meta: { + methodName: details.operationId, + controller: details.tags[0], + }, + method: httpMethod, + path, + } as any + + if (query.length > 0) { + endpoint.query = {} + query.forEach((parameter) => { + endpoint.query[`${parameter.name}${parameter.required ? '' : '?'}`] = parseSchema( + parameter.schema, + ) + }) + } + + if (pathVariables.length > 0) { + endpoint.pathVariables = {} + pathVariables.forEach((parameter) => { + endpoint.pathVariables[`${parameter.name}${parameter.required ? '' : '?'}`] = parseSchema( + parameter.schema, + ) + }) + } + + if (hasBody) { + endpoint.body = { + type: parseSchema(details.requestBody.content['application/json'].schema), + required: details.requestBody.required, + } + } + + if (details.responses['200'].content) { + endpoint.response = parseSchema(details.responses['200'].content['*/*'].schema) + } + + endpoints.push(endpoint) + } + } + return endpoints +} + +const tsStringify = ( + obj: { [index: string | number | symbol]: any }, + addWhitespace = false, + indentCount = 2, +): string => { + const transform = JSON.stringify( + obj, + null, + addWhitespace ? ''.padStart(indentCount, ' ') : undefined, + ) + .replaceAll('"', '') + .replaceAll(':', ': ') + .replaceAll(',', ', ') + .replace(/^}$/m, '}'.padStart(indentCount - 1, ' ')) + if (!addWhitespace) return transform.replace(/^{/m, '{ ').replace(/}$/m, ' }') + return transform +} + +function createFunctionSignature(endpoint: EndpointData): string[] { + const hasQuery = 'query' in endpoint + const hasVariables = 'pathVariables' in endpoint + const hasBody = 'body' in endpoint + + const sum: number = [hasQuery, hasVariables, hasBody] + .map((truthy) => (truthy ? 1 : 0) as number) + .reduce((acc: number, next) => acc + next) + + let fnArguments: string[] = [] + + if (sum === 1) { + if (hasQuery) + fnArguments.push( + ' query: ' + tsStringify(endpoint.query!, Object.keys(endpoint.query!).length > 2, 4), + ) + if (hasVariables) + fnArguments.push( + ' path: ' + + tsStringify(endpoint.pathVariables!, Object.keys(endpoint.pathVariables!).length > 2, 4), + ) + if (hasBody) + fnArguments.push(` body${endpoint.body!.required ? '' : '?'}: ` + endpoint.body!.type) + } else if (sum > 1) { + fnArguments.push( + ' { ' + + [hasQuery ? 'query' : null, hasVariables ? 'path' : null, hasBody ? 'body' : null] + .filter((x) => !!x) + .join(', ') + + ' }: {', + ) + + if (hasQuery) { + const typedef = + Object.keys(endpoint.query!).length > 1 + ? tsStringify(endpoint.query!, true, 6).split('\n') + : [tsStringify(endpoint.query!)] + fnArguments.push(' query: ' + typedef.shift() + (typedef.length > 0 ? '' : ', ')) + if (typedef.length > 0) + typedef.forEach((line, index) => + fnArguments.push(line + (index === typedef.length - 1 ? ', ' : '')), + ) + } + + if (hasVariables) { + const typedef = + Object.keys(endpoint.pathVariables!).length > 1 + ? tsStringify(endpoint.pathVariables!, true, 6).split('\n') + : [tsStringify(endpoint.pathVariables!)] + fnArguments.push( + ' path: ' + typedef.shift() + (typedef.length === 0 && hasBody ? ', ' : ''), + ) + if (typedef.length > 0) + typedef.forEach((line, index) => + fnArguments.push(line + (index === typedef.length - 1 && hasBody ? ', ' : '')), + ) + } + + if (hasBody) { + fnArguments.push(` body${endpoint.body!.required ? '' : '?'}: ` + endpoint.body!.type) + } + + fnArguments.push(' }') + } + + return [ + `export async function ${endpoint.meta.methodName}(`, + ' fetch: Fetch,', + ...fnArguments, + `): Promise<${endpoint?.response ? endpoint.response + ' | undefined' : 'void'}> {`, + ] +} + +function createEndpointMethod(endpoint: EndpointData) { + const hasQuery = 'query' in endpoint + const hasVariables = 'pathVariables' in endpoint + const hasBody = 'body' in endpoint + + const functionSignature = createFunctionSignature(endpoint) + const pathOptions = + hasQuery && hasVariables + ? `{ query, path }` + : hasQuery + ? `{ query }` + : hasVariables + ? `{ path }` + : null + + const declareURL = !!pathOptions + ? [ + `const url = resolveURL(`, + ` new URL('${endpoint.path}', BASE_URL),`, + ` ${pathOptions},`, + `)`, + ] + : [`const url = new URL('${endpoint.path}', BASE_URL).toString()`] + + return [ + ...functionSignature, + ...declareURL.map((text) => ` ${text}`), + ` const options: RequestInit = {`, + ` method: '${endpoint.method}',`, + ` headers: { 'Content-Type': 'application/json' },`, + endpoint.body && endpoint.body.required ? ` body: JSON.stringify(body),` : null, + ` }`, + ...(endpoint.body && !endpoint.body.required + ? ['', ` if(body) options.body = JSON.stringify(body)`] + : [null]), + '', + ` try {`, + ` const response = await fetch(url, options)`, + ` if (!response.ok)`, + ` throw new Error(\`Request failed with status: \${ response.status }\`)`, + endpoint.response ? ` return await response.json() as ${endpoint.response}` : ` return`, + ` } catch(error) {`, + ...(hasBody && endpoint.body!.required + ? [ + ` console.error(\`received error while fetching url("\${ url }") with data(\${ JSON.stringify(body) })\`, error)`, + ] + : hasBody + ? [ + ` if(body) console.error(\`received error while fetching url("\${ url }") with data(\${ JSON.stringify(body) })\`, error)`, + ` else console.error(\`received error while fetching url: \${ url }\`, error)`, + ] + : [` console.error(\`received error while fetching url: \${ url }\`, error)`]), + ` return undefined`, + ` }`, + `}`, + ] + .filter((x) => x !== null) + .join('\n') +} + +function bufferIndex() { + outputBuffer = `export * from './models' +export * from './endpoints' +` +} + +if (source) { + bufferModels() + await writeBufferToFile(outputModelsFile) + + bufferEndpoints() + await writeBufferToFile(outputEndpointsFile) + + bufferIndex() + await writeBufferToFile(outputIndexFile) + + console.log(`Finished Processing`) +} else { + console.error(`Error: Source file ${inputFilePath} not found.`) +} diff --git a/src/lib/api/endpoints.ts b/src/lib/api/endpoints.ts new file mode 100644 index 0000000..d1e7486 --- /dev/null +++ b/src/lib/api/endpoints.ts @@ -0,0 +1,791 @@ +import type { + Book, + Facility, + Note, + PackageContent, + Recipient, + Shipment, + SpecialRequest, + Zine, +} from './models' + +type Fetch = (input: URL | RequestInfo, init?: RequestInit) => Promise + +export const BASE_URL = 'http://localhost:8080' + +function resolveURL( + url: URL, + { query, path }: { query?: { [index: string]: any }; path?: { [index: string]: any } }, +): string { + let resolvedURL = url.toString() + + if (path) { + for (const [key, value] of Object.entries(path)) { + const variablePattern = new RegExp(`{s*${key}s*}`, 'g') + resolvedURL = resolvedURL.replace(variablePattern, value) + } + } + + if (query) { + const searchParams = new URLSearchParams(query) + const queryString = searchParams.toString() + + if (queryString) { + resolvedURL += resolvedURL.includes('?') ? `&${queryString}` : `?${queryString}` + } + } + + return resolvedURL +} + +export async function updateShipment(fetch: Fetch, body: Shipment): Promise { + const url = new URL('/updateShipment', BASE_URL).toString() + const options: RequestInit = { + method: 'put', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Shipment + } catch (error) { + console.error( + `received error while fetching url("${url}") with data(${JSON.stringify(body)})`, + error, + ) + return undefined + } +} + +export async function updateRecipient( + fetch: Fetch, + body: Recipient, +): Promise { + const url = new URL('/updateRecipient', BASE_URL).toString() + const options: RequestInit = { + method: 'put', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Recipient + } catch (error) { + console.error( + `received error while fetching url("${url}") with data(${JSON.stringify(body)})`, + error, + ) + return undefined + } +} + +export async function updateFacility(fetch: Fetch, body: Facility): Promise { + const url = new URL('/updateFacility', BASE_URL).toString() + const options: RequestInit = { + method: 'put', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Facility + } catch (error) { + console.error( + `received error while fetching url("${url}") with data(${JSON.stringify(body)})`, + error, + ) + return undefined + } +} + +export async function updateContent( + fetch: Fetch, + body: Book | Zine, +): Promise { + const url = new URL('/updateContent', BASE_URL).toString() + const options: RequestInit = { + method: 'put', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Book | Zine + } catch (error) { + console.error( + `received error while fetching url("${url}") with data(${JSON.stringify(body)})`, + error, + ) + return undefined + } +} + +export async function addSpecialRequest( + fetch: Fetch, + body: SpecialRequest, +): Promise { + const url = new URL('/addSpecialRequest', BASE_URL).toString() + const options: RequestInit = { + method: 'post', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as SpecialRequest + } catch (error) { + console.error( + `received error while fetching url("${url}") with data(${JSON.stringify(body)})`, + error, + ) + return undefined + } +} + +export async function addShipment(fetch: Fetch, body: Shipment): Promise { + const url = new URL('/addShipment', BASE_URL).toString() + const options: RequestInit = { + method: 'post', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Shipment + } catch (error) { + console.error( + `received error while fetching url("${url}") with data(${JSON.stringify(body)})`, + error, + ) + return undefined + } +} + +export async function addRecipient(fetch: Fetch, body: Recipient): Promise { + const url = new URL('/addRecipient', BASE_URL).toString() + const options: RequestInit = { + method: 'post', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Recipient + } catch (error) { + console.error( + `received error while fetching url("${url}") with data(${JSON.stringify(body)})`, + error, + ) + return undefined + } +} + +export async function addNote(fetch: Fetch, body: Note): Promise { + const url = new URL('/addNote', BASE_URL).toString() + const options: RequestInit = { + method: 'post', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Note + } catch (error) { + console.error( + `received error while fetching url("${url}") with data(${JSON.stringify(body)})`, + error, + ) + return undefined + } +} + +export async function addFacility(fetch: Fetch, body: Facility): Promise { + const url = new URL('/addFacility', BASE_URL).toString() + const options: RequestInit = { + method: 'post', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Facility + } catch (error) { + console.error( + `received error while fetching url("${url}") with data(${JSON.stringify(body)})`, + error, + ) + return undefined + } +} + +export async function addContent( + fetch: Fetch, + body: Book | Zine, +): Promise { + const url = new URL('/addContent', BASE_URL).toString() + const options: RequestInit = { + method: 'post', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Book | Zine + } catch (error) { + console.error( + `received error while fetching url("${url}") with data(${JSON.stringify(body)})`, + error, + ) + return undefined + } +} + +export async function searchBooksByTitleAndAuthor( + fetch: Fetch, + query: { title: string; author?: string }, +): Promise { + const url = resolveURL(new URL('/searchBooks', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Book[] + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function queryGoogleByTitleAndAuthor( + fetch: Fetch, + query: { title: string; author?: string }, +): Promise { + const url = resolveURL(new URL('/queryGoogle', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Book[] + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getZineByCode( + fetch: Fetch, + query: { code: string }, +): Promise { + const url = resolveURL(new URL('/getZineByCode', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Zine + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getShipmentsByDate( + fetch: Fetch, + query: { date: string }, +): Promise { + const url = resolveURL(new URL('/getShipmentsByDate', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Shipment[] + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getShipment( + fetch: Fetch, + query: { id: number }, +): Promise { + const url = resolveURL(new URL('/getShipment', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Shipment + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getShipmentCountBetweenDates( + fetch: Fetch, + query: { date1: string; date2: string }, +): Promise { + const url = resolveURL(new URL('/getShipmentCountBetweenDates', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as number + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getRecipients( + fetch: Fetch, + query: { firstName: string; lastName: string }, +): Promise { + const url = resolveURL(new URL('/getRecipients', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Recipient[] + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getRecipient( + fetch: Fetch, + query: { id: number }, +): Promise { + const url = resolveURL(new URL('/getRecipient', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Recipient + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getRecipientLocation( + fetch: Fetch, + query: { id: string }, +): Promise { + const url = resolveURL(new URL('/getRecipientLocation', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as string + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getRecipientByAssignedId( + fetch: Fetch, + query: { assignedId: string }, +): Promise { + const url = resolveURL(new URL('/getRecipientByAssignedId', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Recipient + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getBooksWithNoIsbn(fetch: Fetch): Promise { + const url = new URL('/getNoIsbnBooks', BASE_URL).toString() + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Book[] + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getFacilityById( + fetch: Fetch, + query: { id: number }, +): Promise { + const url = resolveURL(new URL('/getFacility', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Facility + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getFacilityByNameAndState( + fetch: Fetch, + query: { name: string; state: 'NC' | 'AL' | 'TN' | 'WV' | 'KY' | 'MD' | 'VA' | 'DE' }, +): Promise { + const url = resolveURL(new URL('/getFacilityByName', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Facility[] + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getContent( + fetch: Fetch, + query: { id: number }, +): Promise { + const url = resolveURL(new URL('/getContent', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Book | Zine + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getContentByTitle( + fetch: Fetch, + query: { title: string }, +): Promise { + const url = resolveURL(new URL('/getContentByTitle', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Book[] | Zine[] + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getBookByISBN( + fetch: Fetch, + query: { isbn: string }, +): Promise { + const url = resolveURL(new URL('/getBookByISBN', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Book + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getAllZines(fetch: Fetch): Promise { + const url = new URL('/getAllZines', BASE_URL).toString() + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Zine[] + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getAllSpecialRequests(fetch: Fetch): Promise { + const url = new URL('/getAllSpecialRequests', BASE_URL).toString() + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as SpecialRequest[] + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getAllShipmentsByRecipient( + fetch: Fetch, + query: { id: number }, +): Promise { + const url = resolveURL(new URL('/getAllShipmentsByRecipient', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Shipment[] + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getAllRecipients(fetch: Fetch): Promise { + const url = new URL('/getAllRecipients', BASE_URL).toString() + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Recipient[] + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getAllFacilities(fetch: Fetch): Promise { + const url = new URL('/getAllFacilities', BASE_URL).toString() + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Facility[] + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getAllContent(fetch: Fetch): Promise { + const url = new URL('/getAllContent', BASE_URL).toString() + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Book[] | Zine[] + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getContentByTitleAndAuthor( + fetch: Fetch, + query: { title: string; author?: string }, +): Promise { + const url = resolveURL(new URL('/content', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Book[] | Zine[] + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function deleteShipment(fetch: Fetch, query: { id: number }): Promise { + const url = resolveURL(new URL('/deleteShipment', BASE_URL), { query }) + const options: RequestInit = { + method: 'delete', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function deleteRecipient(fetch: Fetch, query: { id: number }): Promise { + const url = resolveURL(new URL('/deleteRecipient', BASE_URL), { query }) + const options: RequestInit = { + method: 'delete', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function deleteFacility(fetch: Fetch, query: { id: number }): Promise { + const url = resolveURL(new URL('/deleteFacility', BASE_URL), { query }) + const options: RequestInit = { + method: 'delete', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function deleteContent(fetch: Fetch, query: { id: number }): Promise { + const url = resolveURL(new URL('/deleteContent', BASE_URL), { query }) + const options: RequestInit = { + method: 'delete', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function deleteShipmentsByRecipient( + fetch: Fetch, + query: { id: number }, +): Promise { + const url = resolveURL(new URL('/deleteAllShipmentsByRecipientId', BASE_URL), { query }) + const options: RequestInit = { + method: 'delete', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} From 32a561f969e4d7c1a6d3c7de25bab37076410784 Mon Sep 17 00:00:00 2001 From: Coal Cooper Date: Sat, 13 Jan 2024 08:43:57 -0500 Subject: [PATCH 02/12] add script to build fetch methods from backend's openapi json --- scripts/openapi-codegen/bun.lockb | Bin 0 -> 12509 bytes scripts/openapi-codegen/package.json | 16 + scripts/openapi-codegen/src/app.ts | 4 + .../openapi-codegen/src/openapi/openapi.json | 1368 +++++++++++++++++ .../openapi-codegen/src/openapi/openapi.ts | 764 +++++++++ 5 files changed, 2152 insertions(+) create mode 100755 scripts/openapi-codegen/bun.lockb create mode 100644 scripts/openapi-codegen/package.json create mode 100644 scripts/openapi-codegen/src/openapi/openapi.json create mode 100644 scripts/openapi-codegen/src/openapi/openapi.ts diff --git a/scripts/openapi-codegen/bun.lockb b/scripts/openapi-codegen/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..de970faaa7ba1f28f9d0e7f18cdff8ec12ef6cd3 GIT binary patch literal 12509 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 DOfN%^ literal 0 HcmV?d00001 diff --git a/scripts/openapi-codegen/package.json b/scripts/openapi-codegen/package.json new file mode 100644 index 0000000..9b19d7d --- /dev/null +++ b/scripts/openapi-codegen/package.json @@ -0,0 +1,16 @@ +{ + "name": "openapi-codegen", + "module": "src/app.ts", + "type": "module", + "scripts": { + "start": "openapi-typescript src/openapi/openapi.json --output src/openapi/openapi.ts && bun src/app.ts" + }, + "devDependencies": { + "bun-types": "latest", + "openapi-typescript": "^6.7.3", + "typescript": "^5.3.3" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } +} diff --git a/scripts/openapi-codegen/src/app.ts b/scripts/openapi-codegen/src/app.ts index 3832add..db1224c 100644 --- a/scripts/openapi-codegen/src/app.ts +++ b/scripts/openapi-codegen/src/app.ts @@ -106,7 +106,11 @@ function bufferModels() { } function bufferEndpoints() { +<<<<<<< HEAD outputBuffer = `import type {\n` +======= + outputBuffer = `import {\n` +>>>>>>> e75fc75 (add script to build fetch methods from backend's openapi json) interfaceNames.forEach((name) => (outputBuffer += ` ${name},\n`)) outputBuffer += `} from './models'\n\n` outputBuffer += `type Fetch = (input: URL | RequestInfo, init?: RequestInit) => Promise\n\n` diff --git a/scripts/openapi-codegen/src/openapi/openapi.json b/scripts/openapi-codegen/src/openapi/openapi.json new file mode 100644 index 0000000..25426f8 --- /dev/null +++ b/scripts/openapi-codegen/src/openapi/openapi.json @@ -0,0 +1,1368 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "http://localhost:8080", + "description": "Generated server url" + } + ], + "paths": { + "/updateShipment": { + "put": { + "tags": ["shipment-controller"], + "operationId": "updateShipment", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Shipment" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/Shipment" + } + } + } + } + } + } + }, + "/updateRecipient": { + "put": { + "tags": ["recipient-controller"], + "operationId": "updateRecipient", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Recipient" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/Recipient" + } + } + } + } + } + } + }, + "/updateFacility": { + "put": { + "tags": ["facility-controller"], + "operationId": "updateFacility", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Facility" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/Facility" + } + } + } + } + } + } + }, + "/updateContent": { + "put": { + "tags": ["package-content-controller"], + "operationId": "updateContent", + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/Book" + }, + { + "$ref": "#/components/schemas/Zine" + } + ] + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/Book" + }, + { + "$ref": "#/components/schemas/Zine" + } + ] + } + } + } + } + } + } + }, + "/addSpecialRequest": { + "post": { + "tags": ["special-request-controller"], + "operationId": "addSpecialRequest", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SpecialRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/SpecialRequest" + } + } + } + } + } + } + }, + "/addShipment": { + "post": { + "tags": ["shipment-controller"], + "operationId": "addShipment", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Shipment" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/Shipment" + } + } + } + } + } + } + }, + "/addRecipient": { + "post": { + "tags": ["recipient-controller"], + "operationId": "addRecipient", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Recipient" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/Recipient" + } + } + } + } + } + } + }, + "/addNote": { + "post": { + "tags": ["note-controller"], + "operationId": "addNote", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Note" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/Note" + } + } + } + } + } + } + }, + "/addFacility": { + "post": { + "tags": ["facility-controller"], + "operationId": "addFacility", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Facility" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/Facility" + } + } + } + } + } + } + }, + "/addContent": { + "post": { + "tags": ["package-content-controller"], + "operationId": "addContent", + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/Book" + }, + { + "$ref": "#/components/schemas/Zine" + } + ] + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/Book" + }, + { + "$ref": "#/components/schemas/Zine" + } + ] + } + } + } + } + } + } + }, + "/searchBooks": { + "get": { + "tags": ["package-content-controller"], + "operationId": "searchBooksByTitleAndAuthor", + "parameters": [ + { + "name": "title", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "author", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Book" + } + } + } + } + } + } + } + }, + "/queryGoogle": { + "get": { + "tags": ["package-content-controller"], + "operationId": "queryGoogleByTitleAndAuthor", + "parameters": [ + { + "name": "title", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "author", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Book" + } + } + } + } + } + } + } + }, + "/getZineByCode": { + "get": { + "tags": ["package-content-controller"], + "operationId": "getZineByCode", + "parameters": [ + { + "name": "code", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/Zine" + } + } + } + } + } + } + }, + "/getShipmentsByDate": { + "get": { + "tags": ["shipment-controller"], + "operationId": "getShipmentsByDate", + "parameters": [ + { + "name": "date", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Shipment" + } + } + } + } + } + } + } + }, + "/getShipment": { + "get": { + "tags": ["shipment-controller"], + "operationId": "getShipment", + "parameters": [ + { + "name": "id", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/Shipment" + } + } + } + } + } + } + }, + "/getShipmentCountBetweenDates": { + "get": { + "tags": ["shipment-controller"], + "operationId": "getShipmentCountBetweenDates", + "parameters": [ + { + "name": "date1", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "date2", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "integer", + "format": "int64" + } + } + } + } + } + } + }, + "/getRecipients": { + "get": { + "tags": ["recipient-controller"], + "operationId": "getRecipients", + "parameters": [ + { + "name": "firstName", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "lastName", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Recipient" + } + } + } + } + } + } + } + }, + "/getRecipient": { + "get": { + "tags": ["recipient-controller"], + "operationId": "getRecipient", + "parameters": [ + { + "name": "id", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/Recipient" + } + } + } + } + } + } + }, + "/getRecipientLocation": { + "get": { + "tags": ["recipient-controller"], + "operationId": "getRecipientLocation", + "parameters": [ + { + "name": "id", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/getRecipientByAssignedId": { + "get": { + "tags": ["recipient-controller"], + "operationId": "getRecipientByAssignedId", + "parameters": [ + { + "name": "assignedId", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/Recipient" + } + } + } + } + } + } + }, + "/getNoIsbnBooks": { + "get": { + "tags": ["package-content-controller"], + "operationId": "getBooksWithNoIsbn", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Book" + } + } + } + } + } + } + } + }, + "/getFacility": { + "get": { + "tags": ["facility-controller"], + "operationId": "getFacilityById", + "parameters": [ + { + "name": "id", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/Facility" + } + } + } + } + } + } + }, + "/getFacilityByName": { + "get": { + "tags": ["facility-controller"], + "operationId": "getFacilityByNameAndState", + "parameters": [ + { + "name": "name", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "state", + "in": "query", + "required": true, + "schema": { + "type": "string", + "enum": ["NC", "AL", "TN", "WV", "KY", "MD", "VA", "DE"] + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Facility" + } + } + } + } + } + } + } + }, + "/getContent": { + "get": { + "tags": ["package-content-controller"], + "operationId": "getContent", + "parameters": [ + { + "name": "id", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/Book" + }, + { + "$ref": "#/components/schemas/Zine" + } + ] + } + } + } + } + } + } + }, + "/getContentByTitle": { + "get": { + "tags": ["package-content-controller"], + "operationId": "getContentByTitle", + "parameters": [ + { + "name": "title", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/Book" + }, + { + "$ref": "#/components/schemas/Zine" + } + ] + } + } + } + } + } + } + } + }, + "/getBookByISBN": { + "get": { + "tags": ["package-content-controller"], + "operationId": "getBookByISBN", + "parameters": [ + { + "name": "isbn", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/Book" + } + } + } + } + } + } + }, + "/getAllZines": { + "get": { + "tags": ["package-content-controller"], + "operationId": "getAllZines", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Zine" + } + } + } + } + } + } + } + }, + "/getAllSpecialRequests": { + "get": { + "tags": ["special-request-controller"], + "operationId": "getAllSpecialRequests", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SpecialRequest" + } + } + } + } + } + } + } + }, + "/getAllShipmentsByRecipient": { + "get": { + "tags": ["shipment-controller"], + "operationId": "getAllShipmentsByRecipient", + "parameters": [ + { + "name": "id", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Shipment" + } + } + } + } + } + } + } + }, + "/getAllRecipients": { + "get": { + "tags": ["recipient-controller"], + "operationId": "getAllRecipients", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Recipient" + } + } + } + } + } + } + } + }, + "/getAllFacilities": { + "get": { + "tags": ["facility-controller"], + "operationId": "getAllFacilities", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Facility" + } + } + } + } + } + } + } + }, + "/getAllContent": { + "get": { + "tags": ["package-content-controller"], + "operationId": "getAllContent", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/Book" + }, + { + "$ref": "#/components/schemas/Zine" + } + ] + } + } + } + } + } + } + } + }, + "/content": { + "get": { + "tags": ["package-content-controller"], + "operationId": "getContentByTitleAndAuthor", + "parameters": [ + { + "name": "title", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "author", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/Book" + }, + { + "$ref": "#/components/schemas/Zine" + } + ] + } + } + } + } + } + } + } + }, + "/deleteShipment": { + "delete": { + "tags": ["shipment-controller"], + "operationId": "deleteShipment", + "parameters": [ + { + "name": "id", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/deleteRecipient": { + "delete": { + "tags": ["recipient-controller"], + "operationId": "deleteRecipient", + "parameters": [ + { + "name": "id", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/deleteFacility": { + "delete": { + "tags": ["facility-controller"], + "operationId": "deleteFacility", + "parameters": [ + { + "name": "id", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/deleteContent": { + "delete": { + "tags": ["package-content-controller"], + "operationId": "deleteContent", + "parameters": [ + { + "name": "id", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/deleteAllShipmentsByRecipientId": { + "delete": { + "tags": ["shipment-controller"], + "operationId": "deleteShipmentsByRecipient", + "parameters": [ + { + "name": "id", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + } + }, + "components": { + "schemas": { + "Book": { + "required": ["title"], + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/PackageContent" + }, + { + "type": "object", + "properties": { + "authors": { + "type": "string" + }, + "isbn10": { + "type": "string" + }, + "isbn13": { + "type": "string" + } + } + } + ] + }, + "Facility": { + "required": ["city", "name", "state", "street", "zip"], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "additionalInfo": { + "type": "string" + }, + "street": { + "type": "string" + }, + "city": { + "type": "string" + }, + "state": { + "type": "string", + "enum": ["NC", "AL", "TN", "WV", "KY", "MD", "VA", "DE"] + }, + "zip": { + "type": "string" + } + } + }, + "Note": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "content": { + "type": "string" + }, + "date": { + "type": "string", + "format": "date" + } + } + }, + "PackageContent": { + "required": ["title", "type"], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "discriminator": { + "propertyName": "type" + } + }, + "Recipient": { + "required": ["firstName", "lastName"], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "firstName": { + "type": "string" + }, + "middleName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "assignedId": { + "type": "string" + }, + "facility": { + "$ref": "#/components/schemas/Facility" + }, + "shipments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Shipment" + } + }, + "specialRequests": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SpecialRequest" + } + } + } + }, + "Shipment": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "date": { + "type": "string", + "format": "date" + }, + "facility": { + "$ref": "#/components/schemas/Facility" + }, + "recipient": { + "$ref": "#/components/schemas/Recipient" + }, + "notes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Note" + } + }, + "content": { + "uniqueItems": true, + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/Book" + }, + { + "$ref": "#/components/schemas/Zine" + } + ] + } + } + } + }, + "SpecialRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "volunteerName": { + "type": "string" + }, + "request": { + "type": "string" + }, + "specialRequestDate": { + "type": "string", + "format": "date" + }, + "letterMailedDate": { + "type": "string", + "format": "date" + }, + "category": { + "type": "string", + "enum": [ + "VOCATIONAL", + "EDUCATIONAL", + "CAREER_GROWTH", + "FOREIGN_LANGUAGE", + "LEGAL", + "SPIRITUAL_RELIGIOUS", + "OTHER" + ] + }, + "status": { + "type": "string", + "enum": ["OPEN", "COMPLETED", "CANCELLED"] + }, + "recipient": { + "$ref": "#/components/schemas/Recipient" + } + } + }, + "Zine": { + "required": ["code", "title"], + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/PackageContent" + }, + { + "type": "object", + "properties": { + "code": { + "type": "string" + } + } + } + ] + } + } + } +} diff --git a/scripts/openapi-codegen/src/openapi/openapi.ts b/scripts/openapi-codegen/src/openapi/openapi.ts new file mode 100644 index 0000000..0e4f2c3 --- /dev/null +++ b/scripts/openapi-codegen/src/openapi/openapi.ts @@ -0,0 +1,764 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +/** WithRequired type helpers */ +type WithRequired = T & { [P in K]-?: T[P] } + +export interface paths { + '/updateShipment': { + put: operations['updateShipment'] + } + '/updateRecipient': { + put: operations['updateRecipient'] + } + '/updateFacility': { + put: operations['updateFacility'] + } + '/updateContent': { + put: operations['updateContent'] + } + '/addSpecialRequest': { + post: operations['addSpecialRequest'] + } + '/addShipment': { + post: operations['addShipment'] + } + '/addRecipient': { + post: operations['addRecipient'] + } + '/addNote': { + post: operations['addNote'] + } + '/addFacility': { + post: operations['addFacility'] + } + '/addContent': { + post: operations['addContent'] + } + '/searchBooks': { + get: operations['searchBooksByTitleAndAuthor'] + } + '/queryGoogle': { + get: operations['queryGoogleByTitleAndAuthor'] + } + '/getZineByCode': { + get: operations['getZineByCode'] + } + '/getShipmentsByDate': { + get: operations['getShipmentsByDate'] + } + '/getShipment': { + get: operations['getShipment'] + } + '/getShipmentCountBetweenDates': { + get: operations['getShipmentCountBetweenDates'] + } + '/getRecipients': { + get: operations['getRecipients'] + } + '/getRecipient': { + get: operations['getRecipient'] + } + '/getRecipientLocation': { + get: operations['getRecipientLocation'] + } + '/getRecipientByAssignedId': { + get: operations['getRecipientByAssignedId'] + } + '/getNoIsbnBooks': { + get: operations['getBooksWithNoIsbn'] + } + '/getFacility': { + get: operations['getFacilityById'] + } + '/getFacilityByName': { + get: operations['getFacilityByNameAndState'] + } + '/getContent': { + get: operations['getContent'] + } + '/getContentByTitle': { + get: operations['getContentByTitle'] + } + '/getBookByISBN': { + get: operations['getBookByISBN'] + } + '/getAllZines': { + get: operations['getAllZines'] + } + '/getAllSpecialRequests': { + get: operations['getAllSpecialRequests'] + } + '/getAllShipmentsByRecipient': { + get: operations['getAllShipmentsByRecipient'] + } + '/getAllRecipients': { + get: operations['getAllRecipients'] + } + '/getAllFacilities': { + get: operations['getAllFacilities'] + } + '/getAllContent': { + get: operations['getAllContent'] + } + '/content': { + get: operations['getContentByTitleAndAuthor'] + } + '/deleteShipment': { + delete: operations['deleteShipment'] + } + '/deleteRecipient': { + delete: operations['deleteRecipient'] + } + '/deleteFacility': { + delete: operations['deleteFacility'] + } + '/deleteContent': { + delete: operations['deleteContent'] + } + '/deleteAllShipmentsByRecipientId': { + delete: operations['deleteShipmentsByRecipient'] + } +} + +export type webhooks = Record + +export interface components { + schemas: { + Book: WithRequired< + { + type: 'Book' + } & Omit & { + authors?: string + isbn10?: string + isbn13?: string + }, + 'title' + > + Facility: { + /** Format: int64 */ + id?: number + name: string + additionalInfo?: string + street: string + city: string + /** @enum {string} */ + state: 'NC' | 'AL' | 'TN' | 'WV' | 'KY' | 'MD' | 'VA' | 'DE' + zip: string + } + Note: { + /** Format: int64 */ + id?: number + content?: string + /** Format: date */ + date?: string + } + PackageContent: { + /** Format: int64 */ + id?: number + title: string + type: string + } + Recipient: { + /** Format: int64 */ + id?: number + firstName: string + middleName?: string + lastName: string + assignedId?: string + facility?: components['schemas']['Facility'] + shipments?: components['schemas']['Shipment'][] + specialRequests?: components['schemas']['SpecialRequest'][] + } + Shipment: { + /** Format: int64 */ + id?: number + /** Format: date */ + date?: string + facility?: components['schemas']['Facility'] + recipient?: components['schemas']['Recipient'] + notes?: components['schemas']['Note'][] + content?: (components['schemas']['Book'] | components['schemas']['Zine'])[] + } + SpecialRequest: { + /** Format: int64 */ + id?: number + volunteerName?: string + request?: string + /** Format: date */ + specialRequestDate?: string + /** Format: date */ + letterMailedDate?: string + /** @enum {string} */ + category?: + | 'VOCATIONAL' + | 'EDUCATIONAL' + | 'CAREER_GROWTH' + | 'FOREIGN_LANGUAGE' + | 'LEGAL' + | 'SPIRITUAL_RELIGIOUS' + | 'OTHER' + /** @enum {string} */ + status?: 'OPEN' | 'COMPLETED' | 'CANCELLED' + recipient?: components['schemas']['Recipient'] + } + Zine: WithRequired< + { + type: 'Zine' + } & Omit & { + code?: string + }, + 'code' | 'title' + > + } + responses: never + parameters: never + requestBodies: never + headers: never + pathItems: never +} + +export type $defs = Record + +export type external = Record + +export interface operations { + updateShipment: { + requestBody: { + content: { + 'application/json': components['schemas']['Shipment'] + } + } + responses: { + /** @description OK */ + 200: { + content: { + '*/*': components['schemas']['Shipment'] + } + } + } + } + updateRecipient: { + requestBody: { + content: { + 'application/json': components['schemas']['Recipient'] + } + } + responses: { + /** @description OK */ + 200: { + content: { + '*/*': components['schemas']['Recipient'] + } + } + } + } + updateFacility: { + requestBody: { + content: { + 'application/json': components['schemas']['Facility'] + } + } + responses: { + /** @description OK */ + 200: { + content: { + '*/*': components['schemas']['Facility'] + } + } + } + } + updateContent: { + requestBody: { + content: { + 'application/json': components['schemas']['Book'] | components['schemas']['Zine'] + } + } + responses: { + /** @description OK */ + 200: { + content: { + '*/*': components['schemas']['Book'] | components['schemas']['Zine'] + } + } + } + } + addSpecialRequest: { + requestBody: { + content: { + 'application/json': components['schemas']['SpecialRequest'] + } + } + responses: { + /** @description OK */ + 200: { + content: { + '*/*': components['schemas']['SpecialRequest'] + } + } + } + } + addShipment: { + requestBody: { + content: { + 'application/json': components['schemas']['Shipment'] + } + } + responses: { + /** @description OK */ + 200: { + content: { + '*/*': components['schemas']['Shipment'] + } + } + } + } + addRecipient: { + requestBody: { + content: { + 'application/json': components['schemas']['Recipient'] + } + } + responses: { + /** @description OK */ + 200: { + content: { + '*/*': components['schemas']['Recipient'] + } + } + } + } + addNote: { + requestBody: { + content: { + 'application/json': components['schemas']['Note'] + } + } + responses: { + /** @description OK */ + 200: { + content: { + '*/*': components['schemas']['Note'] + } + } + } + } + addFacility: { + requestBody: { + content: { + 'application/json': components['schemas']['Facility'] + } + } + responses: { + /** @description OK */ + 200: { + content: { + '*/*': components['schemas']['Facility'] + } + } + } + } + addContent: { + requestBody: { + content: { + 'application/json': components['schemas']['Book'] | components['schemas']['Zine'] + } + } + responses: { + /** @description OK */ + 200: { + content: { + '*/*': components['schemas']['Book'] | components['schemas']['Zine'] + } + } + } + } + searchBooksByTitleAndAuthor: { + parameters: { + query: { + title: string + author?: string + } + } + responses: { + /** @description OK */ + 200: { + content: { + '*/*': components['schemas']['Book'][] + } + } + } + } + queryGoogleByTitleAndAuthor: { + parameters: { + query: { + title: string + author?: string + } + } + responses: { + /** @description OK */ + 200: { + content: { + '*/*': components['schemas']['Book'][] + } + } + } + } + getZineByCode: { + parameters: { + query: { + code: string + } + } + responses: { + /** @description OK */ + 200: { + content: { + '*/*': components['schemas']['Zine'] + } + } + } + } + getShipmentsByDate: { + parameters: { + query: { + date: string + } + } + responses: { + /** @description OK */ + 200: { + content: { + '*/*': components['schemas']['Shipment'][] + } + } + } + } + getShipment: { + parameters: { + query: { + id: number + } + } + responses: { + /** @description OK */ + 200: { + content: { + '*/*': components['schemas']['Shipment'] + } + } + } + } + getShipmentCountBetweenDates: { + parameters: { + query: { + date1: string + date2: string + } + } + responses: { + /** @description OK */ + 200: { + content: { + '*/*': number + } + } + } + } + getRecipients: { + parameters: { + query: { + firstName: string + lastName: string + } + } + responses: { + /** @description OK */ + 200: { + content: { + '*/*': components['schemas']['Recipient'][] + } + } + } + } + getRecipient: { + parameters: { + query: { + id: number + } + } + responses: { + /** @description OK */ + 200: { + content: { + '*/*': components['schemas']['Recipient'] + } + } + } + } + getRecipientLocation: { + parameters: { + query: { + id: string + } + } + responses: { + /** @description OK */ + 200: { + content: { + '*/*': string + } + } + } + } + getRecipientByAssignedId: { + parameters: { + query: { + assignedId: string + } + } + responses: { + /** @description OK */ + 200: { + content: { + '*/*': components['schemas']['Recipient'] + } + } + } + } + getBooksWithNoIsbn: { + responses: { + /** @description OK */ + 200: { + content: { + '*/*': components['schemas']['Book'][] + } + } + } + } + getFacilityById: { + parameters: { + query: { + id: number + } + } + responses: { + /** @description OK */ + 200: { + content: { + '*/*': components['schemas']['Facility'] + } + } + } + } + getFacilityByNameAndState: { + parameters: { + query: { + name: string + state: 'NC' | 'AL' | 'TN' | 'WV' | 'KY' | 'MD' | 'VA' | 'DE' + } + } + responses: { + /** @description OK */ + 200: { + content: { + '*/*': components['schemas']['Facility'][] + } + } + } + } + getContent: { + parameters: { + query: { + id: number + } + } + responses: { + /** @description OK */ + 200: { + content: { + '*/*': components['schemas']['Book'] | components['schemas']['Zine'] + } + } + } + } + getContentByTitle: { + parameters: { + query: { + title: string + } + } + responses: { + /** @description OK */ + 200: { + content: { + '*/*': (components['schemas']['Book'] | components['schemas']['Zine'])[] + } + } + } + } + getBookByISBN: { + parameters: { + query: { + isbn: string + } + } + responses: { + /** @description OK */ + 200: { + content: { + '*/*': components['schemas']['Book'] + } + } + } + } + getAllZines: { + responses: { + /** @description OK */ + 200: { + content: { + '*/*': components['schemas']['Zine'][] + } + } + } + } + getAllSpecialRequests: { + responses: { + /** @description OK */ + 200: { + content: { + '*/*': components['schemas']['SpecialRequest'][] + } + } + } + } + getAllShipmentsByRecipient: { + parameters: { + query: { + id: number + } + } + responses: { + /** @description OK */ + 200: { + content: { + '*/*': components['schemas']['Shipment'][] + } + } + } + } + getAllRecipients: { + responses: { + /** @description OK */ + 200: { + content: { + '*/*': components['schemas']['Recipient'][] + } + } + } + } + getAllFacilities: { + responses: { + /** @description OK */ + 200: { + content: { + '*/*': components['schemas']['Facility'][] + } + } + } + } + getAllContent: { + responses: { + /** @description OK */ + 200: { + content: { + '*/*': (components['schemas']['Book'] | components['schemas']['Zine'])[] + } + } + } + } + getContentByTitleAndAuthor: { + parameters: { + query: { + title: string + author?: string + } + } + responses: { + /** @description OK */ + 200: { + content: { + '*/*': (components['schemas']['Book'] | components['schemas']['Zine'])[] + } + } + } + } + deleteShipment: { + parameters: { + query: { + id: number + } + } + responses: { + /** @description OK */ + 200: { + content: never + } + } + } + deleteRecipient: { + parameters: { + query: { + id: number + } + } + responses: { + /** @description OK */ + 200: { + content: never + } + } + } + deleteFacility: { + parameters: { + query: { + id: number + } + } + responses: { + /** @description OK */ + 200: { + content: never + } + } + } + deleteContent: { + parameters: { + query: { + id: number + } + } + responses: { + /** @description OK */ + 200: { + content: never + } + } + } + deleteShipmentsByRecipient: { + parameters: { + query: { + id: number + } + } + responses: { + /** @description OK */ + 200: { + content: never + } + } + } +} From 88ffc5907a69a56c384d50c3387b05314efb06ed Mon Sep 17 00:00:00 2001 From: Coal Cooper Date: Sat, 13 Jan 2024 09:08:34 -0500 Subject: [PATCH 03/12] add $api import shortcut, add generated services to src/lib/api --- src/lib/api/endpoints.ts | 4 ++ src/lib/api/index.ts | 2 + src/lib/api/models.ts | 94 ++++++++++++++++++++++++++++++++++++++++ tsconfig.json | 1 + vite.config.js | 1 + 5 files changed, 102 insertions(+) create mode 100644 src/lib/api/index.ts create mode 100644 src/lib/api/models.ts diff --git a/src/lib/api/endpoints.ts b/src/lib/api/endpoints.ts index d1e7486..d47c977 100644 --- a/src/lib/api/endpoints.ts +++ b/src/lib/api/endpoints.ts @@ -1,4 +1,8 @@ +<<<<<<< HEAD import type { +======= +import { +>>>>>>> b81fd30 (add $api import shortcut, add generated services to src/lib/api) Book, Facility, Note, diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts new file mode 100644 index 0000000..89d7090 --- /dev/null +++ b/src/lib/api/index.ts @@ -0,0 +1,2 @@ +export * from './models' +export * from './endpoints' diff --git a/src/lib/api/models.ts b/src/lib/api/models.ts new file mode 100644 index 0000000..6847208 --- /dev/null +++ b/src/lib/api/models.ts @@ -0,0 +1,94 @@ +export type WithRequired = T & { [P in K]-?: T[P] } + +export type Book = WithRequired< + { + type: 'Book' + } & Omit & { + authors?: string + isbn10?: string + isbn13?: string + }, + 'title' +> + +export type Facility = { + /** Format: int64 */ + id?: number + name: string + additionalInfo?: string + street: string + city: string + /** @enum {string} */ + state: 'NC' | 'AL' | 'TN' | 'WV' | 'KY' | 'MD' | 'VA' | 'DE' + zip: string +} + +export type Note = { + /** Format: int64 */ + id?: number + content?: string + /** Format: date */ + date?: string +} + +export type PackageContent = { + /** Format: int64 */ + id?: number + title: string + type: string +} + +export type Recipient = { + /** Format: int64 */ + id?: number + firstName: string + middleName?: string + lastName: string + assignedId?: string + facility?: Facility + shipments?: Shipment[] + specialRequests?: SpecialRequest[] +} + +export type Shipment = { + /** Format: int64 */ + id?: number + /** Format: date */ + date?: string + facility?: Facility + recipient?: Recipient + notes?: Note[] + content?: (Book | Zine)[] +} + +export type SpecialRequest = { + /** Format: int64 */ + id?: number + volunteerName?: string + request?: string + /** Format: date */ + specialRequestDate?: string + /** Format: date */ + letterMailedDate?: string + /** @enum {string} */ + category?: + | 'VOCATIONAL' + | 'EDUCATIONAL' + | 'CAREER_GROWTH' + | 'FOREIGN_LANGUAGE' + | 'LEGAL' + | 'SPIRITUAL_RELIGIOUS' + | 'OTHER' + /** @enum {string} */ + status?: 'OPEN' | 'COMPLETED' | 'CANCELLED' + recipient?: Recipient +} + +export type Zine = WithRequired< + { + type: 'Zine' + } & Omit & { + code?: string + }, + 'code' | 'title' +> diff --git a/tsconfig.json b/tsconfig.json index 9f0a046..807559e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,6 +12,7 @@ "strictNullChecks": false, "paths": { + "$api": ["./src/lib/api"], "$assets/*": ["./src/lib/assets/*"], "$components/*": ["./src/lib/components/*"], "$models/*": ["./src/lib/models/*"], diff --git a/vite.config.js b/vite.config.js index 217a092..464d685 100644 --- a/vite.config.js +++ b/vite.config.js @@ -6,6 +6,7 @@ export default defineConfig({ plugins: [sveltekit()], resolve: { alias: { + $api: resolve('./src/lib/api'), $assets: resolve('./src/lib/assets'), $components: resolve('./src/lib/components'), $models: resolve('./src/lib/models'), From c09ee298c47c1ac97d1179223fa21ceb5de7c250 Mon Sep 17 00:00:00 2001 From: Coal Cooper Date: Sat, 13 Jan 2024 11:13:59 -0500 Subject: [PATCH 04/12] add ForeignKey utility type --- scripts/openapi-codegen/src/app.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/scripts/openapi-codegen/src/app.ts b/scripts/openapi-codegen/src/app.ts index db1224c..993567a 100644 --- a/scripts/openapi-codegen/src/app.ts +++ b/scripts/openapi-codegen/src/app.ts @@ -72,6 +72,9 @@ function parse$Ref(value: string): string { function bufferModels() { outputBuffer = 'export type WithRequired = T & { [P in K]-?: T[P] }\n' + outputBuffer += `export type ForeignKey = Required> & Partial\n` const componentsInterface = source!.statements.find( (statement) => @@ -90,12 +93,16 @@ function bufferModels() { node.forEachChild((node) => { const components = node.getText(source).split(':') const name = components.shift() - const declaration = components + let declaration = components .join(':') .trim() .replace(/components\["schemas"\]\["(\w+)"\]/gi, '$1') .replaceAll(/ {2,}/g, ' ') .replaceAll(/^ \}/gm, '}') + declaration = declaration.replaceAll( + new RegExp(`([^<"])(${interfaceNames.join('|')})`, 'g'), + '$1ForeignKey<$2>', + ) const type = `export type ${name} = ${declaration}`.replaceAll(';', '') outputBuffer += `\n${type}\n` }) @@ -106,11 +113,7 @@ function bufferModels() { } function bufferEndpoints() { -<<<<<<< HEAD outputBuffer = `import type {\n` -======= - outputBuffer = `import {\n` ->>>>>>> e75fc75 (add script to build fetch methods from backend's openapi json) interfaceNames.forEach((name) => (outputBuffer += ` ${name},\n`)) outputBuffer += `} from './models'\n\n` outputBuffer += `type Fetch = (input: URL | RequestInfo, init?: RequestInit) => Promise\n\n` From cb47ad096f21200587e5645f22a515757ad7d245 Mon Sep 17 00:00:00 2001 From: Coal Cooper Date: Sat, 13 Jan 2024 11:17:21 -0500 Subject: [PATCH 05/12] update script to automatically dump output in src/lib/api instead of script's dist folder --- scripts/openapi-codegen/src/app.ts | 2 +- src/lib/api/endpoints.ts | 4 ---- src/lib/api/models.ts | 27 +++++++++++++++++++-------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/scripts/openapi-codegen/src/app.ts b/scripts/openapi-codegen/src/app.ts index 993567a..d84684b 100644 --- a/scripts/openapi-codegen/src/app.ts +++ b/scripts/openapi-codegen/src/app.ts @@ -25,7 +25,7 @@ type EndpointData = { const apiBaseURL = apidocs.servers[0].url const interfaceNames = Object.keys(apidocs.components.schemas) -const outputDirectory = join('dist', 'api') +const outputDirectory = join('..', '..', 'src', 'lib', 'api') const outputModelsFile = 'models.ts' const outputEndpointsFile = 'endpoints.ts' const outputIndexFile = 'index.ts' diff --git a/src/lib/api/endpoints.ts b/src/lib/api/endpoints.ts index d47c977..d1e7486 100644 --- a/src/lib/api/endpoints.ts +++ b/src/lib/api/endpoints.ts @@ -1,8 +1,4 @@ -<<<<<<< HEAD import type { -======= -import { ->>>>>>> b81fd30 (add $api import shortcut, add generated services to src/lib/api) Book, Facility, Note, diff --git a/src/lib/api/models.ts b/src/lib/api/models.ts index 6847208..2a1c771 100644 --- a/src/lib/api/models.ts +++ b/src/lib/api/models.ts @@ -1,4 +1,15 @@ export type WithRequired = T & { [P in K]-?: T[P] } +export type ForeignKey< + Entity extends + | Book + | Facility + | Note + | PackageContent + | Recipient + | Shipment + | SpecialRequest + | Zine, +> = Required> & Partial export type Book = WithRequired< { @@ -45,9 +56,9 @@ export type Recipient = { middleName?: string lastName: string assignedId?: string - facility?: Facility - shipments?: Shipment[] - specialRequests?: SpecialRequest[] + facility?: ForeignKey + shipments?: ForeignKey[] + specialRequests?: ForeignKey[] } export type Shipment = { @@ -55,10 +66,10 @@ export type Shipment = { id?: number /** Format: date */ date?: string - facility?: Facility - recipient?: Recipient - notes?: Note[] - content?: (Book | Zine)[] + facility?: ForeignKey + recipient?: ForeignKey + notes?: ForeignKey[] + content?: (ForeignKey | ForeignKey)[] } export type SpecialRequest = { @@ -81,7 +92,7 @@ export type SpecialRequest = { | 'OTHER' /** @enum {string} */ status?: 'OPEN' | 'COMPLETED' | 'CANCELLED' - recipient?: Recipient + recipient?: ForeignKey } export type Zine = WithRequired< From e929c79c79601f5ed2226a3e3753b78aed1a4c1d Mon Sep 17 00:00:00 2001 From: Coal Cooper Date: Wed, 17 Jan 2024 09:06:46 -0500 Subject: [PATCH 06/12] transition script into a series of CLI scripts (download, convert, & codegen). Add package.json scripts "npm run local", "npm run local:test", and "remote" variants. See "npx bun src/app.ts" for the generated CLI help menu --- scripts/openapi-codegen/bun.lockb | Bin 12509 -> 18502 bytes scripts/openapi-codegen/endpoints.ts | 791 ++++++++++++++++++++ scripts/openapi-codegen/index.ts | 2 + scripts/openapi-codegen/models.ts | 105 +++ scripts/openapi-codegen/package-lock.json | 597 +++++++++++++++ scripts/openapi-codegen/package.json | 16 +- scripts/openapi-codegen/src/app.ts | 441 ++--------- scripts/openapi-codegen/src/lib/codegen.ts | 505 +++++++++++++ scripts/openapi-codegen/src/lib/convert.ts | 50 ++ scripts/openapi-codegen/src/lib/download.ts | 68 ++ scripts/openapi-codegen/src/lib/util.ts | 44 ++ 11 files changed, 2237 insertions(+), 382 deletions(-) create mode 100644 scripts/openapi-codegen/endpoints.ts create mode 100644 scripts/openapi-codegen/index.ts create mode 100644 scripts/openapi-codegen/models.ts create mode 100644 scripts/openapi-codegen/package-lock.json create mode 100644 scripts/openapi-codegen/src/lib/codegen.ts create mode 100644 scripts/openapi-codegen/src/lib/convert.ts create mode 100644 scripts/openapi-codegen/src/lib/download.ts create mode 100644 scripts/openapi-codegen/src/lib/util.ts diff --git a/scripts/openapi-codegen/bun.lockb b/scripts/openapi-codegen/bun.lockb index de970faaa7ba1f28f9d0e7f18cdff8ec12ef6cd3..9c11f00c144243b0af071b66ac883ef682200ff6 100755 GIT binary patch delta 6712 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 WJCg04M-J5QG0`InZqbKTyZ-{75al@l diff --git a/scripts/openapi-codegen/endpoints.ts b/scripts/openapi-codegen/endpoints.ts new file mode 100644 index 0000000..d1e7486 --- /dev/null +++ b/scripts/openapi-codegen/endpoints.ts @@ -0,0 +1,791 @@ +import type { + Book, + Facility, + Note, + PackageContent, + Recipient, + Shipment, + SpecialRequest, + Zine, +} from './models' + +type Fetch = (input: URL | RequestInfo, init?: RequestInit) => Promise + +export const BASE_URL = 'http://localhost:8080' + +function resolveURL( + url: URL, + { query, path }: { query?: { [index: string]: any }; path?: { [index: string]: any } }, +): string { + let resolvedURL = url.toString() + + if (path) { + for (const [key, value] of Object.entries(path)) { + const variablePattern = new RegExp(`{s*${key}s*}`, 'g') + resolvedURL = resolvedURL.replace(variablePattern, value) + } + } + + if (query) { + const searchParams = new URLSearchParams(query) + const queryString = searchParams.toString() + + if (queryString) { + resolvedURL += resolvedURL.includes('?') ? `&${queryString}` : `?${queryString}` + } + } + + return resolvedURL +} + +export async function updateShipment(fetch: Fetch, body: Shipment): Promise { + const url = new URL('/updateShipment', BASE_URL).toString() + const options: RequestInit = { + method: 'put', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Shipment + } catch (error) { + console.error( + `received error while fetching url("${url}") with data(${JSON.stringify(body)})`, + error, + ) + return undefined + } +} + +export async function updateRecipient( + fetch: Fetch, + body: Recipient, +): Promise { + const url = new URL('/updateRecipient', BASE_URL).toString() + const options: RequestInit = { + method: 'put', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Recipient + } catch (error) { + console.error( + `received error while fetching url("${url}") with data(${JSON.stringify(body)})`, + error, + ) + return undefined + } +} + +export async function updateFacility(fetch: Fetch, body: Facility): Promise { + const url = new URL('/updateFacility', BASE_URL).toString() + const options: RequestInit = { + method: 'put', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Facility + } catch (error) { + console.error( + `received error while fetching url("${url}") with data(${JSON.stringify(body)})`, + error, + ) + return undefined + } +} + +export async function updateContent( + fetch: Fetch, + body: Book | Zine, +): Promise { + const url = new URL('/updateContent', BASE_URL).toString() + const options: RequestInit = { + method: 'put', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Book | Zine + } catch (error) { + console.error( + `received error while fetching url("${url}") with data(${JSON.stringify(body)})`, + error, + ) + return undefined + } +} + +export async function addSpecialRequest( + fetch: Fetch, + body: SpecialRequest, +): Promise { + const url = new URL('/addSpecialRequest', BASE_URL).toString() + const options: RequestInit = { + method: 'post', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as SpecialRequest + } catch (error) { + console.error( + `received error while fetching url("${url}") with data(${JSON.stringify(body)})`, + error, + ) + return undefined + } +} + +export async function addShipment(fetch: Fetch, body: Shipment): Promise { + const url = new URL('/addShipment', BASE_URL).toString() + const options: RequestInit = { + method: 'post', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Shipment + } catch (error) { + console.error( + `received error while fetching url("${url}") with data(${JSON.stringify(body)})`, + error, + ) + return undefined + } +} + +export async function addRecipient(fetch: Fetch, body: Recipient): Promise { + const url = new URL('/addRecipient', BASE_URL).toString() + const options: RequestInit = { + method: 'post', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Recipient + } catch (error) { + console.error( + `received error while fetching url("${url}") with data(${JSON.stringify(body)})`, + error, + ) + return undefined + } +} + +export async function addNote(fetch: Fetch, body: Note): Promise { + const url = new URL('/addNote', BASE_URL).toString() + const options: RequestInit = { + method: 'post', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Note + } catch (error) { + console.error( + `received error while fetching url("${url}") with data(${JSON.stringify(body)})`, + error, + ) + return undefined + } +} + +export async function addFacility(fetch: Fetch, body: Facility): Promise { + const url = new URL('/addFacility', BASE_URL).toString() + const options: RequestInit = { + method: 'post', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Facility + } catch (error) { + console.error( + `received error while fetching url("${url}") with data(${JSON.stringify(body)})`, + error, + ) + return undefined + } +} + +export async function addContent( + fetch: Fetch, + body: Book | Zine, +): Promise { + const url = new URL('/addContent', BASE_URL).toString() + const options: RequestInit = { + method: 'post', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Book | Zine + } catch (error) { + console.error( + `received error while fetching url("${url}") with data(${JSON.stringify(body)})`, + error, + ) + return undefined + } +} + +export async function searchBooksByTitleAndAuthor( + fetch: Fetch, + query: { title: string; author?: string }, +): Promise { + const url = resolveURL(new URL('/searchBooks', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Book[] + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function queryGoogleByTitleAndAuthor( + fetch: Fetch, + query: { title: string; author?: string }, +): Promise { + const url = resolveURL(new URL('/queryGoogle', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Book[] + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getZineByCode( + fetch: Fetch, + query: { code: string }, +): Promise { + const url = resolveURL(new URL('/getZineByCode', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Zine + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getShipmentsByDate( + fetch: Fetch, + query: { date: string }, +): Promise { + const url = resolveURL(new URL('/getShipmentsByDate', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Shipment[] + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getShipment( + fetch: Fetch, + query: { id: number }, +): Promise { + const url = resolveURL(new URL('/getShipment', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Shipment + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getShipmentCountBetweenDates( + fetch: Fetch, + query: { date1: string; date2: string }, +): Promise { + const url = resolveURL(new URL('/getShipmentCountBetweenDates', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as number + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getRecipients( + fetch: Fetch, + query: { firstName: string; lastName: string }, +): Promise { + const url = resolveURL(new URL('/getRecipients', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Recipient[] + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getRecipient( + fetch: Fetch, + query: { id: number }, +): Promise { + const url = resolveURL(new URL('/getRecipient', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Recipient + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getRecipientLocation( + fetch: Fetch, + query: { id: string }, +): Promise { + const url = resolveURL(new URL('/getRecipientLocation', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as string + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getRecipientByAssignedId( + fetch: Fetch, + query: { assignedId: string }, +): Promise { + const url = resolveURL(new URL('/getRecipientByAssignedId', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Recipient + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getBooksWithNoIsbn(fetch: Fetch): Promise { + const url = new URL('/getNoIsbnBooks', BASE_URL).toString() + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Book[] + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getFacilityById( + fetch: Fetch, + query: { id: number }, +): Promise { + const url = resolveURL(new URL('/getFacility', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Facility + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getFacilityByNameAndState( + fetch: Fetch, + query: { name: string; state: 'NC' | 'AL' | 'TN' | 'WV' | 'KY' | 'MD' | 'VA' | 'DE' }, +): Promise { + const url = resolveURL(new URL('/getFacilityByName', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Facility[] + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getContent( + fetch: Fetch, + query: { id: number }, +): Promise { + const url = resolveURL(new URL('/getContent', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Book | Zine + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getContentByTitle( + fetch: Fetch, + query: { title: string }, +): Promise { + const url = resolveURL(new URL('/getContentByTitle', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Book[] | Zine[] + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getBookByISBN( + fetch: Fetch, + query: { isbn: string }, +): Promise { + const url = resolveURL(new URL('/getBookByISBN', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Book + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getAllZines(fetch: Fetch): Promise { + const url = new URL('/getAllZines', BASE_URL).toString() + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Zine[] + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getAllSpecialRequests(fetch: Fetch): Promise { + const url = new URL('/getAllSpecialRequests', BASE_URL).toString() + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as SpecialRequest[] + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getAllShipmentsByRecipient( + fetch: Fetch, + query: { id: number }, +): Promise { + const url = resolveURL(new URL('/getAllShipmentsByRecipient', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Shipment[] + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getAllRecipients(fetch: Fetch): Promise { + const url = new URL('/getAllRecipients', BASE_URL).toString() + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Recipient[] + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getAllFacilities(fetch: Fetch): Promise { + const url = new URL('/getAllFacilities', BASE_URL).toString() + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Facility[] + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getAllContent(fetch: Fetch): Promise { + const url = new URL('/getAllContent', BASE_URL).toString() + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Book[] | Zine[] + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function getContentByTitleAndAuthor( + fetch: Fetch, + query: { title: string; author?: string }, +): Promise { + const url = resolveURL(new URL('/content', BASE_URL), { query }) + const options: RequestInit = { + method: 'get', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return (await response.json()) as Book[] | Zine[] + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function deleteShipment(fetch: Fetch, query: { id: number }): Promise { + const url = resolveURL(new URL('/deleteShipment', BASE_URL), { query }) + const options: RequestInit = { + method: 'delete', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function deleteRecipient(fetch: Fetch, query: { id: number }): Promise { + const url = resolveURL(new URL('/deleteRecipient', BASE_URL), { query }) + const options: RequestInit = { + method: 'delete', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function deleteFacility(fetch: Fetch, query: { id: number }): Promise { + const url = resolveURL(new URL('/deleteFacility', BASE_URL), { query }) + const options: RequestInit = { + method: 'delete', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function deleteContent(fetch: Fetch, query: { id: number }): Promise { + const url = resolveURL(new URL('/deleteContent', BASE_URL), { query }) + const options: RequestInit = { + method: 'delete', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} + +export async function deleteShipmentsByRecipient( + fetch: Fetch, + query: { id: number }, +): Promise { + const url = resolveURL(new URL('/deleteAllShipmentsByRecipientId', BASE_URL), { query }) + const options: RequestInit = { + method: 'delete', + headers: { 'Content-Type': 'application/json' }, + } + + try { + const response = await fetch(url, options) + if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) + return + } catch (error) { + console.error(`received error while fetching url: ${url}`, error) + return undefined + } +} diff --git a/scripts/openapi-codegen/index.ts b/scripts/openapi-codegen/index.ts new file mode 100644 index 0000000..89d7090 --- /dev/null +++ b/scripts/openapi-codegen/index.ts @@ -0,0 +1,2 @@ +export * from './models' +export * from './endpoints' diff --git a/scripts/openapi-codegen/models.ts b/scripts/openapi-codegen/models.ts new file mode 100644 index 0000000..2a1c771 --- /dev/null +++ b/scripts/openapi-codegen/models.ts @@ -0,0 +1,105 @@ +export type WithRequired = T & { [P in K]-?: T[P] } +export type ForeignKey< + Entity extends + | Book + | Facility + | Note + | PackageContent + | Recipient + | Shipment + | SpecialRequest + | Zine, +> = Required> & Partial + +export type Book = WithRequired< + { + type: 'Book' + } & Omit & { + authors?: string + isbn10?: string + isbn13?: string + }, + 'title' +> + +export type Facility = { + /** Format: int64 */ + id?: number + name: string + additionalInfo?: string + street: string + city: string + /** @enum {string} */ + state: 'NC' | 'AL' | 'TN' | 'WV' | 'KY' | 'MD' | 'VA' | 'DE' + zip: string +} + +export type Note = { + /** Format: int64 */ + id?: number + content?: string + /** Format: date */ + date?: string +} + +export type PackageContent = { + /** Format: int64 */ + id?: number + title: string + type: string +} + +export type Recipient = { + /** Format: int64 */ + id?: number + firstName: string + middleName?: string + lastName: string + assignedId?: string + facility?: ForeignKey + shipments?: ForeignKey[] + specialRequests?: ForeignKey[] +} + +export type Shipment = { + /** Format: int64 */ + id?: number + /** Format: date */ + date?: string + facility?: ForeignKey + recipient?: ForeignKey + notes?: ForeignKey[] + content?: (ForeignKey | ForeignKey)[] +} + +export type SpecialRequest = { + /** Format: int64 */ + id?: number + volunteerName?: string + request?: string + /** Format: date */ + specialRequestDate?: string + /** Format: date */ + letterMailedDate?: string + /** @enum {string} */ + category?: + | 'VOCATIONAL' + | 'EDUCATIONAL' + | 'CAREER_GROWTH' + | 'FOREIGN_LANGUAGE' + | 'LEGAL' + | 'SPIRITUAL_RELIGIOUS' + | 'OTHER' + /** @enum {string} */ + status?: 'OPEN' | 'COMPLETED' | 'CANCELLED' + recipient?: ForeignKey +} + +export type Zine = WithRequired< + { + type: 'Zine' + } & Omit & { + code?: string + }, + 'code' | 'title' +> diff --git a/scripts/openapi-codegen/package-lock.json b/scripts/openapi-codegen/package-lock.json new file mode 100644 index 0000000..4d07e66 --- /dev/null +++ b/scripts/openapi-codegen/package-lock.json @@ -0,0 +1,597 @@ +{ + "name": "openapi-codegen", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "openapi-codegen", + "dependencies": { + "commander": "^11.1.0", + "node-fetch": "^3.3.2", + "openapi-typescript": "^6.7.3", + "typescript": "^5.3.3" + }, + "devDependencies": { + "@types/node-fetch": "^2.6.11", + "bun": "^1.0.23", + "bun-types": "latest" + } + }, + "node_modules/@fastify/busboy": { + "version": "2.1.0", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@oven/bun-darwin-aarch64": { + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/@oven/bun-darwin-aarch64/-/bun-darwin-aarch64-1.0.23.tgz", + "integrity": "sha512-dkGEABH1Vkjgss9c81AeD5UVEjth+r9LgDZuIooJ+3SqGaHW2fv+9Ywrg5FB50hxb/E3UtxdOTCEsMAQsHNm6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oven/bun-darwin-x64": { + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64/-/bun-darwin-x64-1.0.23.tgz", + "integrity": "sha512-7ZlivtMlb8s07NW9yoUIzEIEY6HsXGAofudIR5THbwsLZGLj5ffg0aCqpfV4Oud3KGuF2lkHGlnFs9iGDouj6A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oven/bun-darwin-x64-baseline": { + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64-baseline/-/bun-darwin-x64-baseline-1.0.23.tgz", + "integrity": "sha512-Y0l3qxJcma1qxDhe4nCUc04CTAHEQQ28HXtNdzrrgKfX/TTBLBmTQxTqb0VeAosLoVF+sgjfUjVFFEFMrYnFgg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oven/bun-linux-aarch64": { + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64/-/bun-linux-aarch64-1.0.23.tgz", + "integrity": "sha512-07BWe6Myzksgzph3rlvGe05OE0IsjX06nIs5lU85GIqHj4/WvFw/efKPGWc+miQTGpTacc9hDBhov+dEnb7iSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-x64": { + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64/-/bun-linux-x64-1.0.23.tgz", + "integrity": "sha512-qywnmRQlQeo1Y1DNJKogtuSyUsw1CnRWOBWOrKoJC5a4L9V+/Ek9qgpccakY4lZb75i4Fb2zBK2RYiB8LSimng==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-x64-baseline": { + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-baseline/-/bun-linux-x64-baseline-1.0.23.tgz", + "integrity": "sha512-hTPxAe1NTFuCJvvwOpptr25BB4nsr/169rVhID2ZZBFbaZAVFQEV+QJsptI2xXq8pc2Gln0+hndli7/9ZYvo9w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@types/node": { + "version": "20.11.5", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.11", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/node/node_modules/undici-types": { + "version": "5.26.5", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.5.10", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ws/node_modules/@types/node": { + "version": "20.11.0", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/ws/node_modules/@types/node/node_modules/undici-types": { + "version": "5.26.5", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "license": "Python-2.0" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/braces": { + "version": "3.0.2", + "license": "MIT", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bun": { + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/bun/-/bun-1.0.23.tgz", + "integrity": "sha512-quznVjIV3yHMkfx4k1vS4z10hL3c6wGb11eKmtQJ1GrKj3tumXEGHTakb+YFTX8+SLw7IUNzlnpur1UJWtY2gw==", + "cpu": [ + "arm64", + "x64" + ], + "dev": true, + "hasInstallScript": true, + "os": [ + "darwin", + "linux" + ], + "bin": { + "bun": "bin/bun", + "bunx": "bin/bun" + }, + "optionalDependencies": { + "@oven/bun-darwin-aarch64": "1.0.23", + "@oven/bun-darwin-x64": "1.0.23", + "@oven/bun-darwin-x64-baseline": "1.0.23", + "@oven/bun-linux-aarch64": "1.0.23", + "@oven/bun-linux-x64": "1.0.23", + "@oven/bun-linux-x64-baseline": "1.0.23" + } + }, + "node_modules/bun-types": { + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.0.23.tgz", + "integrity": "sha512-K3eFQrZk2LWAdp7qcMz+m40Mz+RQc3jpgroQRlQqvQLSFL4qQ3tULBrKHzSwh0P9P7vyHbhyJ5IGKj/cmQFLGg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/ws": "*", + "undici-types": "^5.26.4" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "11.1.0", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.16.0", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "license": "MIT", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/openapi-typescript": { + "version": "6.7.3", + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.3", + "fast-glob": "^3.3.2", + "js-yaml": "^4.1.0", + "supports-color": "^9.4.0", + "undici": "^5.28.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "openapi-typescript": "bin/cli.js" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/reusify": { + "version": "1.0.4", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/supports-color": { + "version": "9.4.0", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "5.28.2", + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/undici-types": { + "version": "5.28.2", + "dev": true, + "license": "MIT" + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.2", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "license": "ISC", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/scripts/openapi-codegen/package.json b/scripts/openapi-codegen/package.json index 9b19d7d..96c5d08 100644 --- a/scripts/openapi-codegen/package.json +++ b/scripts/openapi-codegen/package.json @@ -3,14 +3,20 @@ "module": "src/app.ts", "type": "module", "scripts": { - "start": "openapi-typescript src/openapi/openapi.json --output src/openapi/openapi.ts && bun src/app.ts" + "local": "npx bun src/app.ts download && npx bun src/app.ts convert && npx bun src/app.ts codegen --output ../../src/lib/api", + "local:test": "npx bun src/app.ts download && npx bun src/app.ts convert && npx bun src/app.ts codegen", + "remote": "npx bun src/app.ts download --remote && npx bun src/app.ts convert && npx bun src/app.ts codegen --output ../../src/lib/api", + "remote:test": "npx bun src/app.ts download --remote && npx bun src/app.ts convert && npx bun src/app.ts codegen" }, "devDependencies": { - "bun-types": "latest", + "@types/node-fetch": "^2.6.11", + "bun": "^1.0.23", + "bun-types": "latest" + }, + "dependencies": { + "commander": "^11.1.0", + "node-fetch": "^3.3.2", "openapi-typescript": "^6.7.3", "typescript": "^5.3.3" - }, - "peerDependencies": { - "typescript": "^5.0.0" } } diff --git a/scripts/openapi-codegen/src/app.ts b/scripts/openapi-codegen/src/app.ts index d84684b..701a860 100644 --- a/scripts/openapi-codegen/src/app.ts +++ b/scripts/openapi-codegen/src/app.ts @@ -1,378 +1,65 @@ -import * as ts from 'typescript' -import * as fs from 'fs/promises' -import { join } from 'path' - -import apidocs from './openapi/openapi.json' - -type EndpointData = { - meta: { - methodName: string - controller: string - } - - method: 'get' | 'post' | 'put' | 'delete' - path: string - - query?: { [index: string | number | symbol]: string } - pathVariables?: { [index: string | number | symbol]: string } - body?: { - required: boolean - type: string - } - response?: string -} - -const apiBaseURL = apidocs.servers[0].url -const interfaceNames = Object.keys(apidocs.components.schemas) - -const outputDirectory = join('..', '..', 'src', 'lib', 'api') -const outputModelsFile = 'models.ts' -const outputEndpointsFile = 'endpoints.ts' -const outputIndexFile = 'index.ts' - -const inputFilePath = 'src/openapi/openapi.ts' -const program = ts.createProgram([inputFilePath], {}) - -const source = program.getSourceFile(inputFilePath) -let outputBuffer = '' - -async function writeBufferToFile(file: string) { - try { - await fs.mkdir(outputDirectory, { recursive: true }) - } catch (_) { - /* Directory already exists */ - } - await fs.writeFile(join(outputDirectory, file), outputBuffer) - outputBuffer = '' -} - -function convertValue(type: string): string { - if (type === 'integer') return 'number' - return type -} - -function parseSchema(value: any): string { - // { type: "type or array or undefined", $ref: "undefined or points to schema", items: "present when type=array, presumably has type or ref" } - if (value.oneOf) - return value.oneOf.map(({ $ref }: { $ref: string }) => parse$Ref($ref)).join(' | ') - if (value.enum && value.type === 'string') - return value.enum.map((value: string) => `'${value}'`).join(' | ') - if (value.items && value.type !== 'array') return parseSchema(value.items) - if (value.type === 'array') { - if (value.items.type) return convertValue(value.items.type) + '[]' - return parseSchema(value.items).replaceAll(/(\w+)/g, '$1[]') - } - - return convertValue(value.type) ?? parse$Ref(value.$ref) -} - -function parse$Ref(value: string): string { - return value?.replace('#/components/schemas/', '') -} - -function bufferModels() { - outputBuffer = 'export type WithRequired = T & { [P in K]-?: T[P] }\n' - outputBuffer += `export type ForeignKey = Required> & Partial\n` - - const componentsInterface = source!.statements.find( - (statement) => - ts.isInterfaceDeclaration(statement) && - statement.name && - statement.name.text === 'components', - ) as ts.InterfaceDeclaration | undefined - if (componentsInterface) { - const schemasProperty = componentsInterface.members.find( - (statement) => - ts.isPropertySignature(statement) && statement.name?.getText(source) === 'schemas', - ) as ts.PropertySignature | undefined - if (schemasProperty) { - schemasProperty.forEachChild((node) => { - if (ts.isTypeLiteralNode(node)) { - node.forEachChild((node) => { - const components = node.getText(source).split(':') - const name = components.shift() - let declaration = components - .join(':') - .trim() - .replace(/components\["schemas"\]\["(\w+)"\]/gi, '$1') - .replaceAll(/ {2,}/g, ' ') - .replaceAll(/^ \}/gm, '}') - declaration = declaration.replaceAll( - new RegExp(`([^<"])(${interfaceNames.join('|')})`, 'g'), - '$1ForeignKey<$2>', - ) - const type = `export type ${name} = ${declaration}`.replaceAll(';', '') - outputBuffer += `\n${type}\n` - }) - } - }) - } - } -} - -function bufferEndpoints() { - outputBuffer = `import type {\n` - interfaceNames.forEach((name) => (outputBuffer += ` ${name},\n`)) - outputBuffer += `} from './models'\n\n` - outputBuffer += `type Fetch = (input: URL | RequestInfo, init?: RequestInit) => Promise\n\n` - - outputBuffer += `export const BASE_URL = '${apiBaseURL}'\n\n` - - outputBuffer += `function resolveURL(url: URL, { query, path }: { query?: {[index: string]: any}, path?: {[index: string]: any}}): string {\n` - outputBuffer += ` let resolvedURL = url.toString()\n` - outputBuffer += ` \n` - outputBuffer += ` if (path) {\n` - outputBuffer += ` for (const [key, value] of Object.entries(path)) {\n` - outputBuffer += ` const variablePattern = new RegExp(\`{\s*\${key}\s*}\`, 'g')\n` - outputBuffer += ` resolvedURL = resolvedURL.replace(variablePattern, value)\n` - outputBuffer += ` }\n` - outputBuffer += ` }\n` - outputBuffer += `\n` - outputBuffer += ` if (query) {\n` - outputBuffer += ` const searchParams = new URLSearchParams(query)\n` - outputBuffer += ` const queryString = searchParams.toString()\n` - outputBuffer += `\n` - outputBuffer += ` if (queryString) {\n` - outputBuffer += ` resolvedURL += resolvedURL.includes('?') ? \`&\${queryString}\` : \`?\${queryString}\`\n` - outputBuffer += ` }\n` - outputBuffer += ` }\n` - outputBuffer += `\n` - outputBuffer += ` return resolvedURL\n` - outputBuffer += `}\n\n` - - const endpoints = getEndpoints() - const endpointMethods = endpoints.map(createEndpointMethod) - endpointMethods.forEach((method) => (outputBuffer += method + '\n\n')) -} - -function getEndpoints(): EndpointData[] { - const endpoints = [] - for (const path in apidocs.paths) { - // @ts-expect-error typescript for whatever reason does not recognize path as a valid key of apidocs.paths - const allOperations = apidocs.paths[path] - for (const httpMethod in allOperations) { - const details = allOperations[httpMethod] - const query = (details.parameters?.filter((parameter: any) => parameter.in === 'query') ?? - []) as any[] - const pathVariables = (details.parameters?.filter( - (parameter: any) => parameter.in === 'path', - ) ?? []) as any[] - const hasBody = !!details.requestBody - - const endpoint = { - meta: { - methodName: details.operationId, - controller: details.tags[0], - }, - method: httpMethod, - path, - } as any - - if (query.length > 0) { - endpoint.query = {} - query.forEach((parameter) => { - endpoint.query[`${parameter.name}${parameter.required ? '' : '?'}`] = parseSchema( - parameter.schema, - ) - }) - } - - if (pathVariables.length > 0) { - endpoint.pathVariables = {} - pathVariables.forEach((parameter) => { - endpoint.pathVariables[`${parameter.name}${parameter.required ? '' : '?'}`] = parseSchema( - parameter.schema, - ) - }) - } - - if (hasBody) { - endpoint.body = { - type: parseSchema(details.requestBody.content['application/json'].schema), - required: details.requestBody.required, - } - } - - if (details.responses['200'].content) { - endpoint.response = parseSchema(details.responses['200'].content['*/*'].schema) - } - - endpoints.push(endpoint) - } - } - return endpoints -} - -const tsStringify = ( - obj: { [index: string | number | symbol]: any }, - addWhitespace = false, - indentCount = 2, -): string => { - const transform = JSON.stringify( - obj, - null, - addWhitespace ? ''.padStart(indentCount, ' ') : undefined, +import { program } from 'commander' + +import type { DownloadOptions } from './lib/download' +import type { ConvertOptions } from './lib/convert' +import type { CodegenOptions } from './lib/codegen' + +program + .name('bellbooks-openapi-codegen') + .description('CLI to generate frontend fetch methods to interface with BellBooks backend') + .version('0.0.1') + +program + .command('download') + .description('Download the latest openapi.json from a running instance of the Bellbooks API') + .option( + '-l, --local', + '(default) Use a local instance of the Bellbooks API at http://localhost:8080/api/docs', ) - .replaceAll('"', '') - .replaceAll(':', ': ') - .replaceAll(',', ', ') - .replace(/^}$/m, '}'.padStart(indentCount - 1, ' ')) - if (!addWhitespace) return transform.replace(/^{/m, '{ ').replace(/}$/m, ' }') - return transform -} - -function createFunctionSignature(endpoint: EndpointData): string[] { - const hasQuery = 'query' in endpoint - const hasVariables = 'pathVariables' in endpoint - const hasBody = 'body' in endpoint - - const sum: number = [hasQuery, hasVariables, hasBody] - .map((truthy) => (truthy ? 1 : 0) as number) - .reduce((acc: number, next) => acc + next) - - let fnArguments: string[] = [] - - if (sum === 1) { - if (hasQuery) - fnArguments.push( - ' query: ' + tsStringify(endpoint.query!, Object.keys(endpoint.query!).length > 2, 4), - ) - if (hasVariables) - fnArguments.push( - ' path: ' + - tsStringify(endpoint.pathVariables!, Object.keys(endpoint.pathVariables!).length > 2, 4), - ) - if (hasBody) - fnArguments.push(` body${endpoint.body!.required ? '' : '?'}: ` + endpoint.body!.type) - } else if (sum > 1) { - fnArguments.push( - ' { ' + - [hasQuery ? 'query' : null, hasVariables ? 'path' : null, hasBody ? 'body' : null] - .filter((x) => !!x) - .join(', ') + - ' }: {', - ) - - if (hasQuery) { - const typedef = - Object.keys(endpoint.query!).length > 1 - ? tsStringify(endpoint.query!, true, 6).split('\n') - : [tsStringify(endpoint.query!)] - fnArguments.push(' query: ' + typedef.shift() + (typedef.length > 0 ? '' : ', ')) - if (typedef.length > 0) - typedef.forEach((line, index) => - fnArguments.push(line + (index === typedef.length - 1 ? ', ' : '')), - ) - } - - if (hasVariables) { - const typedef = - Object.keys(endpoint.pathVariables!).length > 1 - ? tsStringify(endpoint.pathVariables!, true, 6).split('\n') - : [tsStringify(endpoint.pathVariables!)] - fnArguments.push( - ' path: ' + typedef.shift() + (typedef.length === 0 && hasBody ? ', ' : ''), - ) - if (typedef.length > 0) - typedef.forEach((line, index) => - fnArguments.push(line + (index === typedef.length - 1 && hasBody ? ', ' : '')), - ) - } - - if (hasBody) { - fnArguments.push(` body${endpoint.body!.required ? '' : '?'}: ` + endpoint.body!.type) - } - - fnArguments.push(' }') - } - - return [ - `export async function ${endpoint.meta.methodName}(`, - ' fetch: Fetch,', - ...fnArguments, - `): Promise<${endpoint?.response ? endpoint.response + ' | undefined' : 'void'}> {`, - ] -} - -function createEndpointMethod(endpoint: EndpointData) { - const hasQuery = 'query' in endpoint - const hasVariables = 'pathVariables' in endpoint - const hasBody = 'body' in endpoint - - const functionSignature = createFunctionSignature(endpoint) - const pathOptions = - hasQuery && hasVariables - ? `{ query, path }` - : hasQuery - ? `{ query }` - : hasVariables - ? `{ path }` - : null - - const declareURL = !!pathOptions - ? [ - `const url = resolveURL(`, - ` new URL('${endpoint.path}', BASE_URL),`, - ` ${pathOptions},`, - `)`, - ] - : [`const url = new URL('${endpoint.path}', BASE_URL).toString()`] - - return [ - ...functionSignature, - ...declareURL.map((text) => ` ${text}`), - ` const options: RequestInit = {`, - ` method: '${endpoint.method}',`, - ` headers: { 'Content-Type': 'application/json' },`, - endpoint.body && endpoint.body.required ? ` body: JSON.stringify(body),` : null, - ` }`, - ...(endpoint.body && !endpoint.body.required - ? ['', ` if(body) options.body = JSON.stringify(body)`] - : [null]), - '', - ` try {`, - ` const response = await fetch(url, options)`, - ` if (!response.ok)`, - ` throw new Error(\`Request failed with status: \${ response.status }\`)`, - endpoint.response ? ` return await response.json() as ${endpoint.response}` : ` return`, - ` } catch(error) {`, - ...(hasBody && endpoint.body!.required - ? [ - ` console.error(\`received error while fetching url("\${ url }") with data(\${ JSON.stringify(body) })\`, error)`, - ] - : hasBody - ? [ - ` if(body) console.error(\`received error while fetching url("\${ url }") with data(\${ JSON.stringify(body) })\`, error)`, - ` else console.error(\`received error while fetching url: \${ url }\`, error)`, - ] - : [` console.error(\`received error while fetching url: \${ url }\`, error)`]), - ` return undefined`, - ` }`, - `}`, - ] - .filter((x) => x !== null) - .join('\n') -} - -function bufferIndex() { - outputBuffer = `export * from './models' -export * from './endpoints' -` -} - -if (source) { - bufferModels() - await writeBufferToFile(outputModelsFile) - - bufferEndpoints() - await writeBufferToFile(outputEndpointsFile) - - bufferIndex() - await writeBufferToFile(outputIndexFile) - - console.log(`Finished Processing`) -} else { - console.error(`Error: Source file ${inputFilePath} not found.`) -} + .option('-r, --remote', 'Use the standard GCP hosted instance of the Bellbooks API') + .option('-u, --url ', 'Designate a custom URL for the instance of the Bellbooks API') + .option( + '-o, --output ', + 'Select a custom output location for the downloaded openapi.json file', + 'src/openapi/openapi.json', + ) + .option('-d, --dryrun', '[Test] Attempt download but do not save to disk', false) + .action(async (options: DownloadOptions) => { + const { downloadOpenAPISpec } = await import('./lib/download') + await downloadOpenAPISpec(options) + }) + +program + .command('convert') + .description( + 'Use `openapi-typescript` library to generate types from a downloaded openapi.json spec', + ) + .option( + '-i, --input ', + 'Designate the source openapi.json file', + 'src/openapi/openapi.json', + ) + .option( + '-o, --output ', + 'Designate the output location for the generated types', + 'src/openapi/openapi.ts', + ) + .option('-d, --dryrun', '[Test] Attempt conversion but do not save to disk', false) + .action(async (options: ConvertOptions) => { + const { convert } = await import('./lib/convert') + await convert(options) + }) + +program + .command('codegen') + .description('Generate frontend http fetch methods for interfacing with Bellbooks backend') + .option('-o, --output ', '', 'dist/api') + .option('-s, --schema ', '', 'src/openapi/openapi.json') + .option('-t, --types ', '', 'src/openapi/openapi.ts') + .option('-d, --dryrun', '[Test] Attempt codegen but do not save to disk', false) + .action(async (options: CodegenOptions) => { + const { codegen } = await import('./lib/codegen') + await codegen(options) + }) + +program.parse() diff --git a/scripts/openapi-codegen/src/lib/codegen.ts b/scripts/openapi-codegen/src/lib/codegen.ts new file mode 100644 index 0000000..d025475 --- /dev/null +++ b/scripts/openapi-codegen/src/lib/codegen.ts @@ -0,0 +1,505 @@ +import * as ts from 'typescript' +import { join } from 'path' +import type { OpenAPI3 } from 'openapi-typescript' + +import { saveFile, readSchemaFromDisk } from './util' + +export type CodegenOptions = Partial<{ + output: string + schema: string + types: string + dryrun: boolean +}> + +type EndpointData = { + meta: { + methodName: string + controller: string + } + + method: 'get' | 'post' | 'put' | 'delete' + path: string + + query?: { [index: string | number | symbol]: string } + pathVariables?: { [index: string | number | symbol]: string } + body?: { + required: boolean + type: string + } + response?: string +} + +function normalizeCodegenOptions(options: CodegenOptions): Required { + const dryrun = options.dryrun ?? false + const output = options.output ?? 'dist/api' + const schema = options.schema ?? 'src/openapi/openapi.json' + const types = options.types ?? 'src/openapi/openapi.ts' + + return { output, schema, types, dryrun } satisfies CodegenOptions +} + +export async function codegen(options: CodegenOptions) { + const { output, schema: schemaPath, types: typesPath, dryrun } = normalizeCodegenOptions(options) + + let schema: Awaited> + try { + schema = await readSchemaFromDisk(schemaPath) + } catch (error) { + return console.error(`❌ Failed to load schema file from "${schemaPath}"`, error) + } + + const interfaceNames = Object.keys(schema.components.schemas!) + + const tsProgram = ts.createProgram([typesPath], {}) + const tsSource = tsProgram.getSourceFile(typesPath) + + if (!tsSource) { + return console.error(`❌ Failed to load "${typesPath}" as a valid typescript target`) + } + + console.log(`Generating HTTP client methods...`) + + try { + await generateModels({ + sourceFile: tsSource, + interfaces: interfaceNames, + outputDirectory: output, + outputFile: 'models.ts', + dryrun, + }) + } catch (error) { + return console.error(` ❌ Failed to generate models from "${typesPath}"`, error) + } + + try { + await generateEndpoints({ + schema, + interfaces: interfaceNames, + outputDirectory: output, + outputFile: 'endpoints.ts', + dryrun, + }) + } catch (error) { + return console.error(` ❌ Failed to generate endpoint methods from "${schemaPath}"`, error) + } + + try { + await generateIndex({ + outputDirectory: output, + outputFile: 'index.ts', + includeFiles: ['./models', './endpoints'], + dryrun, + }) + } catch (error) { + return console.error( + ` ❌ Failed to generate project index file at "${join(output, 'index.ts')}"`, + error, + ) + } + + console.log(`✅ Completed all code generation and saved to "${output}"`) +} + +export async function generateModels({ + sourceFile, + interfaces, + outputDirectory, + outputFile, + dryrun, +}: { + sourceFile: ts.SourceFile + interfaces: string[] + outputDirectory: string + outputFile: string + dryrun: boolean +}) { + const buffer = codegenModels({ sourceFile, interfaces }) + const outputPath = join(outputDirectory, outputFile) + + if (dryrun) { + console.log(buffer) + console.log(` ✅ Successfully generated models (dryrun for "${outputPath}")`) + return + } + + await saveFile(outputPath, buffer) + console.log(` ✅ Successfully generated models and saved to ${outputPath}`) +} + +export async function generateEndpoints({ + schema, + interfaces, + outputDirectory, + outputFile, + dryrun, +}: { + schema: Required> & OpenAPI3 + interfaces: string[] + outputDirectory: string + outputFile: string + dryrun: boolean +}) { + const buffer = codegenEndpoints({ schema, interfaces, url: schema.servers.at(0)!.url }) + const outputPath = join(outputDirectory, outputFile) + + if (dryrun) { + console.log(buffer) + console.log(` ✅ Successfully generated endpoints (dryrun for "${outputPath}")`) + return + } + + await saveFile(outputPath, buffer) + console.log(` ✅ Successfully generated endpoints and saved to ${outputPath}`) +} + +export async function generateIndex({ + outputDirectory, + outputFile, + includeFiles, + dryrun, +}: { + outputDirectory: string + outputFile: string + includeFiles: string[] + dryrun: boolean +}) { + const buffer = includeFiles.map((filename) => `export * from '${filename}'`).join('\n') + '\n' + const outputPath = join(outputDirectory, outputFile) + + if (dryrun) { + console.log(buffer) + console.log(` ✅ Successfully generated index (dryrun for "${outputPath}")`) + return + } + + await saveFile(outputPath, buffer) + console.log(` ✅ Successfully generated index and saved to ${outputPath}`) +} + +function codegenModels({ + interfaces, + sourceFile, +}: { + interfaces: string[] + sourceFile: ts.SourceFile +}): string { + let buffer = 'export type WithRequired = T & { [P in K]-?: T[P] }\n' + buffer += `export type ForeignKey = Required> & Partial\n` + + const componentsInterface = sourceFile.statements.find( + (statement) => + ts.isInterfaceDeclaration(statement) && + statement.name && + statement.name.text === 'components', + ) as ts.InterfaceDeclaration | undefined + if (componentsInterface) { + const schemasProperty = componentsInterface.members.find( + (statement) => + ts.isPropertySignature(statement) && statement.name?.getText(sourceFile) === 'schemas', + ) as ts.PropertySignature | undefined + if (schemasProperty) { + schemasProperty.forEachChild((node) => { + if (ts.isTypeLiteralNode(node)) { + node.forEachChild((node) => { + const components = node.getText(sourceFile).split(':') + const name = components.shift() + let declaration = components + .join(':') + .trim() + .replace(/components\["schemas"\]\["(\w+)"\]/gi, '$1') + .replaceAll(/ {2,}/g, ' ') + .replaceAll(/^ \}/gm, '}') + declaration = declaration.replaceAll( + new RegExp(`([^<"])(${interfaces.join('|')})`, 'g'), + '$1ForeignKey<$2>', + ) + const type = `export type ${name} = ${declaration}`.replaceAll(';', '') + buffer += `\n${type}\n` + }) + } + }) + } + } + + return buffer +} + +function codegenEndpoints({ + schema, + interfaces, + url, +}: { + schema: OpenAPI3 + interfaces: string[] + url: string +}) { + let buffer = `import type {\n` + interfaces.forEach((name) => (buffer += ` ${name},\n`)) + buffer += `} from './models'\n\n` + + buffer += `type Fetch = (input: URL | RequestInfo, init?: RequestInit) => Promise\n\n` + + buffer += `export const BASE_URL = '${url}'\n\n` + + buffer += `function resolveURL(url: URL, { query, path }: { query?: {[index: string]: any}, path?: {[index: string]: any}}): string {\n` + buffer += ` let resolvedURL = url.toString()\n` + buffer += ` \n` + buffer += ` if (path) {\n` + buffer += ` for (const [key, value] of Object.entries(path)) {\n` + buffer += ` const variablePattern = new RegExp(\`{\s*\${key}\s*}\`, 'g')\n` + buffer += ` resolvedURL = resolvedURL.replace(variablePattern, value)\n` + buffer += ` }\n` + buffer += ` }\n` + buffer += `\n` + buffer += ` if (query) {\n` + buffer += ` const searchParams = new URLSearchParams(query)\n` + buffer += ` const queryString = searchParams.toString()\n` + buffer += `\n` + buffer += ` if (queryString) {\n` + buffer += ` resolvedURL += resolvedURL.includes('?') ? \`&\${queryString}\` : \`?\${queryString}\`\n` + buffer += ` }\n` + buffer += ` }\n` + buffer += `\n` + buffer += ` return resolvedURL\n` + buffer += `}\n\n` + + const endpoints = parseEndpoints(schema) + const endpointMethods = endpoints.map(codegenEndpointMethod) + endpointMethods.forEach((method) => (buffer += method + '\n\n')) + + return buffer +} + +function codegenEndpointMethodSignature(endpoint: EndpointData): string[] { + const hasQuery = 'query' in endpoint + const hasVariables = 'pathVariables' in endpoint + const hasBody = 'body' in endpoint + + const sum: number = [hasQuery, hasVariables, hasBody] + .map((truthy) => (truthy ? 1 : 0) as number) + .reduce((acc: number, next) => acc + next) + + let fnArguments: string[] = [] + + if (sum === 1) { + if (hasQuery) + fnArguments.push( + ' query: ' + tsStringify(endpoint.query!, Object.keys(endpoint.query!).length > 2, 4), + ) + if (hasVariables) + fnArguments.push( + ' path: ' + + tsStringify(endpoint.pathVariables!, Object.keys(endpoint.pathVariables!).length > 2, 4), + ) + if (hasBody) + fnArguments.push(` body${endpoint.body!.required ? '' : '?'}: ` + endpoint.body!.type) + } else if (sum > 1) { + fnArguments.push( + ' { ' + + [hasQuery ? 'query' : null, hasVariables ? 'path' : null, hasBody ? 'body' : null] + .filter((x) => !!x) + .join(', ') + + ' }: {', + ) + + if (hasQuery) { + const typedef = + Object.keys(endpoint.query!).length > 1 + ? tsStringify(endpoint.query!, true, 6).split('\n') + : [tsStringify(endpoint.query!)] + fnArguments.push(' query: ' + typedef.shift() + (typedef.length > 0 ? '' : ', ')) + if (typedef.length > 0) + typedef.forEach((line, index) => + fnArguments.push(line + (index === typedef.length - 1 ? ', ' : '')), + ) + } + + if (hasVariables) { + const typedef = + Object.keys(endpoint.pathVariables!).length > 1 + ? tsStringify(endpoint.pathVariables!, true, 6).split('\n') + : [tsStringify(endpoint.pathVariables!)] + fnArguments.push( + ' path: ' + typedef.shift() + (typedef.length === 0 && hasBody ? ', ' : ''), + ) + if (typedef.length > 0) + typedef.forEach((line, index) => + fnArguments.push(line + (index === typedef.length - 1 && hasBody ? ', ' : '')), + ) + } + + if (hasBody) { + fnArguments.push(` body${endpoint.body!.required ? '' : '?'}: ` + endpoint.body!.type) + } + + fnArguments.push(' }') + } + + return [ + `export async function ${endpoint.meta.methodName}(`, + ' fetch: Fetch,', + ...fnArguments, + `): Promise<${endpoint?.response ? endpoint.response + ' | undefined' : 'void'}> {`, + ] +} + +function codegenEndpointMethod(endpoint: EndpointData) { + const hasQuery = 'query' in endpoint + const hasVariables = 'pathVariables' in endpoint + const hasBody = 'body' in endpoint + + const functionSignature = codegenEndpointMethodSignature(endpoint) + const pathOptions = + hasQuery && hasVariables + ? `{ query, path }` + : hasQuery + ? `{ query }` + : hasVariables + ? `{ path }` + : null + + const declareURL = !!pathOptions + ? [ + `const url = resolveURL(`, + ` new URL('${endpoint.path}', BASE_URL),`, + ` ${pathOptions},`, + `)`, + ] + : [`const url = new URL('${endpoint.path}', BASE_URL).toString()`] + + return [ + ...functionSignature, + ...declareURL.map((text) => ` ${text}`), + ` const options: RequestInit = {`, + ` method: '${endpoint.method}',`, + ` headers: { 'Content-Type': 'application/json' },`, + endpoint.body && endpoint.body.required ? ` body: JSON.stringify(body),` : null, + ` }`, + ...(endpoint.body && !endpoint.body.required + ? ['', ` if(body) options.body = JSON.stringify(body)`] + : [null]), + '', + ` try {`, + ` const response = await fetch(url, options)`, + ` if (!response.ok)`, + ` throw new Error(\`Request failed with status: \${ response.status }\`)`, + endpoint.response ? ` return await response.json() as ${endpoint.response}` : ` return`, + ` } catch(error) {`, + ...(hasBody && endpoint.body!.required + ? [ + ` console.error(\`received error while fetching url("\${ url }") with data(\${ JSON.stringify(body) })\`, error)`, + ] + : hasBody + ? [ + ` if(body) console.error(\`received error while fetching url("\${ url }") with data(\${ JSON.stringify(body) })\`, error)`, + ` else console.error(\`received error while fetching url: \${ url }\`, error)`, + ] + : [` console.error(\`received error while fetching url: \${ url }\`, error)`]), + ` return undefined`, + ` }`, + `}`, + ] + .filter((x) => x !== null) + .join('\n') +} + +function parseEndpoints({ paths }: OpenAPI3): EndpointData[] { + const endpoints: EndpointData[] = [] + for (const path in paths) { + const allOperations = paths[path] + for (const httpMethod in allOperations) { + const details = allOperations[httpMethod] + const query = (details.parameters?.filter((parameter: any) => parameter.in === 'query') ?? + []) as any[] + const pathVariables = (details.parameters?.filter( + (parameter: any) => parameter.in === 'path', + ) ?? []) as any[] + const hasBody = !!details.requestBody + + const endpoint = { + meta: { + methodName: details.operationId, + controller: details.tags[0], + }, + method: httpMethod, + path, + } as any + + if (query.length > 0) { + endpoint.query = {} + query.forEach((parameter) => { + endpoint.query[`${parameter.name}${parameter.required ? '' : '?'}`] = parseSchema( + parameter.schema, + ) + }) + } + + if (pathVariables.length > 0) { + endpoint.pathVariables = {} + pathVariables.forEach((parameter) => { + endpoint.pathVariables[`${parameter.name}${parameter.required ? '' : '?'}`] = parseSchema( + parameter.schema, + ) + }) + } + + if (hasBody) { + endpoint.body = { + type: parseSchema(details.requestBody.content['application/json'].schema), + required: details.requestBody.required, + } + } + + if (details.responses['200'].content) { + endpoint.response = parseSchema(details.responses['200'].content['*/*'].schema) + } + + endpoints.push(endpoint) + } + } + return endpoints +} + +function parseSchema(value: any): string { + // { type: "type or array or undefined", $ref: "undefined or points to schema", items: "present when type=array, presumably has type or ref" } + if (value.oneOf) + return value.oneOf.map(({ $ref }: { $ref: string }) => parse$Ref($ref)).join(' | ') + if (value.enum && value.type === 'string') + return value.enum.map((value: string) => `'${value}'`).join(' | ') + if (value.items && value.type !== 'array') return parseSchema(value.items) + if (value.type === 'array') { + if (value.items.type) return safeConvertSchemaValue(value.items.type) + '[]' + return parseSchema(value.items).replaceAll(/(\w+)/g, '$1[]') + } + + return safeConvertSchemaValue(value.type) ?? parse$Ref(value.$ref) +} + +function parse$Ref(value: string): string { + return value?.replace('#/components/schemas/', '') +} + +function safeConvertSchemaValue(type: string): string { + if (type === 'integer') return 'number' + return type +} + +const tsStringify = ( + obj: { [index: string | number | symbol]: any }, + addWhitespace = false, + indentCount = 2, +): string => { + const transform = JSON.stringify( + obj, + null, + addWhitespace ? ''.padStart(indentCount, ' ') : undefined, + ) + .replaceAll('"', '') + .replaceAll(':', ': ') + .replaceAll(',', ', ') + .replace(/^}$/m, '}'.padStart(indentCount - 1, ' ')) + if (!addWhitespace) return transform.replace(/^{/m, '{ ').replace(/}$/m, ' }') + return transform +} diff --git a/scripts/openapi-codegen/src/lib/convert.ts b/scripts/openapi-codegen/src/lib/convert.ts new file mode 100644 index 0000000..656adbf --- /dev/null +++ b/scripts/openapi-codegen/src/lib/convert.ts @@ -0,0 +1,50 @@ +import openapiTS, { type OpenAPI3 } from 'openapi-typescript' +import { readSchemaFromDisk, saveFile } from './util' + +export type ConvertOptions = { + input?: string + output?: string + dryrun?: boolean +} + +function normalizeConvertOptions(options: ConvertOptions): Required { + const dryrun = options.dryrun ?? false + const input = options.input ?? 'src/openapi/openapi.json' + const output = options.output ?? 'src/openapi/openapi.ts' + + return { input, output, dryrun } satisfies ConvertOptions +} + +export async function convert(options: ConvertOptions) { + const { input, output, dryrun } = normalizeConvertOptions(options) + + let openAPISchema: OpenAPI3 + try { + openAPISchema = await readSchemaFromDisk(input) + } catch (error) { + console.error(`❌ Failed to import openAPI schema json from "${input}"`, error) + return + } + + let openAPITypescript: string + try { + openAPITypescript = await openapiTS(openAPISchema) + } catch (error) { + console.error(`❌ Failed to convert openAPI schema json from "${input}"`, error) + return + } + + if (dryrun) { + console.log(openAPITypescript) + console.log(`✅ Dry run successful`) + return + } + + try { + await saveFile(output, openAPITypescript) + } catch (error) { + console.error(`❌ Failed to save openAPI types to disk at "${output}"`, error) + } + + console.log(`✅ Successfully saved openAPI types to "${output}`) +} diff --git a/scripts/openapi-codegen/src/lib/download.ts b/scripts/openapi-codegen/src/lib/download.ts new file mode 100644 index 0000000..9b2cd63 --- /dev/null +++ b/scripts/openapi-codegen/src/lib/download.ts @@ -0,0 +1,68 @@ +import fetch from 'node-fetch' +import type { OpenAPI3 } from 'openapi-typescript' + +import { saveFile, validateOpenAPI3Schema } from './util' + +export type DownloadOptions = { + local?: boolean + remote?: boolean + url?: string + output?: string + dryrun?: boolean +} + +function normalizeDownloadOptions(options: DownloadOptions): Required { + const dryrun = options.dryrun ?? false + const local = + (options.url ? false : undefined) ?? + (options.remote ? false : undefined) ?? + options.local ?? + true + const remote = + (options.url ? false : undefined) ?? (local ? false : undefined) ?? options.remote ?? false + const output = options.output ?? 'src/openapi/openapi.json' + const url = + options.url ?? + (local ? 'http://localhost:8080/api/docs' : undefined) ?? + (remote ? 'https://bellbooks-backend.ue.r.appspot.com/api/docs' : undefined) ?? + 'http://localhost:8080/api/docs' + + return { local, remote, output, url, dryrun } satisfies DownloadOptions +} + +export async function downloadOpenAPISpec(options: DownloadOptions) { + const { url, output, dryrun } = normalizeDownloadOptions(options) + + let jsonContent: unknown + try { + const response = await fetch(url) + jsonContent = await response.json() + } catch (error) { + console.error(`❌ Failed to download openapi spec from "${url}"`, error) + return + } + + if (dryrun) { + console.log(`Received openapi spec:`, jsonContent) + console.log(`✅ Dry run successful`) + return + } + + try { + validateOpenAPI3Schema(jsonContent as unknown as OpenAPI3) + } catch (error) { + console.error(`❌ Response is not a valid OpenAPI3 schema`, error) + console.error(`Response from ${url}: `, JSON.stringify(jsonContent, null, 2)) + return + } + + try { + await saveFile(output, JSON.stringify(jsonContent, null, 2)) + } catch (error) { + console.log(`Received openapi spec:`, jsonContent) + console.error(`❌ Failed to save openapi spec to disk at ${output}`, error) + return + } + + console.log(`✅ Successfully saved openAPI schema from "${url}" to "${output}"`) +} diff --git a/scripts/openapi-codegen/src/lib/util.ts b/scripts/openapi-codegen/src/lib/util.ts new file mode 100644 index 0000000..6577407 --- /dev/null +++ b/scripts/openapi-codegen/src/lib/util.ts @@ -0,0 +1,44 @@ +import { mkdir, writeFile, readFile } from 'fs/promises' +import { join } from 'path' +import type { OpenAPI3 } from 'openapi-typescript' + +export async function createDirectory(path: string) { + const pathSegments = path.split('/') + if (pathSegments.at(-1)?.match(/\.\w+$/m)) pathSegments.pop() + + try { + await mkdir(join(...pathSegments), { recursive: true }) + } catch (e) { + console.log(e) + } +} + +export async function saveFile( + path: string, + contents: + | string + | NodeJS.ArrayBufferView + | Iterable + | AsyncIterable, +) { + await createDirectory(path) + const fileName = path.split('/').at(-1) + if (!fileName) throw Error(`Could not parse the filename for "${path}"`) + await writeFile(path, contents) +} + +export async function readSchemaFromDisk( + path: string, +): Promise> & OpenAPI3> { + const schema = JSON.parse((await readFile(path)).toString()) + validateOpenAPI3Schema(schema) + return schema +} + +export function validateOpenAPI3Schema(schema: OpenAPI3): void { + if (!schema.servers?.at(0)?.url) + throw Error('Parsed API docs do not have a valid server declaration') + if (!schema.components?.schemas) + throw Error('Parsed API docs do not have a valid schemas declaration') + if (!schema.paths) throw Error('Parsed API docs do not have a valid paths declaration') +} From d4cafae1500ee03e1144c0d0c0227252249bc2ab Mon Sep 17 00:00:00 2001 From: Coal Cooper Date: Wed, 17 Jan 2024 09:31:05 -0500 Subject: [PATCH 07/12] remove autogenerated files --- scripts/openapi-codegen/endpoints.ts | 791 --------------------------- scripts/openapi-codegen/index.ts | 2 - scripts/openapi-codegen/models.ts | 105 ---- 3 files changed, 898 deletions(-) delete mode 100644 scripts/openapi-codegen/endpoints.ts delete mode 100644 scripts/openapi-codegen/index.ts delete mode 100644 scripts/openapi-codegen/models.ts diff --git a/scripts/openapi-codegen/endpoints.ts b/scripts/openapi-codegen/endpoints.ts deleted file mode 100644 index d1e7486..0000000 --- a/scripts/openapi-codegen/endpoints.ts +++ /dev/null @@ -1,791 +0,0 @@ -import type { - Book, - Facility, - Note, - PackageContent, - Recipient, - Shipment, - SpecialRequest, - Zine, -} from './models' - -type Fetch = (input: URL | RequestInfo, init?: RequestInit) => Promise - -export const BASE_URL = 'http://localhost:8080' - -function resolveURL( - url: URL, - { query, path }: { query?: { [index: string]: any }; path?: { [index: string]: any } }, -): string { - let resolvedURL = url.toString() - - if (path) { - for (const [key, value] of Object.entries(path)) { - const variablePattern = new RegExp(`{s*${key}s*}`, 'g') - resolvedURL = resolvedURL.replace(variablePattern, value) - } - } - - if (query) { - const searchParams = new URLSearchParams(query) - const queryString = searchParams.toString() - - if (queryString) { - resolvedURL += resolvedURL.includes('?') ? `&${queryString}` : `?${queryString}` - } - } - - return resolvedURL -} - -export async function updateShipment(fetch: Fetch, body: Shipment): Promise { - const url = new URL('/updateShipment', BASE_URL).toString() - const options: RequestInit = { - method: 'put', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(body), - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as Shipment - } catch (error) { - console.error( - `received error while fetching url("${url}") with data(${JSON.stringify(body)})`, - error, - ) - return undefined - } -} - -export async function updateRecipient( - fetch: Fetch, - body: Recipient, -): Promise { - const url = new URL('/updateRecipient', BASE_URL).toString() - const options: RequestInit = { - method: 'put', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(body), - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as Recipient - } catch (error) { - console.error( - `received error while fetching url("${url}") with data(${JSON.stringify(body)})`, - error, - ) - return undefined - } -} - -export async function updateFacility(fetch: Fetch, body: Facility): Promise { - const url = new URL('/updateFacility', BASE_URL).toString() - const options: RequestInit = { - method: 'put', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(body), - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as Facility - } catch (error) { - console.error( - `received error while fetching url("${url}") with data(${JSON.stringify(body)})`, - error, - ) - return undefined - } -} - -export async function updateContent( - fetch: Fetch, - body: Book | Zine, -): Promise { - const url = new URL('/updateContent', BASE_URL).toString() - const options: RequestInit = { - method: 'put', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(body), - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as Book | Zine - } catch (error) { - console.error( - `received error while fetching url("${url}") with data(${JSON.stringify(body)})`, - error, - ) - return undefined - } -} - -export async function addSpecialRequest( - fetch: Fetch, - body: SpecialRequest, -): Promise { - const url = new URL('/addSpecialRequest', BASE_URL).toString() - const options: RequestInit = { - method: 'post', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(body), - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as SpecialRequest - } catch (error) { - console.error( - `received error while fetching url("${url}") with data(${JSON.stringify(body)})`, - error, - ) - return undefined - } -} - -export async function addShipment(fetch: Fetch, body: Shipment): Promise { - const url = new URL('/addShipment', BASE_URL).toString() - const options: RequestInit = { - method: 'post', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(body), - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as Shipment - } catch (error) { - console.error( - `received error while fetching url("${url}") with data(${JSON.stringify(body)})`, - error, - ) - return undefined - } -} - -export async function addRecipient(fetch: Fetch, body: Recipient): Promise { - const url = new URL('/addRecipient', BASE_URL).toString() - const options: RequestInit = { - method: 'post', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(body), - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as Recipient - } catch (error) { - console.error( - `received error while fetching url("${url}") with data(${JSON.stringify(body)})`, - error, - ) - return undefined - } -} - -export async function addNote(fetch: Fetch, body: Note): Promise { - const url = new URL('/addNote', BASE_URL).toString() - const options: RequestInit = { - method: 'post', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(body), - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as Note - } catch (error) { - console.error( - `received error while fetching url("${url}") with data(${JSON.stringify(body)})`, - error, - ) - return undefined - } -} - -export async function addFacility(fetch: Fetch, body: Facility): Promise { - const url = new URL('/addFacility', BASE_URL).toString() - const options: RequestInit = { - method: 'post', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(body), - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as Facility - } catch (error) { - console.error( - `received error while fetching url("${url}") with data(${JSON.stringify(body)})`, - error, - ) - return undefined - } -} - -export async function addContent( - fetch: Fetch, - body: Book | Zine, -): Promise { - const url = new URL('/addContent', BASE_URL).toString() - const options: RequestInit = { - method: 'post', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(body), - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as Book | Zine - } catch (error) { - console.error( - `received error while fetching url("${url}") with data(${JSON.stringify(body)})`, - error, - ) - return undefined - } -} - -export async function searchBooksByTitleAndAuthor( - fetch: Fetch, - query: { title: string; author?: string }, -): Promise { - const url = resolveURL(new URL('/searchBooks', BASE_URL), { query }) - const options: RequestInit = { - method: 'get', - headers: { 'Content-Type': 'application/json' }, - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as Book[] - } catch (error) { - console.error(`received error while fetching url: ${url}`, error) - return undefined - } -} - -export async function queryGoogleByTitleAndAuthor( - fetch: Fetch, - query: { title: string; author?: string }, -): Promise { - const url = resolveURL(new URL('/queryGoogle', BASE_URL), { query }) - const options: RequestInit = { - method: 'get', - headers: { 'Content-Type': 'application/json' }, - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as Book[] - } catch (error) { - console.error(`received error while fetching url: ${url}`, error) - return undefined - } -} - -export async function getZineByCode( - fetch: Fetch, - query: { code: string }, -): Promise { - const url = resolveURL(new URL('/getZineByCode', BASE_URL), { query }) - const options: RequestInit = { - method: 'get', - headers: { 'Content-Type': 'application/json' }, - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as Zine - } catch (error) { - console.error(`received error while fetching url: ${url}`, error) - return undefined - } -} - -export async function getShipmentsByDate( - fetch: Fetch, - query: { date: string }, -): Promise { - const url = resolveURL(new URL('/getShipmentsByDate', BASE_URL), { query }) - const options: RequestInit = { - method: 'get', - headers: { 'Content-Type': 'application/json' }, - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as Shipment[] - } catch (error) { - console.error(`received error while fetching url: ${url}`, error) - return undefined - } -} - -export async function getShipment( - fetch: Fetch, - query: { id: number }, -): Promise { - const url = resolveURL(new URL('/getShipment', BASE_URL), { query }) - const options: RequestInit = { - method: 'get', - headers: { 'Content-Type': 'application/json' }, - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as Shipment - } catch (error) { - console.error(`received error while fetching url: ${url}`, error) - return undefined - } -} - -export async function getShipmentCountBetweenDates( - fetch: Fetch, - query: { date1: string; date2: string }, -): Promise { - const url = resolveURL(new URL('/getShipmentCountBetweenDates', BASE_URL), { query }) - const options: RequestInit = { - method: 'get', - headers: { 'Content-Type': 'application/json' }, - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as number - } catch (error) { - console.error(`received error while fetching url: ${url}`, error) - return undefined - } -} - -export async function getRecipients( - fetch: Fetch, - query: { firstName: string; lastName: string }, -): Promise { - const url = resolveURL(new URL('/getRecipients', BASE_URL), { query }) - const options: RequestInit = { - method: 'get', - headers: { 'Content-Type': 'application/json' }, - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as Recipient[] - } catch (error) { - console.error(`received error while fetching url: ${url}`, error) - return undefined - } -} - -export async function getRecipient( - fetch: Fetch, - query: { id: number }, -): Promise { - const url = resolveURL(new URL('/getRecipient', BASE_URL), { query }) - const options: RequestInit = { - method: 'get', - headers: { 'Content-Type': 'application/json' }, - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as Recipient - } catch (error) { - console.error(`received error while fetching url: ${url}`, error) - return undefined - } -} - -export async function getRecipientLocation( - fetch: Fetch, - query: { id: string }, -): Promise { - const url = resolveURL(new URL('/getRecipientLocation', BASE_URL), { query }) - const options: RequestInit = { - method: 'get', - headers: { 'Content-Type': 'application/json' }, - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as string - } catch (error) { - console.error(`received error while fetching url: ${url}`, error) - return undefined - } -} - -export async function getRecipientByAssignedId( - fetch: Fetch, - query: { assignedId: string }, -): Promise { - const url = resolveURL(new URL('/getRecipientByAssignedId', BASE_URL), { query }) - const options: RequestInit = { - method: 'get', - headers: { 'Content-Type': 'application/json' }, - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as Recipient - } catch (error) { - console.error(`received error while fetching url: ${url}`, error) - return undefined - } -} - -export async function getBooksWithNoIsbn(fetch: Fetch): Promise { - const url = new URL('/getNoIsbnBooks', BASE_URL).toString() - const options: RequestInit = { - method: 'get', - headers: { 'Content-Type': 'application/json' }, - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as Book[] - } catch (error) { - console.error(`received error while fetching url: ${url}`, error) - return undefined - } -} - -export async function getFacilityById( - fetch: Fetch, - query: { id: number }, -): Promise { - const url = resolveURL(new URL('/getFacility', BASE_URL), { query }) - const options: RequestInit = { - method: 'get', - headers: { 'Content-Type': 'application/json' }, - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as Facility - } catch (error) { - console.error(`received error while fetching url: ${url}`, error) - return undefined - } -} - -export async function getFacilityByNameAndState( - fetch: Fetch, - query: { name: string; state: 'NC' | 'AL' | 'TN' | 'WV' | 'KY' | 'MD' | 'VA' | 'DE' }, -): Promise { - const url = resolveURL(new URL('/getFacilityByName', BASE_URL), { query }) - const options: RequestInit = { - method: 'get', - headers: { 'Content-Type': 'application/json' }, - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as Facility[] - } catch (error) { - console.error(`received error while fetching url: ${url}`, error) - return undefined - } -} - -export async function getContent( - fetch: Fetch, - query: { id: number }, -): Promise { - const url = resolveURL(new URL('/getContent', BASE_URL), { query }) - const options: RequestInit = { - method: 'get', - headers: { 'Content-Type': 'application/json' }, - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as Book | Zine - } catch (error) { - console.error(`received error while fetching url: ${url}`, error) - return undefined - } -} - -export async function getContentByTitle( - fetch: Fetch, - query: { title: string }, -): Promise { - const url = resolveURL(new URL('/getContentByTitle', BASE_URL), { query }) - const options: RequestInit = { - method: 'get', - headers: { 'Content-Type': 'application/json' }, - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as Book[] | Zine[] - } catch (error) { - console.error(`received error while fetching url: ${url}`, error) - return undefined - } -} - -export async function getBookByISBN( - fetch: Fetch, - query: { isbn: string }, -): Promise { - const url = resolveURL(new URL('/getBookByISBN', BASE_URL), { query }) - const options: RequestInit = { - method: 'get', - headers: { 'Content-Type': 'application/json' }, - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as Book - } catch (error) { - console.error(`received error while fetching url: ${url}`, error) - return undefined - } -} - -export async function getAllZines(fetch: Fetch): Promise { - const url = new URL('/getAllZines', BASE_URL).toString() - const options: RequestInit = { - method: 'get', - headers: { 'Content-Type': 'application/json' }, - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as Zine[] - } catch (error) { - console.error(`received error while fetching url: ${url}`, error) - return undefined - } -} - -export async function getAllSpecialRequests(fetch: Fetch): Promise { - const url = new URL('/getAllSpecialRequests', BASE_URL).toString() - const options: RequestInit = { - method: 'get', - headers: { 'Content-Type': 'application/json' }, - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as SpecialRequest[] - } catch (error) { - console.error(`received error while fetching url: ${url}`, error) - return undefined - } -} - -export async function getAllShipmentsByRecipient( - fetch: Fetch, - query: { id: number }, -): Promise { - const url = resolveURL(new URL('/getAllShipmentsByRecipient', BASE_URL), { query }) - const options: RequestInit = { - method: 'get', - headers: { 'Content-Type': 'application/json' }, - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as Shipment[] - } catch (error) { - console.error(`received error while fetching url: ${url}`, error) - return undefined - } -} - -export async function getAllRecipients(fetch: Fetch): Promise { - const url = new URL('/getAllRecipients', BASE_URL).toString() - const options: RequestInit = { - method: 'get', - headers: { 'Content-Type': 'application/json' }, - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as Recipient[] - } catch (error) { - console.error(`received error while fetching url: ${url}`, error) - return undefined - } -} - -export async function getAllFacilities(fetch: Fetch): Promise { - const url = new URL('/getAllFacilities', BASE_URL).toString() - const options: RequestInit = { - method: 'get', - headers: { 'Content-Type': 'application/json' }, - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as Facility[] - } catch (error) { - console.error(`received error while fetching url: ${url}`, error) - return undefined - } -} - -export async function getAllContent(fetch: Fetch): Promise { - const url = new URL('/getAllContent', BASE_URL).toString() - const options: RequestInit = { - method: 'get', - headers: { 'Content-Type': 'application/json' }, - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as Book[] | Zine[] - } catch (error) { - console.error(`received error while fetching url: ${url}`, error) - return undefined - } -} - -export async function getContentByTitleAndAuthor( - fetch: Fetch, - query: { title: string; author?: string }, -): Promise { - const url = resolveURL(new URL('/content', BASE_URL), { query }) - const options: RequestInit = { - method: 'get', - headers: { 'Content-Type': 'application/json' }, - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return (await response.json()) as Book[] | Zine[] - } catch (error) { - console.error(`received error while fetching url: ${url}`, error) - return undefined - } -} - -export async function deleteShipment(fetch: Fetch, query: { id: number }): Promise { - const url = resolveURL(new URL('/deleteShipment', BASE_URL), { query }) - const options: RequestInit = { - method: 'delete', - headers: { 'Content-Type': 'application/json' }, - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return - } catch (error) { - console.error(`received error while fetching url: ${url}`, error) - return undefined - } -} - -export async function deleteRecipient(fetch: Fetch, query: { id: number }): Promise { - const url = resolveURL(new URL('/deleteRecipient', BASE_URL), { query }) - const options: RequestInit = { - method: 'delete', - headers: { 'Content-Type': 'application/json' }, - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return - } catch (error) { - console.error(`received error while fetching url: ${url}`, error) - return undefined - } -} - -export async function deleteFacility(fetch: Fetch, query: { id: number }): Promise { - const url = resolveURL(new URL('/deleteFacility', BASE_URL), { query }) - const options: RequestInit = { - method: 'delete', - headers: { 'Content-Type': 'application/json' }, - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return - } catch (error) { - console.error(`received error while fetching url: ${url}`, error) - return undefined - } -} - -export async function deleteContent(fetch: Fetch, query: { id: number }): Promise { - const url = resolveURL(new URL('/deleteContent', BASE_URL), { query }) - const options: RequestInit = { - method: 'delete', - headers: { 'Content-Type': 'application/json' }, - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return - } catch (error) { - console.error(`received error while fetching url: ${url}`, error) - return undefined - } -} - -export async function deleteShipmentsByRecipient( - fetch: Fetch, - query: { id: number }, -): Promise { - const url = resolveURL(new URL('/deleteAllShipmentsByRecipientId', BASE_URL), { query }) - const options: RequestInit = { - method: 'delete', - headers: { 'Content-Type': 'application/json' }, - } - - try { - const response = await fetch(url, options) - if (!response.ok) throw new Error(`Request failed with status: ${response.status}`) - return - } catch (error) { - console.error(`received error while fetching url: ${url}`, error) - return undefined - } -} diff --git a/scripts/openapi-codegen/index.ts b/scripts/openapi-codegen/index.ts deleted file mode 100644 index 89d7090..0000000 --- a/scripts/openapi-codegen/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './models' -export * from './endpoints' diff --git a/scripts/openapi-codegen/models.ts b/scripts/openapi-codegen/models.ts deleted file mode 100644 index 2a1c771..0000000 --- a/scripts/openapi-codegen/models.ts +++ /dev/null @@ -1,105 +0,0 @@ -export type WithRequired = T & { [P in K]-?: T[P] } -export type ForeignKey< - Entity extends - | Book - | Facility - | Note - | PackageContent - | Recipient - | Shipment - | SpecialRequest - | Zine, -> = Required> & Partial - -export type Book = WithRequired< - { - type: 'Book' - } & Omit & { - authors?: string - isbn10?: string - isbn13?: string - }, - 'title' -> - -export type Facility = { - /** Format: int64 */ - id?: number - name: string - additionalInfo?: string - street: string - city: string - /** @enum {string} */ - state: 'NC' | 'AL' | 'TN' | 'WV' | 'KY' | 'MD' | 'VA' | 'DE' - zip: string -} - -export type Note = { - /** Format: int64 */ - id?: number - content?: string - /** Format: date */ - date?: string -} - -export type PackageContent = { - /** Format: int64 */ - id?: number - title: string - type: string -} - -export type Recipient = { - /** Format: int64 */ - id?: number - firstName: string - middleName?: string - lastName: string - assignedId?: string - facility?: ForeignKey - shipments?: ForeignKey[] - specialRequests?: ForeignKey[] -} - -export type Shipment = { - /** Format: int64 */ - id?: number - /** Format: date */ - date?: string - facility?: ForeignKey - recipient?: ForeignKey - notes?: ForeignKey[] - content?: (ForeignKey | ForeignKey)[] -} - -export type SpecialRequest = { - /** Format: int64 */ - id?: number - volunteerName?: string - request?: string - /** Format: date */ - specialRequestDate?: string - /** Format: date */ - letterMailedDate?: string - /** @enum {string} */ - category?: - | 'VOCATIONAL' - | 'EDUCATIONAL' - | 'CAREER_GROWTH' - | 'FOREIGN_LANGUAGE' - | 'LEGAL' - | 'SPIRITUAL_RELIGIOUS' - | 'OTHER' - /** @enum {string} */ - status?: 'OPEN' | 'COMPLETED' | 'CANCELLED' - recipient?: ForeignKey -} - -export type Zine = WithRequired< - { - type: 'Zine' - } & Omit & { - code?: string - }, - 'code' | 'title' -> From 88bf80214405671da15dffd2c6c34e6ce4ec787c Mon Sep 17 00:00:00 2001 From: Coal Cooper Date: Wed, 17 Jan 2024 09:35:10 -0500 Subject: [PATCH 08/12] minor cleanup, make naming conventions more consisten --- scripts/openapi-codegen/src/app.ts | 4 ++-- scripts/openapi-codegen/src/lib/convert.ts | 10 +++++----- scripts/openapi-codegen/src/lib/download.ts | 16 ++++++++-------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/scripts/openapi-codegen/src/app.ts b/scripts/openapi-codegen/src/app.ts index 701a860..1091b95 100644 --- a/scripts/openapi-codegen/src/app.ts +++ b/scripts/openapi-codegen/src/app.ts @@ -25,8 +25,8 @@ program ) .option('-d, --dryrun', '[Test] Attempt download but do not save to disk', false) .action(async (options: DownloadOptions) => { - const { downloadOpenAPISpec } = await import('./lib/download') - await downloadOpenAPISpec(options) + const { download } = await import('./lib/download') + await download(options) }) program diff --git a/scripts/openapi-codegen/src/lib/convert.ts b/scripts/openapi-codegen/src/lib/convert.ts index 656adbf..1b97314 100644 --- a/scripts/openapi-codegen/src/lib/convert.ts +++ b/scripts/openapi-codegen/src/lib/convert.ts @@ -1,11 +1,11 @@ import openapiTS, { type OpenAPI3 } from 'openapi-typescript' import { readSchemaFromDisk, saveFile } from './util' -export type ConvertOptions = { - input?: string - output?: string - dryrun?: boolean -} +export type ConvertOptions = Partial<{ + input: string + output: string + dryrun: boolean +}> function normalizeConvertOptions(options: ConvertOptions): Required { const dryrun = options.dryrun ?? false diff --git a/scripts/openapi-codegen/src/lib/download.ts b/scripts/openapi-codegen/src/lib/download.ts index 9b2cd63..acf8111 100644 --- a/scripts/openapi-codegen/src/lib/download.ts +++ b/scripts/openapi-codegen/src/lib/download.ts @@ -3,13 +3,13 @@ import type { OpenAPI3 } from 'openapi-typescript' import { saveFile, validateOpenAPI3Schema } from './util' -export type DownloadOptions = { - local?: boolean - remote?: boolean - url?: string - output?: string - dryrun?: boolean -} +export type DownloadOptions = Partial<{ + local: boolean + remote: boolean + url: string + output: string + dryrun: boolean +}> function normalizeDownloadOptions(options: DownloadOptions): Required { const dryrun = options.dryrun ?? false @@ -30,7 +30,7 @@ function normalizeDownloadOptions(options: DownloadOptions): Required Date: Wed, 17 Jan 2024 09:44:34 -0500 Subject: [PATCH 09/12] update package.json and bun lock --- bun.lockb | Bin 0 -> 123751 bytes package-lock.json | 256 ++++++++++++++++++++++++++++++++-------------- package.json | 6 +- 3 files changed, 183 insertions(+), 79 deletions(-) create mode 100755 bun.lockb diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..1dd22148b139d0215dc8f84b3a84fb14e06642ad GIT binary patch literal 123751 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 literal 0 HcmV?d00001 diff --git a/package-lock.json b/package-lock.json index f98b615..df7b281 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@types/lodash": "^4.14.195", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", + "bun": "^1.0.23", "eslint": "^8.39.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-svelte": "^2.26.0", @@ -439,6 +440,15 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fastify/busboy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.0.tgz", + "integrity": "sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==", + "dev": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.8", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", @@ -538,6 +548,84 @@ "node": ">= 8" } }, + "node_modules/@oven/bun-darwin-aarch64": { + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/@oven/bun-darwin-aarch64/-/bun-darwin-aarch64-1.0.23.tgz", + "integrity": "sha512-dkGEABH1Vkjgss9c81AeD5UVEjth+r9LgDZuIooJ+3SqGaHW2fv+9Ywrg5FB50hxb/E3UtxdOTCEsMAQsHNm6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oven/bun-darwin-x64": { + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64/-/bun-darwin-x64-1.0.23.tgz", + "integrity": "sha512-7ZlivtMlb8s07NW9yoUIzEIEY6HsXGAofudIR5THbwsLZGLj5ffg0aCqpfV4Oud3KGuF2lkHGlnFs9iGDouj6A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oven/bun-darwin-x64-baseline": { + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64-baseline/-/bun-darwin-x64-baseline-1.0.23.tgz", + "integrity": "sha512-Y0l3qxJcma1qxDhe4nCUc04CTAHEQQ28HXtNdzrrgKfX/TTBLBmTQxTqb0VeAosLoVF+sgjfUjVFFEFMrYnFgg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oven/bun-linux-aarch64": { + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64/-/bun-linux-aarch64-1.0.23.tgz", + "integrity": "sha512-07BWe6Myzksgzph3rlvGe05OE0IsjX06nIs5lU85GIqHj4/WvFw/efKPGWc+miQTGpTacc9hDBhov+dEnb7iSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-x64": { + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64/-/bun-linux-x64-1.0.23.tgz", + "integrity": "sha512-qywnmRQlQeo1Y1DNJKogtuSyUsw1CnRWOBWOrKoJC5a4L9V+/Ek9qgpccakY4lZb75i4Fb2zBK2RYiB8LSimng==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-x64-baseline": { + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-baseline/-/bun-linux-x64-baseline-1.0.23.tgz", + "integrity": "sha512-hTPxAe1NTFuCJvvwOpptr25BB4nsr/169rVhID2ZZBFbaZAVFQEV+QJsptI2xXq8pc2Gln0+hndli7/9ZYvo9w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@playwright/test": { "version": "1.33.0", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.33.0.tgz", @@ -576,25 +664,25 @@ } }, "node_modules/@sveltejs/kit": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.16.0.tgz", - "integrity": "sha512-uCCZuI5MVSAviQFYsts5VHSmvBUp2u/CPeWfwAYxbWZoPg8lsM24JGjLhCh0peQwQjIDXXVDVQI0jdvSGs91Lg==", + "version": "1.30.3", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.30.3.tgz", + "integrity": "sha512-0DzVXfU4h+tChFvoc8C61IqErCyskD4ydSIDjpKS2lYlEzIYrtYrY7juSqACFxqcvZAnOEXvSY+zZ8br0+ZMMg==", "dev": true, "hasInstallScript": true, "dependencies": { - "@sveltejs/vite-plugin-svelte": "^2.1.1", + "@sveltejs/vite-plugin-svelte": "^2.5.0", "@types/cookie": "^0.5.1", "cookie": "^0.5.0", - "devalue": "^4.3.0", + "devalue": "^4.3.1", "esm-env": "^1.0.0", "kleur": "^4.1.5", "magic-string": "^0.30.0", - "mime": "^3.0.0", + "mrmime": "^1.0.1", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", "sirv": "^2.0.2", "tiny-glob": "^0.2.9", - "undici": "~5.22.0" + "undici": "~5.26.2" }, "bin": { "svelte-kit": "svelte-kit.js" @@ -603,28 +691,46 @@ "node": "^16.14 || >=18" }, "peerDependencies": { - "svelte": "^3.54.0", + "svelte": "^3.54.0 || ^4.0.0-next.0 || ^5.0.0-next.0", "vite": "^4.0.0" } }, "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.1.1.tgz", - "integrity": "sha512-7YeBDt4us0FiIMNsVXxyaP4Hwyn2/v9x3oqStkHU3ZdIc5O22pGwUwH33wUqYo+7Itdmo8zxJ45Qvfm3H7UUjQ==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.5.3.tgz", + "integrity": "sha512-erhNtXxE5/6xGZz/M9eXsmI7Pxa6MS7jyTy06zN3Ck++ldrppOnOlJwHHTsMC7DHDQdgUp4NAc4cDNQ9eGdB/w==", "dev": true, "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^1.0.4", "debug": "^4.3.4", "deepmerge": "^4.3.1", "kleur": "^4.1.5", - "magic-string": "^0.30.0", - "svelte-hmr": "^0.15.1", + "magic-string": "^0.30.3", + "svelte-hmr": "^0.15.3", "vitefu": "^0.2.4" }, "engines": { "node": "^14.18.0 || >= 16" }, "peerDependencies": { - "svelte": "^3.54.0", + "svelte": "^3.54.0 || ^4.0.0 || ^5.0.0-next.0", + "vite": "^4.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-1.0.4.tgz", + "integrity": "sha512-zjiuZ3yydBtwpF3bj0kQNV0YXe+iKE545QGZVTaylW3eAzFr+pJ/cwK8lZEaRp4JtaJXhD5DyWAV4AxLh6DgaQ==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": "^14.18.0 || >= 16" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^2.2.0", + "svelte": "^3.54.0 || ^4.0.0", "vite": "^4.0.0" } }, @@ -1122,16 +1228,31 @@ "node": "*" } }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "node_modules/bun": { + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/bun/-/bun-1.0.23.tgz", + "integrity": "sha512-quznVjIV3yHMkfx4k1vS4z10hL3c6wGb11eKmtQJ1GrKj3tumXEGHTakb+YFTX8+SLw7IUNzlnpur1UJWtY2gw==", + "cpu": [ + "arm64", + "x64" + ], "dev": true, - "dependencies": { - "streamsearch": "^1.1.0" + "hasInstallScript": true, + "os": [ + "darwin", + "linux" + ], + "bin": { + "bun": "bin/bun", + "bunx": "bin/bun" }, - "engines": { - "node": ">=10.16.0" + "optionalDependencies": { + "@oven/bun-darwin-aarch64": "1.0.23", + "@oven/bun-darwin-x64": "1.0.23", + "@oven/bun-darwin-x64-baseline": "1.0.23", + "@oven/bun-linux-aarch64": "1.0.23", + "@oven/bun-linux-x64": "1.0.23", + "@oven/bun-linux-x64-baseline": "1.0.23" } }, "node_modules/cac": { @@ -1366,9 +1487,9 @@ } }, "node_modules/devalue": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-4.3.0.tgz", - "integrity": "sha512-n94yQo4LI3w7erwf84mhRUkUJfhLoCZiLyoOZ/QFsDbcWNZePrLwbQpvZBUG2TNxwV3VjCKPxkiiQA6pe3TrTA==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-4.3.2.tgz", + "integrity": "sha512-KqFl6pOgOW+Y6wJgu80rHpo2/3H07vr8ntR9rkkFIRETewbf5GaYYcakYfiKz89K+sLsuPkQIZaXDMjUObZwWg==", "dev": true }, "node_modules/dir-glob": { @@ -1825,9 +1946,9 @@ } }, "node_modules/get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true, "engines": { "node": "*" @@ -2203,12 +2324,12 @@ } }, "node_modules/magic-string": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz", - "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==", + "version": "0.30.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", + "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", "dev": true, "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" + "@jridgewell/sourcemap-codec": "^1.4.15" }, "engines": { "node": ">=12" @@ -2248,18 +2369,6 @@ "node": ">=8.6" } }, - "node_modules/mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -2339,9 +2448,9 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "dev": true, "funding": [ { @@ -2551,9 +2660,9 @@ } }, "node_modules/postcss": { - "version": "8.4.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.23.tgz", - "integrity": "sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==", + "version": "8.4.33", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", + "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", "dev": true, "funding": [ { @@ -2570,7 +2679,7 @@ } ], "dependencies": { - "nanoid": "^3.3.6", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -2866,9 +2975,9 @@ } }, "node_modules/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -2972,15 +3081,6 @@ "integrity": "sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg==", "dev": true }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "dev": true, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -3147,15 +3247,15 @@ } }, "node_modules/svelte-hmr": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.1.tgz", - "integrity": "sha512-BiKB4RZ8YSwRKCNVdNxK/GfY+r4Kjgp9jCLEy0DuqAKfmQtpL38cQK3afdpjw4sqSs4PLi3jIPJIFp259NkZtA==", + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.3.tgz", + "integrity": "sha512-41snaPswvSf8TJUhlkoJBekRrABDXDMdpNpT2tfHIv4JuhgvHqLMhEPGtaQn0BmbNSTkuz2Ed20DF2eHw0SmBQ==", "dev": true, "engines": { "node": "^12.20 || ^14.13.1 || >= 16" }, "peerDependencies": { - "svelte": ">=3.19.0" + "svelte": "^3.19.0 || ^4.0.0" } }, "node_modules/svelte-preprocess": { @@ -3394,12 +3494,12 @@ "dev": true }, "node_modules/undici": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.0.tgz", - "integrity": "sha512-fR9RXCc+6Dxav4P9VV/sp5w3eFiSdOjJYsbtWfd4s5L5C4ogyuVpdKIVHeW0vV1MloM65/f7W45nR9ZxwVdyiA==", + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.26.5.tgz", + "integrity": "sha512-cSb4bPFd5qgR7qr2jYAi0hlX9n5YKK2ONKkLFkxl+v/9BvC0sOpZjBHDBSXc5lWAf5ty9oZdRXytBIHzgUcerw==", "dev": true, "dependencies": { - "busboy": "^1.6.0" + "@fastify/busboy": "^2.0.0" }, "engines": { "node": ">=14.0" @@ -3486,12 +3586,12 @@ } }, "node_modules/vitefu": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.4.tgz", - "integrity": "sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz", + "integrity": "sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==", "dev": true, "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0" + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" }, "peerDependenciesMeta": { "vite": { @@ -3618,9 +3718,9 @@ } }, "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "engines": { "node": ">=0.10.0" diff --git a/package.json b/package.json index 6abaae1..bc6d5f3 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.0.1", "type": "module", "scripts": { + "start": "vite dev --host", "dev": "vite dev", "build": "vite build", "preview": "vite preview", @@ -12,7 +13,9 @@ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "lint": "eslint .", "format": "prettier --plugin-search-dir . --write .", - "format:githook": "prettier --plugin-search-dir . --write" + "format:githook": "prettier --plugin-search-dir . --write", + "openapi-codegen:local": "npm run local --prefix scripts/openapi-codegen/", + "openapi-codegen:remote": "npm run remote --prefix scripts/openapi-codegen/" }, "devDependencies": { "@playwright/test": "^1.19.1", @@ -22,6 +25,7 @@ "@types/lodash": "^4.14.195", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", + "bun": "^1.0.23", "eslint": "^8.39.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-svelte": "^2.26.0", From a4165a76ce81f729fe83989640fd98499ce1893b Mon Sep 17 00:00:00 2001 From: Coal Cooper Date: Wed, 17 Jan 2024 15:17:39 -0500 Subject: [PATCH 10/12] add download-and-generate command --- scripts/openapi-codegen/package-lock.json | 1503 ++++++++++++++++- scripts/openapi-codegen/package.json | 20 +- scripts/openapi-codegen/src/app.ts | 119 +- scripts/openapi-codegen/src/lib/codegen.ts | 35 +- scripts/openapi-codegen/src/lib/convert.ts | 2 +- scripts/openapi-codegen/src/lib/download.ts | 37 +- .../openapi-codegen/src/openapi/openapi.json | 1368 --------------- .../openapi-codegen/src/openapi/openapi.ts | 764 --------- scripts/openapi-codegen/tsconfig.json | 115 ++ 9 files changed, 1763 insertions(+), 2200 deletions(-) delete mode 100644 scripts/openapi-codegen/src/openapi/openapi.json delete mode 100644 scripts/openapi-codegen/src/openapi/openapi.ts create mode 100644 scripts/openapi-codegen/tsconfig.json diff --git a/scripts/openapi-codegen/package-lock.json b/scripts/openapi-codegen/package-lock.json index 4d07e66..d27978b 100644 --- a/scripts/openapi-codegen/package-lock.json +++ b/scripts/openapi-codegen/package-lock.json @@ -14,7 +14,68 @@ "devDependencies": { "@types/node-fetch": "^2.6.11", "bun": "^1.0.23", - "bun-types": "latest" + "bun-types": "latest", + "pkg": "^5.8.1", + "pkg-fetch": "^3.5.2", + "tsc-alias": "^1.8.8" + } + }, + "node_modules/@babel/generator": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.2.tgz", + "integrity": "sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.2", + "@jridgewell/gen-mapping": "^0.3.0", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.4.tgz", + "integrity": "sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz", + "integrity": "sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.18.10", + "@babel/helper-validator-identifier": "^7.18.6", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@fastify/busboy": { @@ -24,6 +85,54 @@ "node": ">=14" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.21", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.21.tgz", + "integrity": "sha512-SRfKmRe1KvYnxjEMtxEr+J4HIeMX5YBg/qhRHpxEIGjhX1rshcHlnFUE9K0GazhVKWM7B+nARSkV8LuvJdJ5/g==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "license": "MIT", @@ -174,6 +283,18 @@ "dev": true, "license": "MIT" }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ansi-colors": { "version": "4.1.3", "license": "MIT", @@ -181,15 +302,124 @@ "node": ">=6" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/argparse": { "version": "2.0.1", "license": "Python-2.0" }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/asynckit": { "version": "0.4.0", "dev": true, "license": "MIT" }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/braces": { "version": "3.0.2", "license": "MIT", @@ -200,6 +430,30 @@ "node": ">=8" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/bun": { "version": "1.0.23", "resolved": "https://registry.npmjs.org/bun/-/bun-1.0.23.tgz", @@ -238,6 +492,96 @@ "undici-types": "^5.26.4" } }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "node_modules/combined-stream": { "version": "1.0.8", "dev": true, @@ -256,6 +600,12 @@ "node": ">=16" } }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "license": "MIT", @@ -263,6 +613,47 @@ "node": ">= 12" } }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "dev": true, @@ -271,6 +662,60 @@ "node": ">=0.4.0" } }, + "node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/fast-glob": { "version": "3.3.2", "license": "MIT", @@ -346,42 +791,276 @@ "node": ">=12.20.0" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "license": "ISC", + "node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "dev": true, "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true }, - "node_modules/is-glob": { - "version": "4.0.3", - "license": "MIT", + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, "dependencies": { - "is-extglob": "^2.1.1" + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/is-number": { - "version": "7.0.0", - "license": "MIT", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=0.12.0" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/js-yaml": { - "version": "4.1.0", + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", + "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/into-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-6.0.0.tgz", + "integrity": "sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==", + "dev": true, + "dependencies": { + "from2": "^2.3.0", + "p-is-promise": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", + "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -390,6 +1069,42 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/merge2": { "version": "1.4.1", "license": "MIT", @@ -427,6 +1142,108 @@ "node": ">= 0.6" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/multistream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/multistream/-/multistream-4.1.0.tgz", + "integrity": "sha512-J1XDiAmmNpRCBfIWJv+n0ymC4ABcf/Pl+5YvC5B/D2f/2+8PtHvCNxMPKiQcZyi922Hq69J2YOpb1pTywfifyw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "once": "^1.4.0", + "readable-stream": "^3.6.0" + } + }, + "node_modules/multistream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/mylas": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/mylas/-/mylas-2.1.13.tgz", + "integrity": "sha512-+MrqnJRtxdF+xngFfUUkIMQrUUL0KsxbADUkn23Z/4ibGg192Q+z+CQyiYwvWTsYjJygmMR8+w3ZDa98Zh6ESg==", + "dev": true, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/raouldeheer" + } + }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "dev": true + }, + "node_modules/node-abi": { + "version": "3.54.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.54.0.tgz", + "integrity": "sha512-p7eGEiQil0YUV3ItH4/tBb781L5impVmmx2E9FRKF7d18XXzp4PGT2tdYMFY6wQqgxD0IwNZOiSJ0/K0fSi/OA==", + "dev": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/node-domexception": { "version": "1.0.0", "funding": [ @@ -460,6 +1277,24 @@ "url": "https://opencollective.com/node-fetch" } }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, "node_modules/openapi-typescript": { "version": "6.7.3", "license": "MIT", @@ -475,6 +1310,30 @@ "openapi-typescript": "bin/cli.js" } }, + "node_modules/p-is-promise": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", + "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/picomatch": { "version": "2.3.1", "license": "MIT", @@ -485,6 +1344,189 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkg": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/pkg/-/pkg-5.8.1.tgz", + "integrity": "sha512-CjBWtFStCfIiT4Bde9QpJy0KeH19jCfwZRJqHFDFXfhUklCx8JoFmMj3wgnEYIwGmZVNkhsStPHEOnrtrQhEXA==", + "dev": true, + "dependencies": { + "@babel/generator": "7.18.2", + "@babel/parser": "7.18.4", + "@babel/types": "7.19.0", + "chalk": "^4.1.2", + "fs-extra": "^9.1.0", + "globby": "^11.1.0", + "into-stream": "^6.0.0", + "is-core-module": "2.9.0", + "minimist": "^1.2.6", + "multistream": "^4.1.0", + "pkg-fetch": "3.4.2", + "prebuild-install": "7.1.1", + "resolve": "^1.22.0", + "stream-meter": "^1.0.4" + }, + "bin": { + "pkg": "lib-es5/bin.js" + }, + "peerDependencies": { + "node-notifier": ">=9.0.1" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/pkg-fetch": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-3.5.2.tgz", + "integrity": "sha512-KlRF3cDS4J5PRTKh5dkF5s+CYKa+4eUXzymWqTKPU/p3WmYlWZu7AS0dH8moPxg+QcJNB4/wu1wVO2a0Asv2Dw==", + "dev": true, + "dependencies": { + "chalk": "^4.1.2", + "fs-extra": "^9.1.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.6", + "progress": "^2.0.3", + "semver": "^7.3.5", + "tar-fs": "^2.1.1", + "yargs": "^16.2.0" + }, + "bin": { + "pkg-fetch": "lib-es5/bin.js" + } + }, + "node_modules/pkg-fetch/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/pkg/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/pkg/node_modules/pkg-fetch": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-3.4.2.tgz", + "integrity": "sha512-0+uijmzYcnhC0hStDjm/cl2VYdrmVVBpe7Q8k9YBojxmR5tG8mvR9/nooQq3QSXiQqORDVOTY3XqMEqJVIzkHA==", + "dev": true, + "dependencies": { + "chalk": "^4.1.2", + "fs-extra": "^9.1.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.6", + "progress": "^2.0.3", + "semver": "^7.3.5", + "tar-fs": "^2.1.1", + "yargs": "^16.2.0" + }, + "bin": { + "pkg-fetch": "lib-es5/bin.js" + } + }, + "node_modules/plimit-lit": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.6.1.tgz", + "integrity": "sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==", + "dev": true, + "dependencies": { + "queue-lit": "^1.5.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", + "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", + "dev": true, + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/queue-lit": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.2.tgz", + "integrity": "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "funding": [ @@ -503,6 +1545,86 @@ ], "license": "MIT" }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve/node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/reusify": { "version": "1.0.4", "license": "MIT", @@ -532,6 +1654,134 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stream-meter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/stream-meter/-/stream-meter-1.0.4.tgz", + "integrity": "sha512-4sOEtrbgFotXwnEuzzsQBYEV1elAeFSO8rSGeTwabuX1RRn/kEq9JVH7I0MRBhKVRR0sJkr0M0QCH7yOLf9fhQ==", + "dev": true, + "dependencies": { + "readable-stream": "^2.1.4" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/supports-color": { "version": "9.4.0", "license": "MIT", @@ -542,6 +1792,69 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "license": "MIT", @@ -552,6 +1865,50 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "node_modules/tsc-alias": { + "version": "1.8.8", + "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.8.tgz", + "integrity": "sha512-OYUOd2wl0H858NvABWr/BoSKNERw3N9GTi3rHPK8Iv4O1UyUXIrTTOAZNHsjlVpXFOhpJBVARI1s+rzwLivN3Q==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.3", + "commander": "^9.0.0", + "globby": "^11.0.4", + "mylas": "^2.1.9", + "normalize-path": "^3.0.0", + "plimit-lit": "^1.2.6" + }, + "bin": { + "tsc-alias": "dist/bin/index.js" + } + }, + "node_modules/tsc-alias/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/typescript": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", @@ -579,6 +1936,21 @@ "dev": true, "license": "MIT" }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, "node_modules/web-streams-polyfill": { "version": "3.3.2", "license": "MIT", @@ -586,12 +1958,93 @@ "node": ">= 8" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/yargs-parser": { "version": "21.1.1", "license": "ISC", "engines": { "node": ">=12" } + }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } } } } diff --git a/scripts/openapi-codegen/package.json b/scripts/openapi-codegen/package.json index 96c5d08..91cd2c1 100644 --- a/scripts/openapi-codegen/package.json +++ b/scripts/openapi-codegen/package.json @@ -1,17 +1,25 @@ { "name": "openapi-codegen", - "module": "src/app.ts", "type": "module", + "module": "src/app.ts", + "main": "build/app.js", + "bin": { + "openapi-codegen": "./build/app.js" + }, "scripts": { - "local": "npx bun src/app.ts download && npx bun src/app.ts convert && npx bun src/app.ts codegen --output ../../src/lib/api", - "local:test": "npx bun src/app.ts download && npx bun src/app.ts convert && npx bun src/app.ts codegen", - "remote": "npx bun src/app.ts download --remote && npx bun src/app.ts convert && npx bun src/app.ts codegen --output ../../src/lib/api", - "remote:test": "npx bun src/app.ts download --remote && npx bun src/app.ts convert && npx bun src/app.ts codegen" + "build": "npx tsc && npx tsc-alias", + "local": "npx bun src/app.ts download-and-generate --output ../../src/lib/api", + "local:test": "npx bun src/app.ts download-and-generate --dryrun", + "remote": "npx bun src/app.ts download-and-generate --url https://bellbooks-backend.ue.r.appspot.com --output ../../src/lib/api", + "remote:test": "npx bun src/app.ts download-and-generate --url https://bellbooks-backend.ue.r.appspot.com --dryrun" }, "devDependencies": { "@types/node-fetch": "^2.6.11", "bun": "^1.0.23", - "bun-types": "latest" + "bun-types": "latest", + "pkg": "^5.8.1", + "pkg-fetch": "^3.5.2", + "tsc-alias": "^1.8.8" }, "dependencies": { "commander": "^11.1.0", diff --git a/scripts/openapi-codegen/src/app.ts b/scripts/openapi-codegen/src/app.ts index 1091b95..524dc72 100644 --- a/scripts/openapi-codegen/src/app.ts +++ b/scripts/openapi-codegen/src/app.ts @@ -1,8 +1,13 @@ +#! /usr/bin/env node + import { program } from 'commander' +import openapiTS, { type OpenAPI3 } from 'openapi-typescript' +import { join } from 'path' -import type { DownloadOptions } from './lib/download' -import type { ConvertOptions } from './lib/convert' -import type { CodegenOptions } from './lib/codegen' +import { download, downloadAndSave, type DownloadOptions } from './lib/download' +import { convertAndSave, type ConvertOptions } from './lib/convert' +import { codegen, codegenAndSave, type CodegenOptions } from './lib/codegen' +import { saveFile } from './lib/util' program .name('bellbooks-openapi-codegen') @@ -23,10 +28,9 @@ program 'Select a custom output location for the downloaded openapi.json file', 'src/openapi/openapi.json', ) - .option('-d, --dryrun', '[Test] Attempt download but do not save to disk', false) + .option('-d, --dryrun', '[Test] Log download to standard output without saving to disk', false) .action(async (options: DownloadOptions) => { - const { download } = await import('./lib/download') - await download(options) + await downloadAndSave(options) }) program @@ -44,22 +48,107 @@ program 'Designate the output location for the generated types', 'src/openapi/openapi.ts', ) - .option('-d, --dryrun', '[Test] Attempt conversion but do not save to disk', false) + .option('-d, --dryrun', '[Test] Log conversion to standard output without saving to disk', false) .action(async (options: ConvertOptions) => { - const { convert } = await import('./lib/convert') - await convert(options) + await convertAndSave(options) }) program .command('codegen') .description('Generate frontend http fetch methods for interfacing with Bellbooks backend') - .option('-o, --output ', '', 'dist/api') - .option('-s, --schema ', '', 'src/openapi/openapi.json') - .option('-t, --types ', '', 'src/openapi/openapi.ts') - .option('-d, --dryrun', '[Test] Attempt codegen but do not save to disk', false) + .option('-o, --output ', 'Destination path to save the generated code', 'dist/api') + .option( + '-s, --schema ', + 'Location on disk where the openAPI schema (.json) file is saved', + 'src/openapi/openapi.json', + ) + .option( + '-t, --types ', + 'Location on disk where the openAPI typescript types (.ts) file is saved', + 'src/openapi/openapi.ts', + ) + .option('-d, --dryrun', '[Test] Log codegen to standard output without saving to disk', false) .action(async (options: CodegenOptions) => { - const { codegen } = await import('./lib/codegen') - await codegen(options) + await codegenAndSave(options) + }) + +type DownloadAndGenerateOptions = Partial<{ + url: string + output: string + dryrun: boolean +}> + +program + .command('download-and-generate') + .description( + 'Run through all of the commands: Download, Convert, and Codegen, and output the generated code to a specified directory', + ) + .option( + '-u, --url ', + 'URL pointing to a live instance of the Bellbooks API', + 'http://localhost:8080/api/docs', + ) + .option( + '-o, --output ', + 'Designate the output directory for the generated code', + 'dist/api', + ) + .option('-d, --dryrun', '[Test] Log to standard output without saving to disk', false) + .action(async ({ url, output, dryrun }: DownloadAndGenerateOptions) => { + if (!output) output = 'dist/api' + + console.log(`Generating client API methods for OpenAPI specification at "${url}"...`) + + let schema: OpenAPI3 | undefined + try { + schema = await download({ url }) + if (!schema) throw Error() + console.log(` ✅ Successfully downloaded OpenAPI Schema from "${url}"`) + } catch (_) { + // error is logged in download function + return + } + + let openAPITypescript: string + try { + openAPITypescript = await openapiTS(schema) + console.log(` ✅ Successfully converted schema to typescript definitions`) + } catch (error) { + console.error(`Using schema: ${JSON.stringify(schema, null, 2)}`) + console.error(` ❌ Failed to convert openAPI schema json from downloaded schema`, error) + return + } + + let code: { models: string; endpoints: string; index: string } + try { + code = codegen(schema, openAPITypescript) + console.log(` ✅ Successfully generated model definitions and API methods`) + } catch (_) { + // Error is logged in codegen function + return + } + + if (dryrun) { + console.log('// models.ts\n') + console.log(code.models, '\n') + + console.log('// endpoints.ts\n') + console.log(code.endpoints, '\n') + + console.log('\n\n// index.ts\n') + console.log(code.index, '\n') + + console.log(`✅ Successfully generated output for OpenAPI Schema from "${url}"`) + return + } + + saveFile(join(output, 'models.ts'), code.models) + saveFile(join(output, 'endpoints.ts'), code.endpoints) + saveFile(join(output, 'index.ts'), code.index) + + console.log( + `✅ Successfully generated output for OpenAPI Schema from "${url}" and saved to "${output}"`, + ) }) program.parse() diff --git a/scripts/openapi-codegen/src/lib/codegen.ts b/scripts/openapi-codegen/src/lib/codegen.ts index d025475..0d44e76 100644 --- a/scripts/openapi-codegen/src/lib/codegen.ts +++ b/scripts/openapi-codegen/src/lib/codegen.ts @@ -38,7 +38,22 @@ function normalizeCodegenOptions(options: CodegenOptions): Required `export * from './${filename}'`).join('\n') + '\n' + + return { models, endpoints, index } +} + +export async function codegenAndSave(options: CodegenOptions) { const { output, schema: schemaPath, types: typesPath, dryrun } = normalizeCodegenOptions(options) let schema: Awaited> @@ -355,10 +370,10 @@ function codegenEndpointMethod(endpoint: EndpointData) { hasQuery && hasVariables ? `{ query, path }` : hasQuery - ? `{ query }` - : hasVariables - ? `{ path }` - : null + ? `{ query }` + : hasVariables + ? `{ path }` + : null const declareURL = !!pathOptions ? [ @@ -392,11 +407,11 @@ function codegenEndpointMethod(endpoint: EndpointData) { ` console.error(\`received error while fetching url("\${ url }") with data(\${ JSON.stringify(body) })\`, error)`, ] : hasBody - ? [ - ` if(body) console.error(\`received error while fetching url("\${ url }") with data(\${ JSON.stringify(body) })\`, error)`, - ` else console.error(\`received error while fetching url: \${ url }\`, error)`, - ] - : [` console.error(\`received error while fetching url: \${ url }\`, error)`]), + ? [ + ` if(body) console.error(\`received error while fetching url("\${ url }") with data(\${ JSON.stringify(body) })\`, error)`, + ` else console.error(\`received error while fetching url: \${ url }\`, error)`, + ] + : [` console.error(\`received error while fetching url: \${ url }\`, error)`]), ` return undefined`, ` }`, `}`, diff --git a/scripts/openapi-codegen/src/lib/convert.ts b/scripts/openapi-codegen/src/lib/convert.ts index 1b97314..cd00d0b 100644 --- a/scripts/openapi-codegen/src/lib/convert.ts +++ b/scripts/openapi-codegen/src/lib/convert.ts @@ -15,7 +15,7 @@ function normalizeConvertOptions(options: ConvertOptions): Required { + const { url } = normalizeDownloadOptions(options) let jsonContent: unknown try { @@ -39,13 +39,7 @@ export async function download(options: DownloadOptions) { jsonContent = await response.json() } catch (error) { console.error(`❌ Failed to download openapi spec from "${url}"`, error) - return - } - - if (dryrun) { - console.log(`Received openapi spec:`, jsonContent) - console.log(`✅ Dry run successful`) - return + return undefined } try { @@ -53,13 +47,34 @@ export async function download(options: DownloadOptions) { } catch (error) { console.error(`❌ Response is not a valid OpenAPI3 schema`, error) console.error(`Response from ${url}: `, JSON.stringify(jsonContent, null, 2)) + return undefined + } + + return jsonContent as unknown as OpenAPI3 +} + +export async function downloadAndSave(options: DownloadOptions) { + const { url, output, dryrun } = normalizeDownloadOptions(options) + + let schema: OpenAPI3 | undefined + try { + schema = await download(options) + if (!schema) throw Error(`Response is not a valid OpenAPI3 schema`) + } catch (error) { + console.error(`❌ Failed to download OpenAPI schema from ${url}`, error) + return + } + + if (dryrun) { + console.log(`Received openapi spec:`, schema) + console.log(`✅ Dry run successful`) return } try { - await saveFile(output, JSON.stringify(jsonContent, null, 2)) + await saveFile(output, JSON.stringify(schema, null, 2)) } catch (error) { - console.log(`Received openapi spec:`, jsonContent) + console.log(`Received openapi spec:`, schema) console.error(`❌ Failed to save openapi spec to disk at ${output}`, error) return } diff --git a/scripts/openapi-codegen/src/openapi/openapi.json b/scripts/openapi-codegen/src/openapi/openapi.json deleted file mode 100644 index 25426f8..0000000 --- a/scripts/openapi-codegen/src/openapi/openapi.json +++ /dev/null @@ -1,1368 +0,0 @@ -{ - "openapi": "3.0.1", - "info": { - "title": "OpenAPI definition", - "version": "v0" - }, - "servers": [ - { - "url": "http://localhost:8080", - "description": "Generated server url" - } - ], - "paths": { - "/updateShipment": { - "put": { - "tags": ["shipment-controller"], - "operationId": "updateShipment", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Shipment" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/Shipment" - } - } - } - } - } - } - }, - "/updateRecipient": { - "put": { - "tags": ["recipient-controller"], - "operationId": "updateRecipient", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Recipient" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/Recipient" - } - } - } - } - } - } - }, - "/updateFacility": { - "put": { - "tags": ["facility-controller"], - "operationId": "updateFacility", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Facility" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/Facility" - } - } - } - } - } - } - }, - "/updateContent": { - "put": { - "tags": ["package-content-controller"], - "operationId": "updateContent", - "requestBody": { - "content": { - "application/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/Book" - }, - { - "$ref": "#/components/schemas/Zine" - } - ] - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/Book" - }, - { - "$ref": "#/components/schemas/Zine" - } - ] - } - } - } - } - } - } - }, - "/addSpecialRequest": { - "post": { - "tags": ["special-request-controller"], - "operationId": "addSpecialRequest", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SpecialRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/SpecialRequest" - } - } - } - } - } - } - }, - "/addShipment": { - "post": { - "tags": ["shipment-controller"], - "operationId": "addShipment", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Shipment" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/Shipment" - } - } - } - } - } - } - }, - "/addRecipient": { - "post": { - "tags": ["recipient-controller"], - "operationId": "addRecipient", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Recipient" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/Recipient" - } - } - } - } - } - } - }, - "/addNote": { - "post": { - "tags": ["note-controller"], - "operationId": "addNote", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Note" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/Note" - } - } - } - } - } - } - }, - "/addFacility": { - "post": { - "tags": ["facility-controller"], - "operationId": "addFacility", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Facility" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/Facility" - } - } - } - } - } - } - }, - "/addContent": { - "post": { - "tags": ["package-content-controller"], - "operationId": "addContent", - "requestBody": { - "content": { - "application/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/Book" - }, - { - "$ref": "#/components/schemas/Zine" - } - ] - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/Book" - }, - { - "$ref": "#/components/schemas/Zine" - } - ] - } - } - } - } - } - } - }, - "/searchBooks": { - "get": { - "tags": ["package-content-controller"], - "operationId": "searchBooksByTitleAndAuthor", - "parameters": [ - { - "name": "title", - "in": "query", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "author", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Book" - } - } - } - } - } - } - } - }, - "/queryGoogle": { - "get": { - "tags": ["package-content-controller"], - "operationId": "queryGoogleByTitleAndAuthor", - "parameters": [ - { - "name": "title", - "in": "query", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "author", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Book" - } - } - } - } - } - } - } - }, - "/getZineByCode": { - "get": { - "tags": ["package-content-controller"], - "operationId": "getZineByCode", - "parameters": [ - { - "name": "code", - "in": "query", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/Zine" - } - } - } - } - } - } - }, - "/getShipmentsByDate": { - "get": { - "tags": ["shipment-controller"], - "operationId": "getShipmentsByDate", - "parameters": [ - { - "name": "date", - "in": "query", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Shipment" - } - } - } - } - } - } - } - }, - "/getShipment": { - "get": { - "tags": ["shipment-controller"], - "operationId": "getShipment", - "parameters": [ - { - "name": "id", - "in": "query", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/Shipment" - } - } - } - } - } - } - }, - "/getShipmentCountBetweenDates": { - "get": { - "tags": ["shipment-controller"], - "operationId": "getShipmentCountBetweenDates", - "parameters": [ - { - "name": "date1", - "in": "query", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "date2", - "in": "query", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "integer", - "format": "int64" - } - } - } - } - } - } - }, - "/getRecipients": { - "get": { - "tags": ["recipient-controller"], - "operationId": "getRecipients", - "parameters": [ - { - "name": "firstName", - "in": "query", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "lastName", - "in": "query", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Recipient" - } - } - } - } - } - } - } - }, - "/getRecipient": { - "get": { - "tags": ["recipient-controller"], - "operationId": "getRecipient", - "parameters": [ - { - "name": "id", - "in": "query", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/Recipient" - } - } - } - } - } - } - }, - "/getRecipientLocation": { - "get": { - "tags": ["recipient-controller"], - "operationId": "getRecipientLocation", - "parameters": [ - { - "name": "id", - "in": "query", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "string" - } - } - } - } - } - } - }, - "/getRecipientByAssignedId": { - "get": { - "tags": ["recipient-controller"], - "operationId": "getRecipientByAssignedId", - "parameters": [ - { - "name": "assignedId", - "in": "query", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/Recipient" - } - } - } - } - } - } - }, - "/getNoIsbnBooks": { - "get": { - "tags": ["package-content-controller"], - "operationId": "getBooksWithNoIsbn", - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Book" - } - } - } - } - } - } - } - }, - "/getFacility": { - "get": { - "tags": ["facility-controller"], - "operationId": "getFacilityById", - "parameters": [ - { - "name": "id", - "in": "query", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/Facility" - } - } - } - } - } - } - }, - "/getFacilityByName": { - "get": { - "tags": ["facility-controller"], - "operationId": "getFacilityByNameAndState", - "parameters": [ - { - "name": "name", - "in": "query", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "state", - "in": "query", - "required": true, - "schema": { - "type": "string", - "enum": ["NC", "AL", "TN", "WV", "KY", "MD", "VA", "DE"] - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Facility" - } - } - } - } - } - } - } - }, - "/getContent": { - "get": { - "tags": ["package-content-controller"], - "operationId": "getContent", - "parameters": [ - { - "name": "id", - "in": "query", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/Book" - }, - { - "$ref": "#/components/schemas/Zine" - } - ] - } - } - } - } - } - } - }, - "/getContentByTitle": { - "get": { - "tags": ["package-content-controller"], - "operationId": "getContentByTitle", - "parameters": [ - { - "name": "title", - "in": "query", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "oneOf": [ - { - "$ref": "#/components/schemas/Book" - }, - { - "$ref": "#/components/schemas/Zine" - } - ] - } - } - } - } - } - } - } - }, - "/getBookByISBN": { - "get": { - "tags": ["package-content-controller"], - "operationId": "getBookByISBN", - "parameters": [ - { - "name": "isbn", - "in": "query", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/Book" - } - } - } - } - } - } - }, - "/getAllZines": { - "get": { - "tags": ["package-content-controller"], - "operationId": "getAllZines", - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Zine" - } - } - } - } - } - } - } - }, - "/getAllSpecialRequests": { - "get": { - "tags": ["special-request-controller"], - "operationId": "getAllSpecialRequests", - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SpecialRequest" - } - } - } - } - } - } - } - }, - "/getAllShipmentsByRecipient": { - "get": { - "tags": ["shipment-controller"], - "operationId": "getAllShipmentsByRecipient", - "parameters": [ - { - "name": "id", - "in": "query", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Shipment" - } - } - } - } - } - } - } - }, - "/getAllRecipients": { - "get": { - "tags": ["recipient-controller"], - "operationId": "getAllRecipients", - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Recipient" - } - } - } - } - } - } - } - }, - "/getAllFacilities": { - "get": { - "tags": ["facility-controller"], - "operationId": "getAllFacilities", - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Facility" - } - } - } - } - } - } - } - }, - "/getAllContent": { - "get": { - "tags": ["package-content-controller"], - "operationId": "getAllContent", - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "oneOf": [ - { - "$ref": "#/components/schemas/Book" - }, - { - "$ref": "#/components/schemas/Zine" - } - ] - } - } - } - } - } - } - } - }, - "/content": { - "get": { - "tags": ["package-content-controller"], - "operationId": "getContentByTitleAndAuthor", - "parameters": [ - { - "name": "title", - "in": "query", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "author", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "oneOf": [ - { - "$ref": "#/components/schemas/Book" - }, - { - "$ref": "#/components/schemas/Zine" - } - ] - } - } - } - } - } - } - } - }, - "/deleteShipment": { - "delete": { - "tags": ["shipment-controller"], - "operationId": "deleteShipment", - "parameters": [ - { - "name": "id", - "in": "query", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/deleteRecipient": { - "delete": { - "tags": ["recipient-controller"], - "operationId": "deleteRecipient", - "parameters": [ - { - "name": "id", - "in": "query", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/deleteFacility": { - "delete": { - "tags": ["facility-controller"], - "operationId": "deleteFacility", - "parameters": [ - { - "name": "id", - "in": "query", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/deleteContent": { - "delete": { - "tags": ["package-content-controller"], - "operationId": "deleteContent", - "parameters": [ - { - "name": "id", - "in": "query", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/deleteAllShipmentsByRecipientId": { - "delete": { - "tags": ["shipment-controller"], - "operationId": "deleteShipmentsByRecipient", - "parameters": [ - { - "name": "id", - "in": "query", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - } - }, - "components": { - "schemas": { - "Book": { - "required": ["title"], - "type": "object", - "allOf": [ - { - "$ref": "#/components/schemas/PackageContent" - }, - { - "type": "object", - "properties": { - "authors": { - "type": "string" - }, - "isbn10": { - "type": "string" - }, - "isbn13": { - "type": "string" - } - } - } - ] - }, - "Facility": { - "required": ["city", "name", "state", "street", "zip"], - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - }, - "additionalInfo": { - "type": "string" - }, - "street": { - "type": "string" - }, - "city": { - "type": "string" - }, - "state": { - "type": "string", - "enum": ["NC", "AL", "TN", "WV", "KY", "MD", "VA", "DE"] - }, - "zip": { - "type": "string" - } - } - }, - "Note": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "content": { - "type": "string" - }, - "date": { - "type": "string", - "format": "date" - } - } - }, - "PackageContent": { - "required": ["title", "type"], - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "title": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "discriminator": { - "propertyName": "type" - } - }, - "Recipient": { - "required": ["firstName", "lastName"], - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "firstName": { - "type": "string" - }, - "middleName": { - "type": "string" - }, - "lastName": { - "type": "string" - }, - "assignedId": { - "type": "string" - }, - "facility": { - "$ref": "#/components/schemas/Facility" - }, - "shipments": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Shipment" - } - }, - "specialRequests": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SpecialRequest" - } - } - } - }, - "Shipment": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "date": { - "type": "string", - "format": "date" - }, - "facility": { - "$ref": "#/components/schemas/Facility" - }, - "recipient": { - "$ref": "#/components/schemas/Recipient" - }, - "notes": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Note" - } - }, - "content": { - "uniqueItems": true, - "type": "array", - "items": { - "oneOf": [ - { - "$ref": "#/components/schemas/Book" - }, - { - "$ref": "#/components/schemas/Zine" - } - ] - } - } - } - }, - "SpecialRequest": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "volunteerName": { - "type": "string" - }, - "request": { - "type": "string" - }, - "specialRequestDate": { - "type": "string", - "format": "date" - }, - "letterMailedDate": { - "type": "string", - "format": "date" - }, - "category": { - "type": "string", - "enum": [ - "VOCATIONAL", - "EDUCATIONAL", - "CAREER_GROWTH", - "FOREIGN_LANGUAGE", - "LEGAL", - "SPIRITUAL_RELIGIOUS", - "OTHER" - ] - }, - "status": { - "type": "string", - "enum": ["OPEN", "COMPLETED", "CANCELLED"] - }, - "recipient": { - "$ref": "#/components/schemas/Recipient" - } - } - }, - "Zine": { - "required": ["code", "title"], - "type": "object", - "allOf": [ - { - "$ref": "#/components/schemas/PackageContent" - }, - { - "type": "object", - "properties": { - "code": { - "type": "string" - } - } - } - ] - } - } - } -} diff --git a/scripts/openapi-codegen/src/openapi/openapi.ts b/scripts/openapi-codegen/src/openapi/openapi.ts deleted file mode 100644 index 0e4f2c3..0000000 --- a/scripts/openapi-codegen/src/openapi/openapi.ts +++ /dev/null @@ -1,764 +0,0 @@ -/** - * This file was auto-generated by openapi-typescript. - * Do not make direct changes to the file. - */ - -/** WithRequired type helpers */ -type WithRequired = T & { [P in K]-?: T[P] } - -export interface paths { - '/updateShipment': { - put: operations['updateShipment'] - } - '/updateRecipient': { - put: operations['updateRecipient'] - } - '/updateFacility': { - put: operations['updateFacility'] - } - '/updateContent': { - put: operations['updateContent'] - } - '/addSpecialRequest': { - post: operations['addSpecialRequest'] - } - '/addShipment': { - post: operations['addShipment'] - } - '/addRecipient': { - post: operations['addRecipient'] - } - '/addNote': { - post: operations['addNote'] - } - '/addFacility': { - post: operations['addFacility'] - } - '/addContent': { - post: operations['addContent'] - } - '/searchBooks': { - get: operations['searchBooksByTitleAndAuthor'] - } - '/queryGoogle': { - get: operations['queryGoogleByTitleAndAuthor'] - } - '/getZineByCode': { - get: operations['getZineByCode'] - } - '/getShipmentsByDate': { - get: operations['getShipmentsByDate'] - } - '/getShipment': { - get: operations['getShipment'] - } - '/getShipmentCountBetweenDates': { - get: operations['getShipmentCountBetweenDates'] - } - '/getRecipients': { - get: operations['getRecipients'] - } - '/getRecipient': { - get: operations['getRecipient'] - } - '/getRecipientLocation': { - get: operations['getRecipientLocation'] - } - '/getRecipientByAssignedId': { - get: operations['getRecipientByAssignedId'] - } - '/getNoIsbnBooks': { - get: operations['getBooksWithNoIsbn'] - } - '/getFacility': { - get: operations['getFacilityById'] - } - '/getFacilityByName': { - get: operations['getFacilityByNameAndState'] - } - '/getContent': { - get: operations['getContent'] - } - '/getContentByTitle': { - get: operations['getContentByTitle'] - } - '/getBookByISBN': { - get: operations['getBookByISBN'] - } - '/getAllZines': { - get: operations['getAllZines'] - } - '/getAllSpecialRequests': { - get: operations['getAllSpecialRequests'] - } - '/getAllShipmentsByRecipient': { - get: operations['getAllShipmentsByRecipient'] - } - '/getAllRecipients': { - get: operations['getAllRecipients'] - } - '/getAllFacilities': { - get: operations['getAllFacilities'] - } - '/getAllContent': { - get: operations['getAllContent'] - } - '/content': { - get: operations['getContentByTitleAndAuthor'] - } - '/deleteShipment': { - delete: operations['deleteShipment'] - } - '/deleteRecipient': { - delete: operations['deleteRecipient'] - } - '/deleteFacility': { - delete: operations['deleteFacility'] - } - '/deleteContent': { - delete: operations['deleteContent'] - } - '/deleteAllShipmentsByRecipientId': { - delete: operations['deleteShipmentsByRecipient'] - } -} - -export type webhooks = Record - -export interface components { - schemas: { - Book: WithRequired< - { - type: 'Book' - } & Omit & { - authors?: string - isbn10?: string - isbn13?: string - }, - 'title' - > - Facility: { - /** Format: int64 */ - id?: number - name: string - additionalInfo?: string - street: string - city: string - /** @enum {string} */ - state: 'NC' | 'AL' | 'TN' | 'WV' | 'KY' | 'MD' | 'VA' | 'DE' - zip: string - } - Note: { - /** Format: int64 */ - id?: number - content?: string - /** Format: date */ - date?: string - } - PackageContent: { - /** Format: int64 */ - id?: number - title: string - type: string - } - Recipient: { - /** Format: int64 */ - id?: number - firstName: string - middleName?: string - lastName: string - assignedId?: string - facility?: components['schemas']['Facility'] - shipments?: components['schemas']['Shipment'][] - specialRequests?: components['schemas']['SpecialRequest'][] - } - Shipment: { - /** Format: int64 */ - id?: number - /** Format: date */ - date?: string - facility?: components['schemas']['Facility'] - recipient?: components['schemas']['Recipient'] - notes?: components['schemas']['Note'][] - content?: (components['schemas']['Book'] | components['schemas']['Zine'])[] - } - SpecialRequest: { - /** Format: int64 */ - id?: number - volunteerName?: string - request?: string - /** Format: date */ - specialRequestDate?: string - /** Format: date */ - letterMailedDate?: string - /** @enum {string} */ - category?: - | 'VOCATIONAL' - | 'EDUCATIONAL' - | 'CAREER_GROWTH' - | 'FOREIGN_LANGUAGE' - | 'LEGAL' - | 'SPIRITUAL_RELIGIOUS' - | 'OTHER' - /** @enum {string} */ - status?: 'OPEN' | 'COMPLETED' | 'CANCELLED' - recipient?: components['schemas']['Recipient'] - } - Zine: WithRequired< - { - type: 'Zine' - } & Omit & { - code?: string - }, - 'code' | 'title' - > - } - responses: never - parameters: never - requestBodies: never - headers: never - pathItems: never -} - -export type $defs = Record - -export type external = Record - -export interface operations { - updateShipment: { - requestBody: { - content: { - 'application/json': components['schemas']['Shipment'] - } - } - responses: { - /** @description OK */ - 200: { - content: { - '*/*': components['schemas']['Shipment'] - } - } - } - } - updateRecipient: { - requestBody: { - content: { - 'application/json': components['schemas']['Recipient'] - } - } - responses: { - /** @description OK */ - 200: { - content: { - '*/*': components['schemas']['Recipient'] - } - } - } - } - updateFacility: { - requestBody: { - content: { - 'application/json': components['schemas']['Facility'] - } - } - responses: { - /** @description OK */ - 200: { - content: { - '*/*': components['schemas']['Facility'] - } - } - } - } - updateContent: { - requestBody: { - content: { - 'application/json': components['schemas']['Book'] | components['schemas']['Zine'] - } - } - responses: { - /** @description OK */ - 200: { - content: { - '*/*': components['schemas']['Book'] | components['schemas']['Zine'] - } - } - } - } - addSpecialRequest: { - requestBody: { - content: { - 'application/json': components['schemas']['SpecialRequest'] - } - } - responses: { - /** @description OK */ - 200: { - content: { - '*/*': components['schemas']['SpecialRequest'] - } - } - } - } - addShipment: { - requestBody: { - content: { - 'application/json': components['schemas']['Shipment'] - } - } - responses: { - /** @description OK */ - 200: { - content: { - '*/*': components['schemas']['Shipment'] - } - } - } - } - addRecipient: { - requestBody: { - content: { - 'application/json': components['schemas']['Recipient'] - } - } - responses: { - /** @description OK */ - 200: { - content: { - '*/*': components['schemas']['Recipient'] - } - } - } - } - addNote: { - requestBody: { - content: { - 'application/json': components['schemas']['Note'] - } - } - responses: { - /** @description OK */ - 200: { - content: { - '*/*': components['schemas']['Note'] - } - } - } - } - addFacility: { - requestBody: { - content: { - 'application/json': components['schemas']['Facility'] - } - } - responses: { - /** @description OK */ - 200: { - content: { - '*/*': components['schemas']['Facility'] - } - } - } - } - addContent: { - requestBody: { - content: { - 'application/json': components['schemas']['Book'] | components['schemas']['Zine'] - } - } - responses: { - /** @description OK */ - 200: { - content: { - '*/*': components['schemas']['Book'] | components['schemas']['Zine'] - } - } - } - } - searchBooksByTitleAndAuthor: { - parameters: { - query: { - title: string - author?: string - } - } - responses: { - /** @description OK */ - 200: { - content: { - '*/*': components['schemas']['Book'][] - } - } - } - } - queryGoogleByTitleAndAuthor: { - parameters: { - query: { - title: string - author?: string - } - } - responses: { - /** @description OK */ - 200: { - content: { - '*/*': components['schemas']['Book'][] - } - } - } - } - getZineByCode: { - parameters: { - query: { - code: string - } - } - responses: { - /** @description OK */ - 200: { - content: { - '*/*': components['schemas']['Zine'] - } - } - } - } - getShipmentsByDate: { - parameters: { - query: { - date: string - } - } - responses: { - /** @description OK */ - 200: { - content: { - '*/*': components['schemas']['Shipment'][] - } - } - } - } - getShipment: { - parameters: { - query: { - id: number - } - } - responses: { - /** @description OK */ - 200: { - content: { - '*/*': components['schemas']['Shipment'] - } - } - } - } - getShipmentCountBetweenDates: { - parameters: { - query: { - date1: string - date2: string - } - } - responses: { - /** @description OK */ - 200: { - content: { - '*/*': number - } - } - } - } - getRecipients: { - parameters: { - query: { - firstName: string - lastName: string - } - } - responses: { - /** @description OK */ - 200: { - content: { - '*/*': components['schemas']['Recipient'][] - } - } - } - } - getRecipient: { - parameters: { - query: { - id: number - } - } - responses: { - /** @description OK */ - 200: { - content: { - '*/*': components['schemas']['Recipient'] - } - } - } - } - getRecipientLocation: { - parameters: { - query: { - id: string - } - } - responses: { - /** @description OK */ - 200: { - content: { - '*/*': string - } - } - } - } - getRecipientByAssignedId: { - parameters: { - query: { - assignedId: string - } - } - responses: { - /** @description OK */ - 200: { - content: { - '*/*': components['schemas']['Recipient'] - } - } - } - } - getBooksWithNoIsbn: { - responses: { - /** @description OK */ - 200: { - content: { - '*/*': components['schemas']['Book'][] - } - } - } - } - getFacilityById: { - parameters: { - query: { - id: number - } - } - responses: { - /** @description OK */ - 200: { - content: { - '*/*': components['schemas']['Facility'] - } - } - } - } - getFacilityByNameAndState: { - parameters: { - query: { - name: string - state: 'NC' | 'AL' | 'TN' | 'WV' | 'KY' | 'MD' | 'VA' | 'DE' - } - } - responses: { - /** @description OK */ - 200: { - content: { - '*/*': components['schemas']['Facility'][] - } - } - } - } - getContent: { - parameters: { - query: { - id: number - } - } - responses: { - /** @description OK */ - 200: { - content: { - '*/*': components['schemas']['Book'] | components['schemas']['Zine'] - } - } - } - } - getContentByTitle: { - parameters: { - query: { - title: string - } - } - responses: { - /** @description OK */ - 200: { - content: { - '*/*': (components['schemas']['Book'] | components['schemas']['Zine'])[] - } - } - } - } - getBookByISBN: { - parameters: { - query: { - isbn: string - } - } - responses: { - /** @description OK */ - 200: { - content: { - '*/*': components['schemas']['Book'] - } - } - } - } - getAllZines: { - responses: { - /** @description OK */ - 200: { - content: { - '*/*': components['schemas']['Zine'][] - } - } - } - } - getAllSpecialRequests: { - responses: { - /** @description OK */ - 200: { - content: { - '*/*': components['schemas']['SpecialRequest'][] - } - } - } - } - getAllShipmentsByRecipient: { - parameters: { - query: { - id: number - } - } - responses: { - /** @description OK */ - 200: { - content: { - '*/*': components['schemas']['Shipment'][] - } - } - } - } - getAllRecipients: { - responses: { - /** @description OK */ - 200: { - content: { - '*/*': components['schemas']['Recipient'][] - } - } - } - } - getAllFacilities: { - responses: { - /** @description OK */ - 200: { - content: { - '*/*': components['schemas']['Facility'][] - } - } - } - } - getAllContent: { - responses: { - /** @description OK */ - 200: { - content: { - '*/*': (components['schemas']['Book'] | components['schemas']['Zine'])[] - } - } - } - } - getContentByTitleAndAuthor: { - parameters: { - query: { - title: string - author?: string - } - } - responses: { - /** @description OK */ - 200: { - content: { - '*/*': (components['schemas']['Book'] | components['schemas']['Zine'])[] - } - } - } - } - deleteShipment: { - parameters: { - query: { - id: number - } - } - responses: { - /** @description OK */ - 200: { - content: never - } - } - } - deleteRecipient: { - parameters: { - query: { - id: number - } - } - responses: { - /** @description OK */ - 200: { - content: never - } - } - } - deleteFacility: { - parameters: { - query: { - id: number - } - } - responses: { - /** @description OK */ - 200: { - content: never - } - } - } - deleteContent: { - parameters: { - query: { - id: number - } - } - responses: { - /** @description OK */ - 200: { - content: never - } - } - } - deleteShipmentsByRecipient: { - parameters: { - query: { - id: number - } - } - responses: { - /** @description OK */ - 200: { - content: never - } - } - } -} diff --git a/scripts/openapi-codegen/tsconfig.json b/scripts/openapi-codegen/tsconfig.json new file mode 100644 index 0000000..208de37 --- /dev/null +++ b/scripts/openapi-codegen/tsconfig.json @@ -0,0 +1,115 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + "lib": [ + "es2021", + ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "ESNext" /* Specify what module code is generated. */, + "rootDir": "src" /* Specify the root folder within your source files. */, + "moduleResolution": "Bundler" /* Specify how TypeScript looks up a file from a given module specifier. */, + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + "resolveJsonModule": true /* Enable importing .json files. */, + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + "allowJs": true /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */, + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "build" /* Specify an output folder for all emitted files. */, + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + + /* Type Checking */ + "strict": true /* Enable all strict type-checking options. */, + "noImplicitAny": false /* Enable error reporting for expressions and declarations with an implied 'any' type. */, + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */, + }, + "tsc-alias": { + "resolveFullPaths": true, + "verbose": false, + }, +} From 2b35c6337e1fde2ab44a2a72ef352be54dbe6bfd Mon Sep 17 00:00:00 2001 From: Coal Cooper Date: Thu, 18 Jan 2024 07:56:32 -0500 Subject: [PATCH 11/12] update script to merge all subcommands into one supercommand --- scripts/openapi-codegen/package.json | 13 +- scripts/openapi-codegen/src/app.ts | 186 ++++++++++----------- scripts/openapi-codegen/src/lib/codegen.ts | 6 +- 3 files changed, 97 insertions(+), 108 deletions(-) diff --git a/scripts/openapi-codegen/package.json b/scripts/openapi-codegen/package.json index 91cd2c1..bf5ce80 100644 --- a/scripts/openapi-codegen/package.json +++ b/scripts/openapi-codegen/package.json @@ -4,14 +4,15 @@ "module": "src/app.ts", "main": "build/app.js", "bin": { - "openapi-codegen": "./build/app.js" + "bellbooks-openapi-codegen": "./build/app.js" }, "scripts": { - "build": "npx tsc && npx tsc-alias", - "local": "npx bun src/app.ts download-and-generate --output ../../src/lib/api", - "local:test": "npx bun src/app.ts download-and-generate --dryrun", - "remote": "npx bun src/app.ts download-and-generate --url https://bellbooks-backend.ue.r.appspot.com --output ../../src/lib/api", - "remote:test": "npx bun src/app.ts download-and-generate --url https://bellbooks-backend.ue.r.appspot.com --dryrun" + "build": "npm run clean && npx tsc && npx tsc-alias", + "clean": "rm -rf build dist src/openapi", + "local": "npx bun src/app.ts --local --output=../../src/lib/api", + "local:test": "npx bun src/app.ts --local --dry-run", + "remote": "npx bun src/app.ts --remote --output=../../src/lib/api", + "remote:test": "npx bun src/app.ts --remote --dry-run" }, "devDependencies": { "@types/node-fetch": "^2.6.11", diff --git a/scripts/openapi-codegen/src/app.ts b/scripts/openapi-codegen/src/app.ts index 524dc72..145de90 100644 --- a/scripts/openapi-codegen/src/app.ts +++ b/scripts/openapi-codegen/src/app.ts @@ -1,109 +1,101 @@ #! /usr/bin/env node -import { program } from 'commander' +import { program, Option } from 'commander' import openapiTS, { type OpenAPI3 } from 'openapi-typescript' import { join } from 'path' -import { download, downloadAndSave, type DownloadOptions } from './lib/download' -import { convertAndSave, type ConvertOptions } from './lib/convert' -import { codegen, codegenAndSave, type CodegenOptions } from './lib/codegen' +import { download } from './lib/download' +import { codegen } from './lib/codegen' import { saveFile } from './lib/util' -program - .name('bellbooks-openapi-codegen') - .description('CLI to generate frontend fetch methods to interface with BellBooks backend') - .version('0.0.1') - -program - .command('download') - .description('Download the latest openapi.json from a running instance of the Bellbooks API') - .option( - '-l, --local', - '(default) Use a local instance of the Bellbooks API at http://localhost:8080/api/docs', - ) - .option('-r, --remote', 'Use the standard GCP hosted instance of the Bellbooks API') - .option('-u, --url ', 'Designate a custom URL for the instance of the Bellbooks API') - .option( - '-o, --output ', - 'Select a custom output location for the downloaded openapi.json file', - 'src/openapi/openapi.json', - ) - .option('-d, --dryrun', '[Test] Log download to standard output without saving to disk', false) - .action(async (options: DownloadOptions) => { - await downloadAndSave(options) - }) - -program - .command('convert') - .description( - 'Use `openapi-typescript` library to generate types from a downloaded openapi.json spec', - ) - .option( - '-i, --input ', - 'Designate the source openapi.json file', - 'src/openapi/openapi.json', - ) - .option( - '-o, --output ', - 'Designate the output location for the generated types', - 'src/openapi/openapi.ts', - ) - .option('-d, --dryrun', '[Test] Log conversion to standard output without saving to disk', false) - .action(async (options: ConvertOptions) => { - await convertAndSave(options) - }) - -program - .command('codegen') - .description('Generate frontend http fetch methods for interfacing with Bellbooks backend') - .option('-o, --output ', 'Destination path to save the generated code', 'dist/api') - .option( - '-s, --schema ', - 'Location on disk where the openAPI schema (.json) file is saved', - 'src/openapi/openapi.json', - ) - .option( - '-t, --types ', - 'Location on disk where the openAPI typescript types (.ts) file is saved', - 'src/openapi/openapi.ts', - ) - .option('-d, --dryrun', '[Test] Log codegen to standard output without saving to disk', false) - .action(async (options: CodegenOptions) => { - await codegenAndSave(options) - }) - -type DownloadAndGenerateOptions = Partial<{ +type LogLevel = 'none' | 'error' | 'info' | 'verbose' +enum LogLevelValue { + 'none' = 0, + 'error' = 2, + 'info' = 5, + 'verbose' = 10, +} +type Options = Partial<{ + local: boolean + remote: boolean url: string output: string - dryrun: boolean + + logLevel: LogLevel + dryRun: boolean }> +function normalizeOptions(options: Options): Required { + const dryRun = options.dryRun ?? false + const logLevel = options.logLevel ?? 'info' + const local = + (options.url ? false : undefined) ?? + (options.remote ? false : undefined) ?? + options.local ?? + true + const remote = + (options.url ? false : undefined) ?? (local ? false : undefined) ?? options.remote ?? false + const output = options.output ?? 'dist/api' + const url = + options.url ?? + (local ? 'http://localhost:8080/api/docs' : undefined) ?? + (remote ? 'https://bellbooks-backend.ue.r.appspot.com/api/docs' : undefined) ?? + 'http://localhost:8080/api/docs' + + return { local, remote, output, url, dryRun, logLevel } satisfies Options +} + +function Logger(allowedLevel: LogLevel) { + const allowed = LogLevelValue[allowedLevel] + return { + verbose: (message: any, ...optionalParams: any[]) => { + if (LogLevelValue['verbose'] <= allowed) console.log(message, ...optionalParams) + }, + info: (message: any, ...optionalParams: any[]) => { + if (LogLevelValue['info'] <= allowed) console.log(message, ...optionalParams) + }, + error: (message: any, ...optionalParams: any[]) => { + if (LogLevelValue['error'] <= allowed) console.error(message, ...optionalParams) + }, + } +} + program - .command('download-and-generate') - .description( - 'Run through all of the commands: Download, Convert, and Codegen, and output the generated code to a specified directory', - ) + .name('bellbooks-openapi-codegen') + .description('CLI to generate client API methods to interface with BellBooks API') + .version('0.0.1') + .option('-l, --local', '(default) Set URL to the local instance of Bellbooks API docs endpoint') + .option('-r, --remote', 'Set URL to the GCP-hosted instance of Bellbooks API docs endpoint') .option( '-u, --url ', - 'URL pointing to a live instance of the Bellbooks API', - 'http://localhost:8080/api/docs', + 'URL pointing to a live instance of the Bellbooks API documentation (default: "http://localhost:8080/api/docs")', ) .option( '-o, --output ', 'Designate the output directory for the generated code', 'dist/api', ) - .option('-d, --dryrun', '[Test] Log to standard output without saving to disk', false) - .action(async ({ url, output, dryrun }: DownloadAndGenerateOptions) => { - if (!output) output = 'dist/api' + .addOption( + new Option('-ll, --log-level ', 'Set logging level') + .default('info') + .choices(['none', 'error', 'info', 'verbose']), + ) + .addOption( + new Option('-v, --verbose', 'Set logging level to verbose').implies({ logLevel: 'verbose' }), + ) + .option('-d, --dry-run', '[Test] Log to standard output without saving to disk', false) + .action(async (options: Options) => { + const { url, output, dryRun, logLevel } = normalizeOptions(options) + const log = Logger(logLevel) - console.log(`Generating client API methods for OpenAPI specification at "${url}"...`) + log.info(`Generating client API methods for OpenAPI specification at "${url}"...`) let schema: OpenAPI3 | undefined try { schema = await download({ url }) if (!schema) throw Error() - console.log(` ✅ Successfully downloaded OpenAPI Schema from "${url}"`) + log.info(` ✅ Successfully downloaded OpenAPI Schema from "${url}"`) + log.verbose({ schema }) } catch (_) { // error is logged in download function return @@ -112,33 +104,28 @@ program let openAPITypescript: string try { openAPITypescript = await openapiTS(schema) - console.log(` ✅ Successfully converted schema to typescript definitions`) + log.info(` ✅ Successfully converted schema to typescript definitions`) + log.verbose('// types.ts', '\n'.padEnd(55, '-'), '\n' + openAPITypescript, '\n') } catch (error) { - console.error(`Using schema: ${JSON.stringify(schema, null, 2)}`) - console.error(` ❌ Failed to convert openAPI schema json from downloaded schema`, error) + log.error(`Using schema: ${JSON.stringify(schema, null, 2)}`) + log.error(` ❌ Failed to convert openAPI schema json from downloaded schema`, error) return } let code: { models: string; endpoints: string; index: string } try { code = codegen(schema, openAPITypescript) - console.log(` ✅ Successfully generated model definitions and API methods`) - } catch (_) { - // Error is logged in codegen function + log.info(` ✅ Successfully generated model definitions and API methods`) + log.verbose('// models.ts', '\n'.padEnd(55, '-'), '\n' + code.models, '\n') + log.verbose('// endpoints.ts', '\n'.padEnd(55, '-'), '\n' + code.endpoints, '\n') + log.verbose('// index.ts', '\n'.padEnd(55, '-'), '\n' + code.index) + } catch (error) { + log.error(` ❌ Failed to generate code`, error) return } - if (dryrun) { - console.log('// models.ts\n') - console.log(code.models, '\n') - - console.log('// endpoints.ts\n') - console.log(code.endpoints, '\n') - - console.log('\n\n// index.ts\n') - console.log(code.index, '\n') - - console.log(`✅ Successfully generated output for OpenAPI Schema from "${url}"`) + if (dryRun) { + log.info(`✅ Successfully generated output for OpenAPI Schema from "${url}"`) return } @@ -146,9 +133,8 @@ program saveFile(join(output, 'endpoints.ts'), code.endpoints) saveFile(join(output, 'index.ts'), code.index) - console.log( + log.info( `✅ Successfully generated output for OpenAPI Schema from "${url}" and saved to "${output}"`, ) }) - -program.parse() + .parse() diff --git a/scripts/openapi-codegen/src/lib/codegen.ts b/scripts/openapi-codegen/src/lib/codegen.ts index 0d44e76..9c7c55d 100644 --- a/scripts/openapi-codegen/src/lib/codegen.ts +++ b/scripts/openapi-codegen/src/lib/codegen.ts @@ -1,4 +1,4 @@ -import * as ts from 'typescript' +import ts from 'typescript' import { join } from 'path' import type { OpenAPI3 } from 'openapi-typescript' @@ -282,7 +282,9 @@ function codegenEndpoints({ const endpoints = parseEndpoints(schema) const endpointMethods = endpoints.map(codegenEndpointMethod) - endpointMethods.forEach((method) => (buffer += method + '\n\n')) + endpointMethods.forEach( + (method, index) => (buffer += method + (index === endpointMethods.length - 1 ? '\n' : '\n\n')), + ) return buffer } From fcf23bf732fcac62cae13a7f975f83d6a16d5b6b Mon Sep 17 00:00:00 2001 From: Coal Cooper Date: Fri, 19 Jan 2024 12:39:05 -0500 Subject: [PATCH 12/12] small update to package.json --- scripts/openapi-codegen/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/openapi-codegen/package.json b/scripts/openapi-codegen/package.json index bf5ce80..511a90d 100644 --- a/scripts/openapi-codegen/package.json +++ b/scripts/openapi-codegen/package.json @@ -4,7 +4,7 @@ "module": "src/app.ts", "main": "build/app.js", "bin": { - "bellbooks-openapi-codegen": "./build/app.js" + "bellbooks-openapi-codegen": "build/app.js" }, "scripts": { "build": "npm run clean && npx tsc && npx tsc-alias",