-
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: rename swagger command to openapi and keep swagger as alias (#333)
* chore: test that oas-normalize works as expected * chore(deps): upgrading oas and oas-normalize * fix: validate files before uploading * feat: add openapi command and keep swagger as an alias * refactor: update openapi command and tests * chore: update wording * test: fix test for related commands * test: add test for swagger command Co-authored-by: Jon Ursenbach <[email protected]>
- Loading branch information
1 parent
f035b24
commit 04e7f93
Showing
4 changed files
with
236 additions
and
187 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ const config = require('config'); | |
const fs = require('fs'); | ||
const promptHandler = require('../../src/lib/prompts'); | ||
const swagger = require('../../src/cmds/swagger'); | ||
const openapi = require('../../src/cmds/openapi'); | ||
|
||
const key = 'Xmw4bGctRVIQz7R7dQXqH9nQe5d0SPQs'; | ||
const version = '1.0.0'; | ||
|
@@ -13,7 +14,7 @@ const getCommandOutput = () => { | |
return [console.warn.mock.calls.join('\n\n'), console.log.mock.calls.join('\n\n')].filter(Boolean).join('\n\n'); | ||
}; | ||
|
||
describe('rdme swagger', () => { | ||
describe('rdme openapi', () => { | ||
const exampleRefLocation = `${config.host}/project/example-project/1.0.1/refs/ex`; | ||
|
||
beforeAll(() => nock.disableNetConnect()); | ||
|
@@ -53,7 +54,7 @@ describe('rdme swagger', () => { | |
// to break. | ||
fs.copyFileSync('./__tests__/__fixtures__/swagger.json', './swagger.json'); | ||
|
||
return swagger.run({ key }).then(() => { | ||
return openapi.run({ key }).then(() => { | ||
expect(console.log).toHaveBeenCalledTimes(2); | ||
|
||
const output = getCommandOutput(); | ||
|
@@ -86,7 +87,7 @@ describe('rdme swagger', () => { | |
help: 'If you need help, email [email protected] and mention log "fake-metrics-uuid".', | ||
}); | ||
|
||
return expect(swagger.run({ spec: './__tests__/__fixtures__/swagger.json', key, version })) | ||
return expect(openapi.run({ spec: './__tests__/__fixtures__/swagger.json', key, version })) | ||
.rejects.toThrow('The version you specified') | ||
.then(() => mock.done()); | ||
}); | ||
|
@@ -103,7 +104,7 @@ describe('rdme swagger', () => { | |
.basicAuth({ user: key }) | ||
.reply(201, { _id: 1 }, { location: exampleRefLocation }); | ||
|
||
return swagger.run({ spec: './__tests__/__fixtures__/swagger.json', key, version }).then(() => { | ||
return openapi.run({ spec: './__tests__/__fixtures__/swagger.json', key, version }).then(() => { | ||
expect(console.log).toHaveBeenCalledTimes(1); | ||
|
||
const output = getCommandOutput(); | ||
|
@@ -134,7 +135,7 @@ describe('rdme swagger', () => { | |
help: 'If you need help, email [email protected] and mention log "fake-metrics-uuid".', | ||
}); | ||
|
||
return expect(swagger.run({ spec: './__tests__/__fixtures__/invalid-swagger.json', key, version })) | ||
return expect(openapi.run({ spec: './__tests__/__fixtures__/invalid-swagger.json', key, version })) | ||
.rejects.toThrow('README VALIDATION ERROR "x-samples-languages" must be of type "Array"') | ||
.then(() => mock.done()); | ||
}); | ||
|
@@ -161,7 +162,7 @@ describe('rdme swagger', () => { | |
.basicAuth({ user: key }) | ||
.reply(201, { _id: 1 }, { location: exampleRefLocation }); | ||
|
||
return swagger.run({ spec: './__tests__/__fixtures__/swagger.json', key }).then(() => { | ||
return openapi.run({ spec: './__tests__/__fixtures__/swagger.json', key }).then(() => { | ||
mock.done(); | ||
}); | ||
}); | ||
|
@@ -174,7 +175,7 @@ describe('rdme swagger', () => { | |
.basicAuth({ user: key }) | ||
.reply(201, { body: '{ id: 1 }' }); | ||
|
||
return swagger.run({ spec: './__tests__/__fixtures__/swagger.json', key, id, version }).then(() => { | ||
return openapi.run({ spec: './__tests__/__fixtures__/swagger.json', key, id, version }).then(() => { | ||
mock.done(); | ||
}); | ||
}); | ||
|
@@ -187,7 +188,7 @@ describe('rdme swagger', () => { | |
.basicAuth({ user: key }) | ||
.reply(201, { id: 1 }, { location: exampleRefLocation }); | ||
|
||
return swagger.run({ spec: './__tests__/__fixtures__/swagger.json', token: `${key}-${id}`, version }).then(() => { | ||
return openapi.run({ spec: './__tests__/__fixtures__/swagger.json', token: `${key}-${id}`, version }).then(() => { | ||
expect(console.warn).toHaveBeenCalledTimes(1); | ||
expect(console.log).toHaveBeenCalledTimes(1); | ||
|
||
|
@@ -200,7 +201,7 @@ describe('rdme swagger', () => { | |
}); | ||
|
||
it('should error if no api key provided', async () => { | ||
await expect(swagger.run({ spec: './__tests__/__fixtures__/swagger.json' })).rejects.toThrow( | ||
await expect(openapi.run({ spec: './__tests__/__fixtures__/swagger.json' })).rejects.toThrow( | ||
'No project API key provided. Please use `--key`.' | ||
); | ||
}); | ||
|
@@ -211,16 +212,27 @@ describe('rdme swagger', () => { | |
.basicAuth({ user: key }) | ||
.reply(200, { version: '1.0.0' }); | ||
|
||
await expect(swagger.run({ key, version })).rejects.toThrow(/We couldn't find a Swagger or OpenAPI file./); | ||
await expect(openapi.run({ key, version })).rejects.toThrow(/We couldn't find a Swagger or OpenAPI file./); | ||
|
||
mock.done(); | ||
}); | ||
|
||
it('should throw an error if file is invalid', async () => { | ||
const id = '5aa0409b7cf527a93bfb44df'; | ||
|
||
await expect(swagger.run({ spec: './__tests__/__fixtures__/invalid-oas.json', key, id, version })).rejects.toThrow( | ||
await expect(openapi.run({ spec: './__tests__/__fixtures__/invalid-oas.json', key, id, version })).rejects.toThrow( | ||
'Token "Error" does not exist.' | ||
); | ||
}); | ||
}); | ||
|
||
describe('rdme swagger', () => { | ||
it('should run `rdme openapi`', async () => { | ||
const id = '5aa0409b7cf527a93bfb44df'; | ||
|
||
await expect(swagger.run({ spec: '', key, id, version })).rejects.toThrow( | ||
"We couldn't find a Swagger or OpenAPI file.\n\n" + | ||
'Run `rdme openapi ./path/to/file` to upload an existing file or `rdme oas init` to create a fresh one!' | ||
); | ||
}); | ||
}); |
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,208 @@ | ||
require('colors'); | ||
const request = require('request-promise-native'); | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
const config = require('config'); | ||
const { prompt } = require('enquirer'); | ||
const OASNormalize = require('oas-normalize'); | ||
const promptOpts = require('../lib/prompts'); | ||
const APIError = require('../lib/apiError'); | ||
|
||
exports.command = 'openapi'; | ||
exports.usage = 'openapi [file] [options]'; | ||
exports.description = 'Upload, or sync, your Swagger/OpenAPI file to ReadMe.'; | ||
exports.category = 'apis'; | ||
exports.position = 1; | ||
|
||
exports.hiddenArgs = ['token', 'spec']; | ||
exports.args = [ | ||
{ | ||
name: 'key', | ||
type: String, | ||
description: 'Project API key', | ||
}, | ||
{ | ||
name: 'id', | ||
type: String, | ||
description: `Unique identifier for your specification. Use this if you're resyncing an existing specification`, | ||
}, | ||
{ | ||
name: 'token', | ||
type: String, | ||
description: 'Project token. Deprecated, please use `--key` instead', | ||
}, | ||
{ | ||
name: 'version', | ||
type: String, | ||
description: 'Project version', | ||
}, | ||
{ | ||
name: 'spec', | ||
type: String, | ||
defaultOption: true, | ||
}, | ||
]; | ||
|
||
exports.run = async function (opts) { | ||
const { spec, version } = opts; | ||
let { key, id } = opts; | ||
let selectedVersion; | ||
let isUpdate; | ||
|
||
if (!key && opts.token) { | ||
console.warn('Using `rdme` with --token has been deprecated. Please use `--key` and `--id` instead.'); | ||
|
||
[key, id] = opts.token.split('-'); | ||
} | ||
|
||
if (!key) { | ||
return Promise.reject(new Error('No project API key provided. Please use `--key`.')); | ||
} | ||
|
||
async function callApi(specPath, versionCleaned) { | ||
// @todo Tailor messaging to what is actually being handled here. If the user is uploading an OpenAPI file, never mention that they uploaded/updated a Swagger file. | ||
|
||
function success(data) { | ||
const message = !isUpdate | ||
? "You've successfully uploaded a new Swagger file to your ReadMe project!" | ||
: "You've successfully updated a Swagger file on your ReadMe project!"; | ||
|
||
console.log( | ||
[ | ||
message, | ||
'', | ||
`\t${`${data.headers.location}`.green}`, | ||
'', | ||
'To update your Swagger or OpenAPI file, run the following:', | ||
'', | ||
// eslint-disable-next-line no-underscore-dangle | ||
`\trdme openapi FILE --key=${key} --id=${JSON.parse(data.body)._id}`.green, | ||
].join('\n') | ||
); | ||
} | ||
|
||
function error(err) { | ||
try { | ||
const parsedError = JSON.parse(err.error); | ||
return Promise.reject(new APIError(parsedError)); | ||
} catch (e) { | ||
return Promise.reject(new Error('There was an error uploading!')); | ||
} | ||
} | ||
|
||
const options = { | ||
formData: { | ||
spec: fs.createReadStream(path.resolve(process.cwd(), specPath)), | ||
}, | ||
headers: { | ||
'x-readme-version': versionCleaned, | ||
'x-readme-source': 'cli', | ||
}, | ||
auth: { user: key }, | ||
resolveWithFullResponse: true, | ||
}; | ||
|
||
function createSpec() { | ||
return request.post(`${config.host}/api/v1/api-specification`, options).then(success, error); | ||
} | ||
|
||
function updateSpec(specId) { | ||
isUpdate = true; | ||
|
||
return request.put(`${config.host}/api/v1/api-specification/${specId}`, options).then(success, error); | ||
} | ||
|
||
if (spec) { | ||
const oas = new OASNormalize(spec, { enablePaths: true }); | ||
await oas.validate().catch(err => { | ||
return Promise.reject(err); | ||
}); | ||
} | ||
|
||
/* | ||
Create a new OAS file in Readme: | ||
- Enter flow if user does not pass an id as cli arg | ||
- Check to see if any existing files exist with a specific version | ||
- If none exist, default to creating a new instance of a spec | ||
- If found, prompt user to either create a new spec or update an existing one | ||
*/ | ||
|
||
if (!id) { | ||
const apiSettings = await request.get(`${config.host}/api/v1/api-specification`, { | ||
headers: { | ||
'x-readme-version': versionCleaned, | ||
}, | ||
json: true, | ||
auth: { user: key }, | ||
}); | ||
|
||
if (!apiSettings.length) return createSpec(); | ||
|
||
const { option, specId } = await prompt(promptOpts.createOasPrompt(apiSettings)); | ||
return option === 'create' ? createSpec() : updateSpec(specId); | ||
} | ||
|
||
/* | ||
Update an existing OAS file in Readme: | ||
- Enter flow if user passes an id as cli arg | ||
*/ | ||
return updateSpec(id); | ||
} | ||
|
||
async function getSwaggerVersion(versionFlag) { | ||
const options = { json: {}, auth: { user: key } }; | ||
|
||
try { | ||
if (versionFlag) { | ||
options.json.version = versionFlag; | ||
const foundVersion = await request.get(`${config.host}/api/v1/version/${versionFlag}`, options); | ||
|
||
return foundVersion.version; | ||
} | ||
|
||
const versionList = await request.get(`${config.host}/api/v1/version`, options); | ||
const { option, versionSelection, newVersion } = await prompt( | ||
promptOpts.generatePrompts(versionList, versionFlag) | ||
); | ||
|
||
if (option === 'update') return versionSelection; | ||
|
||
options.json = { from: versionList[0].version, version: newVersion, is_stable: false }; | ||
await request.post(`${config.host}/api/v1/version`, options); | ||
|
||
return newVersion; | ||
} catch (err) { | ||
return Promise.reject(new APIError(err)); | ||
} | ||
} | ||
|
||
if (!id) { | ||
selectedVersion = await getSwaggerVersion(version).catch(e => { | ||
return Promise.reject(e); | ||
}); | ||
} | ||
|
||
if (spec) { | ||
return callApi(spec, selectedVersion); | ||
} | ||
|
||
// If the user didn't supply a specification, let's try to locate what they've got, and upload | ||
// that. If they don't have any, let's let the user know how they can get one going. | ||
return new Promise((resolve, reject) => { | ||
['swagger.json', 'swagger.yaml', 'openapi.json', 'openapi.yaml'].forEach(file => { | ||
if (!fs.existsSync(file)) { | ||
return; | ||
} | ||
|
||
console.log(`We found ${file} and are attempting to upload it.`.yellow); | ||
resolve(callApi(file, selectedVersion)); | ||
}); | ||
|
||
reject( | ||
new Error( | ||
"We couldn't find a Swagger or OpenAPI file.\n\n" + | ||
'Run `rdme openapi ./path/to/file` to upload an existing file or `rdme oas init` to create a fresh one!' | ||
) | ||
); | ||
}); | ||
}; |
Oops, something went wrong.