On this page
Workspace data layer for monorepo support handling discovery, loading, saving, and resolution of workspaces from workspace.toml config.
#rlsbl.workspace
#rlsbl.workspace
Workspace data layer for monorepo support handling discovery, loading, saving, and resolution of workspaces from workspace.toml config.
#read_releasable_version
def read_releasable_version(workspace_root, releasable_name)Read the version string from a releasable's version file.
Args:
workspace_root: path to the monorepo root.releasable_name: name of the releasable.
Returns:
- The version string (stripped of whitespace).
Raises:
WorkspaceError: if the version file does not exist or is empty.
#write_releasable_version
def write_releasable_version(workspace_root, releasable_name, version)Write a version string to a releasable's version file atomically.
Creates the releasable directory if it does not exist. Writes to a temporary file in the same directory and then atomically replaces the target file via os.replace().
Args:
workspace_root: path to the monorepo root.releasable_name: name of the releasable.version: the version string to write.
#is_explicit_mode
def is_explicit_mode(workspace_root)Check whether the workspace has a [[releasables]] section.
Returns True when [[releasables]] is present in workspace.toml, False otherwise.
Args:
workspace_root: path to the monorepo root.
Returns:
- bool
#find_workspace_root
def find_workspace_root(start_path='.')Walk up from start_path looking for a .rlsbl-monorepo/workspace.toml.
Returns the directory containing .rlsbl-monorepo/, or None if not found.
#load_workspace
def load_workspace(root)Read and validate workspace.toml, returning a list of WorkspaceProject.
Each project has at least 'path' (str) and 'name' (str, defaults to basename of path). The returned WorkspaceProject instances support dict-like access for backward compatibility.
Raises FileNotFoundError if workspace.toml doesn't exist. Raises WorkspaceError on invalid structure.
#load_releasables
def load_releasables(root, projects=None)Load releasable definitions from workspace.toml.
Reads and validates the [[releasables]] section, then validates that every releasable project has a valid releasable field referencing a defined releasable name (or false).
Args:
root: path to the monorepo root (containing .rlsbl-monorepo/).projects: optional pre-loaded project list. If None, loads via
load_workspace(root).
Returns:
- A list of Releasable instances.
Raises:
- WorkspaceError if
[[releasables]]is missing, or on invalid - releasable definitions or missing/invalid project releasable fields.
#_load_explicit_releasables
def _load_explicit_releasables(raw_releasables, projects)Parse [[releasables]] section and validate project membership.
Every releasable project must have a releasable field that is either a string referencing a defined releasable name, or false.
#members_of
def members_of(releasable_name, projects)Return the list of projects that belong to a given releasable.
Projects with releasable = "<name>" matching the given name are returned as members.
Args:
releasable_name: the releasable name to look up.projects: list of WorkspaceProject or dict instances.
Returns:
- List of projects that are members of the releasable.
#resolve_releasable_for_project
def resolve_releasable_for_project(proj, releasables)Return the Releasable that a project belongs to, or None.
Looks up the project's releasable field and matches it against the list of releasables.
Args:
proj: WorkspaceProject or dict with at leastnameand optionally
releasable.
releasables: list of Releasable instances.
Returns:
- The matching Releasable, or None if the project is not releasable
- (
releasable = false) or no match is found.
#_get_releasable_value
def _get_releasable_value(proj)Extract the releasable value from a project (WorkspaceProject or dict).
Returns str, False, or None. Does not validate -- just reads the raw value.
#_build_project_table
def _build_project_table(d)Build a fresh tomlkit table for a project dict.
Key order: path, name, then all remaining keys sorted. This matches the layout used when scaffolding a brand-new workspace.toml.
#_build_releasable_table
def _build_releasable_table(d)Build a fresh tomlkit table for a releasable desired-dict.
d carries name and (optionally) tag_format.
#_update_table_fields
def _update_table_fields(table, desired)Update a tomlkit table in place to match desired (a plain dict).
- Existing keys are reassigned only when their value actually changed
(so untouched keys keep their original formatting and inline comments).
- New keys are appended (preserving the order of already-present keys).
- Keys absent from
desiredare removed.
Intra-table comments attached to surviving keys are preserved by tomlkit.
#_sync_aot_in_place
def _sync_aot_in_place(aot, desired_list, id_key, build_fn)Reconcile an existing tomlkit array-of-tables with a desired list.
Items are matched by identity (id_key: path for projects, name for releasables). Matched tables are updated field-by-field in place (preserving comments and key order). Tables whose identity is not in desired_list are removed. Desired items with no matching table are appended (in desired order) as fresh tables with a leading blank line so they read like the surrounding array-of-tables.
#save_workspace
def save_workspace(root, projects, releasables=None)Write workspace.toml atomically, editing the existing document in place.
When the file already exists it is parsed with tomlkit and the [[projects]] (and, when requested, [[releasables]]) arrays-of-tables are reconciled surgically: matched items (by path for projects and name for releasables) are updated field-by-field, absent items are removed, and new items are appended. Untouched tables, intra-table comments, key order, and every other top-level section are preserved byte-for-byte. When the file does not yet exist, a fresh document is created.
When releasables is passed (a list of Releasable instances), the [[releasables]] section is reconciled in place. When releasables is None, any existing [[releasables]] section is preserved untouched. Pass an empty list to explicitly remove the section.
Creates .rlsbl-monorepo/ directory if it doesn't exist.
#resolve_project
def resolve_project(root, cwd='.')Determine which project cwd is inside, returning a WorkspaceProject or None.
If multiple projects match (nested paths), returns the most specific one.
#_derive_standalone_name
def _derive_standalone_name(project_root, detected_targets=None, targets_map=None)Derive a project name for the standalone releasable.
Tries target read_name (first detected target), then falls back to the directory basename.
Args:
project_root: path to the project root (str or Path).detected_targets: pre-detected list of TargetEntry instances.targets_map: dict mapping target names to target objects.
Returns:
- A non-empty name string.
#load_standalone_releasable
def load_standalone_releasable(project_root)Load an explicit releasable definition from .rlsbl/releasable.toml.
If the file exists, reads name and tag_format from it. If absent, returns None (caller should use create_standalone_releasable).
Args:
project_root: path to the project root (str or Path).
Returns:
- A Releasable instance, or None if the file does not exist.
Raises:
- WorkspaceError on invalid file contents.
#create_standalone_releasable
def create_standalone_releasable(project_root)Return a Releasable representing a single-project repo.
If .rlsbl/releasable.toml exists, uses its explicit configuration. Otherwise, derives the name from the project's target metadata (e.g., pyproject.toml [project].name) or the directory basename, and uses the standalone tag format (v{version}).
This function does NOT create any files on disk -- the releasable is purely an internal abstraction.
Args:
project_root: path to the project root (str or Path).
Returns:
- A Releasable instance.