feat: configurable filename separator for split command (#610)

* Make file path separator configurable in split command

* chore: add tests for the file name separator

* chore: update snapshots

* docs: split separator option description

* chore: code style fixes
This commit is contained in:
volodymyr-rutskyi
2022-03-28 18:44:43 +03:00
committed by GitHub
parent 3e8b08449d
commit 1f10c7cd77
9 changed files with 54 additions and 15 deletions

1
.gitignore vendored
View File

@@ -7,5 +7,6 @@ coverage/
yarn.lock yarn.lock
dist/ dist/
lib/ lib/
output/
*.tar.gz *.tar.gz
*.tsbuildinfo *.tsbuildinfo

View File

@@ -10,9 +10,10 @@ Positionals:
entrypoint API definition file that you want to split [string] [required] entrypoint API definition file that you want to split [string] [required]
Options: Options:
--version Show version number. [boolean] --version Show version number. [boolean]
--help Show help. [boolean] --help Show help. [boolean]
--outDir Output directory where files will be saved. [string] [required] --outDir Output directory where files will be saved. [string] [required]
--separator File path separator used while splitting. [string] [default: "_"]
Missing required argument: outDir Missing required argument: outDir

View File

@@ -109,7 +109,7 @@ paths:
/pets: /pets:
$ref: paths/pets.yaml $ref: paths/pets.yaml
/pets/{petId}: /pets/{petId}:
$ref: paths/pets@{petId}.yaml $ref: paths/pets_{petId}.yaml
🪓 Document: ../../../__tests__/split/oas3-no-errors/openapi.yaml is successfully split 🪓 Document: ../../../__tests__/split/oas3-no-errors/openapi.yaml is successfully split
and all related files are saved to the directory: output and all related files are saved to the directory: output

View File

@@ -22,6 +22,7 @@ Option | Type | Required | Default | Description
--------------------------|:---------:|:------------:|:-----------:|------------ --------------------------|:---------:|:------------:|:-----------:|------------
`entrypoint` | `string` | yes | - | Path to the API definition file that you want to split into a multi-file structure `entrypoint` | `string` | yes | - | Path to the API definition file that you want to split into a multi-file structure
`--outDir` | `string` | yes | - | Path to the directory where you want to save split files. If the specified directory doesn't exist, it will be created automatically. `--outDir` | `string` | yes | - | Path to the directory where you want to save split files. If the specified directory doesn't exist, it will be created automatically.
`separator` | `string` | no | `_` | File path separator used while splitting. Will affect file names in the `paths` folder (e.g. `user_create.yaml`)
`--help` | `boolean` | no | - | Show help `--help` | `boolean` | no | - | Show help
`--version` | `boolean` | no | - | Show version number `--version` | `boolean` | no | - | Show version number

View File

@@ -1,4 +1,4 @@
import { isSubdir } from '../utils'; import { isSubdir, pathToFilename } from '../utils';
jest.mock("os"); jest.mock("os");
@@ -40,3 +40,11 @@ describe('isSubdir', () => {
jest.resetModules() jest.resetModules()
}) })
}); });
describe('pathToFilename', () => {
it('should use correct path separator', () => {
const processedPath = pathToFilename('/user/createWithList', '_');
expect(processedPath).toEqual('user_createWithList');
});
});

View File

