diff --git a/README.md b/README.md index 41d64ceaf..073c2908c 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,9 @@ Build status

-With `rdme`, you can manage your API definition (we support [OpenAPI](https://spec.openapis.org/oas/v3.1.0.html) or [Swagger](https://swagger.io/specification/v2/)) and sync it to your API reference docs on ReadMe. You can also access other parts of [ReadMe's RESTful API](https://docs.readme.com/reference), including syncing Markdown documentation with your ReadMe project and managing project versions. + + +With `rdme`, you can manage your API definition (we support [OpenAPI](https://spec.openapis.org/oas/v3.1.0.html), [Swagger](https://swagger.io/specification/v2/), and [Postman](https://schema.postman.com/)) and sync it to your API reference docs on ReadMe. You can also access other parts of [ReadMe's RESTful API](https://docs.readme.com/reference), including syncing Markdown documentation with your ReadMe project and managing project versions. Not using ReadMe for your docs? No worries. `rdme` has a variety of tools to help you identify issues with your API definition — no ReadMe account required. @@ -36,7 +38,7 @@ https://github.com/jonschlinkert/markdown-toc/issues/119 - [GitHub Actions Configuration](#github-actions-configuration) - [Usage](#usage) - [Common `rdme` Options](#common-rdme-options) - - [OpenAPI / Swagger 📚](#openapi--swagger-) + - [API Definitions 📚](#api-definitions-) - [Docs (a.k.a. Guides) 📖](#docs-aka-guides-) - [Changelog 📣](#changelog-) - [Custom Pages 📄](#custom-pages-) @@ -116,16 +118,20 @@ If you wish to get more information about any command within `rdme`, you can exe - `--key `: The API key associated with your ReadMe project. Note that most of the commands below require API key authentication, even though the `--key` flag is omitted from the examples. See the [Authentication](#authentication) section above for more information. - `--version `: Your project version. See [our docs](https://docs.readme.com/docs/versions) for more information. -### OpenAPI / Swagger 📚 +### API Definitions 📚 -With `rdme`, you have access to a variety of tools to manage your OpenAPI or Swagger definition, most of which don't require an account on ReadMe. These tools include: +With `rdme`, you have access to a variety of tools to manage your API definition, most of which don't require an account on ReadMe. These tools include: -- [Reduction](#reducing-an-api-definition) 📉 - [Syncing](#syncing-an-api-definition-to-readme) 🦉 - [Validation](#validating-an-api-definition) ✅ +- [Reduction](#reducing-an-api-definition) 📉 `rdme` supports [OpenAPI 3.1](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md), [OpenAPI 3.0](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md), and [Swagger 2.x](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md). + + +You can also pass in [Postman Collections](https://www.postman.com/collection/). Postman Collections are converted to OpenAPI using [`postman-to-openapi`](https://github.com/joolfe/postman-to-openapi) prior to any syncing/validation/reduction. + The following examples use JSON files, but `rdme` supports API Definitions that are written in either JSON or YAML. #### Syncing an API Definition to ReadMe diff --git a/__tests__/index.test.ts b/__tests__/index.test.ts index c2ce54132..41a99db9f 100644 --- a/__tests__/index.test.ts +++ b/__tests__/index.test.ts @@ -101,7 +101,7 @@ describe('cli', () => { beforeEach(() => { conf.set('email', 'owlbert@readme.io'); - conf.set('project', 'owlbert'); + conf.set('project', 'project-owlbert'); conf.set('apiKey', key); consoleInfoSpy = jest.spyOn(console, 'info').mockImplementation(); }); @@ -133,7 +133,9 @@ describe('cli', () => { 'The `--create` and `--update` options cannot be used simultaneously. Please use one or the other!' ); - expect(getCommandOutput()).toMatch('is currently logged in, using the stored API key for this project:'); + expect(getCommandOutput()).toMatch( + 'owlbert@readme.io is currently logged in, using the stored API key for this project: project-owlbert' + ); }); it('should not inform a logged in user when they pass their own key', async () => { diff --git a/src/lib/baseCommand.ts b/src/lib/baseCommand.ts index e15d6e96c..03b4a4207 100644 --- a/src/lib/baseCommand.ts +++ b/src/lib/baseCommand.ts @@ -97,8 +97,8 @@ export default class Command { if (this.args.some(arg => arg.name === 'key')) { if (opts.key && configstore.get('apiKey') === opts.key) { info( - `🔑 ${configstore.get( - 'email' + `🔑 ${chalk.green( + configstore.get('email') )} is currently logged in, using the stored API key for this project: ${chalk.blue( configstore.get('project') )}`, diff --git a/src/lib/prepareOas.ts b/src/lib/prepareOas.ts index 96f592d60..44d499827 100644 --- a/src/lib/prepareOas.ts +++ b/src/lib/prepareOas.ts @@ -7,10 +7,24 @@ import { debug, info, oraOptions } from './logger'; import promptTerminal from './promptWrapper'; import readdirRecursive from './readdirRecursive'; +type FoundSpecFile = { + /** path to the spec file */ + filePath: string; + specType: 'OpenAPI' | 'Swagger' | 'Postman'; + /** + * OpenAPI or Postman specification version + * @example '3.1' + */ + version: string; +}; + type FileSelection = { file: string; }; +const capitalizeSpecType = (type: string) => + type === 'openapi' ? 'OpenAPI' : type.charAt(0).toUpperCase() + type.slice(1); + /** * Normalizes, validates, and (optionally) bundles an OpenAPI definition. * @@ -58,7 +72,7 @@ export default async function prepareOas(path: string, command: 'openapi' | 'ope debug(`number of JSON or YAML files found: ${jsonAndYamlFiles.length}`); - const possibleSpecFiles = ( + const possibleSpecFiles: FoundSpecFile[] = ( await Promise.all( jsonAndYamlFiles.map(file => { debug(`attempting to oas-normalize ${file}`); @@ -68,11 +82,13 @@ export default async function prepareOas(path: string, command: 'openapi' | 'ope .then(({ specification, version }) => { debug(`specification type for ${file}: ${specification}`); debug(`version for ${file}: ${version}`); - return ['openapi', 'swagger', 'postman'].includes(specification) ? file : ''; + return ['openapi', 'swagger', 'postman'].includes(specification) + ? { filePath: file, specType: capitalizeSpecType(specification), version } + : null; }) .catch(e => { debug(`error extracting API definition specification version for ${file}: ${e.message}`); - return ''; + return null; }); }) ) @@ -87,10 +103,10 @@ export default async function prepareOas(path: string, command: 'openapi' | 'ope ); } - specPath = possibleSpecFiles[0]; + specPath = possibleSpecFiles[0].filePath; if (possibleSpecFiles.length === 1) { - fileFindingSpinner.succeed(`${fileFindingSpinner.text} found! 🔍`); + fileFindingSpinner.stop(); info(chalk.yellow(`We found ${specPath} and are attempting to ${action} it.`)); } else if (possibleSpecFiles.length > 1) { if (isCI()) { @@ -98,13 +114,17 @@ export default async function prepareOas(path: string, command: 'openapi' | 'ope throw new Error('Multiple API definitions found in current directory. Please specify file.'); } - fileFindingSpinner.succeed(`${fileFindingSpinner.text} found! 🔍`); + fileFindingSpinner.stop(); const selection: FileSelection = await promptTerminal({ name: 'file', message: `Multiple potential API definitions found! Which one would you like to ${action}?`, type: 'select', - choices: possibleSpecFiles.map(file => ({ title: file, value: file })), + choices: possibleSpecFiles.map(file => ({ + title: file.filePath, + value: file.filePath, + description: `${file.specType} ${file.version}`, + })), }); specPath = selection.file; @@ -118,7 +138,7 @@ export default async function prepareOas(path: string, command: 'openapi' | 'ope debug('spec normalized'); // We're retrieving the original specification type here instead of after validation because if - // they give us a Postman colletion we should tell them that we handled a Postman collection, not + // they give us a Postman collection we should tell them that we handled a Postman collection, not // an OpenAPI definition (eventhough we'll actually convert it to OpenAPI under the hood). // // And though `.validate()` will run `.load()` itself running `.load()` here will not have any @@ -126,7 +146,7 @@ export default async function prepareOas(path: string, command: 'openapi' | 'ope // run it. const specType = await oas.load().then(schema => { const type = getAPIDefinitionType(schema); - return type === 'openapi' ? 'OpenAPI' : type.charAt(0).toUpperCase() + type.slice(1); + return capitalizeSpecType(type); }); // If we were supplied a Postman collection this will **always** convert it to OpenAPI 3.0. @@ -143,7 +163,7 @@ export default async function prepareOas(path: string, command: 'openapi' | 'ope debug(`spec type: ${specType}`); // No need to optional chain here since `info.version` is required to pass validation - const specVersion = api.info.version; + const specVersion: string = api.info.version; debug(`version in spec: ${specVersion}`); let bundledSpec = ''; @@ -156,5 +176,16 @@ export default async function prepareOas(path: string, command: 'openapi' | 'ope debug('spec bundled'); } - return { bundledSpec, specPath, specType, specVersion }; + return { + bundledSpec, + specPath, + specType, + /** + * The `info.version` field, extracted from the normalized spec. + * This is **not** the OpenAPI version (e.g., 3.1, 3.0), + * this is a user input that we use to specify the version in ReadMe + * (if they use the `useSpecVersion` flag) + */ + specVersion, + }; } diff --git a/src/lib/streamSpecToRegistry.ts b/src/lib/streamSpecToRegistry.ts index 2feefcee3..caadc83f3 100644 --- a/src/lib/streamSpecToRegistry.ts +++ b/src/lib/streamSpecToRegistry.ts @@ -38,7 +38,7 @@ export default async function streamSpecToRegistry(spec: string) { return fetch(`${config.get('host')}/api/v1/api-registry`, options) .then(res => handleRes(res)) .then(body => { - spinner.succeed(`${spinner.text} done! 🚀`); + spinner.stop(); return body.registryUUID; }) .catch(e => {