On this page
Shared CI workflow YAML helpers for GitHub Actions: parsing, serialization, and content injection used by standalone scaffold and monorepo sync.
#rlsbl.ci_yaml
#rlsbl.ci_yaml
Shared CI workflow YAML helpers for GitHub Actions: parsing, serialization, and content injection used by standalone scaffold and monorepo sync.
These functions are used by both standalone scaffold (init_cmd) and monorepo sync to manipulate GitHub Actions workflow YAML documents.
#conflict_regions
def conflict_regions(text)Return the 1-based (start_line, end_line) of each conflict region.
A region runs from its <<<<<<< marker to the matching >>>>>>>. An unterminated region ends at the last line, so a truncated conflict is still reported rather than silently ignored.
#describe_conflicts
def describe_conflicts(source, regions)Render a human-readable file:lines description of conflict regions.
#parse_ci_workflow
def parse_ci_workflow(content, source=None)Parse CI workflow YAML content using round-trip mode (preserves comments, ordering).
Returns the parsed document, or None if the content is empty or has no jobs: key.
Conflict-marked text is refused up front with a :class:ConfigError naming source and the conflicting line ranges. Feeding merge output straight to the YAML scanner produced a bare while scanning a simple key error that named no file, so an unresolved scaffold conflict surfaced as an opaque crash. Any other YAML error is likewise re-raised with source attached.
#emit_ci_workflow
def emit_ci_workflow(doc)Serialize a parsed workflow document back to YAML string.
Uses round-trip mode to preserve comment annotations and key order from the original parse.
#_emit_ci_workflow_faithful
def _emit_ci_workflow_faithful(doc)Serialize a workflow with indentation matching the scaffold templates.
indent(mapping=2, sequence=4, offset=2) reproduces the template style ( - uses: ... block sequences) and a wide width prevents ruamel from wrapping long scalars (service options string, DSNs). With these settings ruamel round-trips an untouched template byte-for-byte, so the services injection only changes the region it adds -- unrelated hand edits survive the three-way merge, and a re-scaffold re-emits identical output.
#inject_working_directory
def inject_working_directory(doc, path)Add defaults.run.working-directory to each job in a parsed YAML document.
Merges with existing defaults.run (e.g. preserving shell) rather than overwriting. Only adds working-directory if it is not already present.
#rewrite_version_file_inputs
def rewrite_version_file_inputs(doc, project_path)Prefix version-file values with project path in setup actions.
Actions like actions/setup-go resolve go-version-file relative to the repo root, not working-directory. When a workflow is copied into a monorepo sub-project we must adjust these inputs so the runner can still find the file.
Known inputs: go-version-file, python-version-file, node-version-file.
#_ci_target_from_basename
def _ci_target_from_basename(basename, single_target)Return the release-target name a CI workflow filename maps to.
ci.yml maps to single_target (the lone target on the single-target scaffold path). ci-<target>.yml maps to <target>. Anything else (e.g. ci-custom.yml, a user-owned file) returns None so it is skipped.
#_build_health_options
def _build_health_options(health)Build the options string (docker create flags) from a health map.
Space-joined on one line: GitHub passes options verbatim to docker create, so the flags must be space-separated, never newline- separated. Emitted as a plain scalar (the wide emitter width keeps it on a single line rather than folding it into newline-separated fragments).
#_build_service_map
def _build_service_map(svc)Build the per-service YAML mapping (image/env/ports/options).
#_build_verify_line
def _build_verify_line(svc)Build the verification command line for a service's setup block.
verify_cmd is emitted verbatim. verify_sql is turned into a psql invocation using the postgres-convention env vars (POSTGRES_USER -> -U, POSTGRES_PASSWORD -> PGPASSWORD, POSTGRES_DB -> -d), connecting over localhost.
#_build_setup_step
def _build_setup_step(name, svc)Build the setup step (docker exec + optional verify) for a service.
#_pick_test_job
def _pick_test_job(doc)Return the job that runs tests: the test job, else the first job.
#_inject_services_into_doc
def _inject_services_into_doc(doc, services, test_env)Inject services, test_env, and setup steps into a parsed CI doc.
services is a map of name -> definition (already scoped to this workflow's target). test_env is a scalar map. Mutates doc in place. Idempotent: keys already present are overwritten, not duplicated.
#_inject_workflow_text
def _inject_workflow_text(text, scoped_services, scoped_env, source=None)Parse text, inject services/env, and re-emit; passthrough on parse fail.
#make_ci_workflow_transform
def make_ci_workflow_transform(config, *, single_target=None, working_dir=None)Build a plan_mappings transform for CI workflow templates, or None.
The returned callable takes (target_path, rendered_template_text) and returns the text scaffold actually wants on disk: the subdirectory working-directory injection (when working_dir names a subdirectory) plus the service containers and test_env declared in config, scoped to the release target the CI filename maps to.
These rewrites used to run AFTER plan_mappings had merged, patching the plan's content and storing the rewritten text as the merge BASE while "theirs" stayed the raw template. Base and theirs then differed by the whole rewrite on every run, so any local edit overlapping the rewritten region conflicted -- and the rewrite went on to parse that conflict-marked text as YAML and crash. Applying the rewrite to "theirs" BEFORE the merge makes base and theirs come out of one pipeline, which is what removes the phantom diff.
Returns None when there is nothing to rewrite, so the common case costs no parse/re-emit round trip (and cannot perturb a byte-stable template).