Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/redocly/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
plugins:
- './schema-name-prefix.plugin.js'

decorators:
schema-prefix/preserve-schema-name-prefixes: on

rules:
security-defined: off

152 changes: 152 additions & 0 deletions .github/redocly/schema-name-prefix.plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/**
* Redocly plugin to preserve component name prefixes based on directory structure.
* Workaround for: https://github.com/Redocly/redocly-cli/issues/661
*
* When bundling, Redocly auto-renames conflicting components (e.g., link.yaml -> link-2).
* This plugin modifies the bundled output to use directory-based prefixes for ALL component types
* (schemas, responses, parameters, examples, requestBodies, headers, securitySchemes, links, callbacks).
*/

// Store mapping of component objects to their source information
const componentSourceMap = new WeakMap();

const PreserveComponentNamePrefixes = () => {
return {
any: {
enter(node, ctx) {
// Track all nodes from component files
if (!node || typeof node !== 'object') {
return;
}

const location = ctx.location;
if (!location || !location.source || !location.source.absoluteRef) {
return;
}

const filePath = location.source.absoluteRef;

// Skip component references
if (filePath.includes('#/components/')) {
return;
}

// Extract directory and filename from various component paths:
// - schemas/{directory}/{filename}.yaml
// - responses/{directory}/{filename}.yaml
// - parameters/{directory}/{filename}.yaml
// etc.
const match = filePath.match(/\/(schemas|responses|parameters|examples|requestBodies|headers|securitySchemes|links|callbacks)\/([^\/]+)\/([^\/]+)\.yaml$/);
if (match) {
const [, componentType, directory, filename] = match;

if (directory && directory !== componentType) {
// Store the source info for this node
componentSourceMap.set(node, {
componentType,
directory,
filename,
prefixedName: `${directory}-${filename}`
});
}
}
}
},
Root: {
leave(root) {
// Post-process: rename all component types after bundling
if (!root.components) {
return;
}

// List of all possible component types in OpenAPI 3.x
const componentTypes = [
'schemas',
'responses',
'parameters',
'examples',
'requestBodies',
'headers',
'securitySchemes',
'links',
'callbacks'
];

const renameMap = new Map();

// Process each component type
for (const componentType of componentTypes) {
const components = root.components[componentType];
if (!components) {
continue;
}

// Identify components that should be renamed
for (const [componentName, componentContent] of Object.entries(components)) {
const sourceInfo = componentSourceMap.get(componentContent);

if (sourceInfo && sourceInfo.prefixedName) {
// Only rename if the name differs
if (componentName !== sourceInfo.prefixedName) {
const key = `${componentType}/${componentName}`;
renameMap.set(key, {
componentType,
oldName: componentName,
newName: sourceInfo.prefixedName
});
}
}
}
}

// Apply renames
for (const [key, { componentType, oldName, newName }] of renameMap.entries()) {
const components = root.components[componentType];
if (components && components[oldName]) {
components[newName] = components[oldName];
delete components[oldName];
}
}

// Update all $ref occurrences throughout the document
function updateRefs(obj) {
if (!obj || typeof obj !== 'object') {
return;
}

if (obj.$ref && typeof obj.$ref === 'string') {
for (const [key, { componentType, oldName, newName }] of renameMap.entries()) {
const oldRef = `#/components/${componentType}/${oldName}`;
const newRef = `#/components/${componentType}/${newName}`;
if (obj.$ref === oldRef) {
obj.$ref = newRef;
}
}
}

// Recursively update refs in nested objects
for (const value of Object.values(obj)) {
if (value && typeof value === 'object') {
updateRefs(value);
}
}
}

updateRefs(root);
}
}
};
};

module.exports = {
id: 'schema-prefix',
decorators: {
oas3: {
'preserve-schema-name-prefixes': PreserveComponentNamePrefixes,
}
}
};




38 changes: 38 additions & 0 deletions .github/workflows/openapi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: API linting

on:
- pull_request
- push

jobs:
lint-check-openapi:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: '0'
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}

- name: Set up node
uses: actions/setup-node@v6
- name: Install Redocly CLI
run: npm install -g @redocly/cli@latest
- name: Bundle OpenAPI
run: openapi/update.sh
- name: Run linting
run: |
redocly lint \
--format github-actions \
--config .github/redocly/config.yaml \
openapi/ogcapi-processes.bundled.json

- name: Commit updated OpenAPI bundle
uses: stefanzweifel/git-auto-commit-action@v7
with:
commit_message: Auto-update OpenAPI bundle
file_pattern: 'openapi/ogcapi-processes.bundled.json'
branch: ${{ github.head_ref }}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,6 @@ extensions/workflows/21-009.doc
extensions/workflows/21-009.err
extensions/workflows/21-009.xml
extensions/workflows/21-009.presentation.xml
node_modules
package.json
package-lock.json
Loading