@@ -28,6 +28,7 @@ describe('#split', () => {
{ {
entrypoint: filePath, entrypoint: filePath,
outDir: openapiDir, outDir: openapiDir,
separator: '_',
} }
); );
@@ -41,12 +42,31 @@ describe('#split', () => {
); );
}); });
it('should use the correct separator', async () => {
const filePath = "./packages/cli/src/commands/split/__tests__/fixtures/spec.json";
const utils = require('../../../utils');
jest.spyOn(utils, 'pathToFilename').mockImplementation(() => 'newFilePath');
await handleSplit (
{
entrypoint: filePath,
outDir: openapiDir,
separator: '_',
}
);
expect(utils.pathToFilename).toBeCalledWith(expect.anything(), '_');
utils.pathToFilename.mockRestore();
});
it('should have correct path with paths', () => { it('should have correct path with paths', () => {
const openapi = require("./fixtures/spec.json"); const openapi = require("./fixtures/spec.json");
jest.spyOn(openapiCore, 'slash').mockImplementation(() => 'paths/test.yaml'); jest.spyOn(openapiCore, 'slash').mockImplementation(() => 'paths/test.yaml');
jest.spyOn(path, 'relative').mockImplementation(() => 'paths/test.yaml'); jest.spyOn(path, 'relative').mockImplementation(() => 'paths/test.yaml');
iteratePathItems(openapi.paths, openapiDir, path.join(openapiDir, 'paths'), componentsFiles); iteratePathItems(openapi.paths, openapiDir, path.join(openapiDir, 'paths'), componentsFiles, '_');
expect(openapiCore.slash).toHaveBeenCalledWith('paths/test.yaml'); expect(openapiCore.slash).toHaveBeenCalledWith('paths/test.yaml');
expect(path.relative).toHaveBeenCalledWith('test', 'test/paths/test.yaml'); expect(path.relative).toHaveBeenCalledWith('test', 'test/paths/test.yaml');

View File

@@ -29,12 +29,13 @@ import {
export async function handleSplit (argv: { export async function handleSplit (argv: {
entrypoint: string; entrypoint: string;
outDir: string outDir: string
separator: string
}) { }) {
const startedAt = performance.now(); const startedAt = performance.now();
const { entrypoint, outDir } = argv; const { entrypoint, outDir, separator } = argv;
validateDefinitionFileName(entrypoint!); validateDefinitionFileName(entrypoint!);
const openapi = readYaml(entrypoint!) as Oas3Definition | Oas3_1Definition; const openapi = readYaml(entrypoint!) as Oas3Definition | Oas3_1Definition;
splitDefinition(openapi, outDir); splitDefinition(openapi, outDir, separator);
process.stderr.write( process.stderr.write(
`🪓 Document: ${blue(entrypoint!)} ${green('is successfully split')} `🪓 Document: ${blue(entrypoint!)} ${green('is successfully split')}
and all related files are saved to the directory: ${blue(outDir)} \n`, and all related files are saved to the directory: ${blue(outDir)} \n`,
@@ -42,15 +43,15 @@ export async function handleSplit (argv: {
printExecutionTime('split', startedAt, entrypoint!); printExecutionTime('split', startedAt, entrypoint!);
} }
function splitDefinition(openapi: Oas3Definition | Oas3_1Definition, openapiDir: string) { function splitDefinition(openapi: Oas3Definition | Oas3_1Definition, openapiDir: string, pathSeparator: string) {
fs.mkdirSync(openapiDir, { recursive: true }); fs.mkdirSync(openapiDir, { recursive: true });
const componentsFiles: ComponentsFiles = {}; const componentsFiles: ComponentsFiles = {};
iterateComponents(openapi, openapiDir, componentsFiles); iterateComponents(openapi, openapiDir, componentsFiles);
iteratePathItems(openapi.paths, openapiDir, path.join(openapiDir, 'paths'), componentsFiles); iteratePathItems(openapi.paths, openapiDir, path.join(openapiDir, 'paths'), componentsFiles, pathSeparator);
const webhooks = (openapi as Oas3_1Definition).webhooks || (openapi as Oas3Definition)['x-webhooks']; const webhooks = (openapi as Oas3_1Definition).webhooks || (openapi as Oas3Definition)['x-webhooks'];
// use webhook_ prefix for code samples to prevent potential name-clashes with paths samples // use webhook_ prefix for code samples to prevent potential name-clashes with paths samples
iteratePathItems(webhooks, openapiDir, path.join(openapiDir, 'webhooks'), componentsFiles, 'webhook_'); iteratePathItems(webhooks, openapiDir, path.join(openapiDir, 'webhooks'), componentsFiles, pathSeparator, 'webhook_');
replace$Refs(openapi, openapiDir, componentsFiles); replace$Refs(openapi, openapiDir, componentsFiles);
writeYaml(openapi, path.join(openapiDir, 'openapi.yaml')); writeYaml(openapi, path.join(openapiDir, 'openapi.yaml'));
@@ -247,13 +248,14 @@ function iteratePathItems(
openapiDir: string, openapiDir: string,
outDir: string, outDir: string,
componentsFiles: object, componentsFiles: object,
pathSeparator: string,
codeSamplesPathPrefix: string = '', codeSamplesPathPrefix: string = '',
) { ) {
if (!pathItems) return; if (!pathItems) return;
fs.mkdirSync(outDir, { recursive: true }); fs.mkdirSync(outDir, { recursive: true });
for (const pathName of Object.keys(pathItems)) { for (const pathName of Object.keys(pathItems)) {
const pathFile = `${path.join(outDir, pathToFilename(pathName))}.yaml`; const pathFile = `${path.join(outDir, pathToFilename(pathName, pathSeparator))}.yaml`;
const pathData = pathItems[pathName] as Oas3PathItem; const pathData = pathItems[pathName] as Oas3PathItem;
if (isRef(pathData)) continue; if (isRef(pathData)) continue;
@@ -270,7 +272,7 @@ function iteratePathItems(
openapiDir, openapiDir,
'code_samples', 'code_samples',
sample.lang, sample.lang,
codeSamplesPathPrefix + pathToFilename(pathName), codeSamplesPathPrefix + pathToFilename(pathName, pathSeparator),
method + langToExt(sample.lang), method + langToExt(sample.lang),
); );

View File

@@ -46,6 +46,12 @@ yargs
required: true, required: true,
type: 'string', type: 'string',
}, },
separator: {
description: 'File path separator used while splitting.',
required: false,
type: 'string',
default: '_',
},
}) })
.demandOption('entrypoint'), .demandOption('entrypoint'),
handleSplit, handleSplit,

View File

@@ -66,12 +66,12 @@ export function printExecutionTime(commandName: string, startedAt: number, entry
process.stderr.write(gray(`\n${entrypoint}: ${commandName} processed in ${elapsed}\n\n`)); process.stderr.write(gray(`\n${entrypoint}: ${commandName} processed in ${elapsed}\n\n`));
} }
export function pathToFilename(path: string) { export function pathToFilename(path: string, pathSeparator: string) {
return path return path
.replace(/~1/g, '/') .replace(/~1/g, '/')
.replace(/~0/g, '~') .replace(/~0/g, '~')
.replace(/^\//, '') .replace(/^\//, '')
.replace(/\//g, '@'); .replace(/\//g, pathSeparator);
} }
export class CircularJSONNotSupportedError extends Error { export class CircularJSONNotSupportedError extends Error {