From 1097fb57b062148e3206e7469969f829c9a4b4db Mon Sep 17 00:00:00 2001
From: Kanad Gupta <8854718+kanadgupta@users.noreply.github.com>
Date: Tue, 15 Nov 2022 17:55:26 -0600
Subject: [PATCH] feat: postman-related enhancements, output cleanup (#673)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* docs: add language on postman
* chore: typo
* chore: stop certain spinners
some of the spinner behavior felt like a lot. went through and cleaned up some of the spinners that the user didn't need if they went through the entire flow successfully.
* test: stricter test
* feat: make email address green so it pops 💥
* docs: stricter type + docs for `specVersion`
* refactor: extract function
this way we can reuse the capitalization logic
* feat: add spec type + version to selection prompt
* docs: one more place
* fix: alex ignore
* docs: schema link
---
README.md | 16 ++++++----
__tests__/index.test.ts | 6 ++--
src/lib/baseCommand.ts | 4 +--
src/lib/prepareOas.ts | 53 ++++++++++++++++++++++++++-------
src/lib/streamSpecToRegistry.ts | 2 +-
5 files changed, 60 insertions(+), 21 deletions(-)
diff --git a/README.md b/README.md
index 41d64ceaf..073c2908c 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,9 @@
-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 => {