Last active
November 19, 2025 18:58
-
-
Save nickboldt/bb7cfdd516f524391351a5dacbe15ce0 to your computer and use it in GitHub Desktop.
convert dynamic-plugins.default.yaml into a list of packages, from which we can then later recreate the dynamic-plugins.default.yaml using the latest pluginConfig content
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Step 1: create a copy of ``dynamic-plugins.default.yaml` called `default.packages.yaml` | |
| This file will remove all the pluginConfig content and keep only the `plugins[].package` content. Those plugins should then be moved into two arrays, | |
| `packages.enabled[]` and `packages.disabled[], under which we will list the `package` values. | |
| Step 2: using the list of packages, sorted into enabled and disabled groups in the new `default.packages.yaml` file, we now need to convert those package path values to actual package references. | |
| For each `package` that refers to a path under `./dynamic-plugins/dist/`, | |
| compute the associated package name from the related path under `dynamic-plugins/wrappers/` to get | |
| the value of the related dependencies. | |
| For example in `dynamic-plugins/wrappers/backstage-community-plugin-3scale-backend-dynamic/package.json` | |
| we see the primary dependency in this plugin wrapper is "@backstage-community/plugin-3scale-backend" | |
| So the resulting file would have | |
| ``` | |
| packages: | |
| disabled: | |
| - package: "@backstage-community/plugin-3scale-backend" | |
| ``` | |
| etc. | |
| For values of `package` which already refer to a package, like `"@redhat/[email protected]"`, strip the version from the string so we have only | |
| ``` | |
| packages: | |
| disabled: | |
| - package: "@redhat/backstage-plugin-orchestrator" | |
| ``` | |
| Step 3: sort `default.packages.yaml` with the enabled packages listed before the disabled ones, | |
| and the packages within each grouping sorted alphabetically | |
| Step 4: save a copy of the transformation script into rhdh-plugin-catalog/build/scripts/extract_packages.py |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| Genarated-By: Cursor | |
| Process dynamic-plugins.default.yaml to create default.packages.yaml | |
| This script converts a dynamic-plugins.default.yaml file into a simplified | |
| default.packages.yaml file that contains only package references (no config), | |
| organized into enabled and disabled arrays. | |
| For packages with local paths (./dynamic-plugins/dist/...), it resolves them | |
| to their actual npm package names by reading the corresponding wrapper's package.json. | |
| For packages with version strings (@scope/package@version), it strips the version. | |
| Packages are sorted alphabetically (case-insensitive) within each group. | |
| """ | |
| import yaml | |
| import json | |
| import os | |
| import re | |
| from pathlib import Path | |
| def get_package_from_wrapper(wrapper_name): | |
| """Get the primary dependency package from a wrapper's package.json""" | |
| wrapper_path = Path(f"dynamic-plugins/wrappers/{wrapper_name}/package.json") | |
| if not wrapper_path.exists(): | |
| print(f"Warning: Could not find {wrapper_path}") | |
| return None | |
| try: | |
| with open(wrapper_path, 'r') as f: | |
| pkg_data = json.load(f) | |
| dependencies = pkg_data.get('dependencies', {}) | |
| # Filter out non-plugin dependencies | |
| excluded_deps = [ | |
| '@mui/', '@material-ui/', 'react', '@types/', | |
| '@backstage/core-', '@backstage/theme-' | |
| ] | |
| plugin_deps = [] | |
| for dep in dependencies: | |
| # Skip excluded dependencies | |
| if any(dep.startswith(excl) for excl in excluded_deps): | |
| continue | |
| # Only include plugin-like dependencies | |
| if any(dep.startswith(prefix) for prefix in [ | |
| '@backstage/', '@backstage-community/', '@roadiehq/', | |
| '@red-hat-developer-hub/', '@redhat/', '@pagerduty/', | |
| '@parfuemerie-douglas/', '@immobiliarelabs/' | |
| ]): | |
| plugin_deps.append(dep) | |
| if not plugin_deps: | |
| return None | |
| # Try to find the best match based on the wrapper name | |
| # First, try to find an exact match (converting wrapper name to package format) | |
| wrapper_base = wrapper_name.replace('-dynamic', '') | |
| for dep in plugin_deps: | |
| # Extract the package name without scope | |
| dep_name = dep.split('/')[-1] if '/' in dep else dep | |
| # Check if wrapper name contains or matches the package name | |
| if wrapper_base in dep or dep_name.replace('backstage-', '').replace('plugin-', '') in wrapper_base: | |
| # If there's a close match, prefer it | |
| if wrapper_base.replace('-', '') in dep.replace('-', '').replace('/', '').replace('@', ''): | |
| return dep | |
| # Return the longest matching dependency (usually the most specific one) | |
| plugin_deps.sort(key=len, reverse=True) | |
| return plugin_deps[0] | |
| except Exception as e: | |
| print(f"Error reading {wrapper_path}: {e}") | |
| return None | |
| def process_package_string(package_str): | |
| """Convert a package string to a clean package name""" | |
| # If it's a path under ./dynamic-plugins/dist/ | |
| if package_str.startswith('./dynamic-plugins/dist/'): | |
| # Extract the wrapper name from the path | |
| wrapper_name = package_str.replace('./dynamic-plugins/dist/', '') | |
| # Get the package name from the wrapper's package.json | |
| pkg_name = get_package_from_wrapper(wrapper_name) | |
| if pkg_name: | |
| return pkg_name | |
| else: | |
| print(f"Warning: Could not resolve package for {wrapper_name}") | |
| return None | |
| else: | |
| # It's already a package reference, strip the version | |
| # Pattern: @scope/package@version or package@version | |
| package_no_version = re.sub(r'@[\d.]+.*$', '', package_str) | |
| return package_no_version | |
| def main(): | |
| # Load the original YAML | |
| with open('dynamic-plugins.default.yaml', 'r') as f: | |
| data = yaml.safe_load(f) | |
| enabled_packages = [] | |
| disabled_packages = [] | |
| # Process each plugin | |
| for plugin in data.get('plugins', []): | |
| package_str = plugin.get('package', '') | |
| disabled = plugin.get('disabled', False) | |
| # Convert the package string | |
| clean_package = process_package_string(package_str) | |
| if clean_package: | |
| if disabled: | |
| disabled_packages.append(clean_package) | |
| else: | |
| enabled_packages.append(clean_package) | |
| # Sort packages alphabetically (case-insensitive) | |
| enabled_packages.sort(key=str.lower) | |
| disabled_packages.sort(key=str.lower) | |
| # Create the new structure | |
| output = { | |
| 'packages': { | |
| 'enabled': [{'package': pkg} for pkg in enabled_packages], | |
| 'disabled': [{'package': pkg} for pkg in disabled_packages] | |
| } | |
| } | |
| # Write the output | |
| with open('default.packages.yaml', 'w') as f: | |
| yaml.dump(output, f, default_flow_style=False, sort_keys=False, width=120) | |
| print(f"Processed {len(enabled_packages)} enabled and {len(disabled_packages)} disabled packages") | |
| print(f"Packages sorted alphabetically within each group") | |
| print(f"Output written to default.packages.yaml") | |
| if __name__ == '__main__': | |
| main() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment