|
|
@@ -6,6 +6,7 @@ import tempfile
|
|
|
from dataclasses import dataclass
|
|
|
from pathlib import Path
|
|
|
from typing import Any, ClassVar
|
|
|
+from urllib.parse import urlparse
|
|
|
|
|
|
import yaml
|
|
|
|
|
|
@@ -19,6 +20,40 @@ DEFAULT_LIBRARY_BRANCH = "main"
|
|
|
DEFAULT_LIBRARY_URL = "https://github.com/christianlempa/boilerplates-library.git"
|
|
|
LEGACY_DEFAULT_LIBRARY_DIRECTORY = "library"
|
|
|
LEGACY_DEFAULT_LIBRARY_URL = "https://github.com/christianlempa/boilerplates.git"
|
|
|
+LEGACY_DEFAULT_LIBRARY_REPO = "github.com/christianlempa/boilerplates"
|
|
|
+
|
|
|
+
|
|
|
+def normalize_git_url(url: str | None) -> str:
|
|
|
+ """Normalize git URLs so SSH/HTTPS variants can be compared safely."""
|
|
|
+ if not url:
|
|
|
+ return ""
|
|
|
+
|
|
|
+ candidate = url.strip()
|
|
|
+ if not candidate:
|
|
|
+ return ""
|
|
|
+
|
|
|
+ if candidate.startswith("git@"):
|
|
|
+ host_and_path = candidate[4:]
|
|
|
+ host, _, path = host_and_path.partition(":")
|
|
|
+ normalized = f"{host}/{path}"
|
|
|
+ elif "://" in candidate:
|
|
|
+ parsed = urlparse(candidate)
|
|
|
+ normalized = f"{parsed.netloc}{parsed.path}"
|
|
|
+ else:
|
|
|
+ normalized = candidate
|
|
|
+
|
|
|
+ normalized = normalized.rstrip("/")
|
|
|
+ if normalized.endswith(".git"):
|
|
|
+ normalized = normalized[:-4]
|
|
|
+ if normalized.startswith("www."):
|
|
|
+ normalized = normalized[4:]
|
|
|
+
|
|
|
+ return normalized.lower()
|
|
|
+
|
|
|
+
|
|
|
+def is_legacy_default_library_url(url: str | None) -> bool:
|
|
|
+ """Return True when a URL points at the legacy christianlempa/boilerplates repo."""
|
|
|
+ return normalize_git_url(url) == LEGACY_DEFAULT_LIBRARY_REPO
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
@@ -133,8 +168,8 @@ class ConfigManager:
|
|
|
library["type"] = "git"
|
|
|
needs_migration = True
|
|
|
|
|
|
- default_library_migrated = self._migrate_default_library(config)
|
|
|
- needs_migration = needs_migration or default_library_migrated
|
|
|
+ boilerplates_repo_migrated = self._migrate_boilerplates_repo_libraries(config)
|
|
|
+ needs_migration = needs_migration or boilerplates_repo_migrated
|
|
|
|
|
|
# Write back if migration was needed
|
|
|
if needs_migration:
|
|
|
@@ -146,22 +181,17 @@ class ConfigManager:
|
|
|
logger.error(f"Config migration failed: {e}")
|
|
|
raise ConfigError(f"Failed to migrate configuration '{self.config_path}': {e}") from e
|
|
|
|
|
|
- def _migrate_default_library(self, config: dict[str, Any]) -> bool:
|
|
|
- """Rewrite the built-in default git library entry to the 0.2.0 library repo."""
|
|
|
+ def _migrate_boilerplates_repo_libraries(self, config: dict[str, Any]) -> bool:
|
|
|
+ """Rewrite any legacy christianlempa/boilerplates library entry to the new repo."""
|
|
|
libraries = config.get("libraries", [])
|
|
|
migrated = False
|
|
|
+ migrated_libraries: list[str] = []
|
|
|
|
|
|
for library in libraries:
|
|
|
- if library.get("name") != DEFAULT_LIBRARY_NAME:
|
|
|
- continue
|
|
|
-
|
|
|
if library.get("type", "git") != "git":
|
|
|
continue
|
|
|
|
|
|
- if library.get("url") != LEGACY_DEFAULT_LIBRARY_URL:
|
|
|
- continue
|
|
|
-
|
|
|
- if library.get("directory", LEGACY_DEFAULT_LIBRARY_DIRECTORY) != LEGACY_DEFAULT_LIBRARY_DIRECTORY:
|
|
|
+ if not is_legacy_default_library_url(library.get("url")):
|
|
|
continue
|
|
|
|
|
|
target_state = {
|
|
|
@@ -171,26 +201,32 @@ class ConfigManager:
|
|
|
|
|
|
changed = any(library.get(key) != value for key, value in target_state.items())
|
|
|
if not changed:
|
|
|
- break
|
|
|
+ continue
|
|
|
|
|
|
+ library_name = library.get("name", DEFAULT_LIBRARY_NAME)
|
|
|
previous_location = library.get("url") or library.get("path") or "<unknown>"
|
|
|
library.update(target_state)
|
|
|
migrated = True
|
|
|
+ migrated_libraries.append(library_name)
|
|
|
logger.info(
|
|
|
- "Migrated default library from %s to %s (%s)",
|
|
|
+ "Migrated library '%s' from %s to %s (%s)",
|
|
|
+ library_name,
|
|
|
previous_location,
|
|
|
DEFAULT_LIBRARY_URL,
|
|
|
DEFAULT_LIBRARY_DIRECTORY,
|
|
|
)
|
|
|
+
|
|
|
+ if migrated:
|
|
|
+ migrated_names = ", ".join(sorted(migrated_libraries))
|
|
|
self._add_migration_notice(
|
|
|
kind="default_library_repo",
|
|
|
message=(
|
|
|
- "Your default template library was migrated to "
|
|
|
+ "Migrated template library configuration"
|
|
|
+ f" ({migrated_names}) from christianlempa/boilerplates to "
|
|
|
"christianlempa/boilerplates-library. "
|
|
|
- "Run 'boilerplates repo update' to sync the new templates."
|
|
|
+ "Run 'boilerplates repo update' to resync the managed library checkout."
|
|
|
),
|
|
|
)
|
|
|
- break
|
|
|
|
|
|
return migrated
|
|
|
|