Skip to content
rlsbl.commands.monorepo.sync
On this page

Monorepo sync command and helpers: trigger rewriting, working-directory injection, inlined CI router generation, import name detection, and tag prefixes.

#rlsbl.commands.monorepo.sync

#rlsbl.commands.monorepo.sync

Monorepo sync command and all sync helpers: working-directory injection, inline CI router generation.

#_inject_packages_dir

python
def _inject_packages_dir(doc, project_path)

Add packages-dir to PyPI publish steps.

When working-directory is set, uv build creates artifacts in {project_path}/dist/ but the publish action looks for dist/ at the repo root. We inject packages-dir so it finds the right directory.

#count_reusable_workflow_calls

python
def count_reusable_workflow_calls(jobs)

Count job-level uses: entries (reusable workflow calls) in a jobs mapping.

Step-level uses: (actions) are not counted -- only jobs whose mapping carries a top-level uses: key, which is what GitHub treats as a reusable workflow call.

#validate_router_reusable_calls

python
def validate_router_reusable_calls(jobs, router_name)

Hard-error when a generated router carries >= 20 reusable workflow calls.

GitHub rejects workflow files with too many reusable-workflow calls outright -- the router never runs and CI silently stops. Generated routers must inline jobs instead of calling reusable workflows.

Raises:

  • ConfigError: when jobs contains GITHUB_MAX_REUSABLE_CALLS or

more job-level uses: entries.

#_strip_expression_wrapper

python
def _strip_expression_wrapper(expr)

Strip an optional ${{ ... }} wrapper from a GitHub expression.

#_generate_router

python
def _generate_router(projects, filters)

Generate ci-router.yml content with every project's CI jobs inlined.

Each project dict must carry _ci_docs: a list of (job_prefix, doc) pairs where doc is a parsed CI workflow (working-directory already injected) and job_prefix is the per-file key ({name}-ci or {name}-ci-{target}).

filters is a :class:rlsbl.router_filters.RouterFilters built over the WHOLE workspace, not just projects: a member's filter is derived from territories it does not own (its dependencies', and -- for the root member -- every other member's), so the derivation cannot be done from the CI-carrying subset alone.

GitHub rejects workflow files with 20+ reusable-workflow calls, so the router inlines every project's CI jobs directly instead of uses: calls. Each inlined job:

  • gets its key prefixed with the CI file's job prefix (unique per file),
  • gets an explicit name: "{prefix} / {job}" so check-run names stay

identical to the reusable-workflow era (publish gate regexes and branch protection rules keep matching),

  • is gated on needs: detect + if: needs.detect.outputs.{project},

short-circuited by the run_all dispatch input (see :data:RUN_ALL_INPUT),

  • keeps intra-workflow needs: (rewritten to the prefixed keys).

#_get_monorepo_tag_prefix

python
def _get_monorepo_tag_prefix(project, root, releasables=None)

Return the tag prefix for a monorepo project's publish router condition.

When releasables are provided and the project belongs to a releasable (releasable = "X"), the prefix is derived from the releasable's tag_format (e.g. "{name}@v{version}" -> "X@v").

Otherwise falls back to the target's monorepo_tag_glob (glob minus trailing *). For Go projects this yields go/v, for others name@v.

#_root_is_publisher

python
def _root_is_publisher(project, root)

Return True when the root project (path='.') actually publishes.

A root publisher has publish_mode != "none" and at least one detectable publish target. Detection is config-based (not based on the on-disk publish.yml, which is the generated router itself). A ConfigError during publish_mode or target detection means the root is not a resolvable publisher -- treat it as a non-publisher rather than crashing sync.

#_member_suppresses_publish

python
def _member_suppresses_publish(project, root)

Return True when project's effective config declares publish_mode: "none".

The publish router inlines each member's publish jobs verbatim, so a member that declares it must not publish has to be excluded BEFORE inlining -- otherwise a stale per-member publish.yml (e.g. a legacy PyPI job left behind when the repo went private) becomes a live registry publish in the generated root router.

