Skip to content

Commit 367355a

Browse files
tatomyrLukeHagar
andauthored
fix!: split root-level paths correctly (#2485)
Co-authored-by: Luke Hagar <[email protected]>
1 parent 408ab96 commit 367355a

File tree

7 files changed

+287
-48
lines changed

7 files changed

+287
-48
lines changed

.changeset/silent-facts-wonder.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@redocly/cli": patch
3+
---
4+
5+
Fixed the `split` command to properly handle root-level paths.
6+
Previously, the root path `/` was converted to an empty string as a filename, leading to incorrect file structure and broken links.
7+
Now, it correctly maps to the specified path separator.

docs/@v2/commands/split.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,15 @@ redocly split --version
2424

2525
## Options
2626

27-
| Option | Type | Description |
28-
| ------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
29-
| api | string | **REQUIRED.** Path to the API description file that you want to split into a multi-file structure. |
30-
| --config | string | Specify path to the [configuration file](../configuration/index.md). |
31-
| --help | boolean | Show help. |
32-
| --lint-config | string | Specify the severity level for the configuration file. <br/> **Possible values:** `warn`, `error`, `off`. Default value is `warn`. |
33-
| --outDir | string | **REQUIRED.** Path to the directory where you want to save the split files. If the specified directory doesn't exist, it is created automatically. |
34-
| --separator | string | File path separator used while splitting. The default value is `_`. This controls the file names generated in the `paths` folder (e.g. `/users/create` path becomes `user_create.yaml`). |
35-
| --version | boolean | Show version number. |
27+
| Option | Type | Description |
28+
| ------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
29+
| api | string | **REQUIRED.** Path to the API description file that you want to split into a multi-file structure. |
30+
| --config | string | Specify path to the [configuration file](../configuration/index.md). |
31+
| --help | boolean | Show help. |
32+
| --lint-config | string | Specify the severity level for the configuration file. <br/> **Possible values:** `warn`, `error`, `off`. Default value is `warn`. |
33+
| --outDir | string | **REQUIRED.** Path to the directory where you want to save the split files. If the specified directory doesn't exist, it is created automatically. |
34+
| --separator | string | File path separator used while splitting. The default value is `_`. This controls the file names generated in the `paths` folder (e.g. `/users/create` path becomes `user_create.yaml`, root level path `/` becomes `_.yaml`, and so on). |
35+
| --version | boolean | Show version number. |
3636

3737
## Examples
3838

package-lock.json

Lines changed: 0 additions & 36 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/cli/src/utils/miscellaneous.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,15 @@ export function printExecutionTime(commandName: string, startedAt: number, api:
121121
}
122122

123123
export function pathToFilename(path: string, pathSeparator: string) {
124+
if (path === '/') {
125+
return pathSeparator;
126+
}
127+
124128
return path
125-
.replace(/~1/g, '/')
126-
.replace(/~0/g, '~')
129+
.replaceAll('~1', '/')
130+
.replaceAll('~0', '~')
127131
.replace(/^\//, '')
128-
.replace(/\//g, pathSeparator);
132+
.replaceAll('/', pathSeparator);
129133
}
130134

131135
export function escapeLanguageName(lang: string) {

tests/e2e/commands.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,45 @@ describe('E2E', () => {
375375
const result = getCommandOutput(args, { testPath });
376376
await expect(cleanupOutput(result)).toMatchFileSnapshot(join(testPath, 'snapshot.txt'));
377377
});
378+
379+
test('root endpoint correctly split', async () => {
380+
const testPath = join(__dirname, `split/root-endpoint`);
381+
382+
const args = getParams(indexEntryPoint, ['split', 'openapi.yaml', '--outDir=output']);
383+
const result = getCommandOutput(args, { testPath });
384+
await expect(cleanupOutput(result)).toMatchFileSnapshot(join(testPath, 'snapshot.txt'));
385+
});
386+
387+
test('root endpoint split and bundle again to the content', async () => {
388+
const testPath = join(__dirname, `split/root-endpoint`);
389+
390+
let args = getParams(indexEntryPoint, ['split', 'openapi.yaml', '--outDir=output']);
391+
spawnSync('node', args, {
392+
cwd: testPath,
393+
env: {
394+
...process.env,
395+
NODE_ENV: 'production',
396+
NO_COLOR: 'TRUE',
397+
},
398+
});
399+
400+
args = getParams(indexEntryPoint, [
401+
'bundle',
402+
'output/openapi.yaml',
403+
'-o=output/bundled.yaml',
404+
]);
405+
getCommandOutput(args, { testPath });
406+
407+
const expected = readFileSync(join(testPath, 'openapi.yaml'), 'utf8');
408+
const actual = readFileSync(join(testPath, 'output/bundled.yaml'), 'utf8');
409+
410+
// Clean up output folder after splitting so the produced files do not interfere with other tests
411+
spawnSync('rm', ['-rf', 'output'], {
412+
cwd: testPath,
413+
});
414+
415+
expect(actual).toEqual(expected);
416+
});
378417
});
379418

380419
describe('join', () => {
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
openapi: 3.0.0
2+
info:
3+
version: 1.0.0
4+
title: Test
5+
license:
6+
name: MIT
7+
servers:
8+
- url: http://localhost/v1
9+
paths:
10+
/:
11+
get:
12+
summary: List all pets
13+
operationId: listPets
14+
tags:
15+
- pets
16+
parameters:
17+
- name: limit
18+
in: query
19+
description: How many items to return at one time (max 100)
20+
required: false
21+
schema:
22+
type: integer
23+
format: int32
24+
responses:
25+
'200':
26+
description: A paged array of pets
27+
headers:
28+
x-next:
29+
description: A link to the next page of responses
30+
schema:
31+
type: string
32+
content:
33+
application/json:
34+
schema:
35+
$ref: '#/components/schemas/Pets'
36+
default:
37+
description: unexpected error
38+
content:
39+
application/json:
40+
schema:
41+
$ref: '#/components/schemas/Error'
42+
post:
43+
summary: Create a pet
44+
operationId: createPets
45+
tags:
46+
- pets
47+
responses:
48+
'201':
49+
description: Null response
50+
default:
51+
description: unexpected error
52+
content:
53+
application/json:
54+
schema:
55+
$ref: '#/components/schemas/Error'
56+
/pets/{petId}:
57+
get:
58+
summary: Info for a specific pet
59+
operationId: showPetById
60+
tags:
61+
- pets
62+
parameters:
63+
- name: petId
64+
in: path
65+
required: true
66+
description: The id of the pet to retrieve
67+
schema:
68+
type: string
69+
responses:
70+
'200':
71+
description: Expected response to a valid request
72+
content:
73+
application/json:
74+
schema:
75+
$ref: '#/components/schemas/Pet'
76+
default:
77+
description: unexpected error
78+
content:
79+
application/json:
80+
schema:
81+
$ref: '#/components/schemas/Error'
82+
components:
83+
schemas:
84+
Error:
85+
type: object
86+
required:
87+
- code
88+
- message
89+
properties:
90+
code:
91+
type: integer
92+
format: int32
93+
message:
94+
type: string
95+
Pet:
96+
type: object
97+
required:
98+
- id
99+
- name
100+
properties:
101+
id:
102+
type: integer
103+
format: int64
104+
name:
105+
type: string
106+
tag:
107+
type: string
108+
Pets:
109+
type: array
110+
items:
111+
$ref: '#/components/schemas/Pet'
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
2+
type: object
3+
required:
4+
- code
5+
- message
6+
properties:
7+
code:
8+
type: integer
9+
format: int32
10+
message:
11+
type: string
12+
type: object
13+
required:
14+
- id
15+
- name
16+
properties:
17+
id:
18+
type: integer
19+
format: int64
20+
name:
21+
type: string
22+
tag:
23+
type: string
24+
type: array
25+
items:
26+
$ref: ./Pet.yaml
27+
get:
28+
summary: List all pets
29+
operationId: listPets
30+
tags:
31+
- pets
32+
parameters:
33+
- name: limit
34+
in: query
35+
description: How many items to return at one time (max 100)
36+
required: false
37+
schema:
38+
type: integer
39+
format: int32
40+
responses:
41+
'200':
42+
description: A paged array of pets
43+
headers:
44+
x-next:
45+
description: A link to the next page of responses
46+
schema:
47+
type: string
48+
content:
49+
application/json:
50+
schema:
51+
$ref: '#/components/schemas/Pets'
52+
default:
53+
description: unexpected error
54+
content:
55+
application/json:
56+
schema:
57+
$ref: '#/components/schemas/Error'
58+
post:
59+
summary: Create a pet
60+
operationId: createPets
61+
tags:
62+
- pets
63+
responses:
64+
'201':
65+
description: Null response
66+
default:
67+
description: unexpected error
68+
content:
69+
application/json:
70+
schema:
71+
$ref: '#/components/schemas/Error'
72+
get:
73+
summary: Info for a specific pet
74+
operationId: showPetById
75+
tags:
76+
- pets
77+
parameters:
78+
- name: petId
79+
in: path
80+
required: true
81+
description: The id of the pet to retrieve
82+
schema:
83+
type: string
84+
responses:
85+
'200':
86+
description: Expected response to a valid request
87+
content:
88+
application/json:
89+
schema:
90+
$ref: '#/components/schemas/Pet'
91+
default:
92+
description: unexpected error
93+
content:
94+
application/json:
95+
schema:
96+
$ref: '#/components/schemas/Error'
97+
openapi: 3.0.0
98+
info:
99+
version: 1.0.0
100+
title: Test
101+
license:
102+
name: MIT
103+
servers:
104+
- url: http://localhost/v1
105+
paths:
106+
/:
107+
$ref: paths/_.yaml
108+
/pets/{petId}:
109+
$ref: paths/pets_{petId}.yaml
110+
🪓 Document: openapi.yaml is successfully split
111+
and all related files are saved to the directory: output
112+
113+
openapi.yaml: split processed in <test>ms
114+

0 commit comments

Comments
 (0)