diff --git a/__tests__/lib/prompts.test.js b/__tests__/lib/prompts.test.js index e11bdbb03..20c73915c 100644 --- a/__tests__/lib/prompts.test.js +++ b/__tests__/lib/prompts.test.js @@ -21,6 +21,15 @@ const specList = [ }, ]; +const getSpecs = () => { + return [ + { + _id: 'spec3', + title: 'spec3_title', + }, + ]; +}; + describe('prompt test bed', () => { let enquirer; @@ -70,23 +79,31 @@ describe('prompt test bed', () => { await prompt.keypress(null, { name: 'down' }); await prompt.submit(); }); - const answer = await enquirer.prompt(promptHandler.createOasPrompt([{}])); + const answer = await enquirer.prompt(promptHandler.createOasPrompt([{}], null, 1, null)); expect(answer.option).toBe('create'); - expect(answer.specId).toBe(''); }); it('should return specId if user chooses to update file', async () => { + jest.mock('enquirer'); enquirer.on('prompt', async prompt => { await prompt.keypress(null, { name: 'down' }); await prompt.keypress(null, { name: 'up' }); await prompt.submit(); - await prompt.submit(); }); - const answer = await enquirer.prompt(promptHandler.createOasPrompt(specList)); + enquirer.prompt = jest.fn(); + enquirer.prompt.mockReturnValue('spec1'); + const parsedDocs = { + next: { + page: null, + }, + prev: { + page: null, + }, + }; + const answer = await enquirer.prompt(promptHandler.createOasPrompt(specList, parsedDocs, 1, getSpecs)); - expect(answer.option).toBe('update'); - expect(answer.specId).toBe('spec1'); + expect(answer).toBe('spec1'); }); }); diff --git a/package-lock.json b/package-lock.json index 9b28c0b6f..bedb7e757 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "oas": "^14.0.0", "oas-normalize": "^3.0.4", "open": "^8.2.1", + "parse-link-header": "^1.0.1", "read": "^1.0.7", "semver": "^7.0.0", "table-layout": "^1.0.0" @@ -11593,6 +11594,14 @@ "node": ">=0.10.0" } }, + "node_modules/parse-link-header": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-link-header/-/parse-link-header-1.0.1.tgz", + "integrity": "sha1-vt/g0hGK64S+deewJUGeyKYRQKc=", + "dependencies": { + "xtend": "~4.0.1" + } + }, "node_modules/parse5": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", @@ -13456,7 +13465,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, "engines": { "node": ">=0.4" } @@ -22347,6 +22355,14 @@ "error-ex": "^1.2.0" } }, + "parse-link-header": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-link-header/-/parse-link-header-1.0.1.tgz", + "integrity": "sha1-vt/g0hGK64S+deewJUGeyKYRQKc=", + "requires": { + "xtend": "~4.0.1" + } + }, "parse5": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", @@ -23832,8 +23848,7 @@ "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" }, "y18n": { "version": "5.0.8", diff --git a/package.json b/package.json index 101191fd0..60cc872d5 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "oas": "^14.0.0", "oas-normalize": "^3.0.4", "open": "^8.2.1", + "parse-link-header": "^1.0.1", "read": "^1.0.7", "semver": "^7.0.0", "table-layout": "^1.0.0" diff --git a/src/cmds/openapi.js b/src/cmds/openapi.js index 17fae13f0..1985c9f23 100644 --- a/src/cmds/openapi.js +++ b/src/cmds/openapi.js @@ -8,6 +8,7 @@ const APIError = require('../lib/apiError'); const { getProjectVersion } = require('../lib/versionSelect'); const fetch = require('node-fetch'); const FormData = require('form-data'); +const parse = require('parse-link-header'); exports.command = 'openapi'; exports.usage = 'openapi [file] [options]'; @@ -159,19 +160,28 @@ exports.run = async function (opts) { - If found, prompt user to either create a new spec or update an existing one */ - if (!id) { - const apiSettings = await fetch(`${config.host}/api/v1/api-specification`, { + function getSpecs(url) { + return fetch(`${config.host}${url}`, { method: 'get', headers: { 'x-readme-version': versionCleaned, Authorization: `Basic ${encodedString}`, }, - }).then(res => res.json()); + }); + } + + if (!id) { + const apiSettings = await getSpecs(`/api/v1/api-specification`); + + const totalPages = Math.ceil(apiSettings.headers.get('x-total-count') / 10); + const parsedDocs = parse(apiSettings.headers.get('link')); - if (!apiSettings.length) return createSpec(); + const apiSettingsBody = await apiSettings.json(); + if (!apiSettingsBody.length) return createSpec(); - const { option, specId } = await prompt(promptOpts.createOasPrompt(apiSettings)); - return option === 'create' ? createSpec() : updateSpec(specId); + const { option } = await prompt(promptOpts.createOasPrompt(apiSettingsBody, parsedDocs, totalPages, getSpecs)); + if (!option) return null; + return option === 'create' ? createSpec() : updateSpec(option); } /* diff --git a/src/lib/prompts.js b/src/lib/prompts.js index bd4d40f33..4fd7316ce 100644 --- a/src/lib/prompts.js +++ b/src/lib/prompts.js @@ -1,4 +1,6 @@ const semver = require('semver'); +const { prompt } = require('enquirer'); +const parse = require('parse-link-header'); exports.generatePrompts = (versionList, selectOnly = false) => [ { @@ -38,7 +40,59 @@ exports.generatePrompts = (versionList, selectOnly = false) => [ }, ]; -exports.createOasPrompt = specList => [ +function specOptions(specList, parsedDocs, currPage, totalPages) { + const specs = specList.map(s => { + return { + message: s.title, + value: s._id, // eslint-disable-line no-underscore-dangle + }; + }); + if (parsedDocs.prev.page) specs.push({ message: `< Prev (page ${currPage - 1} of ${totalPages})`, value: 'prev' }); + if (parsedDocs.next.page) { + specs.push({ message: `Next (page ${currPage + 1} of ${totalPages}) >`, value: 'next' }); + } + return specs; +} + +const updateOasPrompt = (specList, parsedDocs, currPage, totalPages, getSpecs) => [ + { + type: 'select', + name: 'specId', + message: 'Select your desired file to update', + choices: specOptions(specList, parsedDocs, currPage, totalPages), + async result(spec) { + if (spec === 'prev') { + try { + const newSpecs = await getSpecs(`${parsedDocs.prev.url}`); + const newParsedDocs = parse(newSpecs.headers.get('link')); + const newSpecList = await newSpecs.json(); + const { specId } = await prompt( + updateOasPrompt(newSpecList, newParsedDocs, currPage - 1, totalPages, getSpecs) + ); + return specId; + } catch (e) { + return null; + } + } + if (spec === 'next') { + try { + const newSpecs = await getSpecs(`${parsedDocs.next.url}`); + const newParsedDocs = parse(newSpecs.headers.get('link')); + const newSpecList = await newSpecs.json(); + const { specId } = await prompt( + updateOasPrompt(newSpecList, newParsedDocs, currPage + 1, totalPages, getSpecs) + ); + return specId; + } catch (e) { + return null; + } + } + return spec; + }, + }, +]; + +exports.createOasPrompt = (specList, parsedDocs, totalPages, getSpecs) => [ { type: 'select', name: 'option', @@ -47,20 +101,17 @@ exports.createOasPrompt = specList => [ { message: 'Update existing', value: 'update' }, { message: 'Create a new spec', value: 'create' }, ], - }, - { - type: 'select', - name: 'specId', - message: 'Select your desired file to update', - skip() { - return this.enquirer.answers.option !== 'update'; + async result(picked) { + if (picked === 'update') { + try { + const { specId } = await prompt(updateOasPrompt(specList, parsedDocs, 1, totalPages, getSpecs)); + return specId; + } catch (e) { + return null; + } + } + return picked; }, - choices: specList.map(s => { - return { - message: s.title, - value: s._id, // eslint-disable-line no-underscore-dangle - }; - }), }, ];