-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(openapi): exposing our spec conversion tooling to a new `:conver…
…t` command (#717) * feat(openapi): exposing our spec conversion tooling to a new `:convert` command * fix: alex issues * fix: eslint issues * Update README.md Co-authored-by: Kanad Gupta <[email protected]> * Update src/cmds/openapi/convert.ts Co-authored-by: Kanad Gupta <[email protected]> * chore: tweaking the reduce command a bit * fix: pr feedback * fix: pr feedback * chore: revert change to package.json Co-authored-by: Kanad Gupta <[email protected]>
- Loading branch information
1 parent
b5c64c8
commit 0482a1f
Showing
11 changed files
with
235 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import fs from 'fs'; | ||
|
||
import prompts from 'prompts'; | ||
|
||
import OpenAPIConvertCommand from '../../../src/cmds/openapi/convert'; | ||
|
||
const convert = new OpenAPIConvertCommand(); | ||
|
||
const successfulConversion = () => 'Your converted API definition has been saved to output.json!'; | ||
|
||
const testWorkingDir = process.cwd(); | ||
|
||
describe('rdme openapi:convert', () => { | ||
afterEach(() => { | ||
process.chdir(testWorkingDir); | ||
|
||
jest.clearAllMocks(); | ||
}); | ||
|
||
describe('converting', () => { | ||
it.each([ | ||
['Swagger 2.0', 'json', '2.0'], | ||
['Swagger 2.0', 'yaml', '2.0'], | ||
])('should support reducing a %s definition (format: %s)', async (_, format, specVersion) => { | ||
const spec = require.resolve(`@readme/oas-examples/${specVersion}/${format}/petstore-simple.${format}`); | ||
|
||
let reducedSpec; | ||
fs.writeFileSync = jest.fn((fileName, data) => { | ||
reducedSpec = JSON.parse(data as string); | ||
}); | ||
|
||
prompts.inject(['output.json']); | ||
|
||
await expect( | ||
convert.run({ | ||
spec, | ||
}) | ||
).resolves.toBe(successfulConversion()); | ||
|
||
expect(fs.writeFileSync).toHaveBeenCalledWith('output.json', expect.any(String)); | ||
expect(reducedSpec.tags).toHaveLength(1); | ||
expect(Object.keys(reducedSpec.paths)).toStrictEqual(['/pet/{petId}']); | ||
expect(Object.keys(reducedSpec.paths['/pet/{petId}'])).toStrictEqual(['get', 'post', 'delete']); | ||
}); | ||
|
||
it('should convert with no prompts via opts', async () => { | ||
const spec = 'petstore-simple.json'; | ||
|
||
let reducedSpec; | ||
fs.writeFileSync = jest.fn((fileName, data) => { | ||
reducedSpec = JSON.parse(data as string); | ||
}); | ||
|
||
await expect( | ||
convert.run({ | ||
spec, | ||
workingDirectory: require.resolve(`@readme/oas-examples/2.0/json/${spec}`).replace(spec, ''), | ||
out: 'output.json', | ||
}) | ||
).resolves.toBe(successfulConversion()); | ||
|
||
expect(fs.writeFileSync).toHaveBeenCalledWith('output.json', expect.any(String)); | ||
expect(Object.keys(reducedSpec.paths)).toStrictEqual(['/pet/{petId}']); | ||
expect(Object.keys(reducedSpec.paths['/pet/{petId}'])).toStrictEqual(['get', 'post', 'delete']); | ||
}); | ||
}); | ||
|
||
describe('error handling', () => { | ||
it.each([['json'], ['yaml']])('should fail if given an OpenAPI 3.0 definition (format: %s)', async format => { | ||
const spec = require.resolve(`@readme/oas-examples/3.0/${format}/petstore.${format}`); | ||
|
||
await expect( | ||
convert.run({ | ||
spec, | ||
}) | ||
).rejects.toStrictEqual( | ||
new Error("Sorry, this API definition is already an OpenAPI definition and doesn't need to be converted.") | ||
); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import type { CommandOptions } from '../../lib/baseCommand'; | ||
|
||
import fs from 'fs'; | ||
import path from 'path'; | ||
|
||
import chalk from 'chalk'; | ||
import prompts from 'prompts'; | ||
|
||
import Command, { CommandCategories } from '../../lib/baseCommand'; | ||
import { checkFilePath } from '../../lib/checkFile'; | ||
import prepareOas from '../../lib/prepareOas'; | ||
import promptTerminal from '../../lib/promptWrapper'; | ||
|
||
export interface Options { | ||
spec?: string; | ||
out?: string; | ||
workingDirectory?: string; | ||
} | ||
|
||
export default class OpenAPIConvertCommand extends Command { | ||
constructor() { | ||
super(); | ||
|
||
this.command = 'openapi:convert'; | ||
this.usage = 'openapi:convert [file|url] [options]'; | ||
this.description = 'Convert a Swagger or Postman Collection to OpenAPI.'; | ||
this.cmdCategory = CommandCategories.APIS; | ||
|
||
this.hiddenArgs = ['spec']; | ||
this.args = [ | ||
{ | ||
name: 'spec', | ||
type: String, | ||
defaultOption: true, | ||
}, | ||
{ | ||
name: 'out', | ||
type: String, | ||
description: 'Output file path to write converted file to', | ||
}, | ||
{ | ||
name: 'workingDirectory', | ||
type: String, | ||
description: 'Working directory (for usage with relative external references)', | ||
}, | ||
]; | ||
} | ||
|
||
async run(opts: CommandOptions<Options>) { | ||
await super.run(opts); | ||
|
||
const { spec, workingDirectory } = opts; | ||
|
||
if (workingDirectory) { | ||
process.chdir(workingDirectory); | ||
} | ||
|
||
const { preparedSpec, specPath, specType } = await prepareOas(spec, 'openapi:convert', { convertToLatest: true }); | ||
const parsedPreparedSpec = JSON.parse(preparedSpec); | ||
|
||
if (specType === 'OpenAPI') { | ||
throw new Error("Sorry, this API definition is already an OpenAPI definition and doesn't need to be converted."); | ||
} | ||
|
||
prompts.override({ | ||
outputPath: opts.out, | ||
}); | ||
|
||
const promptResults = await promptTerminal([ | ||
{ | ||
type: 'text', | ||
name: 'outputPath', | ||
message: 'Enter the path to save your converted API definition to:', | ||
initial: () => { | ||
const extension = path.extname(specPath); | ||
return `${path.basename(specPath).split(extension)[0]}.openapi${extension}`; | ||
}, | ||
validate: value => checkFilePath(value), | ||
}, | ||
]); | ||
|
||
Command.debug(`saving converted spec to ${promptResults.outputPath}`); | ||
|
||
fs.writeFileSync(promptResults.outputPath, JSON.stringify(parsedPreparedSpec, null, 2)); | ||
|
||
Command.debug('converted spec saved'); | ||
|
||
return Promise.resolve(chalk.green(`Your converted API definition has been saved to ${promptResults.outputPath}!`)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.