Skip to content

Commit f1e4d16

Browse files
fix(openapi/upload): improved handling for YAML files (#1154)
| 🚥 Resolves RM-11893, #1142 | | :------------------- | ## 🧰 Changes When uploading an API definition, `rdme` converts all files to JSON, which means that YAML files are uploaded in JSON but with the file extension of `.yaml` or `.yml`. This will convert files originally in YAML back to YAML before uploading to ReadMe. ## 🧬 QA & Testing Upload a spec in YAML and confirm in GitHug that it's been stored in YAML. Also confirm that JSON specs are uploaded in JSON as well. --------- Co-authored-by: Kanad Gupta <[email protected]>
1 parent 4da5cbc commit f1e4d16

File tree

5 files changed

+57
-9
lines changed

5 files changed

+57
-9
lines changed

__tests__/commands/openapi/__snapshots__/upload.test.ts.snap

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ exports[`rdme openapi upload > given that the API definition is a local file > g
233233
}
234234
`;
235235
236-
exports[`rdme openapi upload > given that the API definition is a local file > should create a new API definition in ReadMe 1`] = `
236+
exports[`rdme openapi upload > given that the API definition is a local file > should create a new JSON API definition in ReadMe 1`] = `
237237
{
238238
"result": {
239239
"status": "done",
@@ -252,6 +252,25 @@ exports[`rdme openapi upload > given that the API definition is a local file > s
252252
}
253253
`;
254254
255+
exports[`rdme openapi upload > given that the API definition is a local file > should create a new YAML API definition in ReadMe 1`] = `
256+
{
257+
"result": {
258+
"status": "done",
259+
"uri": "/versions/1.0.0/apis/__tests____fixtures__postmanpetstore.collection.yaml",
260+
},
261+
"stderr": "- Validating the API definition located at __tests__/__fixtures__/postman/petstore.collection.yaml...
262+
Warning: The slug of your API Definition will be set to
263+
__tests____fixtures__postmanpetstore.collection.yaml in ReadMe. This slug
264+
is not visible to your end users. To set this slug to something else, use
265+
the \`--slug\` flag.
266+
- Creating your API definition to ReadMe...
267+
✔ Creating your API definition to ReadMe... done!
268+
",
269+
"stdout": "🚀 Your API definition (__tests____fixtures__postmanpetstore.collection.yaml) was successfully created in ReadMe!
270+
",
271+
}
272+
`;
273+
255274
exports[`rdme openapi upload > given that the API definition is a local file > should handle upload failures 1`] = `
256275
{
257276
"error": [Error: Your API definition upload failed with an unexpected error. Please get in touch with us at support@readme.io.],

__tests__/commands/openapi/upload.test.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ import { after, before } from '../../helpers/setup-gha-env.js';
1212
const key = 'rdme_123';
1313
const version = '1.0.0';
1414
const filename = '__tests__/__fixtures__/petstore-simple-weird-version.json';
15+
const yamlFile = '__tests__/__fixtures__/postman/petstore.collection.yaml';
1516
const fileUrl = 'https://example.com/openapi.json';
1617
const slugifiedFilename = slugify.default(filename);
18+
const slugifiedYamlFile = slugify.default(yamlFile);
1719

1820
describe('rdme openapi upload', () => {
1921
let run: (args?: string[]) => OclifOutput;
@@ -30,12 +32,12 @@ describe('rdme openapi upload', () => {
3032
});
3133

3234
describe('given that the API definition is a local file', () => {
33-
it('should create a new API definition in ReadMe', async () => {
35+
it('should create a new JSON API definition in ReadMe', async () => {
3436
const mock = getAPIv2Mock({ authorization: `Bearer ${key}` })
3537
.get(`/versions/${version}/apis`)
3638
.reply(200, { data: [] })
3739
.post(`/versions/${version}/apis`, body =>
38-
body.match(`form-data; name="schema"; filename="${slugifiedFilename}"`),
40+
body.match(`form-data; name="schema"; filename="${slugifiedFilename}"\r\nContent-Type: application/json`),
3941
)
4042
.reply(200, {
4143
data: {
@@ -50,6 +52,26 @@ describe('rdme openapi upload', () => {
5052
mock.done();
5153
});
5254

55+
it('should create a new YAML API definition in ReadMe', async () => {
56+
const mock = getAPIv2Mock({ authorization: `Bearer ${key}` })
57+
.get(`/versions/${version}/apis`)
58+
.reply(200, { data: [] })
59+
.post(`/versions/${version}/apis`, body =>
60+
body.match(`form-data; name="schema"; filename="${slugifiedYamlFile}"\r\nContent-Type: application/x-yaml`),
61+
)
62+
.reply(200, {
63+
data: {
64+
upload: { status: 'done' },
65+
uri: `/versions/${version}/apis/${slugifiedYamlFile}`,
66+
},
67+
});
68+
69+
const result = await run(['--version', version, yamlFile, '--key', key]);
70+
expect(result).toMatchSnapshot();
71+
72+
mock.done();
73+
});
74+
5375
it('should update an existing API definition in ReadMe', async () => {
5476
prompts.inject([true]);
5577

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"debug": "^4.3.3",
5757
"gray-matter": "^4.0.1",
5858
"ignore": "^7.0.0",
59+
"js-yaml": "^4.1.0",
5960
"mime-types": "^2.1.35",
6061
"oas": "^25.3.0",
6162
"oas-normalize": "^12.1.0",
@@ -95,7 +96,6 @@
9596
"alex": "^11.0.0",
9697
"eslint": "^8.47.0",
9798
"husky": "^9.0.10",
98-
"js-yaml": "^4.1.0",
9999
"json-schema-to-ts": "^3.1.1",
100100
"knip": "^5.0.2",
101101
"nock": "^14.0.0",

src/commands/openapi/upload.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import fs from 'node:fs';
22
import nodePath from 'node:path';
33

44
import { Flags } from '@oclif/core';
5+
import yaml from 'js-yaml';
56
import ora from 'ora';
67
import prompts from 'prompts';
78
import slugify from 'slugify';
@@ -115,8 +116,8 @@ export default class OpenAPIUploadCommand extends BaseCommand<typeof OpenAPIUplo
115116
);
116117
}
117118

119+
const fileExtension = nodePath.extname(filename);
118120
if (this.flags.slug) {
119-
const fileExtension = nodePath.extname(filename);
120121
const slugExtension = nodePath.extname(this.flags.slug);
121122
if (slugExtension && (!['.json', '.yaml', '.yml'].includes(slugExtension) || fileExtension !== slugExtension)) {
122123
throw new Error(
@@ -165,19 +166,25 @@ export default class OpenAPIUploadCommand extends BaseCommand<typeof OpenAPIUplo
165166
this.debug('attaching URL to form data payload');
166167
body.append('url', specPath);
167168
} else {
169+
const isYaml = fileExtension === '.yaml' || fileExtension === '.yml';
170+
// Convert YAML files back to YAML before uploading
171+
let specToUpload = preparedSpec;
172+
if (isYaml) {
173+
specToUpload = yaml.dump(JSON.parse(preparedSpec));
174+
}
168175
// Create a temporary file to write the bundled spec to,
169176
// which we will then stream into the form data body
170-
const { path } = await tmpFile({ prefix: 'rdme-openapi-', postfix: '.json' });
177+
const { path } = await tmpFile({ prefix: 'rdme-openapi-', postfix: fileExtension });
171178
this.debug(`creating temporary file at ${path}`);
172-
fs.writeFileSync(path, preparedSpec);
179+
fs.writeFileSync(path, specToUpload);
173180
const stream = fs.createReadStream(path);
174181

175182
this.debug('file and stream created, streaming into form data payload');
176183
body.append('schema', {
177184
[Symbol.toStringTag]: 'File',
178185
name: filename,
179186
stream: () => stream,
180-
type: 'application/json',
187+
type: isYaml ? 'application/x-yaml' : 'application/json',
181188
});
182189
}
183190

0 commit comments

Comments
 (0)