publish_mode is read with releasable-level inheritance, mirroring :func:_root_is_publisher. A member with no resolvable publish_mode is NOT suppressed: absence of a declaration is not a declaration, and the publish-mode config check hard-errors on the missing key separately.

#_build_project_template_vars

python
def _build_project_template_vars(project_dir, root)

Build a template vars dict for a project, with both namespaced and un-namespaced keys.

Detects the project's targets, calls each target's template_vars(), and returns a merged dict where each target's vars appear under both their bare names and {target_name}.{key} namespaced names. This allows process_template to resolve patterns like {{pypi.minRequiredPython}} in workflow comments.

#scaffold_releasable_dirs

python
def scaffold_releasable_dirs(workspace_root)

Create the directory structure for each releasable in the workspace.

Every releasable declared in [[releasables]] gets:

  • .rlsbl-monorepo/releasables/{name}/version (user-owned, never overwritten)
  • .rlsbl-monorepo/releasables/{name}/changes/unreleased.jsonl (user-owned)

Hook scripts are no longer scaffolded here -- hooks are config-driven (see hooks key in config.json). Version and unreleased.jsonl are user-owned: created once, never overwritten.

Args:

  • workspace_root: path to the monorepo root.

Returns:

  • A list of file paths that were created or updated (for commit tracking).

#_saferm_workflow

python
def _saferm_workflow(filepath, description)

Delete a stale generated workflow file via saferm (audit trail).

Raises RuntimeError if saferm is not on PATH and propagates subprocess.CalledProcessError if saferm exits non-zero.

#_sync_import_names

python
def _sync_import_names(root, projects)

Auto-populate import_name for Python projects whose import name differs from their project name.

For each Python project (has pyproject.toml with [project]), detects the package root via detect_python_package_root and compares the derived import name against the underscored project name. When they differ and import_name is not already set in workspace.toml, writes import_name to the project's entry.

Returns the workspace.toml path if it was modified, or None.

More tools from this site

  • claudestream Drive Claude Code from Python: run it as a subprocess and read its output as typed events, with async and sync sessions, sandbox policies, and tools you define in Python
  • claudewheel A TUI Claude Code Launcher that lets you have more than one profile, manage sessions lifecycle, pick the exact CC version, model to use (even older unlisted ones), pick which GitHub account to use, etc.
  • dirstat Fast, single-binary directory statistics CLI: every file under a tree grouped by format, with counts, sizes, and lines of code, as a colored terminal table or as JSON
  • fastware A batteries-included ASGI framework: msgspec JSON, a managed Granian server, dependency injection, SSE, WebSockets, auth, and a test client
  • go-toml-edit Zero-dep TOML editing library for Go with comment preservation
  • howmuchleft The fastest Claude Code statusline: context window, 5-hour, and weekly limit usage as three customizable gradient bars, rendering in about 6 ms
  • orxtra
  • pgdesign
  • predraw Declarative rendering pipeline: describe a scene in JSON and get SVG, PNG and WebP out, with light and dark style tokens, reusable components and text converted to path outlines
  • reposummary Turn a git repository's history into a Markdown journal: pick a time window or revision range and get a readable digest of what changed, optionally narrated by an LLM
  • safegit git wrapper CLI that gives each commit its own temporary index and retries ref updates on conflict, so concurrent agents share one repository
  • saferm Command-line replacement for rm that archives every deletion with a mandatory reason and the context it ran in, so deleted files can be listed, inspected and restored
  • selfdoc Static Site Generator that builds a project's documentation site directly from its source code, so the docs can never drift from the code they describe, with SEO/AEO, first-class blog, search, and cross-project linking built in
  • strictcli
  • stricttest An always-on test-isolation floor: a pytest plugin and a Go env-hygiene module that make a test suite structurally unable to reach real credentials, the real HOME, the network, or the development repository.
  • wesktop A Python framework that turns an ASGI web app into a desktop application, serving it from a local Granian server and displaying it in a native OS window via pywebview
Search