On this page
Release target protocol defining the formal interface that all target implementations must satisfy for detection, versioning, and scaffolding.
#rlsbl.targets.protocol
#rlsbl.targets.protocol
Release target protocol defining the formal interface that all target implementations must satisfy for detection, versioning, and scaffolding.
#ReleaseTarget
Protocol defining a release target.
Targets handle version management, scaffolding templates, and optionally build/publish steps for a specific ecosystem (npm, pypi, go, codehome, docs, etc.)
#name
def name(self) -> strUnique identifier for this target (e.g. 'npm', 'pypi', 'codehome').
#supports_publication_probe
def supports_publication_probe(self) -> boolWhether publication_probe gives a real answer for this target.
#supports_cached_registry_probe
def supports_cached_registry_probe(self) -> boolWhether cached_registry_probe gives a real answer for this target.
#release_materialization_policy
def release_materialization_policy(self) -> strWhether a reconcile may recreate this target's missing release refs.
"materialize" -- recreating a released version's absent ref is a pure repair. "refuse-identity-transition" -- the target's tags ARE its published artifact, so a version whose published identity has since changed must not have its refs recreated under the new one.
#supports_read_name
def supports_read_name(self) -> boolWhether read_name reads a real name for this target.
#supports_read_metadata
def supports_read_metadata(self) -> boolWhether read_metadata reads real metadata for this target.
#supports_dev_install
def supports_dev_install(self) -> boolWhether dev_install_command yields a spec for any mode.
#provides_ci_templates
def provides_ci_templates(self) -> boolWhether this target ships a CI workflow template to scaffold.
#supports_import_analysis
def supports_import_analysis(self) -> boolWhether rlsbl can read this target's sources to follow imports.
#supports_circular_dep_analysis
def supports_circular_dep_analysis(self) -> boolWhether cycle detection is meaningful for this target's ecosystem.
#has_builtin_test_runner
def has_builtin_test_runner(self) -> boolWhether this target ships a built-in test runner.
#supports_version_query
def supports_version_query(self) -> boolWhether this target's registry answers a latest-version query.
#supports_name_claim
def supports_name_claim(self) -> boolWhether a name can be claimed on this target's registry.
#supports_yank
def supports_yank(self) -> boolWhether this target's registry offers a removal action.
#detect
def detect(self, dir_path: str) -> boolCheck if this target is present/applicable in the given directory.
#read_version
def read_version(self, dir_path: str) -> strRead the current version from the target's manifest file.
#read_name
def read_name(self, dir_path: str, ctx) -> str | NoneRead the project's package name from the manifest, or None.
#read_metadata
def read_metadata(self, dir_path: str) -> dict[str, str]Read optional metadata (license, description) from the manifest.
#write_version
def write_version(self, dir_path: str, version: str, ctx) -> NoneWrite a new version to the target's manifest file (atomic).
#version_file
def version_file(self, dir_path: str | None=None) -> str | NoneFilename that holds the version (e.g. 'package.json'), or None if inherited.
When dir_path is provided, implementations may resolve the filename dynamically (e.g. Deno choosing between deno.json and deno.jsonc).
#tag_format
def tag_format(self, version: str) -> strFormat the git tag for a release. Returns f'v{version}' by default.
#monorepo_tag_format
def monorepo_tag_format(self, name: str, version: str, path: str | None=None) -> strFormat the git tag for a monorepo release. Default: f'{name}@v{version}'.
#monorepo_tag_glob
def monorepo_tag_glob(self, name: str, path: str | None=None) -> strReturn a glob pattern matching all monorepo version tags. Default: f'{name}@v*'.
#companion_tags
def companion_tags(self, name: str, version: str, path: str | None=None) -> list[str]Return additional tags to create alongside the primary release tag.
Ecosystems that require extra tags (e.g. Go module proxy tags) override this. The default returns no companion tags.
#expected_refs
def expected_refs(self, version: str, context)Every git ref version owns: the primary tag, companions, aliases.
THE single authority for a released version's ref set. context is a :class:~rlsbl.targets.refs.RefContext; the return is an :class:~rlsbl.targets.refs.ExpectedRefs. The release flow creates and pushes exactly this set and the unpublished-refs check renders exactly this set against reality, so the two can never diverge.
Composed from the per-target facts above rather than overridden: no target implements this itself.
#format_version
def format_version(self, version: str) -> strTranslate a semver version into this ecosystem's version format.
The default is the identity, which is correct for npm, Go, Deno and most others. PyPI overrides it for PEP 440.
#normalize_package_name
def normalize_package_name(self, raw_name: str) -> strReduce a package name to the form this registry compares by.
PyPI folds runs of -_. to a single hyphen, npm removes them, Go compares the last path segment. The default lowercases.
#query_latest_version
def query_latest_version(self, name: str) -> dictAsk this target's registry for the latest published version.
Returns a dict with status "found" (plus version), "not_found", or "error" (plus message). The default answers error naming the target, so "this ecosystem has no version API" is never mistaken for "the package is unpublished".
#claim_placeholder
def claim_placeholder(self, name: str, tmpdir: str) -> strPublish a minimal placeholder package to reserve name.
Targets whose registry accepts a publish override this; the default raises, and claimable_targets() derives the accepted set from exactly this method.
#registry_display_name
def registry_display_name(self) -> strHow to spell this target's registry in user-facing output.
#template_dir
def template_dir(self) -> str | NoneAbsolute path to target-specific template directory, or None.
#shared_template_dir
def shared_template_dir(self) -> str | NoneAbsolute path to shared template directory, or None.
#template_vars
def template_vars(self, dir_path: str, ctx) -> dict[str, str]Extract template placeholder values from the project.
#template_mappings
def template_mappings(self, ctx) -> list[dict[str, str]]Target-specific template-to-output-path mappings.
#shared_template_mappings
def shared_template_mappings(self, ctx) -> list[dict[str, str]]Shared template-to-output-path mappings.
#check_project_exists
def check_project_exists(self, dir_path: str) -> boolCheck if the target's project file exists (alias for detect).
#get_project_init_hint
def get_project_init_hint(self) -> strHuman-readable hint for initializing a project for this target.
#publication_probe
def publication_probe(self, dir_path: str, version: str, ctx=None)Probe the registry to determine if a specific version is published.
Returns a PublicationProbeResult (PUBLISHED, UNPUBLISHED, or UNPROBEABLE). Default: UNPROBEABLE.
#cached_registry_probe
def cached_registry_probe(self, dir_path: str, version: str, ctx=None)Ask the REGISTRY ITSELF whether a version is out in the world.
The second probe, for a target whose primary one answers from somewhere other than the registry. Two-valued: PUBLISHED or UNPROBEABLE, never UNPUBLISHED -- a lazily-indexed registry's silence is not evidence. Default: UNPROBEABLE. The fact is supports_cached_registry_probe.
#build
def build(self, dir_path: str, version: str, *, config: dict | None=None) -> NonePre-publish build step (e.g. generate docs). No-op by default.
#dev_install_command
def dev_install_command(self, project_dir: str) -> dict[str, dict | None]Return the local-install specs for this target, keyed by mode.
Used by rlsbl dev install to install/uninstall the project for local development. Returns a dict with two keys:
"global": spec for the global-install mode, where the project is installed as a globally-available tool or symlink (e.g. uv tool install -e ., npm link, go install). None if the target has no global-install concept.
"venv": spec for the local/venv-install mode, where dependencies are fetched into the project's own environment without exposing a global CLI (e.g. uv sync, npm install). None for targets that have no separate local-environment concept (e.g. Go, Zig, Swift).
Each spec dict has the shape: { "tool": "uv", "args": ["tool", "install", "-e", "."], "uninstall_args_template": ["tool", "uninstall", "{name}"], "purpose": "for editable Python install", }
Fields: tool: CLI tool that must be on PATH. args: argv passed to the tool to install. uninstall_args_template: argv list of templates passed to the tool to uninstall. Each entry may contain {name} (replaced with the project's package name) or {dir} (replaced with the project directory basename). None means uninstall is not supported for this mode of this target. purpose: human-readable string for the require_tool error message.
The returned dict may also carry an optional top-level "reason" key: a human-readable explanation for why the modes are None (e.g. "Go library: nothing to install"). rlsbl dev install surfaces it in the skip message instead of the generic "not yet supported" line.
#find_dead_modules
def find_dead_modules(self, root: str, *, exclude_dirs=None, suppress=frozenset()) -> list[tuple[str, str]]Find source files or packages nothing else references.
Returns (path, reason) pairs, where reason is the ecosystem-specific explanation shown to the user. The default returns nothing: a target with no import scanner has no opinion, and supports_import_analysis is derived from this override.
#find_circular_dependencies
def find_circular_dependencies(self, root: str, *, exclude_dirs=None) -> list[list[str]]Find import cycles within this target's sources.
Returns a list of cycles, each a list of module identifiers. The default returns nothing, and supports_circular_dep_analysis is derived from this override.
#run_tests
def run_tests(self, *, project_dir: str | None=None, workspace_root: str | None=None, skip_sync: bool=False, config: dict | None=None, check_timeout: int | None=None)Run this target's built-in test suite.
Returns a SuiteRunOutcome. The default answers SKIPPED naming the target, so a project whose target ships no runner records a visible skip rather than a passing step for a suite that never ran.
#rewrite_mirror_identity
def rewrite_mirror_identity(self, clone_dir: str, mirror_remote: str) -> listRewrite mirror_identity_files onto the mirror's own identity.
Returns the repository-relative paths rewritten. Raises when the mirror's identity cannot be derived: a manifest still naming the monorepo is one that does not resolve from the mirror.
#yank
def yank(self, project_dir: str, version: str, tag: str, *, reason: str | None=None, dry_run: bool=False)Remove a published version from this target's registry.
Returns a YankOutcome. The default answers UNSUPPORTED naming the target, so rlsbl release yank reports a target it cannot act on instead of passing over it.