Skip to content

Commit 7435fb9

Browse files
authored
Merge pull request #63 from sacconazzo/sacconazzo/issue22
Authorize does not work and custom endpoints within bundle extensions
2 parents 42609d3 + f2df83d commit 7435fb9

File tree

8 files changed

+185
-11
lines changed

8 files changed

+185
-11
lines changed

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,38 @@ components:
7171

7272
For each endpoint extension, you can define OpenAPI partials by adding an `oas.yaml` file in the root of that endpoint's folder.
7373

74+
### Non-bundled extensions
75+
76+
Place the `oas.yaml` file directly in the extension folder:
77+
78+
```
79+
- ./extensions/
80+
─ oasconfig.yaml (optional)
81+
- my-endpoint-extension/
82+
- oas.yaml
83+
```
84+
85+
### Bundled extensions
86+
87+
For bundled extensions, place `oas.yaml` files in each sub-extension's folder under the `src` directory:
88+
89+
```
90+
- ./extensions/
91+
─ oasconfig.yaml (optional)
92+
- my-bundle-extension/
93+
- src/
94+
- routes-endpoint/
95+
- oas.yaml
96+
- admin-endpoint/
97+
- oas.yaml
98+
```
99+
100+
This structure follows Directus's standard bundle architecture where each sub-extension (routes, endpoints, hooks, etc.) has its own folder under `src/`. The extension will automatically discover and merge all `oas.yaml` files from these subdirectories.
101+
102+
### Mixed environments
103+
104+
Both bundled and non-bundled extensions can coexist in the same project. The extension will automatically detect and merge all `oas.yaml` files from both types.
105+
74106
Properties:
75107

76108
- `tags` _optional_ openapi custom tags

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "directus-extension-api-docs",
3-
"version": "2.2.3",
3+
"version": "2.3.0",
44
"description": "directus extension for swagger interface and openapi including custom endpoints definitions // custom endpoint validations middleware based on openapi",
55
"licence": "MIT",
66
"icon": "api",

src/utils.ts

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -93,19 +93,43 @@ export function getConfig(): oasConfig {
9393
config.components = merge(config.components || {}, oas.components || {});
9494
};
9595

96+
const scanDirectory = (dirPath: string) => {
97+
if (!fs.existsSync(dirPath)) return;
98+
99+
const files = fs.readdirSync(dirPath, { withFileTypes: true });
100+
for (const file of files) {
101+
if (!file.isDirectory()) continue;
102+
103+
const extensionPath = path.join(dirPath, file.name);
104+
105+
// Check for oas.yaml at root level (non-bundle extensions)
106+
const rootOasPath = path.join(extensionPath, 'oas.yaml');
107+
if (fs.existsSync(rootOasPath)) {
108+
mergeConfig(rootOasPath);
109+
}
110+
111+
// Check for oas.yaml in src subdirectories (bundled extensions - green option)
112+
const srcPath = path.join(extensionPath, 'src');
113+
if (fs.existsSync(srcPath)) {
114+
const srcFiles = fs.readdirSync(srcPath, { withFileTypes: true });
115+
for (const srcFile of srcFiles) {
116+
if (srcFile.isDirectory()) {
117+
const bundleOasPath = path.join(srcPath, srcFile.name, 'oas.yaml');
118+
if (fs.existsSync(bundleOasPath)) {
119+
mergeConfig(bundleOasPath);
120+
}
121+
}
122+
}
123+
}
124+
}
125+
};
126+
96127
const extensionsPath = path.join(directusDir(), extensionDir);
97-
const files = fs.readdirSync(extensionsPath, { withFileTypes: true });
98-
for (const file of files) {
99-
const oasPath = `${extensionsPath}/${file.name}/oas.yaml`;
100-
if (file.isDirectory() && fs.existsSync(oasPath)) mergeConfig(oasPath);
101-
}
128+
scanDirectory(extensionsPath);
102129

130+
// Legacy support for /endpoints subfolder
103131
const legacyEndpointsPath = path.join(directusDir(), extensionDir, '/endpoints');
104-
const legacyFiles = fs.readdirSync(legacyEndpointsPath, { withFileTypes: true });
105-
for (const file of legacyFiles) {
106-
const oasPath = `${legacyEndpointsPath}/${file.name}/oas.yaml`;
107-
if (file.isDirectory() && fs.existsSync(oasPath)) mergeConfig(oasPath);
108-
}
132+
scanDirectory(legacyEndpointsPath);
109133

