diff --git a/autoform_cli/project/create.py b/autoform_cli/project/create.py index 8e1c30be..fe331733 100644 --- a/autoform_cli/project/create.py +++ b/autoform_cli/project/create.py @@ -15,13 +15,13 @@ from pathlib import Path, PurePosixPath from urllib.parse import urlsplit -from .. import scaffold from ..claims import _reject_json_constant, _strict_json_object from ..graph import _parse_node from ..scaffold import ( - DEFAULT_AUTOFORM_SOURCE, ScaffoldError, + _FULL_SHA, _TEMPLATES, + _checkout_pin, _normalize_autoform_source, _read_templates, _require_complete_templates, @@ -33,7 +33,6 @@ PROJECT_CREATION_SCHEMA = "autoform-project-creation/v1" _CREATION_RELEASE_SCHEMA = "autoform-project-creation-release/v1" _PACKAGE_NAME = re.compile(r"[A-Z][A-Za-z0-9]*") -_FULL_SHA = re.compile(r"[0-9a-f]{40}") _RESERVED_PACKAGE_NAMES = frozenset({"Prop", "Sort", "Type"}) _RELEASE_ID = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]*") _STAGE_ATTEMPTS = 32 @@ -437,12 +436,8 @@ def _resolve_workflow_pin(source: str, ref: str, *, templates: tuple[tuple[str, "The Autoform workflow source must be a safe credential-free HTTPS Git URL ending in .git.", ) return given_source, given_ref - # Looked up on the module so one replacement governs `init` and `project new`. - pinned_source, pinned_ref = scaffold.plugin_pin(templates) - safe_pinned_source = _normalize_autoform_source(pinned_source, allow_github_scp=True) - if safe_pinned_source is None or _FULL_SHA.fullmatch(pinned_ref.lower()) is None: - safe_pinned_source, pinned_ref = None, "" - return safe_pinned_source or DEFAULT_AUTOFORM_SOURCE, given_ref or pinned_ref.lower() + pinned_source, pinned_ref = _checkout_pin(templates) + return pinned_source, given_ref or pinned_ref def _validate_target(target: str | Path | None) -> Path: diff --git a/autoform_cli/scaffold.py b/autoform_cli/scaffold.py index 96a88e4e..4c472f21 100644 --- a/autoform_cli/scaffold.py +++ b/autoform_cli/scaffold.py @@ -483,6 +483,16 @@ def plugin_pin( return source, ref +def _checkout_pin(templates: tuple[tuple[str, bytes, int], ...]) -> tuple[str, str]: + """`plugin_pin` reduced to a safe source and lowercase commit, else the default source and no commit.""" + + pinned_source, pinned_ref = plugin_pin(templates) + safe_source = _normalize_autoform_source(pinned_source, allow_github_scp=True) + if safe_source is None or not _FULL_SHA.fullmatch(pinned_ref.lower()): + return DEFAULT_AUTOFORM_SOURCE, "" + return safe_source, pinned_ref.lower() + + class ScaffoldError(ValueError): """The project could not be scaffolded safely.""" @@ -1048,18 +1058,12 @@ def scaffold_project( # from the commit named by the workflows. templates = _read_templates(_TEMPLATES) _require_complete_templates(templates) - pinned_source, pinned_ref = ("", "") if given_source else plugin_pin(templates) - safe_pinned_source = _normalize_autoform_source(pinned_source, allow_github_scp=True) - if safe_pinned_source is None or not _FULL_SHA.fullmatch(pinned_ref.lower()): - pinned_source, pinned_ref = "", "" - else: - pinned_source, pinned_ref = safe_pinned_source, pinned_ref.lower() - source = given_source or pinned_source or DEFAULT_AUTOFORM_SOURCE + source, pinned_ref = (given_source, "") if given_source else _checkout_pin(templates) # A ref identifies a commit in one repository. Naming a different source # while inheriting this checkout's HEAD produces `git+other.git@our-sha`, # which does not resolve there, so an explicit source carries its own ref # or none at all. - ref = given_ref or ("" if given_source else pinned_ref) + ref = given_ref or pinned_ref # CI installs Autoform from a Git ref. Where Autoform lives is a fixed fact # worth defaulting; which commit is not, and a guessed one publishes a # project whose first CI step fails for a reason no file in it explains. So