chore(): start moving some packages to the core directory (#7215)
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import { OpenAPIObject, SchemaObject } from "openapi3-ts"
|
||||
import OpenAPIParser from "@readme/openapi-parser"
|
||||
import { $Refs } from "@readme/json-schema-ref-parser"
|
||||
import { jsonObjectToYamlString } from "./yaml-utils"
|
||||
|
||||
export const getCircularReferences = async (
|
||||
srcFile: string
|
||||
): Promise<{ circularRefs: string[]; oas: OpenAPIObject }> => {
|
||||
const parser = new OpenAPIParser()
|
||||
const oas = (await parser.validate(srcFile, {
|
||||
dereference: {
|
||||
circular: "ignore",
|
||||
},
|
||||
})) as OpenAPIObject
|
||||
if (parser.$refs.circular) {
|
||||
const $refs = parser.$refs as $Refs
|
||||
let circularRefs = $refs.circularRefs.map(
|
||||
(ref) => ref.match(/#\/components\/schemas\/.*/)![0]
|
||||
)
|
||||
circularRefs = [...new Set(circularRefs)]
|
||||
circularRefs.sort()
|
||||
return { circularRefs, oas }
|
||||
}
|
||||
return { circularRefs: [], oas }
|
||||
}
|
||||
|
||||
export const getCircularPatchRecommendation = (
|
||||
circularRefs: string[],
|
||||
oas: OpenAPIObject
|
||||
): Record<string, string[]> => {
|
||||
type circularReferenceMatch = {
|
||||
schema: string
|
||||
property: string
|
||||
isArray: boolean
|
||||
referencedSchema: string
|
||||
}
|
||||
const matches: circularReferenceMatch[] = circularRefs
|
||||
.map((ref) => {
|
||||
let match =
|
||||
ref.match(
|
||||
/(?:.*)(?:#\/components\/schemas\/)(.*)(?:\/properties\/?)(.*)/
|
||||
) ?? []
|
||||
let schema = match[1]
|
||||
let property = match[2]
|
||||
let isArray = false
|
||||
if (property.endsWith("/items")) {
|
||||
property = property.replace("/items", "")
|
||||
isArray = true
|
||||
}
|
||||
return { schema, property, isArray }
|
||||
})
|
||||
.filter((match) => match.property !== "")
|
||||
.map((match) => {
|
||||
const baseSchema = oas.components!.schemas![match.schema] as SchemaObject
|
||||
const propertySpec = match.isArray
|
||||
? (baseSchema.properties![match.property] as SchemaObject).items!
|
||||
: baseSchema.properties![match.property]
|
||||
const referencedSchema = propertySpec["$ref"].match(
|
||||
/(?:#\/components\/schemas\/)(.*)/
|
||||
)![1]
|
||||
return {
|
||||
schema: match.schema,
|
||||
property: match.property,
|
||||
isArray: match.isArray,
|
||||
referencedSchema,
|
||||
}
|
||||
})
|
||||
|
||||
const schemas = {}
|
||||
for (const match of matches) {
|
||||
if (!schemas.hasOwnProperty(match.schema)) {
|
||||
schemas[match.schema] = []
|
||||
}
|
||||
schemas[match.schema].push(match.referencedSchema)
|
||||
}
|
||||
return schemas
|
||||
}
|
||||
|
||||
export const formatHintRecommendation = (
|
||||
recommendation: Record<string, string[]>
|
||||
) => {
|
||||
return jsonObjectToYamlString({
|
||||
decorators: { "medusa/circular-patch": { schemas: recommendation } },
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import { OpenAPIObject } from "openapi3-ts"
|
||||
import { upperFirst } from "lodash"
|
||||
|
||||
export async function combineOAS(
|
||||
adminOAS: OpenAPIObject,
|
||||
storeOAS: OpenAPIObject
|
||||
): Promise<OpenAPIObject> {
|
||||
prepareOASForCombine(adminOAS, "admin")
|
||||
prepareOASForCombine(storeOAS, "store")
|
||||
|
||||
const combinedOAS: OpenAPIObject = {
|
||||
openapi: "3.0.0",
|
||||
info: { title: "Medusa API", version: "1.0.0" },
|
||||
servers: [],
|
||||
paths: {},
|
||||
tags: [],
|
||||
components: {
|
||||
callbacks: {},
|
||||
examples: {},
|
||||
headers: {},
|
||||
links: {},
|
||||
parameters: {},
|
||||
requestBodies: {},
|
||||
responses: {},
|
||||
schemas: {},
|
||||
securitySchemes: {},
|
||||
},
|
||||
}
|
||||
|
||||
for (const oas of [adminOAS, storeOAS]) {
|
||||
/**
|
||||
* Combine paths
|
||||
*/
|
||||
Object.assign(combinedOAS.paths, oas.paths)
|
||||
/**
|
||||
* Combine tags
|
||||
*/
|
||||
if (oas.tags) {
|
||||
combinedOAS.tags = [...combinedOAS.tags!, ...oas.tags]
|
||||
}
|
||||
/**
|
||||
* Combine components
|
||||
*/
|
||||
if (oas.components) {
|
||||
for (const componentGroup of [
|
||||
"callbacks",
|
||||
"examples",
|
||||
"headers",
|
||||
"links",
|
||||
"parameters",
|
||||
"requestBodies",
|
||||
"responses",
|
||||
"schemas",
|
||||
"securitySchemes",
|
||||
]) {
|
||||
if (Object.keys(oas.components).includes(componentGroup)) {
|
||||
Object.assign(
|
||||
combinedOAS.components![componentGroup],
|
||||
oas.components[componentGroup]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return combinedOAS
|
||||
}
|
||||
|
||||
function prepareOASForCombine(
|
||||
oas: OpenAPIObject,
|
||||
apiType: ApiType
|
||||
): OpenAPIObject {
|
||||
console.log(
|
||||
`🔵 Prefixing ${apiType} tags and operationId with ${upperFirst(apiType)}`
|
||||
)
|
||||
for (const pathKey in oas.paths) {
|
||||
for (const operationKey in oas.paths[pathKey]) {
|
||||
/**
|
||||
* Prefix tags declared on routes
|
||||
* e.g.: Admin Customer, Store Customer
|
||||
*/
|
||||
if (oas.paths[pathKey][operationKey].tags) {
|
||||
oas.paths[pathKey][operationKey].tags = oas.paths[pathKey][
|
||||
operationKey
|
||||
].tags.map((tag) => getPrefixedTagName(tag, apiType))
|
||||
}
|
||||
/**
|
||||
* Prefix operationId
|
||||
* e.g.: AdminGetCustomers, StoreGetCustomers
|
||||
*/
|
||||
if (oas.paths[pathKey][operationKey].operationId) {
|
||||
oas.paths[pathKey][operationKey].operationId = getPrefixedOperationId(
|
||||
oas.paths[pathKey][operationKey].operationId,
|
||||
apiType
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefix tags globally
|
||||
* e.g.: Admin Customer, Store Customer
|
||||
*/
|
||||
if (oas.tags) {
|
||||
for (const tag of oas.tags) {
|
||||
tag.name = getPrefixedTagName(tag.name, apiType)
|
||||
}
|
||||
}
|
||||
return oas
|
||||
}
|
||||
|
||||
function getPrefixedTagName(tagName: string, apiType: ApiType): string {
|
||||
return `${upperFirst(apiType)} ${tagName}`
|
||||
}
|
||||
|
||||
function getPrefixedOperationId(operationId: string, apiType: ApiType): string {
|
||||
return `${upperFirst(apiType)}${operationId}`
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { access, lstat, mkdtemp } from "fs/promises"
|
||||
import path from "path"
|
||||
import { sep } from "path"
|
||||
import { tmpdir } from "os"
|
||||
|
||||
export async function isFile(filePath: string): Promise<boolean> {
|
||||
try {
|
||||
return (await lstat(path.resolve(filePath))).isFile()
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export async function exists(filePath: string): Promise<boolean> {
|
||||
try {
|
||||
await access(path.resolve(filePath))
|
||||
return true
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export const getTmpDirectory = async () => {
|
||||
/**
|
||||
* RUNNER_TEMP: GitHub action, the path to a temporary directory on the runner.
|
||||
*/
|
||||
const tmpDir = process.env["RUNNER_TEMP"] ?? tmpdir()
|
||||
return await mkdtemp(`${tmpDir}${sep}`)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import fs from "fs/promises"
|
||||
|
||||
export const readJson = async (filePath: string): Promise<unknown> => {
|
||||
const jsonString = await fs.readFile(filePath, "utf8")
|
||||
return JSON.parse(jsonString)
|
||||
}
|
||||
|
||||
export const writeJson = async (
|
||||
filePath: string,
|
||||
jsonObject: unknown
|
||||
): Promise<void> => {
|
||||
const jsonString = JSON.stringify(jsonObject)
|
||||
await fs.writeFile(filePath, jsonString, "utf8")
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import { OpenAPIObject, TagObject } from "openapi3-ts"
|
||||
|
||||
export function mergeBaseIntoOAS(
|
||||
targetOAS: OpenAPIObject,
|
||||
sourceOAS: OpenAPIObject
|
||||
): void {
|
||||
/**
|
||||
* replace strategy for OpenAPIObject properties
|
||||
*/
|
||||
targetOAS.openapi = sourceOAS.openapi ?? targetOAS.openapi
|
||||
targetOAS.info = sourceOAS.info ?? targetOAS.info
|
||||
targetOAS.servers = sourceOAS.servers ?? targetOAS.servers
|
||||
targetOAS.security = sourceOAS.security ?? targetOAS.security
|
||||
targetOAS.externalDocs = sourceOAS.externalDocs ?? targetOAS.externalDocs
|
||||
targetOAS.webhooks = sourceOAS.webhooks ?? targetOAS.webhooks
|
||||
/**
|
||||
* merge + concat strategy for tags
|
||||
*/
|
||||
const targetTags = targetOAS.tags ?? []
|
||||
const sourceTags = sourceOAS.tags ?? []
|
||||
const combinedTags: TagObject[] = []
|
||||
const sourceIndexes: number[] = []
|
||||
for (const targetTag of targetTags) {
|
||||
for (const [sourceTagIndex, sourceTag] of sourceTags.entries()) {
|
||||
if (targetTag.name === sourceTag.name) {
|
||||
combinedTags.push(sourceTag)
|
||||
sourceIndexes.push(sourceTagIndex)
|
||||
continue
|
||||
}
|
||||
combinedTags.push(targetTag)
|
||||
}
|
||||
}
|
||||
for (const [sourceTagIndex, sourceTag] of sourceTags.entries()) {
|
||||
if (!sourceIndexes.includes(sourceTagIndex)) {
|
||||
combinedTags.push(sourceTag)
|
||||
}
|
||||
}
|
||||
targetOAS.tags = combinedTags
|
||||
/**
|
||||
* merge strategy for paths
|
||||
*/
|
||||
targetOAS.paths = Object.assign(targetOAS.paths ?? {}, sourceOAS.paths ?? {})
|
||||
/**
|
||||
* merge strategy for components
|
||||
*/
|
||||
if (!sourceOAS.components) {
|
||||
return
|
||||
}
|
||||
if (!targetOAS.components) {
|
||||
targetOAS.components = {}
|
||||
}
|
||||
for (const componentGroup of [
|
||||
"callbacks",
|
||||
"examples",
|
||||
"headers",
|
||||
"links",
|
||||
"parameters",
|
||||
"requestBodies",
|
||||
"responses",
|
||||
"schemas",
|
||||
"securitySchemes",
|
||||
]) {
|
||||
if (Object.keys(sourceOAS.components).includes(componentGroup)) {
|
||||
targetOAS.components[componentGroup] = Object.assign(
|
||||
targetOAS.components[componentGroup] ?? {},
|
||||
sourceOAS.components[componentGroup]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function mergePathsAndSchemasIntoOAS(
|
||||
targetOAS: OpenAPIObject,
|
||||
sourceOAS: OpenAPIObject
|
||||
): void {
|
||||
/**
|
||||
* merge paths
|
||||
*/
|
||||
Object.assign(targetOAS.paths, sourceOAS.paths)
|
||||
|
||||
/**
|
||||
* merge components.schemas
|
||||
*/
|
||||
if (sourceOAS.components?.schemas) {
|
||||
if (!targetOAS.components) {
|
||||
targetOAS.components = {}
|
||||
}
|
||||
if (!targetOAS.components.schemas) {
|
||||
targetOAS.components.schemas = {}
|
||||
}
|
||||
Object.assign(targetOAS.components.schemas, sourceOAS.components.schemas)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import fs from "fs/promises"
|
||||
import * as yaml from "js-yaml"
|
||||
|
||||
export const readYaml = async (filePath): Promise<unknown> => {
|
||||
const yamlString = await fs.readFile(filePath, "utf8")
|
||||
return yaml.load(yamlString)
|
||||
}
|
||||
|
||||
export const writeYaml = async (filePath: string, yamlContent: string): Promise<void> => {
|
||||
await fs.writeFile(filePath, yamlContent, "utf8")
|
||||
}
|
||||
|
||||
export const writeYamlFromJson = async (filePath, jsonObject): Promise<void> => {
|
||||
const yamlString = yaml.dump(jsonObject)
|
||||
await fs.writeFile(filePath, yamlString, "utf8")
|
||||
}
|
||||
|
||||
export const jsonObjectToYamlString = (jsonObject): string => {
|
||||
return yaml.dump(jsonObject)
|
||||
}
|
||||
|
||||
export const jsonFileToYamlFile = async (inputJsonFile, outputYamlFile) => {
|
||||
const jsonString = await fs.readFile(inputJsonFile, "utf8")
|
||||
const jsonObject = JSON.parse(jsonString)
|
||||
const yamlString = yaml.dump(jsonObject)
|
||||
await fs.writeFile(outputYamlFile, yamlString, "utf8")
|
||||
}
|
||||
Reference in New Issue
Block a user