110134
return config;
111135
} catch (e) {

tests/index.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,3 +284,41 @@ describe('getConfig edge cases', () => {
284284
expect(test.docsPath).toBeDefined();
285285
});
286286
});
287+
288+
describe('bundled extension support', () => {
289+
test('should merge oas.yaml from bundle extension src subdirectories', () => {
290+
jest.spyOn(process, 'cwd').mockImplementation(() => {
291+
return './tests/mocks/bundle';
292+
});
293+
const test = getConfig();
294+
expect(test).toHaveProperty('docsPath');
295+
expect(test).toHaveProperty('tags');
296+
expect(test).toHaveProperty('paths');
297+
expect(test).toHaveProperty('components');
298+
299+
// Check that routes from bundled sub-extensions are included
300+
expect(test.paths).toHaveProperty('/bundle/items');
301+
expect(test.paths).toHaveProperty('/bundle/users');
302+
303+
// Check that tags from bundled sub-extensions are included
304+
expect(test.tags).toEqual(expect.arrayContaining([expect.objectContaining({ name: 'bundle-routes' }), expect.objectContaining({ name: 'bundle-users' })]));
305+
306+
// Check that components from bundled sub-extensions are included
307+
expect(test.components).toHaveProperty('schemas');
308+
expect(test.components.schemas).toHaveProperty('BundleItem');
309+
});
310+
311+
test('should merge both bundled and non-bundled extensions', () => {
312+
jest.spyOn(process, 'cwd').mockImplementation(() => {
313+
return './tests/mocks/mixed';
314+
});
315+
const test = getConfig();
316+
317+
// Check that routes from both types are included
318+
expect(test.paths).toHaveProperty('/regular/endpoint');
319+
expect(test.paths).toHaveProperty('/bundle/route-a');
320+
321+
// Check that tags from both types are included
322+
expect(test.tags).toEqual(expect.arrayContaining([expect.objectContaining({ name: 'regular' }), expect.objectContaining({ name: 'bundle-a' })]));
323+
});
324+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
openapi: 3.0.0
2+
info:
3+
title: Bundle Extension API
4+
version: 1.0.0
5+
description: API from bundled extension
6+
tags:
7+
- name: bundle-routes
8+
description: Routes from bundle extension
9+
paths:
10+
/bundle/items:
11+
get:
12+
tags:
13+
- bundle-routes
14+
summary: Get items from bundle
15+
responses:
16+
'200':
17+
description: Success
18+
content:
19+
application/json:
20+
schema:
21+
type: array
22+
items:
23+
type: object
24+
components:
25+
schemas:
26+
BundleItem:
27+
type: object
28+
properties:
29+
id:
30+
type: string
31+
name:
32+
type: string
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
openapi: 3.0.0
2+
info:
3+
title: Another Bundle Route
4+
version: 1.0.0
5+
tags:
6+
- name: bundle-users
7+
description: User routes from bundle
8+
paths:
9+
/bundle/users:
10+
get:
11+
tags:
12+
- bundle-users
13+
summary: Get users from bundle
14+
responses:
15+
'200':
16+
description: Success
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
openapi: 3.0.0
2+
info:
3+
title: Bundle Extension Routes A
4+
version: 1.0.0
5+
tags:
6+
- name: bundle-a
7+
description: Bundled extension route A
8+
paths:
9+
/bundle/route-a:
10+
get:
11+
tags:
12+
- bundle-a
13+
summary: Bundle route A
14+
responses:
15+
'200':
16+
description: Success
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
openapi: 3.0.0
2+
info:
3+
title: Regular Extension API
4+
version: 1.0.0
5+
tags:
6+
- name: regular
7+
description: Regular non-bundled extension
8+
paths:
9+
/regular/endpoint:
10+
get:
11+
tags:
12+
- regular
13+
summary: Regular extension endpoint
14+
responses:
15+
'200':
16+
description: Success

0 commit comments

Comments
 (0)