feat(oas-cli): combine admin + store + custom OAS (#3411)
This commit is contained in:
1 parent
ef4a36794e
commit
966aea65c2
16 files changed
+818
-39
No files matched your search
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/medusa-oas-cli": minor
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(oas-cli): combine admin + store + custom OAS
|
||||||
File renamed without changes.
File renamed without changes.
@@ -13,7 +13,8 @@
|
|||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist",
|
||||||
|
"oas"
|
||||||
],
|
],
|
||||||
"author": "Sebastian Rindom",
|
"author": "Sebastian Rindom",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -25,15 +25,16 @@ yarn medusa-oas <command>
|
|||||||
|
|
||||||
This command will scan the `@medusajs/medusa` package in order to extract JSDoc OAS into a json file.
|
This command will scan the `@medusajs/medusa` package in order to extract JSDoc OAS into a json file.
|
||||||
|
|
||||||
By default, the command will output the two files `admin.oas.json` and `store.oas.json` in the same directory that the
|
The command will output one of three the files `admin.oas.json`, `store.oas.json` or `combined.oas.json` in the same
|
||||||
command was run.
|
directory that the command was run.
|
||||||
|
|
||||||
Invalid OAS with throw an error and will prevent the files from being outputted.
|
Invalid OAS with throw an error and will prevent the files from being outputted.
|
||||||
|
|
||||||
#### `--type <string>`
|
#### `--type <string>`
|
||||||
|
|
||||||
Specify which API OAS to create. Accepts `all`, `admin`, `store`.
|
Specify which API OAS to create. Accepts `admin`, `store`, `combined`.
|
||||||
Defaults to `all`.
|
|
||||||
|
The `combined` option will merge both the admin and the store APIs into a single OAS file.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn medusa-oas oas --type admin
|
yarn medusa-oas oas --type admin
|
||||||
@@ -57,6 +58,15 @@ It accepts multiple entries.
|
|||||||
yarn medusa-oas oas --paths ~/medusa-server/src
|
yarn medusa-oas oas --paths ~/medusa-server/src
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### `--base <path>`
|
||||||
|
|
||||||
|
Allows overwriting the content the API's base.yaml OAS that is fed to swagger-inline.
|
||||||
|
Paths, tags, and components will be merged together. Other OAS properties will be overwritten.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn medusa-oas oas --base ~/medusa-server/oas/custom.oas.base.yaml
|
||||||
|
```
|
||||||
|
|
||||||
#### `--dry-run`
|
#### `--dry-run`
|
||||||
|
|
||||||
Will package the OAS but will not output file. Useful for validating OAS.
|
Will package the OAS but will not output file. Useful for validating OAS.
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
module.exports = {
|
||||||
|
globals: {
|
||||||
|
"ts-jest": {
|
||||||
|
tsConfig: "tsconfig.json",
|
||||||
|
isolatedModules: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
transform: {
|
||||||
|
"^.+\\.[jt]s?$": "ts-jest",
|
||||||
|
},
|
||||||
|
testEnvironment: `node`,
|
||||||
|
moduleFileExtensions: [`js`, `ts`],
|
||||||
|
testTimeout: 30000,
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
openapi: 3.0.0
|
||||||
|
info:
|
||||||
|
version: 1.0.0
|
||||||
|
title: Medusa API
|
||||||
|
paths: { }
|
||||||
@@ -7,7 +7,8 @@
|
|||||||
"medusa-oas": "./dist/index.js"
|
"medusa-oas": "./dist/index.js"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist",
|
||||||
|
"oas"
|
||||||
],
|
],
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -20,15 +21,19 @@
|
|||||||
"author": "Medusa",
|
"author": "Medusa",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"jest": "^25.5.4",
|
||||||
|
"js-yaml": "^4.1.0",
|
||||||
|
"ts-jest": "^25.5.1",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"typescript": "4.9.5"
|
"typescript": "4.9.5",
|
||||||
|
"uuid": "^9.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "cross-env NODE_ENV=production yarn run build",
|
"prepare": "cross-env NODE_ENV=production yarn run build",
|
||||||
"build": "tsc --build",
|
"build": "tsc --build",
|
||||||
"cli": "ts-node src/index.ts",
|
"cli": "ts-node src/index.ts",
|
||||||
"test": "jest --passWithNoTests",
|
"test": "jest src",
|
||||||
"test:unit": "jest --passWithNoTests"
|
"test:unit": "jest src"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@medusajs/medusa": "*",
|
"@medusajs/medusa": "*",
|
||||||
|
|||||||
@@ -0,0 +1,465 @@
|
|||||||
|
import os from "os"
|
||||||
|
import fs from "fs/promises"
|
||||||
|
import execa from "execa"
|
||||||
|
import path from "path"
|
||||||
|
import { v4 as uid } from "uuid"
|
||||||
|
import { OpenAPIObject, SchemaObject } from "openapi3-ts"
|
||||||
|
import * as yaml from "js-yaml"
|
||||||
|
import { OperationObject } from "openapi3-ts/src/model/OpenApi"
|
||||||
|
|
||||||
|
const medusaPackagePath = path.dirname(
|
||||||
|
require.resolve("@medusajs/medusa/package.json")
|
||||||
|
)
|
||||||
|
const basePath = path.resolve(__dirname, `../../`)
|
||||||
|
|
||||||
|
const getTmpDirectory = async () => {
|
||||||
|
/**
|
||||||
|
* RUNNER_TEMP: GitHub action, the path to a temporary directory on the runner.
|
||||||
|
*/
|
||||||
|
const tmpDir = process.env["RUNNER_TEMP"] ?? os.tmpdir()
|
||||||
|
return await fs.mkdtemp(tmpDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
const runCLI = async (command: string, options: string[] = []) => {
|
||||||
|
const params = ["run", "cli", command, ...options]
|
||||||
|
try {
|
||||||
|
const { all: logs } = await execa("yarn", params, {
|
||||||
|
cwd: basePath,
|
||||||
|
all: true,
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFile = async (filePath): Promise<boolean> => {
|
||||||
|
return (await fs.lstat(path.resolve(filePath))).isFile()
|
||||||
|
}
|
||||||
|
|
||||||
|
const readJsonFile = async (filePath): Promise<unknown> => {
|
||||||
|
const jsonString = await fs.readFile(filePath, "utf8")
|
||||||
|
return JSON.parse(jsonString)
|
||||||
|
}
|
||||||
|
|
||||||
|
const readYamlFile = async (filePath): Promise<unknown> => {
|
||||||
|
const yamlString = await fs.readFile(filePath, "utf8")
|
||||||
|
return yaml.load(yamlString)
|
||||||
|
}
|
||||||
|
|
||||||
|
const listOperations = (oas: OpenAPIObject): OperationObject[] => {
|
||||||
|
const operations: OperationObject[] = []
|
||||||
|
for (const url in oas.paths) {
|
||||||
|
if (oas.paths.hasOwnProperty(url)) {
|
||||||
|
const path = oas.paths[url]
|
||||||
|
for (const method in path) {
|
||||||
|
if (path.hasOwnProperty(method)) {
|
||||||
|
switch (method) {
|
||||||
|
case "get":
|
||||||
|
case "put":
|
||||||
|
case "post":
|
||||||
|
case "delete":
|
||||||
|
case "options":
|
||||||
|
case "head":
|
||||||
|
case "patch":
|
||||||
|
operations.push(path[method])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return operations
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("command oas", () => {
|
||||||
|
let tmpDir: string
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
tmpDir = await getTmpDirectory()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("--type admin", () => {
|
||||||
|
let oas: OpenAPIObject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const outDir = path.resolve(tmpDir, uid())
|
||||||
|
await runCLI("oas", ["--type", "admin", "--out-dir", outDir])
|
||||||
|
const generatedFilePath = path.resolve(outDir, "admin.oas.json")
|
||||||
|
oas = (await readJsonFile(generatedFilePath)) as OpenAPIObject
|
||||||
|
})
|
||||||
|
|
||||||
|
it("generates oas with admin routes only", async () => {
|
||||||
|
const routes = Object.keys(oas.paths)
|
||||||
|
expect(routes.includes("/admin/products")).toBeTruthy()
|
||||||
|
expect(routes.includes("/store/products")).toBeFalsy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("generates oas using admin.oas.base.yaml", async () => {
|
||||||
|
const yamlFilePath = path.resolve(
|
||||||
|
medusaPackagePath,
|
||||||
|
"oas",
|
||||||
|
"admin.oas.base.yaml"
|
||||||
|
)
|
||||||
|
const oasBase = (await readYamlFile(yamlFilePath)) as OpenAPIObject
|
||||||
|
expect(oas.info.title).toEqual(oasBase.info.title)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("--type store", () => {
|
||||||
|
let oas: OpenAPIObject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const outDir = path.resolve(tmpDir, uid())
|
||||||
|
await runCLI("oas", ["--type", "store", "--out-dir", outDir])
|
||||||
|
const generatedFilePath = path.resolve(outDir, "store.oas.json")
|
||||||
|
oas = (await readJsonFile(generatedFilePath)) as OpenAPIObject
|
||||||
|
})
|
||||||
|
|
||||||
|
it("generates oas with store routes only", async () => {
|
||||||
|
const routes = Object.keys(oas.paths)
|
||||||
|
expect(routes.includes("/admin/products")).toBeFalsy()
|
||||||
|
expect(routes.includes("/store/products")).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("generates oas using store.oas.base.yaml", async () => {
|
||||||
|
const yamlFilePath = path.resolve(
|
||||||
|
medusaPackagePath,
|
||||||
|
"oas",
|
||||||
|
"store.oas.base.yaml"
|
||||||
|
)
|
||||||
|
const oasBase = (await readYamlFile(yamlFilePath)) as OpenAPIObject
|
||||||
|
expect(oas.info.title).toEqual(oasBase.info.title)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("--type combined", () => {
|
||||||
|
let oas: OpenAPIObject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const outDir = path.resolve(tmpDir, uid())
|
||||||
|
await runCLI("oas", ["--type", "combined", "--out-dir", outDir])
|
||||||
|
const generatedFilePath = path.resolve(outDir, "combined.oas.json")
|
||||||
|
oas = (await readJsonFile(generatedFilePath)) as OpenAPIObject
|
||||||
|
})
|
||||||
|
|
||||||
|
it("generates oas with admin and store routes", async () => {
|
||||||
|
const routes = Object.keys(oas.paths)
|
||||||
|
expect(routes.includes("/admin/products")).toBeTruthy()
|
||||||
|
expect(routes.includes("/store/products")).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("generates oas using default.oas.base.yaml", async () => {
|
||||||
|
const yamlFilePath = path.resolve(
|
||||||
|
basePath,
|
||||||
|
"oas",
|
||||||
|
"default.oas.base.yaml"
|
||||||
|
)
|
||||||
|
const oasBase = (await readYamlFile(yamlFilePath)) as OpenAPIObject
|
||||||
|
expect(oas.info.title).toEqual(oasBase.info.title)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("prefixes tags with api type", async () => {
|
||||||
|
const found = (oas.tags ?? []).filter((tag) => {
|
||||||
|
return !(tag.name.startsWith("Admin") || tag.name.startsWith("Store"))
|
||||||
|
})
|
||||||
|
expect(found).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("prefixes route's tags with api type", async () => {
|
||||||
|
const tags: string[] = listOperations(oas)
|
||||||
|
.map((operation) => {
|
||||||
|
return operation.tags ?? []
|
||||||
|
})
|
||||||
|
.flat()
|
||||||
|
const found = tags.filter((tag) => {
|
||||||
|
return !(tag.startsWith("Admin") || tag.startsWith("Store"))
|
||||||
|
})
|
||||||
|
expect(found).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("prefixes route's operationId with api type", async () => {
|
||||||
|
const operationIds: string[] = listOperations(oas)
|
||||||
|
.map((operation) => operation.operationId)
|
||||||
|
.filter((operationId): operationId is string => !!operationId)
|
||||||
|
const found = operationIds.filter((tag) => {
|
||||||
|
return !(tag.startsWith("Admin") || tag.startsWith("Store"))
|
||||||
|
})
|
||||||
|
expect(found).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("combines components.schemas from admin and store", async () => {
|
||||||
|
const schemas = Object.keys(oas.components?.schemas ?? {})
|
||||||
|
expect(schemas.includes("AdminProductsListRes")).toBeTruthy()
|
||||||
|
expect(schemas.includes("StoreProductsListRes")).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* to optimize test suite time, we only test --paths with the store api
|
||||||
|
*/
|
||||||
|
describe("--paths", () => {
|
||||||
|
let oas: OpenAPIObject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const fileContent = `
|
||||||
|
/** @oas [get] /foobar/tests
|
||||||
|
* operationId: GetFoobarTests
|
||||||
|
*/
|
||||||
|
/** @oas [get] /store/regions
|
||||||
|
* operationId: OverwrittenOperation
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @schema FoobarTestSchema
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* foo:
|
||||||
|
* type: string
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @schema StoreRegionsListRes
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* foo:
|
||||||
|
* type: string
|
||||||
|
*/
|
||||||
|
`
|
||||||
|
const additionalPath = path.resolve(tmpDir, uid())
|
||||||
|
const filePath = path.resolve(additionalPath, "foobar.ts")
|
||||||
|
await fs.mkdir(additionalPath, { recursive: true })
|
||||||
|
await fs.writeFile(filePath, fileContent, "utf8")
|
||||||
|
|
||||||
|
const outDir = path.resolve(tmpDir, uid())
|
||||||
|
await runCLI("oas", [
|
||||||
|
"--type",
|
||||||
|
"store",
|
||||||
|
"--out-dir",
|
||||||
|
outDir,
|
||||||
|
"--paths",
|
||||||
|
additionalPath,
|
||||||
|
])
|
||||||
|
const generatedFilePath = path.resolve(outDir, "store.oas.json")
|
||||||
|
oas = (await readJsonFile(generatedFilePath)) as OpenAPIObject
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should add new path to existing paths", async () => {
|
||||||
|
const routes = Object.keys(oas.paths)
|
||||||
|
expect(routes.includes("/store/products")).toBeTruthy()
|
||||||
|
expect(routes.includes("/foobar/tests")).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should overwrite existing path", async () => {
|
||||||
|
expect(oas.paths["/store/regions"]["get"].operationId).toBe(
|
||||||
|
"OverwrittenOperation"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should add new schema to existing schemas", async () => {
|
||||||
|
const schemas = Object.keys(oas.components?.schemas ?? {})
|
||||||
|
expect(schemas.includes("StoreProductsListRes")).toBeTruthy()
|
||||||
|
expect(schemas.includes("FoobarTestSchema")).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should overwrite existing schema", async () => {
|
||||||
|
const schema = oas.components?.schemas?.StoreRegionsListRes as
|
||||||
|
| SchemaObject
|
||||||
|
| undefined
|
||||||
|
expect(schema?.properties?.foo).toBeDefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* to optimize test suite time, we only test --base with the store api
|
||||||
|
*/
|
||||||
|
describe("--base", () => {
|
||||||
|
let oas: OpenAPIObject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const fileContent = `
|
||||||
|
openapi: 3.1.0
|
||||||
|
info:
|
||||||
|
version: 1.0.1
|
||||||
|
title: Custom API
|
||||||
|
servers:
|
||||||
|
- url: https://foobar.com
|
||||||
|
security:
|
||||||
|
- api_key: []
|
||||||
|
externalDocs:
|
||||||
|
url: https://docs.com
|
||||||
|
webhooks:
|
||||||
|
"foo-hook":
|
||||||
|
get:
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
tags:
|
||||||
|
- name: Products
|
||||||
|
description: Overwritten tag
|
||||||
|
- name: FoobarTag
|
||||||
|
description: Foobar tag description
|
||||||
|
paths:
|
||||||
|
"/foobar/tests":
|
||||||
|
get:
|
||||||
|
operationId: GetFoobarTests
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
"/store/regions":
|
||||||
|
get:
|
||||||
|
operationId: OverwrittenOperation
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
FoobarTestSchema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
foo:
|
||||||
|
type: string
|
||||||
|
StoreRegionsListRes:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
foo:
|
||||||
|
type: string
|
||||||
|
callbacks:
|
||||||
|
fooCallback:
|
||||||
|
get:
|
||||||
|
description: foo callback
|
||||||
|
examples:
|
||||||
|
fooExample:
|
||||||
|
description: foo example
|
||||||
|
headers:
|
||||||
|
fooHeader:
|
||||||
|
description: foo header
|
||||||
|
links:
|
||||||
|
fooLink:
|
||||||
|
description: foo link
|
||||||
|
operationRef: GetFoobarTests
|
||||||
|
parameters:
|
||||||
|
fooParameter:
|
||||||
|
description: foo parameter
|
||||||
|
name: foobar
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
requestBodies:
|
||||||
|
fooRequestBody:
|
||||||
|
description: foo requestBody
|
||||||
|
content:
|
||||||
|
"application/octet-stream": { }
|
||||||
|
responses:
|
||||||
|
fooResponse:
|
||||||
|
description: foo response
|
||||||
|
securitySchemes:
|
||||||
|
fooSecurity:
|
||||||
|
description: foo security
|
||||||
|
type: apiKey
|
||||||
|
name: foo-api-key
|
||||||
|
in: header
|
||||||
|
`
|
||||||
|
const targetDir = path.resolve(tmpDir, uid())
|
||||||
|
const filePath = path.resolve(targetDir, "custom.oas.base.yaml")
|
||||||
|
await fs.mkdir(targetDir, { recursive: true })
|
||||||
|
await fs.writeFile(filePath, fileContent, "utf8")
|
||||||
|
|
||||||
|
const outDir = path.resolve(tmpDir, uid())
|
||||||
|
await runCLI("oas", [
|
||||||
|
"--type",
|
||||||
|
"store",
|
||||||
|
"--out-dir",
|
||||||
|
outDir,
|
||||||
|
"--base",
|
||||||
|
filePath,
|
||||||
|
])
|
||||||
|
const generatedFilePath = path.resolve(outDir, "store.oas.json")
|
||||||
|
oas = (await readJsonFile(generatedFilePath)) as OpenAPIObject
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should add new path to existing paths", async () => {
|
||||||
|
const routes = Object.keys(oas.paths)
|
||||||
|
expect(routes.includes("/store/products")).toBeTruthy()
|
||||||
|
expect(routes.includes("/foobar/tests")).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should overwrite existing path", async () => {
|
||||||
|
expect(oas.paths["/store/regions"]["get"].operationId).toBe(
|
||||||
|
"OverwrittenOperation"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should add new schema to existing schemas", async () => {
|
||||||
|
const schemas = Object.keys(oas.components?.schemas ?? {})
|
||||||
|
expect(schemas.includes("StoreProductsListRes")).toBeTruthy()
|
||||||
|
expect(schemas.includes("FoobarTestSchema")).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should overwrite existing schema", async () => {
|
||||||
|
const schema = oas.components?.schemas?.StoreRegionsListRes as
|
||||||
|
| SchemaObject
|
||||||
|
| undefined
|
||||||
|
expect(schema?.properties?.foo).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should replace base properties", async () => {
|
||||||
|
expect(oas.openapi).toBe("3.1.0")
|
||||||
|
expect(oas.info).toEqual({ version: "1.0.1", title: "Custom API" })
|
||||||
|
expect(oas.servers).toEqual([{ url: "https://foobar.com" }])
|
||||||
|
expect(oas.security).toEqual([{ api_key: [] }])
|
||||||
|
expect(oas.externalDocs).toEqual({ url: "https://docs.com" })
|
||||||
|
expect(oas.webhooks).toEqual({
|
||||||
|
"foo-hook": { get: { responses: { "200": { description: "OK" } } } },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should add new tag", async () => {
|
||||||
|
expect(oas.tags).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
name: "FoobarTag",
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should overwrite existing tag", async () => {
|
||||||
|
expect(oas.tags).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
name: "Products",
|
||||||
|
description: "Overwritten tag",
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should add new components", async () => {
|
||||||
|
const components = oas.components ?? {}
|
||||||
|
expect(
|
||||||
|
Object.keys(components.callbacks ?? {}).includes("fooCallback")
|
||||||
|
).toBeTruthy()
|
||||||
|
expect(
|
||||||
|
Object.keys(components.examples ?? {}).includes("fooExample")
|
||||||
|
).toBeTruthy()
|
||||||
|
expect(
|
||||||
|
Object.keys(components.headers ?? {}).includes("fooHeader")
|
||||||
|
).toBeTruthy()
|
||||||
|
expect(
|
||||||
|
Object.keys(components.links ?? {}).includes("fooLink")
|
||||||
|
).toBeTruthy()
|
||||||
|
expect(
|
||||||
|
Object.keys(components.parameters ?? {}).includes("fooParameter")
|
||||||
|
).toBeTruthy()
|
||||||
|
expect(
|
||||||
|
Object.keys(components.requestBodies ?? {}).includes("fooRequestBody")
|
||||||
|
).toBeTruthy()
|
||||||
|
expect(
|
||||||
|
Object.keys(components.responses ?? {}).includes("fooResponse")
|
||||||
|
).toBeTruthy()
|
||||||
|
expect(
|
||||||
|
Object.keys(components.securitySchemes ?? {}).includes("fooSecurity")
|
||||||
|
).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -4,6 +4,11 @@ import swaggerInline from "swagger-inline"
|
|||||||
import OpenAPIParser from "@readme/openapi-parser"
|
import OpenAPIParser from "@readme/openapi-parser"
|
||||||
import { OpenAPIObject } from "openapi3-ts"
|
import { OpenAPIObject } from "openapi3-ts"
|
||||||
import { Command, Option, OptionValues } from "commander"
|
import { Command, Option, OptionValues } from "commander"
|
||||||
|
import { combineOAS } from "./utils/combine-oas"
|
||||||
|
import {
|
||||||
|
mergeBaseIntoOAS,
|
||||||
|
mergePathsAndSchemasIntoOAS,
|
||||||
|
} from "./utils/merge-oas"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constants
|
* Constants
|
||||||
@@ -12,8 +17,7 @@ import { Command, Option, OptionValues } from "commander"
|
|||||||
const medusaPackagePath = path.dirname(
|
const medusaPackagePath = path.dirname(
|
||||||
require.resolve("@medusajs/medusa/package.json")
|
require.resolve("@medusajs/medusa/package.json")
|
||||||
)
|
)
|
||||||
|
const basePath = path.resolve(__dirname, "../")
|
||||||
type ApiType = "store" | "admin"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CLI Command declaration
|
* CLI Command declaration
|
||||||
@@ -23,9 +27,9 @@ export const commandDescription =
|
|||||||
"Compile full OAS from swagger-inline compliant JSDoc."
|
"Compile full OAS from swagger-inline compliant JSDoc."
|
||||||
|
|
||||||
export const commandOptions: Option[] = [
|
export const commandOptions: Option[] = [
|
||||||
new Option("-t, --type <type>", "API type to compile []")
|
new Option("-t, --type <type>", "API type to compile.")
|
||||||
.choices(["all", "admin", "store"])
|
.choices(["admin", "store", "combined"])
|
||||||
.default("all"),
|
.makeOptionMandatory(),
|
||||||
new Option(
|
new Option(
|
||||||
"-o, --out-dir <outDir>",
|
"-o, --out-dir <outDir>",
|
||||||
"Destination directory to output generated OAS files."
|
"Destination directory to output generated OAS files."
|
||||||
@@ -35,6 +39,10 @@ export const commandOptions: Option[] = [
|
|||||||
"-p, --paths <paths...>",
|
"-p, --paths <paths...>",
|
||||||
"Additional paths to crawl for OAS JSDoc."
|
"Additional paths to crawl for OAS JSDoc."
|
||||||
),
|
),
|
||||||
|
new Option(
|
||||||
|
"-b, --base <base>",
|
||||||
|
"Custom base OAS file to use for swagger-inline."
|
||||||
|
),
|
||||||
new Option("-F, --force", "Ignore OAS validation and output OAS files."),
|
new Option("-F, --force", "Ignore OAS validation and output OAS files."),
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -58,8 +66,7 @@ export async function execute(cliParams: OptionValues) {
|
|||||||
const dryRun = !!cliParams.dryRun
|
const dryRun = !!cliParams.dryRun
|
||||||
const force = !!cliParams.force
|
const force = !!cliParams.force
|
||||||
|
|
||||||
const apiTypesToExport =
|
const apiType: ApiType = cliParams.type
|
||||||
cliParams.type === "all" ? ["store", "admin"] : [cliParams.type]
|
|
||||||
|
|
||||||
const outDir = path.resolve(cliParams.outDir)
|
const outDir = path.resolve(cliParams.outDir)
|
||||||
|
|
||||||
@@ -72,6 +79,13 @@ export async function execute(cliParams: OptionValues) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const baseFile = cliParams.base ? path.resolve(cliParams.base) : undefined
|
||||||
|
if (baseFile) {
|
||||||
|
if (!(await isFile(cliParams.base))) {
|
||||||
|
throw new Error(`--base must be a file - ${baseFile}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Command execution
|
* Command execution
|
||||||
*/
|
*/
|
||||||
@@ -79,13 +93,30 @@ export async function execute(cliParams: OptionValues) {
|
|||||||
await mkdir(outDir, { recursive: true })
|
await mkdir(outDir, { recursive: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const apiType of apiTypesToExport) {
|
let oas: OpenAPIObject
|
||||||
console.log(`🟣 Generating OAS - ${apiType}`)
|
console.log(`🟣 Generating OAS - ${apiType}`)
|
||||||
const oas = await getOASFromCodebase(apiType as ApiType, additionalPaths)
|
|
||||||
await validateOAS(oas, apiType as ApiType, force)
|
if (apiType === "combined") {
|
||||||
if (!dryRun) {
|
const adminOAS = await getOASFromCodebase("admin")
|
||||||
await exportOASToJSON(oas, apiType as ApiType, outDir)
|
const storeOAS = await getOASFromCodebase("store")
|
||||||
|
oas = await combineOAS(adminOAS, storeOAS)
|
||||||
|
} else {
|
||||||
|
oas = await getOASFromCodebase(apiType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (additionalPaths.length || baseFile) {
|
||||||
|
const customOAS = await getOASFromPaths(additionalPaths, baseFile)
|
||||||
|
if (baseFile) {
|
||||||
|
mergeBaseIntoOAS(oas, customOAS)
|
||||||
}
|
}
|
||||||
|
if (additionalPaths.length) {
|
||||||
|
mergePathsAndSchemasIntoOAS(oas, customOAS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await validateOAS(oas, apiType, force)
|
||||||
|
if (!dryRun) {
|
||||||
|
await exportOASToJSON(oas, apiType, outDir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +125,7 @@ export async function execute(cliParams: OptionValues) {
|
|||||||
*/
|
*/
|
||||||
async function getOASFromCodebase(
|
async function getOASFromCodebase(
|
||||||
apiType: ApiType,
|
apiType: ApiType,
|
||||||
additionalPaths: string[] = []
|
customBaseFile?: string
|
||||||
): Promise<OpenAPIObject> {
|
): Promise<OpenAPIObject> {
|
||||||
const gen = await swaggerInline(
|
const gen = await swaggerInline(
|
||||||
[
|
[
|
||||||
@@ -102,18 +133,30 @@ async function getOASFromCodebase(
|
|||||||
path.resolve(medusaPackagePath, "dist", "types"),
|
path.resolve(medusaPackagePath, "dist", "types"),
|
||||||
path.resolve(medusaPackagePath, "dist", "api/middlewares"),
|
path.resolve(medusaPackagePath, "dist", "api/middlewares"),
|
||||||
path.resolve(medusaPackagePath, "dist", `api/routes/${apiType}`),
|
path.resolve(medusaPackagePath, "dist", `api/routes/${apiType}`),
|
||||||
...additionalPaths,
|
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
base: path.resolve(
|
base:
|
||||||
medusaPackagePath,
|
customBaseFile ??
|
||||||
"oas",
|
path.resolve(medusaPackagePath, "oas", `${apiType}.oas.base.yaml`),
|
||||||
`${apiType}-spec3-base.yaml`
|
|
||||||
),
|
|
||||||
format: ".json",
|
format: ".json",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
return await OpenAPIParser.parse(JSON.parse(gen))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getOASFromPaths(
|
||||||
|
additionalPaths: string[] = [],
|
||||||
|
customBaseFile?: string
|
||||||
|
): Promise<OpenAPIObject> {
|
||||||
|
console.log(`🔵 Gathering custom OAS`)
|
||||||
|
const gen = await swaggerInline(additionalPaths, {
|
||||||
|
base:
|
||||||
|
customBaseFile ?? path.resolve(basePath, "oas", "default.oas.base.yaml"),
|
||||||
|
format: ".json",
|
||||||
|
logger: (log) => {
|
||||||
|
console.log(log)
|
||||||
|
},
|
||||||
|
})
|
||||||
return await OpenAPIParser.parse(JSON.parse(gen))
|
return await OpenAPIParser.parse(JSON.parse(gen))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,5 +188,19 @@ async function exportOASToJSON(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function isDirectory(dirPath: string): Promise<boolean> {
|
async function isDirectory(dirPath: string): Promise<boolean> {
|
||||||
return (await lstat(path.resolve(dirPath))).isDirectory()
|
try {
|
||||||
|
return (await lstat(path.resolve(dirPath))).isDirectory()
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function isFile(filePath: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
return (await lstat(path.resolve(filePath))).isFile()
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
type ApiType = "store" | "admin" | "combined"
|
||||||
@@ -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,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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,8 +24,11 @@
|
|||||||
"src"
|
"src"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules",
|
"dist",
|
||||||
"**/tests/*"
|
"src/**/__tests__",
|
||||||
|
"src/**/__mocks__",
|
||||||
|
"src/**/__fixtures__",
|
||||||
|
"node_modules"
|
||||||
],
|
],
|
||||||
"ts-node": {
|
"ts-node": {
|
||||||
"transpileOnly": true
|
"transpileOnly": true
|
||||||
|
|||||||
@@ -19,9 +19,8 @@ const redoclyConfigPath = path.resolve(
|
|||||||
const run = async () => {
|
const run = async () => {
|
||||||
const outputPath = isDryRun ? await getTmpDirectory() : docsApiPath
|
const outputPath = isDryRun ? await getTmpDirectory() : docsApiPath
|
||||||
|
|
||||||
await generateOASSources(outputPath)
|
|
||||||
|
|
||||||
for (const apiType of ["store", "admin"]) {
|
for (const apiType of ["store", "admin"]) {
|
||||||
|
await generateOASSource(outputPath, apiType)
|
||||||
const inputJsonFile = path.resolve(outputPath, `${apiType}.oas.json`)
|
const inputJsonFile = path.resolve(outputPath, `${apiType}.oas.json`)
|
||||||
const outputYamlFile = path.resolve(outputPath, `${apiType}.oas.yaml`)
|
const outputYamlFile = path.resolve(outputPath, `${apiType}.oas.yaml`)
|
||||||
|
|
||||||
@@ -34,11 +33,8 @@ const run = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const generateOASSources = async (outDir, isDryRun) => {
|
const generateOASSource = async (outDir, apiType) => {
|
||||||
const params = ["oas", `--out-dir=${outDir}`]
|
const params = ["oas", `--type=${apiType}`, `--out-dir=${outDir}`]
|
||||||
if (isDryRun) {
|
|
||||||
params.push("--dry-run")
|
|
||||||
}
|
|
||||||
const { all: logs } = await execa("medusa-oas", params, {
|
const { all: logs } = await execa("medusa-oas", params, {
|
||||||
cwd: basePath,
|
cwd: basePath,
|
||||||
all: true,
|
all: true,
|
||||||
@@ -78,6 +74,7 @@ const circularReferenceCheck = async (srcFile) => {
|
|||||||
`🔴 Unhandled circular references - ${fileName} - Please patch in docs-util/redocly/config.yaml`
|
`🔴 Unhandled circular references - ${fileName} - Please patch in docs-util/redocly/config.yaml`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
console.log(`🟢 All circular references handled`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const generateReference = async (srcFile, apiType) => {
|
const generateReference = async (srcFile, apiType) => {
|
||||||
|
|||||||
@@ -5913,11 +5913,15 @@ __metadata:
|
|||||||
"@readme/openapi-parser": ^2.4.0
|
"@readme/openapi-parser": ^2.4.0
|
||||||
"@types/lodash": ^4.14.191
|
"@types/lodash": ^4.14.191
|
||||||
commander: ^10.0.0
|
commander: ^10.0.0
|
||||||
|
jest: ^25.5.4
|
||||||
|
js-yaml: ^4.1.0
|
||||||
lodash: ^4.17.21
|
lodash: ^4.17.21
|
||||||
openapi3-ts: ^3.1.2
|
openapi3-ts: ^3.1.2
|
||||||
swagger-inline: ^6.1.0
|
swagger-inline: ^6.1.0
|
||||||
|
ts-jest: ^25.5.1
|
||||||
ts-node: ^10.9.1
|
ts-node: ^10.9.1
|
||||||
typescript: 4.9.5
|
typescript: 4.9.5
|
||||||
|
uuid: ^9.0.0
|
||||||
bin:
|
bin:
|
||||||
medusa-oas: ./dist/index.js
|
medusa-oas: ./dist/index.js
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
|
|||||||
Reference in new issue
Block a user