## What
Introduce a CLI for extracting OAS from the core `medusa` package.
## Why
We need to decouple OAS tooling from documentation tooling in order to allow packages and external systems to leverage our OAS has a dependency.
## How
Introduce a new OAS workspace within packages in order to organize current and future OAS related package. Only 1 OAS package for now.
Introduce a new CLI only package to act as the main gateway for all upcoming OAS tooling. Only 1 command for now.
Update documentation tooling pertaining to OAS to use the CLI instead.
## Test
### Prerequisite
From the monorepo root:
* `yarn install`
* `yarn build`
### Documentation
#### Case - validation only - success
* Run `yarn openapi:generate --dry-run`
* Expect console output `🟢 Valid OAS` but no mention of `🔵 Exported OAS`
#### Case - validation only - invalid
* Introduce a bug by renaming `@schema Cart` to `@schema Kart` in [models/cart.ts](0adb0d9ff9/packages/medusa/src/models/cart.ts (L2))
* Run `yarn build` to update `@medusajs/medusa` package with the bug.
* Run `yarn openapi:generate --dry-run`
* Expect console output `🔴 Invalid OAS` with a stack trace of the issue.
#### Case - docs generation
* Run `yarn openapi:generate`
* Expect `docs/api/` directory to contain:
* `admin.oas.json` (raw OAS)
* `store.oas.json` (raw OAS)
* `admin.oas.yaml` (sanitized OAS)
* `store.oas.yaml` (sanitized OAS)
* `admin/` (updated redocly split output)
* `store/` (updated redocly split output)
### CLI
#### Case - crawl additional paths
* From a local medusa server (`medusa-starter-default`), add an `index.ts` file in `src/models/`
* In the `index.ts`, add dummy OAS JSDoc like `/** @schema Foobar */`
* From the root of the monorepo, run `yarn medusa-oas --type store --paths path-to-medusa-server/src`
* Expect a `store.oas.json` to be created at the root of the monorepo.
* The `store.oas.json` should contain an additional `Foobar` entry in `components.schemas`.
92 lines
2.4 KiB
JavaScript
Executable File
92 lines
2.4 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
const fs = require("fs/promises")
|
|
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 docsApiPath = path.resolve(basePath, "docs/api/")
|
|
|
|
const run = async () => {
|
|
await generateOASSources(docsApiPath, isDryRun)
|
|
if (isDryRun) {
|
|
return
|
|
}
|
|
|
|
for (const apiType of ["store", "admin"]) {
|
|
const inputJsonFile = path.resolve(docsApiPath, `${apiType}.oas.json`)
|
|
const outputYamlFile = path.resolve(docsApiPath, `${apiType}.oas.yaml`)
|
|
|
|
await jsonFileToYamlFile(inputJsonFile, outputYamlFile)
|
|
await sanitizeOAS(outputYamlFile)
|
|
await circularReferenceCheck(outputYamlFile)
|
|
await generateReference(outputYamlFile, apiType)
|
|
}
|
|
}
|
|
|
|
const generateOASSources = async (outDir, isDryRun) => {
|
|
const params = ["oas", `--out-dir=${outDir}`]
|
|
if (isDryRun) {
|
|
params.push("--dry-run")
|
|
}
|
|
const { all: logs } = await execa("medusa-oas", params, {
|
|
cwd: basePath,
|
|
all: true,
|
|
})
|
|
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=docs-util/redocly/config.yaml",
|
|
],
|
|
{ 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) {
|
|
console.log(`🔴 Unhandled circular references - ${srcFile}`)
|
|
const circularRefs = [...parser.$refs.circularRefs]
|
|
circularRefs.sort()
|
|
console.log(circularRefs)
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
void (async () => {
|
|
await run()
|
|
})()
|