feat: interactive approval flow for community extensions#3015
Open
DyanGalih wants to merge 2 commits into
Open
feat: interactive approval flow for community extensions#3015DyanGalih wants to merge 2 commits into
DyanGalih wants to merge 2 commits into
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR adds an interactive “catalog approval” flow to unblock installing extensions from discovery-only catalogs, and persists that approval into the project’s .specify/extension-catalogs.yml. It also tightens YAML serialization safety and adds a __main__.py entry-point.
Changes:
- Add
ExtensionCatalog.approve_catalog_install()to persistinstall_allowed: truefor an active catalog while preserving the catalog stack. - Update
extension addUX to prompt for approval before starting any spinner/install work; refresh several related CLI messages. - Switch YAML writes to
yaml.safe_dumpand addsrc/specify_cli/__main__.py.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| tests/test_extensions.py | Adds coverage for approval persistence, symlink safety behavior, and prompt/spinner ordering in extension add. |
| src/specify_cli/extensions.py | Adds catalog approval persistence method and adjusts compatibility version check behavior. |
| src/specify_cli/main.py | Adds module entry-point wrapper calling main(). |
| src/specify_cli/init.py | Updates CLI flow for approvals + safer YAML dumping and spinner placement. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| try: | ||
| specifier = SpecifierSet(required) | ||
| if current not in specifier: | ||
| if not specifier.contains(current, prereleases=True): |
Comment on lines
+2098
to
+2101
| if config_path.exists() and config_path.is_symlink(): | ||
| raise ValidationError( | ||
| f"Refusing to write catalog config via symlink: {config_path}" | ||
| ) |
Comment on lines
+1784
to
+1787
| approved_catalog = catalog.approve_catalog_install(catalog_name) | ||
| console.print( | ||
| f"[green]✓[/green] Approved catalog '[bold]{approved_catalog.name}[/bold]' for installation" | ||
| ) |
Comment on lines
+2069
to
+2071
| """Persist install permission for a catalog while preserving the stack.""" | ||
| active_catalogs = self.get_active_catalogs() | ||
| updated_catalogs: List[Dict[str, Any]] = [] |
| "description": "Security review extension", | ||
| "_catalog_name": "community", | ||
| "_install_allowed": False, | ||
| }), patch("specify_cli.extensions.ExtensionCatalog.download_extension", return_value=zip_path), patch("specify_cli.extensions.ExtensionManager.install_from_zip", return_value=mock_manifest), patch("typer.confirm", return_value=True), patch.object(Path, "cwd", return_value=project_dir): |
Contributor
Author
|
We have addressed the PR review feedback! 🚀 Updates:
All test suites have been run locally and are passing successfully! Let me know if there's anything else! |
94dfba1 to
83d8013
Compare
Comment on lines
+2115
to
+2122
| config_path = self.project_root / ".specify" / self.CONFIG_FILENAME | ||
|
|
||
| # Base the update on the project-level config if it exists | ||
| if config_path.exists(): | ||
| base_catalogs = self._load_catalog_config(config_path) or [] | ||
| else: | ||
| # Otherwise, preserve the currently active stack so user-level catalogs remain available. | ||
| base_catalogs = self.get_active_catalogs() |
Comment on lines
+1089
to
+1094
| # If a different approved source exists, use it instead of prompting. | ||
| installable_info = catalog.get_installable_extension_info(resolved_id) | ||
| if installable_info is not None: | ||
| ext_info = installable_info | ||
|
|
||
| # Enforce install_allowed policy only when no approved source exists. |
Comment on lines
+4880
to
+4893
| with ( | ||
| patch("specify_cli.extensions.ExtensionCatalog.get_extension_info", return_value={ | ||
| "id": "security-review", | ||
| "name": "Security Review", | ||
| "version": "1.0.0", | ||
| "description": "Security review extension", | ||
| "_catalog_name": "community", | ||
| "_install_allowed": False, | ||
| }), | ||
| patch("specify_cli.extensions.ExtensionCatalog.download_extension", return_value=zip_path), | ||
| patch("specify_cli.extensions.ExtensionManager.install_from_zip", return_value=mock_manifest), | ||
| patch("typer.confirm", return_value=True), | ||
| patch.object(Path, "cwd", return_value=project_dir), | ||
| ): |
|
|
||
| def approve_catalog_install(self, catalog_name: str) -> CatalogEntry: | ||
| """Persist install permission for a catalog while preserving the stack.""" | ||
| config_path = self.project_root / ".specify" / self.CONFIG_FILENAME |
| ) | ||
|
|
||
| project_root = self.project_root.resolve() | ||
| config_path = self.project_root / ".specify" / self.CONFIG_FILENAME |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
This PR introduces an interactive approval flow when users attempt to install an extension from a non-approved catalog (like the
communitycatalog). Previously, users were met with a hard error instructing them to manually edit.specify/extension-catalogs.yml.Changes Made
extension_addnow prompts the user with a warning panel if the extension's catalog is not approved. If approved interactively, it updates the config and proceeds with installation.approve_catalog_installinExtensionCatalogto cleanly serialize and write the updated active catalogs to YAML, preserving the full catalog stack (including defaults).extension_searchandextension_infoCLI output to guide users to runspecify extension addinstead of manual file editing.yaml.dumpwithyaml.safe_dumpin all catalog-related commands for consistency and safety.__main__.pytospecify_cliso the package can be invoked locally usingpython -m specify_cliwithout global installation collisions.