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](d180f47e16/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 13:16:15 -04:00
committed by GitHub
parent 1d50a5e5c1
commit 1456056e8f
20 changed files with 1744 additions and 152 deletions

View File

@@ -4,37 +4,42 @@ const fs = require("fs/promises")
const os = require("os")
const path = require("path")
const execa = require("execa")
const yaml = require("js-yaml")
const OpenAPIParser = require("@readme/openapi-parser")
const isDryRun = process.argv.indexOf("--dry-run") !== -1
const basePath = path.resolve(__dirname, `../`)
const repoRootPath = path.resolve(basePath, `../../../`)
const docsApiPath = path.resolve(repoRootPath, "docs/api/")
const redoclyConfigPath = path.resolve(
repoRootPath,
"docs-util/redocly/config.yaml"
)
const run = async () => {
const outputPath = isDryRun ? await getTmpDirectory() : docsApiPath
const oasOutDir = isDryRun ? await getTmpDirectory() : docsApiPath
for (const apiType of ["store", "admin"]) {
await generateOASSource(outputPath, apiType)
const inputJsonFile = path.resolve(outputPath, `${apiType}.oas.json`)
const outputYamlFile = path.resolve(outputPath, `${apiType}.oas.yaml`)
await jsonFileToYamlFile(inputJsonFile, outputYamlFile)
await sanitizeOAS(outputYamlFile)
await circularReferenceCheck(outputYamlFile)
if (!isDryRun) {
await generateReference(outputYamlFile, apiType)
}
await generateOASSource(oasOutDir, apiType)
const oasSrcFile = path.resolve(oasOutDir, `${apiType}.oas.json`)
const docsOutDir = path.resolve(oasOutDir, apiType)
await generateDocs(oasSrcFile, docsOutDir, isDryRun)
}
}
const generateOASSource = async (outDir, apiType) => {
const params = ["oas", `--type=${apiType}`, `--out-dir=${outDir}`]
const { all: logs } = await execa(
"medusa-oas",
["oas", `--type=${apiType}`, `--out-dir=${outDir}`],
{ cwd: basePath, all: true }
)
console.log(logs)
}
const generateDocs = async (srcFile, outDir, isDryRun) => {
const params = [
"docs",
`--src-file=${srcFile}`,
`--out-dir=${outDir}`,
`--clean`,
`--split`,
]
if (isDryRun) {
params.push("--dry-run")
}
const { all: logs } = await execa("medusa-oas", params, {
cwd: basePath,
all: true,
@@ -42,52 +47,6 @@ const generateOASSource = async (outDir, apiType) => {
console.log(logs)
}
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")
}
const sanitizeOAS = async (srcFile) => {
const { all: logs } = await execa(
"redocly",
["bundle", srcFile, `--output=${srcFile}`, `--config=${redoclyConfigPath}`],
{ cwd: basePath, all: true }
)
console.log(logs)
}
const circularReferenceCheck = async (srcFile) => {
const parser = new OpenAPIParser()
await parser.validate(srcFile, {
dereference: {
circular: "ignore",
},
})
if (parser.$refs.circular) {
const fileName = path.basename(srcFile)
const circularRefs = [...parser.$refs.circularRefs]
circularRefs.sort()
console.log(circularRefs)
throw new Error(
`🔴 Unhandled circular references - ${fileName} - Please patch in docs-util/redocly/config.yaml`
)
}
console.log(`🟢 All circular references handled`)
}
const generateReference = async (srcFile, apiType) => {
const outDir = path.resolve(docsApiPath, `${apiType}`)
await fs.rm(outDir, { recursive: true, force: true })
const { all: logs } = await execa(
"redocly",
["split", srcFile, `--outDir=${outDir}`],
{ cwd: basePath, all: true }
)
console.log(logs)
}
const getTmpDirectory = async () => {
/**
* RUNNER_TEMP: GitHub action, the path to a temporary directory on the runner.