Skip to content

Commit

Permalink
feat(openapi): add tool to generate openapi docs (#31)
Browse files Browse the repository at this point in the history
Signed-off-by: Charly Molter <[email protected]>
  • Loading branch information
lahabana authored Oct 9, 2023
1 parent a9748ec commit 0098c0a
Show file tree
Hide file tree
Showing 9 changed files with 4,287 additions and 2 deletions.
9 changes: 8 additions & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,11 @@ updates:
schedule:
interval: "weekly"
labels:
- "dependencies"
- "dependencies"

- package-ecosystem: "npm"
directory: "/openapi-tools"
schedule:
interval: "weekly"
labels:
- "dependencies"
33 changes: 32 additions & 1 deletion .github/workflows/build-docker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ permissions:
packages: write

jobs:
build:
build-ubuntu-netools:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -42,3 +42,34 @@ jobs:
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
file: docker/ubuntu-netools.Dockerfile
build-openapi-tool:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-qemu-action@v2
- id: buildx
uses:
docker/setup-buildx-action@v3
- if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- id: meta
uses: docker/metadata-action@v5
with:
# list of Docker images to use as base name for tags
images: ghcr.io/${{github.repository_owner}}/openapi-tool
# Docker tags based on the following events/attributes
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha
- uses: docker/build-push-action@v5
with:
context: openapi-tool
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
file: openapi-tool/Dockerfile
1 change: 1 addition & 0 deletions openapi-tool/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
11 changes: 11 additions & 0 deletions openapi-tool/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM node:20-alpine

WORKDIR /usr/src/app

COPY package*.json ./

RUN npm install

COPY . .

ENTRYPOINT [ "node", "./index.js" ]
1 change: 1 addition & 0 deletions openapi-tool/Dockerfile.ignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
93 changes: 93 additions & 0 deletions openapi-tool/commands/generate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
const yaml = require("js-yaml");
const fg = require('fast-glob');
const {createInterface} = require('readline')

const {
formatProblems,
getTotals,
coreVersion,
loadConfig,
bundle,
} = require('@redocly/openapi-core');
const {merger} = require('oas-toolkit');
const fs = require("fs"); // Replace with actual function from OAS Toolkit

exports.generateFiles = async (argv) => {
const files = argv.files;

if (!files || files.length === 0) {
console.error('No files provided.');
return;
}

const config = await loadConfig({
// configPath: redocConfigurationPath
});
let hasProblems = false;
let expandedFiles = await fg.glob(files, {concurrency: 1})
console.error('got all files', expandedFiles)
let apis = (await Promise.all(expandedFiles.map(async (filename) => {
// Check it's a openapi spec
if (!await fileIsOpenApiSpec(filename)) {
return null;
}
// Bundle each file independently
const {
bundle: result,
problems,
...meta
} = await bundle({
config: config,
ref: filename,
dereference: false,
removeUnusedComponents: false,
});
if (problems.length > 0) {
formatProblems(problems, {totals: getTotals(problems), version: coreVersion})
hasProblems = true;
// Return proactively if there are problems
return null;
}
const spec = result.parsed

// Resolve `schema.json` bits
const name = spec.info['x-ref-schema-name']
if (name) {
if (spec.components.schemas[`${name}Item`]?.["$ref"] === "#/components/schemas/schema") {
spec.components.schemas[`${name}Item`] = spec.components.schemas.schema
delete spec.components.schemas.schema
}
deepReplace(spec, "$ref", "#/components/schemas/schema", `#/components/schemas/${name}Item`)
}
return spec
}))).filter((n) => n !== null);
if (hasProblems) {
throw Error("Problems when bundling, not trying to merge")
}
console.log(yaml.dump(merger(apis)));
};

function deepReplace(obj, onKey, from, to) {
for (let key in obj) {
if (key === onKey && obj[key] === from) {
obj[key] = to
} else if (typeof obj[key] === 'object' && obj[key] !== null) {
// If the current property is an object, recurse into it
deepReplace(obj[key], onKey, from, to);
}
}
}

async function fileIsOpenApiSpec(path) {
const inputStream = fs.createReadStream(path);
try {
for await (const line of createInterface(inputStream)) {
if (line.match(/^openapi:\s*[0-9]+\.[0-9]+\.[0-9]+/)) {
return true;
}
}
return false;
} finally {
inputStream.destroy(); // Destroy file stream.
}
}
16 changes: 16 additions & 0 deletions openapi-tool/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env node

const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');
const { generateFiles } = require('./commands/generate');

const argv = yargs(hideBin(process.argv))
.command('generate [files..]', 'Generate files', (yargs) => {
yargs.positional('files', {
describe: 'List of files to generate',
type: 'array'
});
}, generateFiles)
.demandCommand(1, 'You need at least one command before moving on')
.help()
.argv;
Loading

0 comments on commit 0098c0a

Please sign in to comment.