diff --git a/__tests__/cmds/docs.test.js b/__tests__/cmds/docs.test.js index 9c1168096..66b4dd07b 100644 --- a/__tests__/cmds/docs.test.js +++ b/__tests__/cmds/docs.test.js @@ -36,11 +36,6 @@ describe('rdme docs', () => { return expect(docs.run({})).rejects.toThrow('No project API key provided. Please use `--key`.'); }); - it('should error if no version provided', () => { - expect.assertions(1); - return expect(docs.run({ key })).rejects.toThrow('No project version provided. Please use `--version`.'); - }); - it('should error if no folder provided', () => { expect.assertions(1); return expect(docs.run({ key, version: '1.0.0' })).rejects.toThrow( @@ -103,6 +98,11 @@ describe('rdme docs', () => { .basicAuth({ user: key }) .reply(200); + const versionMock = nock(config.host) + .get(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(200, { version }); + return docs.run({ folder: './__tests__/__fixtures__/existing-docs', key, version }).then(skippedDocs => { // All docs should have been updated because their hashes from the GET request were different from what they // are currently. @@ -110,6 +110,7 @@ describe('rdme docs', () => { getMocks.done(); updateMocks.done(); + versionMock.done(); }); }); @@ -124,6 +125,11 @@ describe('rdme docs', () => { .basicAuth({ user: key }) .reply(200, { category, slug: anotherDoc.slug, lastUpdatedHash: anotherDoc.hash }); + const versionMock = nock(config.host) + .get(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(200, { version }); + return docs.run({ folder: './__tests__/__fixtures__/existing-docs', key, version }).then(skippedDocs => { expect(skippedDocs).toStrictEqual([ '`simple-doc` was not updated because there were no changes.', @@ -131,6 +137,7 @@ describe('rdme docs', () => { ]); getMocks.done(); + versionMock.done(); }); }); }); @@ -156,9 +163,15 @@ describe('rdme docs', () => { .basicAuth({ user: key }) .reply(201); + const versionMock = nock(config.host) + .get(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(200, { version }); + return docs.run({ folder: './__tests__/__fixtures__/new-docs', key, version }).then(() => { getMock.done(); postMock.done(); + versionMock.done(); }); }); @@ -219,6 +232,11 @@ describe('rdme docs', () => { category, }); + const versionMock = nock(config.host) + .get(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(200, { version }); + return docs.run({ folder: './__tests__/__fixtures__/failure-docs', key, version }).then(message => { expect(console.log).toHaveBeenCalledTimes(1); expect(message).toStrictEqual([ @@ -242,6 +260,7 @@ describe('rdme docs', () => { getMocks.done(); postMocks.done(); + versionMock.done(); console.log.mockRestore(); }); @@ -262,10 +281,6 @@ describe('rdme docs:edit', () => { return expect(docsEdit.run({})).rejects.toThrow('No project API key provided. Please use `--key`.'); }); - it('should error if no version provided', () => { - return expect(docsEdit.run({ key })).rejects.toThrow('No project version provided. Please use `--version`.'); - }); - it('should error if no slug provided', () => { return expect(docsEdit.run({ key, version: '1.0.0' })).rejects.toThrow( 'No slug provided. Usage `rdme docs:edit [options]`.' @@ -292,6 +307,11 @@ describe('rdme docs:edit', () => { .basicAuth({ user: key }) .reply(200); + const versionMock = nock(config.host) + .get(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(200, { version }); + function mockEditor(filename, cb) { expect(filename).toBe(`${slug}.md`); expect(fs.existsSync(filename)).toBe(true); @@ -301,6 +321,7 @@ describe('rdme docs:edit', () => { return docsEdit.run({ slug, key, version: '1.0.0', mockEditor }).then(() => { getMock.done(); putMock.done(); + versionMock.done(); expect(fs.existsSync(`${slug}.md`)).toBe(false); expect(console.log).toHaveBeenCalledWith('Doc successfully updated. Cleaning up local file.'); @@ -320,8 +341,14 @@ describe('rdme docs:edit', () => { help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".', }); + const versionMock = nock(config.host) + .get(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(200, { version }); + return docsEdit.run({ slug, key, version: '1.0.0' }).catch(err => { getMock.done(); + versionMock.done(); expect(err.code).toBe('DOC_NOTFOUND'); expect(err.message).toContain("The doc with the slug 'no-such-doc' couldn't be found"); }); @@ -341,6 +368,11 @@ describe('rdme docs:edit', () => { help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".', }); + const versionMock = nock(config.host) + .get(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(200, { version }); + function mockEditor(filename, cb) { return cb(0); } @@ -349,6 +381,7 @@ describe('rdme docs:edit', () => { expect(err.code).toBe('DOC_INVALID'); getMock.done(); putMock.done(); + versionMock.done(); expect(fs.existsSync(`${slug}.md`)).toBe(true); fs.unlinkSync(`${slug}.md`); }); @@ -359,13 +392,19 @@ describe('rdme docs:edit', () => { const slug = 'getting-started'; const body = 'abcdef'; - nock(config.host).get(`/api/v1/docs/${slug}`).reply(200, { body }); + const getMock = nock(config.host) + .get(`/api/v1/docs/${slug}`) + .reply(200, { body }) + .get(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(200, { version }); function mockEditor(filename, cb) { return cb(1); } return docsEdit.run({ slug, key, version: '1.0.0', mockEditor }).catch(err => { + getMock.done(); expect(err.message).toBe('Non zero exit code from $EDITOR'); fs.unlinkSync(`${slug}.md`); }); diff --git a/__tests__/cmds/versions.test.js b/__tests__/cmds/versions.test.js index 426b99330..491188d00 100644 --- a/__tests__/cmds/versions.test.js +++ b/__tests__/cmds/versions.test.js @@ -102,13 +102,6 @@ describe('rdme versions*', () => { }); }); - it('should error if no version provided', () => { - expect.assertions(1); - return createVersion.run({ key }).catch(err => { - expect(err.message).toBe('Please specify a semantic version. See `rdme help versions:create` for help.'); - }); - }); - it('should get a specific version object', () => { promptHandler.createVersionPrompt.mockResolvedValue({ is_stable: true, @@ -158,15 +151,14 @@ describe('rdme versions*', () => { }); }); - it('should error if no version provided', () => { - expect.assertions(1); - return deleteVersion.run({ key }).catch(err => { - expect(err.message).toBe('Please specify a semantic version. See `rdme help versions:delete` for help.'); - }); - }); - it('should delete a specific version', () => { - const mockRequest = nock(config.host).delete(`/api/v1/version/${version}`).basicAuth({ user: key }).reply(200); + const mockRequest = nock(config.host) + .delete(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(200) + .get(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(200, { version }); return deleteVersion.run({ key, version }).then(() => { mockRequest.done(); @@ -174,13 +166,19 @@ describe('rdme versions*', () => { }); it('should catch any request errors', () => { - const mockRequest = nock(config.host).delete(`/api/v1/version/${version}`).basicAuth({ user: key }).reply(404, { - error: 'VERSION_NOTFOUND', - message: - "The version you specified ({version}) doesn't match any of the existing versions ({versions_list}) in ReadMe.", - suggestion: '...a suggestion to resolve the issue...', - help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".', - }); + const mockRequest = nock(config.host) + .delete(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(404, { + error: 'VERSION_NOTFOUND', + message: + "The version you specified ({version}) doesn't match any of the existing versions ({versions_list}) in ReadMe.", + suggestion: '...a suggestion to resolve the issue...', + help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".', + }) + .get(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(200, { version }); return deleteVersion.run({ key, version }).catch(err => { expect(err.message).toContain('The version you specified'); @@ -197,13 +195,6 @@ describe('rdme versions*', () => { }); }); - it('should error if no version provided', () => { - expect.assertions(1); - return updateVersion.run({ key }).catch(err => { - expect(err.message).toBe('Please specify a semantic version. See `rdme help versions:update` for help.'); - }); - }); - it('should update a specific version object', () => { promptHandler.createVersionPrompt.mockResolvedValue({ is_stable: false, @@ -217,7 +208,10 @@ describe('rdme versions*', () => { .reply(200, { version }) .put(`/api/v1/version/${version}`) .basicAuth({ user: key }) - .reply(201, { version }); + .reply(201, { version }) + .get(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(200, { version }); return updateVersion.run({ key, version }).then(() => { mockRequest.done(); @@ -242,7 +236,10 @@ describe('rdme versions*', () => { message: "You can't make a stable version non-stable", suggestion: '...a suggestion to resolve the issue...', help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".', - }); + }) + .get(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(200, { version }); return updateVersion.run({ key, version }).catch(err => { expect(err.message).toContain("You can't make a stable version non-stable"); diff --git a/__tests__/index.test.js b/__tests__/index.test.js index 53842d1cf..13c80016c 100644 --- a/__tests__/index.test.js +++ b/__tests__/index.test.js @@ -114,7 +114,7 @@ describe('cli', () => { conf.set('apiKey', '123456'); return cli(['docs']).catch(err => { conf.delete('apiKey'); - expect(err.message).toBe('No project version provided. Please use `--version`.'); + expect(err.message).toBe('No folder provided. Usage `rdme docs [options]`.'); }); }); diff --git a/src/cmds/docs/edit.js b/src/cmds/docs/edit.js index 533ea4bb4..96322681b 100644 --- a/src/cmds/docs/edit.js +++ b/src/cmds/docs/edit.js @@ -4,6 +4,7 @@ const fs = require('fs'); const editor = require('editor'); const { promisify } = require('util'); const APIError = require('../../lib/apiError'); +const { getProjectVersion } = require('../../lib/versionSelect'); const writeFile = promisify(fs.writeFile); const readFile = promisify(fs.readFile); @@ -41,19 +42,19 @@ exports.run = async function (opts) { return Promise.reject(new Error('No project API key provided. Please use `--key`.')); } - if (!version) { - return Promise.reject(new Error('No project version provided. Please use `--version`.')); - } - if (!slug) { return Promise.reject(new Error(`No slug provided. Usage \`${config.cli} ${exports.usage}\`.`)); } + const selectedVersion = await getProjectVersion(version, key, true).catch(e => { + return Promise.reject(e); + }); + const filename = `${slug}.md`; const options = { auth: { user: key }, headers: { - 'x-readme-version': version, + 'x-readme-version': selectedVersion, }, }; diff --git a/src/cmds/docs/index.js b/src/cmds/docs/index.js index 3ffb23958..3d1f0f7bd 100644 --- a/src/cmds/docs/index.js +++ b/src/cmds/docs/index.js @@ -7,6 +7,7 @@ const crypto = require('crypto'); const frontMatter = require('gray-matter'); const { promisify } = require('util'); const APIError = require('../../lib/apiError'); +const { getProjectVersion } = require('../../lib/versionSelect'); const readFile = promisify(fs.readFile); @@ -42,14 +43,14 @@ exports.run = async function (opts) { return Promise.reject(new Error('No project API key provided. Please use `--key`.')); } - if (!version) { - return Promise.reject(new Error('No project version provided. Please use `--version`.')); - } - if (!folder) { return Promise.reject(new Error(`No folder provided. Usage \`${config.cli} ${exports.usage}\`.`)); } + const selectedVersion = await getProjectVersion(version, key, true).catch(e => { + return Promise.reject(e); + }); + // Find the files to sync const readdirRecursive = folderToSearch => { const filesInFolder = fs.readdirSync(folderToSearch, { withFileTypes: true }); @@ -72,7 +73,7 @@ exports.run = async function (opts) { const options = { auth: { user: key }, headers: { - 'x-readme-version': version, + 'x-readme-version': selectedVersion, }, }; diff --git a/src/cmds/openapi.js b/src/cmds/openapi.js index cb1affa03..58792ea16 100644 --- a/src/cmds/openapi.js +++ b/src/cmds/openapi.js @@ -6,6 +6,7 @@ const { prompt } = require('enquirer'); const OASNormalize = require('oas-normalize'); const promptOpts = require('../lib/prompts'); const APIError = require('../lib/apiError'); +const { getProjectVersion } = require('../lib/versionSelect'); exports.command = 'openapi'; exports.usage = 'openapi [file] [options]'; @@ -163,35 +164,8 @@ exports.run = async function (opts) { 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 => { + selectedVersion = await getProjectVersion(version, key, true).catch(e => { return Promise.reject(e); }); } diff --git a/src/cmds/versions/delete.js b/src/cmds/versions/delete.js index cdc9f4716..8d7eebe95 100644 --- a/src/cmds/versions/delete.js +++ b/src/cmds/versions/delete.js @@ -1,7 +1,7 @@ const request = require('request-promise-native'); const config = require('config'); -const semver = require('semver'); const APIError = require('../../lib/apiError'); +const { getProjectVersion } = require('../../lib/versionSelect'); exports.command = 'versions:delete'; exports.usage = 'versions:delete --version= [options]'; @@ -30,17 +30,15 @@ exports.run = async function (opts) { return Promise.reject(new Error('No project API key provided. Please use `--key`.')); } - if (!version || !semver.valid(semver.coerce(version))) { - return Promise.reject( - new Error(`Please specify a semantic version. See \`${config.cli} help ${exports.command}\` for help.`) - ); - } + const selectedVersion = await getProjectVersion(version, key, false).catch(e => { + return Promise.reject(e); + }); return request - .delete(`${config.host}/api/v1/version/${version}`, { + .delete(`${config.host}/api/v1/version/${selectedVersion}`, { json: true, auth: { user: key }, }) - .then(() => Promise.resolve(`Version ${version} deleted successfully.`)) + .then(() => Promise.resolve(`Version ${selectedVersion} deleted successfully.`)) .catch(err => Promise.reject(new APIError(err))); }; diff --git a/src/cmds/versions/update.js b/src/cmds/versions/update.js index 1e8fbbf8b..c4e80d438 100644 --- a/src/cmds/versions/update.js +++ b/src/cmds/versions/update.js @@ -1,9 +1,9 @@ const request = require('request-promise-native'); const config = require('config'); -const semver = require('semver'); const { prompt } = require('enquirer'); const promptOpts = require('../../lib/prompts'); const APIError = require('../../lib/apiError'); +const { getProjectVersion } = require('../../lib/versionSelect'); exports.command = 'versions:update'; exports.usage = 'versions:update --version= [options]'; @@ -51,14 +51,12 @@ exports.run = async function (opts) { return Promise.reject(new Error('No project API key provided. Please use `--key`.')); } - if (!version || !semver.valid(semver.coerce(version))) { - return Promise.reject( - new Error(`Please specify a semantic version. See \`${config.cli} help ${exports.command}\` for help.`) - ); - } + const selectedVersion = await getProjectVersion(version, key, false).catch(e => { + return Promise.reject(e); + }); const foundVersion = await request - .get(`${config.host}/api/v1/version/${version}`, { + .get(`${config.host}/api/v1/version/${selectedVersion}`, { json: true, auth: { user: key }, }) @@ -78,7 +76,7 @@ exports.run = async function (opts) { }; return request - .put(`${config.host}/api/v1/version/${version}`, options) - .then(() => Promise.resolve(`Version ${version} updated successfully.`)) + .put(`${config.host}/api/v1/version/${selectedVersion}`, options) + .then(() => Promise.resolve(`Version ${selectedVersion} updated successfully.`)) .catch(err => Promise.reject(new APIError(err))); }; diff --git a/src/lib/prompts.js b/src/lib/prompts.js index bbb72b330..bd4d40f33 100644 --- a/src/lib/prompts.js +++ b/src/lib/prompts.js @@ -1,10 +1,13 @@ const semver = require('semver'); -exports.generatePrompts = versionList => [ +exports.generatePrompts = (versionList, selectOnly = false) => [ { type: 'select', name: 'option', message: 'Would you like to use an existing version or create a new one to associate with your OAS file?', + skip() { + return selectOnly; + }, choices: [ { message: 'Use existing', value: 'update' }, { message: 'Create a new version', value: 'create' }, @@ -15,7 +18,7 @@ exports.generatePrompts = versionList => [ name: 'versionSelection', message: 'Select your desired version', skip() { - return this.enquirer.answers.option !== 'update'; + return selectOnly ? false : this.enquirer.answers.option !== 'update'; }, choices: versionList.map(v => { return { @@ -29,7 +32,7 @@ exports.generatePrompts = versionList => [ name: 'newVersion', message: "What's your new version?", skip() { - return this.enquirer.answers.option === 'update'; + return selectOnly ? true : this.enquirer.answers.option === 'update'; }, hint: '1.0.0', }, diff --git a/src/lib/versionSelect.js b/src/lib/versionSelect.js new file mode 100644 index 000000000..0526f1b1d --- /dev/null +++ b/src/lib/versionSelect.js @@ -0,0 +1,38 @@ +const { prompt } = require('enquirer'); +const promptOpts = require('./prompts'); +const request = require('request-promise-native'); +const config = require('config'); +const APIError = require('./apiError'); + +async function getProjectVersion(versionFlag, key, allowNewVersion) { + 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); + + if (allowNewVersion) { + const { option, versionSelection, newVersion } = await prompt(promptOpts.generatePrompts(versionList)); + + 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; + } + + const { versionSelection } = await prompt(promptOpts.generatePrompts(versionList, true)); + return versionSelection; + } catch (err) { + return Promise.reject(new APIError(err)); + } +} + +module.exports = { getProjectVersion };