mirror of
https://github.com/LukeHagar/redocly-cli.git
synced 2025-12-09 20:57:44 +00:00
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:
committed by
GitHub
parent
3e8b08449d
commit
1f10c7cd77
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,5 +7,6 @@ coverage/
|
|||||||
yarn.lock
|
yarn.lock
|
||||||
dist/
|
dist/
|
||||||
lib/
|
lib/
|
||||||
|
output/
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user