Skip to content

Commit 903b8fb

Browse files
authored
Split and Bundle references (#144)
* Bundle local and remote file references * Split openAPI in files with references
1 parent 96237ae commit 903b8fb

File tree

19 files changed

+679
-58
lines changed

19 files changed

+679
-58
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
## unreleased
22

3+
- CLI - Bundle local and remote file references
4+
- CLI - Split OpenAPI in a multi-file structure
35
- Add type definitions for openapi-format
46

57
## [1.23.2] - 2024-09-16

bin/__snapshots__/cli.test.js.snap

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`openapi-format CLI command should load the default .openapiformatrc if configFile is not provided 1`] = `
3+
exports[`openapi-format CLI command should bundle reference 1`] = `
44
"================================================================================
55
OpenAPI-Format CLI settings:
6-
- Sort file: (defaultSort.json)
7-
- Input file: test/yaml-default/input.yaml
6+
- Input file: test/yaml-ref-quotes/input.yaml
87
================================================================================
98
✅ OpenAPI formatted successfully
109
================================================================================
@@ -23,6 +22,8 @@ OpenAPI-Format CLI options:
2322
| sort | false |
2423
| keepComments | true |
2524
| lineWidth | -1 |
25+
| bundle | true |
26+
| split | false |
2627
| filterFile | test/yaml-no-sort-keep-comments/customFilter.yaml |
2728
| verbose | 3 |
2829
|--------------|---------------------------------------------------|
@@ -33,6 +34,27 @@ OpenAPI-Format CLI options:
3334
"
3435
`;
3536

37+
exports[`openapi-format CLI command should load the default .openapiformatrc if configFile is not provided 1`] = `
38+
"================================================================================
39+
OpenAPI-Format CLI settings:
40+
- Sort file: (defaultSort.json)
41+
- Input file: test/yaml-default/input.yaml
42+
================================================================================
43+
✅ OpenAPI formatted successfully
44+
================================================================================
45+
"
46+
`;
47+
48+
exports[`openapi-format CLI command should not bundle reference 1`] = `
49+
"================================================================================
50+
OpenAPI-Format CLI settings:
51+
- Input file: test/yaml-ref-quotes/input.yaml
52+
================================================================================
53+
✅ OpenAPI formatted successfully
54+
================================================================================
55+
"
56+
`;
57+
3658
exports[`openapi-format CLI command should not convert large numbers in JSON 1`] = `
3759
"================================================================================
3860
OpenAPI-Format CLI settings:
@@ -71,6 +93,8 @@ Options:
7193
--lineWidth <lineWidth> max line width of YAML output (default: -1)
7294
--rename <oaTitle> overwrite the title in the OpenAPI document
7395
--convertTo <oaVersion> convert the OpenAPI document to OpenAPI version 3.1
96+
--no-bundle Bundle the local and remote $ref in the OpenAPI document.
97+
--split Split the OpenAPI document into a multi-file structure. (default: false)
7498
--json print the file to stdout as JSON
7599
--yaml print the file to stdout as YAML
76100
-p, --playground Open config in online playground
@@ -110,6 +134,17 @@ Total components removed: 16
110134
"
111135
`;
112136
137+
exports[`openapi-format CLI command should split the openapi file 1`] = `
138+
"================================================================================
139+
OpenAPI-Format CLI settings:
140+
- Input file: ../__utils__/train.yaml
141+
- Output location: output.yaml
142+
================================================================================
143+
✅ OpenAPI formatted successfully
144+
================================================================================
145+
"
146+
`;
147+
113148
exports[`openapi-format CLI command should stop and show error 1`] = `
114149
"================================================================================
115150
OpenAPI-Format CLI settings:
@@ -132,7 +167,7 @@ exports[`openapi-format CLI command should stop and show error about remote file
132167
OpenAPI-Format CLI settings:
133168
- Sort file: (defaultSort.json)
134169
- Input file: https://raw.githubusercontent.com/thim81/openapi-format/main/test/yaml-default/foo.yaml
135-
[31m Input file error - Failed to download file: 404 Not Found
170+
[31m Input file error - Failed to read file: 404 Not Found
136171
"
137172
`;
138173
@@ -194,6 +229,8 @@ OpenAPI-Format CLI options:
194229
| sort | false |
195230
| keepComments | false |
196231
| lineWidth | -1 |
232+
| bundle | true |
233+
| split | false |
197234
| convertTo | 3.1 |
198235
| verbose | 3 |
199236
|--------------|-------|

bin/cli.js

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ program
2828
.option('--lineWidth <lineWidth>', 'max line width of YAML output', -1)
2929
.option('--rename <oaTitle>', 'overwrite the title in the OpenAPI document')
3030
.option('--convertTo <oaVersion>', 'convert the OpenAPI document to OpenAPI version 3.1')
31+
.option('--no-bundle', 'Bundle the local and remote $ref in the OpenAPI document.', false)
32+
.option('--split', 'Split the OpenAPI document into a multi-file structure.', false)
3133
.option('--json', 'print the file to stdout as JSON')
3234
.option('--yaml', 'print the file to stdout as YAML')
3335
.option('-p, --playground', 'Open config in online playground')
@@ -92,13 +94,18 @@ async function run(oaFile, options) {
9294
configOptions = Object.assign({}, defaultOptions, configOptions);
9395
options.lineWidth = configOptions?.lineWidth ?? options.lineWidth;
9496
options.sort = configOptions?.sort ?? options.sort;
97+
options.bundle = configOptions?.bundle ?? options.bundle;
9598

9699
// Merge configOptions and CLI options
97100
options = Object.assign({}, configOptions, options);
98101
if (options['no-sort'] && options['no-sort'] === true) {
99102
options.sort = !options['no-sort'];
100103
delete options['no-sort'];
101104
}
105+
if (options['no-bundle'] && options['no-bundle'] === true) {
106+
options.bundle = !options['no-bundle'];
107+
delete options['no-bundle'];
108+
}
102109

103110
// LOG - Render info table with options
104111
outputLogOptions = infoTable(options, options.verbose);
@@ -194,7 +201,7 @@ async function run(oaFile, options) {
194201
let resObj = {};
195202
let output = {};
196203
let input = {};
197-
let fileOptions = {keepComments: options?.keepComments || false};
204+
let fileOptions = {keepComments: options.keepComments ?? false, bundle: options.bundle ?? true};
198205

199206
try {
200207
infoOut(`- Input file:\t\t${oaFile}`); // LOG - Input file
@@ -204,7 +211,7 @@ async function run(oaFile, options) {
204211
input = resObj;
205212
} catch (err) {
206213
if (err.code !== 'ENOENT') {
207-
console.error('\x1b[31m', `Input file error - Failed to download file: ${err.message}`);
214+
console.error('\x1b[31m', `Input file error - Failed to read file: ${err.message}`);
208215
process.exit(1);
209216
}
210217
console.error('\x1b[31m', `Input file error - Failed to read file: ${oaFile}`);
@@ -258,14 +265,27 @@ async function run(oaFile, options) {
258265

259266
options.yamlComments = fileOptions.yamlComments || {};
260267
if (options.output) {
261-
try {
262-
// Write OpenAPI string to file
263-
await openapiFormat.writeFile(options.output, resObj, options);
264-
infoOut(`- Output file:\t\t${options.output}`); // LOG - config file
265-
} catch (err) {
266-
console.error('\x1b[31m', `Output file error - no such file or directory "${options.output}"`);
267-
if (options.verbose >= 1) {
268-
console.error(err);
268+
if (options.split !== true) {
269+
try {
270+
// Write OpenAPI string to single file
271+
await openapiFormat.writeFile(options.output, resObj, options);
272+
infoOut(`- Output file:\t\t${options.output}`); // LOG - config file
273+
} catch (err) {
274+
console.error('\x1b[31m', `Output file error - no such file or directory "${options.output}"`);
275+
if (options.verbose >= 1) {
276+
console.error(err);
277+
}
278+
}
279+
} else {
280+
try {
281+
// Write Split files
282+
await openapiFormat.openapiSplit(resObj, options);
283+
infoOut(`- Output location:\t${options.output}`); // LOG - config file
284+
} catch (err) {
285+
console.error('\x1b[31m', `Split error - no such file or directory "${options.output}"`);
286+
if (options.verbose >= 1) {
287+
console.error(err);
288+
}
269289
}
270290
}
271291
} else {

bin/cli.test.js

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const testUtils = require('../test/__utils__/test-utils');
22
const fs = require('fs');
33
const {describe, it, expect} = require('@jest/globals');
4-
const {getLocalFile, getRemoteFile, parseFile} = require('../utils/file');
4+
const {getLocalFile} = require('../utils/file');
55

66
describe('openapi-format CLI command', () => {
77
it('should output the help', async () => {
@@ -191,6 +191,66 @@ describe('openapi-format CLI command', () => {
191191
expect(sanitize(result.stderr)).toStrictEqual(sanitize(output));
192192
});
193193

194+
it('should bundle reference', async () => {
195+
const path = `test/yaml-ref-quotes`;
196+
const inputFile = `${path}/input.yaml`;
197+
const outputFile = `${path}/bundled.yaml`;
198+
const output = await getLocalFile(outputFile);
199+
200+
let result = await testUtils.cli([inputFile, `--no-sort`], '.');
201+
// console.log('result', result)
202+
expect(result.code).toBe(0);
203+
expect(result.stdout).toContain('formatted successfully');
204+
expect(result.stdout).toMatchSnapshot();
205+
expect(sanitize(result.stderr)).toStrictEqual(sanitize(output));
206+
});
207+
208+
it('should not bundle reference', async () => {
209+
const path = `test/yaml-ref-quotes`;
210+
const inputFile = `${path}/input.yaml`;
211+
const outputFile = `${path}/output.yaml`;
212+
const output = await getLocalFile(outputFile);
213+
214+
let result = await testUtils.cli([inputFile, `--no-bundle`, `--no-sort`], '.');
215+
// console.log('result', result)
216+
expect(result.code).toBe(0);
217+
expect(result.stdout).toContain('formatted successfully');
218+
expect(result.stdout).toMatchSnapshot();
219+
expect(sanitize(result.stderr)).toStrictEqual(sanitize(output));
220+
});
221+
222+
it('should split the openapi file', async () => {
223+
const path = `test/_split`;
224+
const inputFile = `../__utils__/train.yaml`;
225+
const outputFile = `output.yaml`;
226+
227+
const snap = await getLocalFile(`${path}/snap.yaml`);
228+
const snap_station = await getLocalFile(`${path}/snap_station.yaml`);
229+
const snap_station_id = await getLocalFile(`${path}/snap_station_id.yaml`);
230+
231+
let result = await testUtils.cli([inputFile, `--output ${outputFile}`, `--split`, `--no-sort`], path);
232+
233+
const outputPath = `${path}/${outputFile}`;
234+
const outputStationPath = `${path}/paths/stations_{station_id}.yaml`;
235+
const outputStationIdPath = `${path}/components/parameters/StationId.yaml`;
236+
// const outputStationSchemaPath = `${path}/components/schemas/Station.yaml`
237+
const output = await getLocalFile(outputPath);
238+
const output_station = await getLocalFile(outputStationPath);
239+
const output_station_id = await getLocalFile(outputStationIdPath);
240+
241+
// console.log('result', result)
242+
expect(result.code).toBe(0);
243+
expect(result.stdout).toContain('formatted successfully');
244+
expect(result.stdout).toMatchSnapshot();
245+
expect(sanitize(output)).toStrictEqual(sanitize(snap));
246+
expect(sanitize(output_station)).toStrictEqual(sanitize(snap_station));
247+
expect(sanitize(output_station_id)).toStrictEqual(sanitize(snap_station_id));
248+
249+
fs.rmSync(`${path}/paths`, {recursive: true, force: true});
250+
fs.rmSync(`${path}/components`, {recursive: true, force: true});
251+
fs.unlinkSync(outputPath);
252+
});
253+
194254
it.skip('should generate a playground share URL', async () => {
195255
const path = `test/yaml-filter-custom`;
196256
const inputFile = `${path}/input.yaml`;

openapi-format.js

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ const {
2828
convertExclusiveMaximum,
2929
setInObject
3030
} = require('./utils/convert');
31-
const {parseFile, writeFile, stringify, detectFormat, parseString, analyzeOpenApi} = require('./utils/file');
31+
const {parseFile, writeFile, stringify, detectFormat, parseString, analyzeOpenApi, readFile} = require('./utils/file');
3232
const {parseTpl, getOperation} = require('./utils/parseTpl');
33+
const {writePaths, writeComponents, writeSplitOpenAPISpec} = require('./utils/split');
34+
const {dirname, extname} = require('node:path');
3335

3436
/**
3537
* OpenAPI sort function
@@ -1015,6 +1017,31 @@ async function openapiGenerate(oaObj, options) {
10151017
return {data: jsonObj, resultData: {}};
10161018
}
10171019

1020+
/**
1021+
* Split the OpenAPI document into a multi-file structure
1022+
* @param {object} oaObj OpenAPI document
1023+
* @param options
1024+
* @returns {Promise<void>}
1025+
*/
1026+
async function openapiSplit(oaObj, options = {}) {
1027+
if (!options.output) {
1028+
throw new Error('Output is required');
1029+
}
1030+
1031+
options.outputDir = dirname(options.output);
1032+
options.extension = extname(options.output).substring(1);
1033+
1034+
if (oaObj?.components) {
1035+
await writeComponents(oaObj.components, options);
1036+
}
1037+
1038+
if (oaObj?.paths) {
1039+
await writePaths(oaObj.paths, options);
1040+
}
1041+
1042+
await writeSplitOpenAPISpec(oaObj, options);
1043+
}
1044+
10181045
/**
10191046
* OpenAPI convert version function
10201047
* Convert OpenAPI from version 3.0 to 3.1
@@ -1118,8 +1145,10 @@ module.exports = {
11181145
openapiGenerate: openapiGenerate,
11191146
openapiSort: openapiSort,
11201147
openapiChangeCase: openapiChangeCase,
1148+
openapiSplit: openapiSplit,
11211149
openapiConvertVersion: openapiConvertVersion,
11221150
openapiRename: openapiRename,
1151+
readFile: readFile,
11231152
parseFile: parseFile,
11241153
parseString: parseString,
11251154
stringify: stringify,

0 commit comments

Comments
 (0)