On this page
Inline publish logic for monorepo projects: GitHub Actions workflow parsing, job extraction, trigger rewriting, and YAML emission.
#rlsbl.commands.monorepo.publish_inline
#rlsbl.commands.monorepo.publish_inline
Inline publish logic for monorepo projects: workflow parsing and YAML emission.
#parse_publish_workflow
def parse_publish_workflow(path: str) -> dictParse a GitHub Actions publish workflow file.
Reads the YAML file at path, validates it has a jobs: key, and returns a dict with the top-level keys that matter for inline publish generation.
Returns a dict with keys: jobs -- the jobs mapping from the workflow permissions -- workflow-level permissions mapping, or None env -- workflow-level env mapping, or None name -- workflow name string, or None
#_literal_str_representer
def _literal_str_representer(representer, data)Represent multi-line strings with | literal block style.
#emit_workflow
def emit_workflow(workflow_dict: dict) -> strEmit a workflow dict as a YAML string.
Uses literal block style (|) for multi-line strings and preserves key order. The custom representer is registered on a private Dumper subclass so the global yaml state is never modified.
#prefix_jobs
def prefix_jobs(project_name: str, jobs: dict) -> dictPrefix every job key with {project_name}- and rewrite needs: references.
Returns a new dict; the original jobs is not mutated.
#inject_job_metadata
def inject_job_metadata(jobs: dict, tag_prefix: str, working_dir: str) -> dictAdd if: condition and defaults.run.working-directory to every job.
Returns a new dict; the original jobs is not mutated.
#rewrite_action_paths
def rewrite_action_paths(jobs: dict, project_path: str) -> dictRewrite action inputs that contain file paths so they are relative to project_path.
Handles:
pypa/gh-action-pypi-publish: setswith.packages-dirto{project_path}/dist/actions/setup-{go,python,node}: prefixes version-file paths with project_path
Returns a new dict; the original jobs is not mutated.
#resolve_permissions
def resolve_permissions(jobs: dict, workflow_permissions: dict | None) -> dictPush workflow-level permissions down to jobs that lack their own.
- Jobs with an explicit
permissions:key keep it unchanged. - Jobs without
permissions:inherit workflow_permissions (if non-None). - If workflow_permissions is None and the job has no permissions, nothing is added.
Returns a new dict; the original jobs is not mutated.
#transform_project_jobs
def transform_project_jobs(project_name: str, project_path: str, tag_prefix: str, workflow_path: str) -> dictParse a sub-project's publish workflow and transform its jobs for the monorepo router.
Applies all transforms in the correct order:
- resolve_permissions (before prefixing — references original job structure)
- rewrite_action_paths
- inject_job_metadata
- prefix_jobs (last — changes keys)
Returns a dict of transformed jobs ready for merging into the root workflow.
#generate_inline_publish_router
def generate_inline_publish_router(projects_with_publish: list, root: str) -> strGenerate a monorepo publish router with all sub-project jobs inlined.
Instead of calling per-project reusable workflows via workflow_call, this inlines every sub-project's publish jobs directly into a single publish.yml. Each job gets an if: startsWith(...) condition so only the relevant project's jobs run on a given release.
Returns the complete YAML string, ready to write to disk.
#compute_publish_hashes
def compute_publish_hashes(projects: list, root: str) -> dictCompute SHA256 hashes of each project's publish workflow.
Returns a dict mapping project name to the hex digest of its publish.yml content, or None if the project has no publish workflow.
#load_publish_cache
def load_publish_cache(monorepo_dir: str) -> dict | NoneLoad the publish hash cache from monorepo_dir.
Returns the parsed dict, or None if the cache file does not exist or contains invalid JSON.
#save_publish_cache
def save_publish_cache(monorepo_dir: str, hashes: dict) -> strWrite the publish hash cache to monorepo_dir.
Returns the absolute path to the written cache file.
#should_regenerate_router
def should_regenerate_router(cached: dict | None, current: dict, router_path: str) -> boolDecide whether the publish router needs regeneration.
Returns False (skip) only when cached matches current exactly AND router_path exists on disk. Any mismatch -- missing cache, changed hash, added/removed project, missing router file -- returns True.