feat(oas): new medusa-oas docs for Redocly and circular references (#3745)

## What

New `medusa-oas docs` to improve OAS compatibility with for Redocly API documentation viewer and handle deep circular references.

## Why

We wish to share the tooling we have been using to generate our public API documentation.

## How

* Move `/docs-utils/redocly` under `medusa-oas-cli` package.
* Move  some of the operations from `oas-github-ci/scripts/build-openapi.js` under the `medusa-oas docs` command.
* Improve DevX when dealing with circular references by outputting precise troubleshooting recommendations in the error message.
* Extract some common operations in utility methods.

## Tests

### Step 1
* Run `yarn install`
* Run `yarn build`
* Run `yarn openapi:generate --dry-run`
* Expect same behaviour as before where OAS is validated and circular references are checked.

### Step 2
* Run `yarn openapi:generate`
* Expect same behaviour as before where API documentation is generated in `/docs`

### Step 3
* Move to the `packages/oas/medusa-oas-cli`
* Run `yarn medusa-oas oas --type store --out-dir ~/tmp/oas` to generate the raw OAS file.
* Run `yarn medusa-oas docs --src-file ~/tmp/oas/store.oas.json --preview`
* Open url from the console output in a browser
* Expect a preview of the API documentation using Redocly.

### Step 4
* Run `yarn medusa-oas docs --src-file ~/tmp/oas/store.oas.json --out-dir ~/tmp/docs/store --clean --split`
* Expect a similiar output as `yarn openapi:generate`

### Step 5
* Run `yarn medusa-oas docs --src-file ~/tmp/oas/store.oas.json --out-dir ~/tmp/docs/store --clean --html`
* Expect `index.html` to have been created.
* Run `npx http-server ~/tmp/docs/store -p 8000`
* Open http://127.0.0.1:8000 in a browser.
* Expect a zero-dependency static rendering of the API documentation using Redocly.

### Step 6
* To emulate an unhandled circular reference, edit [packages/oas/medusa-oas-cli/redocly/redocly-config.yaml](https://github.com/medusajs/medusa/blob/d180f47e1609e74f46f72cdeef6265a654da3e7e/packages/oas/medusa-oas-cli/redocly/redocly-config.yaml#L9-L10) and comment out "Address: - Customer"
* Run `yarn medusa-oas docs --src-file ~/tmp/oas/store.oas.json --dry-run`
* Expect an error message with a hint on how to resolve the issue.
* Create a file `~/tmp/redocly-config.yaml` and paste in the recommendation from the error message.
* Run `yarn medusa-oas docs --src-file ~/tmp/oas/store.oas.json --dry-run --config ~/tmp/redocly-config.yaml` 
* Expect Dry run to succeed.
This commit is contained in:
Patrick
2023-04-12 17:16:15 +00:00
committed by GitHub
parent 1d50a5e5c1
commit 1456056e8f
20 changed files with 1744 additions and 152 deletions
-157
View File
@@ -1,157 +0,0 @@
plugins:
- "./plugins/plugin.js"
# Allows to replace a $ref with `type: object` in order to avoid infinite loops
# when Redocly attempts to render circular references.
decorators:
plugin/circular-patch:
verbose: false
schemas:
Address:
- Customer
Cart:
- Customer
- Order
- Payment
- PaymentSession
ClaimImage:
- ClaimItem
ClaimItem:
- ClaimOrder
ClaimOrder:
- Fulfillment
- Order
- Return
Country:
- Region
Customer:
- Order
CustomerGroup:
- Customer
- PriceList
Discount:
- Discount
DiscountRule:
- DiscountCondition
DraftOrder:
- Cart
- Order
Fulfillment:
- ClaimOrder
- Order
- Swap
FulfillmentItem:
- Fulfillment
GiftCard:
- Order
GiftCardTransaction:
- GiftCard
- Order
LineItem:
- Cart
- ClaimOrder
- Order
- OrderEdit
- Swap
LineItemAdjustment:
- LineItem
LineItemTaxLine:
- LineItem
MoneyAmount:
- PriceList
- ProductVariant
- Region
Notification:
- Notification
Order:
- Cart
- ClaimOrder
- Customer
- DraftOrder
- Fulfillment
- OrderEdit
- Payment
- Refund
- Return
- Swap
OrderEdit:
- Order
OrderItemChange:
- OrderEdit
Payment:
- Cart
- Order
- Swap
ProductCategory:
- ProductCategory
- Product
ProductCollection:
- Product
ProductOption:
- Product
ProductOptionValue:
- ProductOption
- ProductVariant
ProductVariant:
- Product
ProductVariantInventoryItem:
- ProductVariant
Refund:
- Order
- Payment
Return:
- ClaimOrder
- Order
- Swap
ReturnItem:
- Return
ReturnReason:
- ReturnReason
SalesChannelLocation:
- SalesChannel
ShippingMethod:
- Cart
- ClaimOrder
- Order
- Payment
- Return
- Swap
ShippingMethodTaxLine:
- ShippingMethod
ShippingOption:
- Region
ShippingOptionRequirement:
- ShippingOption
ShippingProfile:
- Product
- ShippingOption
Swap:
- Cart
- Fulfillment
- Order
- Payment
- Return
TaxRate:
- Region
TrackingLink:
- Fulfillment
# Similar config to /www/docs/docusaurus.config.js > redocusaurus
# Allows to emulate rendering of API public documentation when using `yarn redocly preview-docs openapi.yaml`
theme:
openapi:
theme:
colors:
primary:
dart: "#242526"
sidebar:
width: "250px"
disableSearch: true
expandResponses: "200,204"
generatedPayloadSamplesMaxDepth: 4
hideDownloadButton: true
hideRequestPayloadSample: true
nativeScrollbars: true
requiredPropsFirst: true
showObjectSchemaExamples: true
sortTagsAlphabetically: true
@@ -1,87 +0,0 @@
module.exports = CircularPatch
/**
* Since ref() is triggered upon reaching a $ref OAS line (leaf),
* we want to inverse `schemas` instructions in order to optimize iterators.
*/
function preparePatches(schemas) {
const patches = []
const patchesObj = {}
for (const schemaToPatch in schemas) {
for (const schemaName of schemas[schemaToPatch]) {
if (!patchesObj[schemaName]) {
patchesObj[schemaName] = {
schemaName,
schemaPointer: `#/components/schemas/${schemaName}`,
schemaToPatchPointers: [],
}
}
const schemaToPatchPointer = `#/components/schemas/${schemaToPatch}`
if (
!patchesObj[schemaName].schemaToPatchPointers.includes(
schemaToPatchPointer
)
) {
patchesObj[schemaName].schemaToPatchPointers.push(schemaToPatchPointer)
}
}
}
for (const key in patchesObj) {
patches.push(patchesObj[key])
}
return patches
}
function applyPatch(node, schemaName) {
delete node["$ref"]
node.type = "object"
if (!node.description && schemaName) {
node.description = `${schemaName} object.`
}
}
function CircularPatch({ schemas = {}, verbose = false }) {
const logs = []
const patches = preparePatches(schemas)
const refPathPrefix = "#/components/schemas/"
const refPathPrefixLength = refPathPrefix.length
const refPathPrefixRegex = /#\/components\/schemas\/\w+/
return {
ref(ref, ctx, resolved) {
if (
ctx.type.name.toLowerCase() !== "schema" ||
!resolved?.location?.pointer
) {
return
}
for (const patch of patches) {
if (resolved.location.pointer !== patch.schemaPointer) {
continue
}
const ctxSchemaPointer =
ctx.location.pointer.match(refPathPrefixRegex)[0]
if (!patch.schemaToPatchPointers.includes(ctxSchemaPointer)) {
continue
}
applyPatch(ref)
if (verbose) {
logs.push(
`${ctxSchemaPointer.substring(refPathPrefixLength)} patch $ref to ${
patch.schemaName
}`
)
}
}
},
Root: {
leave() {
if (verbose) {
logs.sort()
for (const log of logs) {
console.debug(log)
}
}
},
},
}
}
-14
View File
@@ -1,14 +0,0 @@
const CircularPatch = require("./decorators/circular-patch")
const id = "plugin"
const decorators = {
oas3: {
"circular-patch": CircularPatch,
},
}
module.exports = {
id,
decorators,
}