fix(medusa-oas-cli): fix tool not working in Medusa backends (#6812)
* fix(medusa-oas-cli): fix tool not working in custom projects * fix changeset message --------- Co-authored-by: Riqwan Thamir <rmthamir@gmail.com>
This commit is contained in:
1 parent
509ddf9a56
commit
e005987adf
6 files changed
+263
-26
No files matched your search
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/client-types": patch
|
||||||
|
"@medusajs/medusa-oas-cli": patch
|
||||||
|
"@medusajs/oas-github-ci": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix(medusa-oas-cli): fix tool not working in Medusa backends
|
||||||
@@ -16,7 +16,7 @@ async function run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const generateOASSources = async (outDir: string) => {
|
const generateOASSources = async (outDir: string) => {
|
||||||
const params = ["oas", `--out-dir=${outDir}`, "--type=combined"]
|
const params = ["oas", `--out-dir=${outDir}`, "--type=combined", "--local"]
|
||||||
const { all: logs } = await execa("medusa-oas", params, {
|
const { all: logs } = await execa("medusa-oas", params, {
|
||||||
cwd: basePath,
|
cwd: basePath,
|
||||||
all: true,
|
all: true,
|
||||||
|
|||||||
@@ -8,9 +8,6 @@ import { readYaml } from "../utils/yaml-utils"
|
|||||||
import { readJson } from "../utils/json-utils"
|
import { readJson } from "../utils/json-utils"
|
||||||
import execa from "execa"
|
import execa from "execa"
|
||||||
|
|
||||||
const medusaPackagePath = path.dirname(
|
|
||||||
require.resolve("@medusajs/medusa/package.json")
|
|
||||||
)
|
|
||||||
/**
|
/**
|
||||||
* OAS output directory
|
* OAS output directory
|
||||||
*
|
*
|
||||||
@@ -77,7 +74,7 @@ describe("command oas", () => {
|
|||||||
*/
|
*/
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const outDir = path.resolve(tmpDir, uid())
|
const outDir = path.resolve(tmpDir, uid())
|
||||||
await runCLI("oas", ["--type", "admin", "--out-dir", outDir])
|
await runCLI("oas", ["--type", "admin", "--out-dir", outDir, "--local"])
|
||||||
const generatedFilePath = path.resolve(outDir, "admin.oas.json")
|
const generatedFilePath = path.resolve(outDir, "admin.oas.json")
|
||||||
oas = (await readJson(generatedFilePath)) as OpenAPIObject
|
oas = (await readJson(generatedFilePath)) as OpenAPIObject
|
||||||
})
|
})
|
||||||
@@ -104,7 +101,7 @@ describe("command oas", () => {
|
|||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const outDir = path.resolve(tmpDir, uid())
|
const outDir = path.resolve(tmpDir, uid())
|
||||||
await runCLI("oas", ["--type", "store", "--out-dir", outDir])
|
await runCLI("oas", ["--type", "store", "--out-dir", outDir, "--local"])
|
||||||
const generatedFilePath = path.resolve(outDir, "store.oas.json")
|
const generatedFilePath = path.resolve(outDir, "store.oas.json")
|
||||||
oas = (await readJson(generatedFilePath)) as OpenAPIObject
|
oas = (await readJson(generatedFilePath)) as OpenAPIObject
|
||||||
})
|
})
|
||||||
@@ -131,7 +128,7 @@ describe("command oas", () => {
|
|||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const outDir = path.resolve(tmpDir, uid())
|
const outDir = path.resolve(tmpDir, uid())
|
||||||
await runCLI("oas", ["--type", "combined", "--out-dir", outDir])
|
await runCLI("oas", ["--type", "combined", "--out-dir", outDir, "--local"])
|
||||||
const generatedFilePath = path.resolve(outDir, "combined.oas.json")
|
const generatedFilePath = path.resolve(outDir, "combined.oas.json")
|
||||||
oas = (await readJson(generatedFilePath)) as OpenAPIObject
|
oas = (await readJson(generatedFilePath)) as OpenAPIObject
|
||||||
})
|
})
|
||||||
@@ -230,6 +227,7 @@ describe("command oas", () => {
|
|||||||
outDir,
|
outDir,
|
||||||
"--paths",
|
"--paths",
|
||||||
additionalPath,
|
additionalPath,
|
||||||
|
"--local"
|
||||||
])
|
])
|
||||||
const generatedFilePath = path.resolve(outDir, "store.oas.json")
|
const generatedFilePath = path.resolve(outDir, "store.oas.json")
|
||||||
oas = (await readJson(generatedFilePath)) as OpenAPIObject
|
oas = (await readJson(generatedFilePath)) as OpenAPIObject
|
||||||
@@ -365,6 +363,7 @@ components:
|
|||||||
outDir,
|
outDir,
|
||||||
"--base",
|
"--base",
|
||||||
filePath,
|
filePath,
|
||||||
|
"--local"
|
||||||
])
|
])
|
||||||
const generatedFilePath = path.resolve(outDir, "store.oas.json")
|
const generatedFilePath = path.resolve(outDir, "store.oas.json")
|
||||||
oas = (await readJson(generatedFilePath)) as OpenAPIObject
|
oas = (await readJson(generatedFilePath)) as OpenAPIObject
|
||||||
@@ -455,4 +454,216 @@ components:
|
|||||||
).toBeTruthy()
|
).toBeTruthy()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("public OAS", () => {
|
||||||
|
let oas: OpenAPIObject
|
||||||
|
/**
|
||||||
|
* In a CI context, beforeAll might exceed the configured jest timeout.
|
||||||
|
* Until we upgrade our jest version, the timeout error will be swallowed
|
||||||
|
* and the test will fail in unexpected ways.
|
||||||
|
*/
|
||||||
|
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 readJson(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()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("public OAS with 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 readJson(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()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
@@ -26,16 +26,6 @@ const medusaTypesPath = path.dirname(
|
|||||||
const medusaUtilsPath = path.dirname(
|
const medusaUtilsPath = path.dirname(
|
||||||
require.resolve("@medusajs/utils/package.json")
|
require.resolve("@medusajs/utils/package.json")
|
||||||
)
|
)
|
||||||
/**
|
|
||||||
* OAS output directory
|
|
||||||
*
|
|
||||||
* @privateRemark
|
|
||||||
* This should be the only directory OAS is loaded from for Medusa V2.
|
|
||||||
* For now, we only use it if the --v2 flag it passed to the CLI tool.
|
|
||||||
*/
|
|
||||||
const oasOutputPath = path.resolve(
|
|
||||||
__dirname, "..", "..", "..", "..", "docs-util", "oas-output"
|
|
||||||
)
|
|
||||||
const basePath = path.resolve(__dirname, "../")
|
const basePath = path.resolve(__dirname, "../")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,6 +56,10 @@ export const commandOptions: Option[] = [
|
|||||||
new Option(
|
new Option(
|
||||||
"--v2",
|
"--v2",
|
||||||
"Generate OAS files for V2 endpoints. This loads OAS from docs-util/oas-output/operations directory"
|
"Generate OAS files for V2 endpoints. This loads OAS from docs-util/oas-output/operations directory"
|
||||||
|
),
|
||||||
|
new Option(
|
||||||
|
"--local",
|
||||||
|
"Generate OAS from local files rather than public OAS. This is useful for generating references in the Medusa monorepo."
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -90,6 +84,7 @@ export async function execute(cliParams: OptionValues) {
|
|||||||
const dryRun = !!cliParams.dryRun
|
const dryRun = !!cliParams.dryRun
|
||||||
const force = !!cliParams.force
|
const force = !!cliParams.force
|
||||||
const v2 = !!cliParams.v2
|
const v2 = !!cliParams.v2
|
||||||
|
const local = !!cliParams.local
|
||||||
|
|
||||||
const apiType: ApiType = cliParams.type
|
const apiType: ApiType = cliParams.type
|
||||||
|
|
||||||
@@ -122,11 +117,11 @@ export async function execute(cliParams: OptionValues) {
|
|||||||
console.log(`🟣 Generating OAS - ${apiType}`)
|
console.log(`🟣 Generating OAS - ${apiType}`)
|
||||||
|
|
||||||
if (apiType === "combined") {
|
if (apiType === "combined") {
|
||||||
const adminOAS = await getOASFromCodebase("admin", undefined, v2)
|
const adminOAS = !local ? await getPublicOas("admin", v2) : await getOASFromCodebase("admin", v2)
|
||||||
const storeOAS = await getOASFromCodebase("store", undefined, v2)
|
const storeOAS = !local ? await getPublicOas("store", v2) : await getOASFromCodebase("store", v2)
|
||||||
oas = await combineOAS(adminOAS, storeOAS)
|
oas = await combineOAS(adminOAS, storeOAS)
|
||||||
} else {
|
} else {
|
||||||
oas = await getOASFromCodebase(apiType, undefined, v2)
|
oas = !local ? await getPublicOas(apiType, v2) : await getOASFromCodebase(apiType, v2)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (additionalPaths.length || baseFile) {
|
if (additionalPaths.length || baseFile) {
|
||||||
@@ -152,9 +147,18 @@ export async function execute(cliParams: OptionValues) {
|
|||||||
*/
|
*/
|
||||||
async function getOASFromCodebase(
|
async function getOASFromCodebase(
|
||||||
apiType: ApiType,
|
apiType: ApiType,
|
||||||
customBaseFile?: string,
|
|
||||||
v2?: boolean
|
v2?: boolean
|
||||||
): Promise<OpenAPIObject> {
|
): Promise<OpenAPIObject> {
|
||||||
|
/**
|
||||||
|
* OAS output directory
|
||||||
|
*
|
||||||
|
* @privateRemark
|
||||||
|
* This should be the only directory OAS is loaded from for Medusa V2.
|
||||||
|
* For now, we only use it if the --v2 flag it passed to the CLI tool.
|
||||||
|
*/
|
||||||
|
const oasOutputPath = path.resolve(
|
||||||
|
__dirname, "..", "..", "..", "..", "docs-util", "oas-output"
|
||||||
|
)
|
||||||
const gen = await swaggerInline(
|
const gen = await swaggerInline(
|
||||||
v2 ? [
|
v2 ? [
|
||||||
path.resolve(oasOutputPath, "operations", apiType),
|
path.resolve(oasOutputPath, "operations", apiType),
|
||||||
@@ -171,15 +175,21 @@ async function getOASFromCodebase(
|
|||||||
path.resolve(medusaPackagePath, "dist", `api/routes/${apiType}`),
|
path.resolve(medusaPackagePath, "dist", `api/routes/${apiType}`),
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
base:
|
base: path.resolve(oasOutputPath, v2 ? "base-v2" : "base", `${apiType}.oas.base.yaml`),
|
||||||
customBaseFile ??
|
|
||||||
path.resolve(oasOutputPath, v2 ? "base-v2" : "base", `${apiType}.oas.base.yaml`),
|
|
||||||
format: ".json",
|
format: ".json",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return (await OpenAPIParser.parse(JSON.parse(gen))) as OpenAPIObject
|
return (await OpenAPIParser.parse(JSON.parse(gen))) as OpenAPIObject
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getPublicOas(
|
||||||
|
apiType: ApiType,
|
||||||
|
v2?: boolean
|
||||||
|
) {
|
||||||
|
const url = `https://docs.medusajs.com/api/download/${apiType}?version=${v2 ? "2" : "1"}`
|
||||||
|
return await OpenAPIParser.parse(url) as OpenAPIObject
|
||||||
|
}
|
||||||
|
|
||||||
async function getOASFromPaths(
|
async function getOASFromPaths(
|
||||||
additionalPaths: string[] = [],
|
additionalPaths: string[] = [],
|
||||||
customBaseFile?: string
|
customBaseFile?: string
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const run = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const generateOASSource = async (outDir, apiType) => {
|
const generateOASSource = async (outDir, apiType) => {
|
||||||
const commandParams = ["oas", `--type=${apiType}`, `--out-dir=${outDir}`]
|
const commandParams = ["oas", `--type=${apiType}`, `--out-dir=${outDir}`, "--local"]
|
||||||
if (v2) {
|
if (v2) {
|
||||||
commandParams.push(`--v2`)
|
commandParams.push(`--v2`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { existsSync, readFileSync } from "fs"
|
import { existsSync, readFileSync } from "fs"
|
||||||
import { NextResponse } from "next/server"
|
import { NextResponse } from "next/server"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
|
import { Version } from "../../../../types/openapi"
|
||||||
|
|
||||||
type DownloadParams = {
|
type DownloadParams = {
|
||||||
params: {
|
params: {
|
||||||
@@ -9,8 +10,16 @@ type DownloadParams = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function GET(request: Request, { params }: DownloadParams) {
|
export function GET(request: Request, { params }: DownloadParams) {
|
||||||
|
const { searchParams } = new URL(request.url)
|
||||||
const { area } = params
|
const { area } = params
|
||||||
const filePath = path.join(process.cwd(), `specs/${area}/openapi.full.yaml`)
|
const version =
|
||||||
|
process.env.NEXT_PUBLIC_VERSIONING === "true"
|
||||||
|
? (searchParams.get("version") as Version) || "1"
|
||||||
|
: "1"
|
||||||
|
const filePath = path.join(
|
||||||
|
process.cwd(),
|
||||||
|
`${version === "1" ? "specs" : "specs-v2"}/${area}/openapi.full.yaml`
|
||||||
|
)
|
||||||
|
|
||||||
if (!existsSync(filePath)) {
|
if (!existsSync(filePath)) {
|
||||||
return new NextResponse(null, {
|
return new NextResponse(null, {
|
||||||
|
|||||||
Reference in new issue
Block a user