-
Notifications
You must be signed in to change notification settings - Fork 7.2k
fix: unofficial PyPI warning (#1982) and legacy extension command name auto-correction (#2017) #2027
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
fix: unofficial PyPI warning (#1982) and legacy extension command name auto-correction (#2017) #2027
Changes from all commits
879ddd4
b2c4f99
44d1996
fc99452
16f03ff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -130,6 +130,7 @@ def __init__(self, manifest_path: Path): | |
| ValidationError: If manifest is invalid | ||
| """ | ||
| self.path = manifest_path | ||
| self.warnings: List[str] = [] | ||
| self.data = self._load_yaml(manifest_path) | ||
| self._validate() | ||
|
|
||
|
|
@@ -186,18 +187,91 @@ def _validate(self): | |
| if "commands" not in provides or not provides["commands"]: | ||
| raise ValidationError("Extension must provide at least one command") | ||
|
|
||
| # Validate commands | ||
| # Validate commands; track renames so hook references can be rewritten. | ||
| rename_map: Dict[str, str] = {} | ||
| for cmd in provides["commands"]: | ||
| if "name" not in cmd or "file" not in cmd: | ||
| raise ValidationError("Command missing 'name' or 'file'") | ||
|
|
||
| # Validate command name format | ||
| if EXTENSION_COMMAND_NAME_PATTERN.match(cmd["name"]) is None: | ||
| if not EXTENSION_COMMAND_NAME_PATTERN.match(cmd["name"]): | ||
| corrected = self._try_correct_command_name(cmd["name"], ext["id"]) | ||
| if corrected: | ||
| self.warnings.append( | ||
| f"Command name '{cmd['name']}' does not follow the required pattern " | ||
| f"'speckit.{{extension}}.{{command}}'. Registering as '{corrected}'. " | ||
| f"The extension author should update the manifest to use this name." | ||
| ) | ||
| rename_map[cmd["name"]] = corrected | ||
| cmd["name"] = corrected | ||
| else: | ||
| raise ValidationError( | ||
| f"Invalid command name '{cmd['name']}': " | ||
| "must follow pattern 'speckit.{extension}.{command}'" | ||
| ) | ||
|
|
||
| # Validate and auto-correct alias name formats | ||
| aliases = cmd.get("aliases") | ||
| if aliases is None: | ||
| aliases = [] | ||
| if not isinstance(aliases, list): | ||
|
Comment on lines
+213
to
+217
|
||
| raise ValidationError( | ||
| f"Invalid command name '{cmd['name']}': " | ||
| "must follow pattern 'speckit.{extension}.{command}'" | ||
| f"Aliases for command '{cmd['name']}' must be a list" | ||
| ) | ||
| for i, alias in enumerate(aliases): | ||
| if not isinstance(alias, str): | ||
| raise ValidationError( | ||
| f"Aliases for command '{cmd['name']}' must be strings" | ||
| ) | ||
| if not EXTENSION_COMMAND_NAME_PATTERN.match(alias): | ||
| corrected = self._try_correct_command_name(alias, ext["id"]) | ||
| if corrected: | ||
| self.warnings.append( | ||
| f"Alias '{alias}' does not follow the required pattern " | ||
| f"'speckit.{{extension}}.{{command}}'. Registering as '{corrected}'. " | ||
| f"The extension author should update the manifest to use this name." | ||
| ) | ||
| rename_map[alias] = corrected | ||
| aliases[i] = corrected | ||
| else: | ||
mnriem marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| raise ValidationError( | ||
| f"Invalid alias '{alias}': " | ||
| "must follow pattern 'speckit.{extension}.{command}'" | ||
| ) | ||
|
|
||
| # Rewrite any hook command references that pointed at a renamed command. | ||
| for hook_name, hook_data in self.data.get("hooks", {}).items(): | ||
| if isinstance(hook_data, dict) and hook_data.get("command") in rename_map: | ||
| old_ref = hook_data["command"] | ||
| hook_data["command"] = rename_map[old_ref] | ||
| self.warnings.append( | ||
| f"Hook '{hook_name}' referenced renamed command '{old_ref}'; " | ||
| f"updated to '{rename_map[old_ref]}'. " | ||
| f"The extension author should update the manifest." | ||
| ) | ||
|
|
||
| @staticmethod | ||
| def _try_correct_command_name(name: str, ext_id: str) -> Optional[str]: | ||
| """Try to auto-correct a non-conforming command name to the required pattern. | ||
|
|
||
| Handles the two legacy formats used by community extensions: | ||
| - 'speckit.command' → 'speckit.{ext_id}.command' | ||
| - '{ext_id}.command' → 'speckit.{ext_id}.command' | ||
|
|
||
| The 'X.Y' form is only corrected when X matches ext_id to ensure the | ||
| result passes the install-time namespace check. Any other prefix is | ||
| uncorrectable and will produce a ValidationError at the call site. | ||
|
|
||
| Returns the corrected name, or None if no safe correction is possible. | ||
| """ | ||
| parts = name.split('.') | ||
| if len(parts) == 2: | ||
| if parts[0] == 'speckit' or parts[0] == ext_id: | ||
| candidate = f"speckit.{ext_id}.{parts[1]}" | ||
| if EXTENSION_COMMAND_NAME_PATTERN.match(candidate): | ||
| return candidate | ||
| return None | ||
|
|
||
| @property | ||
| def id(self) -> str: | ||
| """Get extension ID.""" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1174,8 +1174,7 @@ def test_search_with_cached_data(self, project_dir, monkeypatch): | |
| """Test search with cached catalog data.""" | ||
| from unittest.mock import patch | ||
|
|
||
| # Only use the default catalog to prevent fetching the community catalog from the network | ||
| monkeypatch.setenv("SPECKIT_PRESET_CATALOG_URL", PresetCatalog.DEFAULT_CATALOG_URL) | ||
| monkeypatch.delenv("SPECKIT_PRESET_CATALOG_URL", raising=False) | ||
| catalog = PresetCatalog(project_dir) | ||
|
Comment on lines
+1177
to
1178
|
||
| catalog.cache_dir.mkdir(parents=True, exist_ok=True) | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.