rlsbl v0.40.1 /rlsbl.commands.init_cmd
On this page

Scaffold command that generates and updates release infrastructure from templates with three-way merge, CI workflows, hooks, and multi-target support.

#rlsbl.commands.init_cmd

#rlsbl.commands.init_cmd

Init command that scaffolds release infrastructure from templates, creating CI workflows, hooks, changelog, and config files.

#_check_npm_lockfile_missing

python
def _check_npm_lockfile_missing()

Check if any npm lockfile exists from cwd up to the git root.

Returns True if no lockfile is found (i.e., lockfile is missing). Prints a warning to stderr when missing.

#file_hash

python
def file_hash(path)

SHA-256 hash of a file's contents.

#load_hashes

python
def load_hashes()

Load stored file hashes from .rlsbl/hashes.json.

#save_hashes

python
def save_hashes(hashes)

Write file hashes to .rlsbl/hashes.json.

#_ensure_target_in_config

python
def _ensure_target_in_config(registry_name)

Add registry_name to the targets array in .rlsbl/config.json if not already present.

#process_template

python
def process_template(template_content, vars_dict, template_path=None)

Process a template string with a two-pass substitution.

Pass 1 resolves {{action "owner/name"}} placeholders against the central action-version table (rlsbl/data/action_versions.toml). An unknown action raises :class:UnknownActionError immediately -- no implicit defaults.

Pass 2 resolves the existing {{varName}} (and dotted {{a.b}}) placeholders against vars_dict.

Returns (content, unreplaced) where unreplaced is the list of variable names in pass 2 that had no entry in vars_dict. Pass 1 misses raise instead of being collected.

#_save_base

python
def _save_base(target, content)

Save rendered template content as the merge base for future three-way merges.

#_load_base

python
def _load_base(target)

Load the stored merge base for a target file. Returns None if not stored.

#_three_way_merge

python
def _three_way_merge(ours_text, base_text, theirs_text)

Three-way merge using git merge-file.

Writes three temp files in the project dir (not /tmp), runs git merge-file -p ours base theirs, and returns (merged_text, has_conflicts). Exit code: 0 = clean merge, positive = number of conflicts, negative = error.

#plan_mappings

python
def plan_mappings(template_dir, mappings, vars_dict, force, update=False)

Compute what process_mappings would do, without writing anything.

Returns a list of plan dicts. Each plan represents one mapping and contains: - "target": the target file path - "status": one of "new", "updated", "unchanged", "skipped", "user-owned", or a string starting with "CONFLICTS"; or status values like "overwritten", "created", "merged", "updated (additive merge)", "year updated (...)" -- the same vocabulary the original function produced for the (created, skipped) lists. - "bucket": "created" or "skipped" -- which result list this entry belongs in - "action": one of "write", "save_base_only", "license_year_update", "gitignore_merge", "merge_write", "none". Tells apply_plans what to do. - "content": the bytes to write (when action requires it). None otherwise. - "base_content": template content to save as the new merge base. None when no base should be saved this run. - "warning": optional extra warning string emitted alongside this plan - "unreplaced": list of unreplaced template var names (for warnings) - "year_update": for license_year_update, a dict with "current_year", "old_year" so apply can recompute the new content - "additive_lines": for gitignore_merge, the lines to append - "existing_content": for gitignore_merge, the original content - "template_not_found": True for warning-only entries

#apply_plans

python
def apply_plans(plans)

Apply a list of plans from plan_mappings, performing all side effects.

Returns (created, skipped, warnings, new_hashes) matching the original process_mappings return shape.

#process_mappings

python
def process_mappings(template_dir, mappings, vars_dict, force, update=False, existing_hashes=None)

Process a list of template mappings: read each template, apply vars, write target files.

Uses a universal three-way merge (via git merge-file) for existing files: base (last scaffolded version) + ours (user's current file) + theirs (new template). USER_OWNED files are never overwritten or merged (except LICENSE year update).

Returns (created, skipped, warnings, new_hashes). created/skipped are lists of (target, status) tuples for unified display.

Implemented as plan_mappings() (pure analysis) + apply_plans() (side effects).

#_print_file_status_table

python
def _print_file_status_table(created, skipped)

Print the unified file list table with dot-padded status column.

#_print_dry_run_report

python
def _print_dry_run_report(plans_groups, registry=None, registries=None)

Print the file status table from plans without applying them.

plans_groups is a list of plan lists (registry plans, shared plans, etc.).

#_install_or_update_pre_push_hook

python
def _install_or_update_pre_push_hook()

Install the rlsbl pre-push hook, upgrading older versions in place.

See rlsbl/hook_hashes.py for the historical hash set.

Behavior: - .git missing -> no-op - hook missing -> write current template, chmod 755 - hook matches current hash -> no-op (already up to date) - hook matches old known hash -> overwrite, print upgrade notice - hook hash unknown -> skip, print warning + unified diff

#_finalize_scaffold

python
def _finalize_scaffold(existing_hashes, all_hash_dicts, created, skipped, warnings, registry=None, flags=None, registries=None, npm_lockfile_missing=False)

Shared post-processing for scaffold: chmod, hooks, version marker, hashes, tagging, summary.

all_hash_dicts is a list of dicts to merge into existing_hashes. flags is the CLI flags dict (used for tagging check). registries is a list of registry names (used for tagging). npm_lockfile_missing: if True, prepend a lockfile step to npm next steps.

#_resolve_private

python
def _resolve_private(flags)

Determine if this is a private repository.

Checks --private flag first, then saved config, then auto-detects via GitHub API.

On --update: if private is missing from config and no --private flag was passed, prints an error and exits. The user must add the key explicitly.

On new scaffold: auto-detects if needed and returns the result. The caller is responsible for persisting the value to config.json.

Returns True/False, or False if detection fails (new scaffold only).

#_filter_mappings_for_private

python
def _filter_mappings_for_private(mappings)

Remove publish template mappings (private repos don't publish to registries).

#_append_deploy_workflow_if_configured

python
def _append_deploy_workflow_if_configured(mappings)

Add deploy workflow template to mappings if deploy config exists.

#_print_private_summary

python
def _print_private_summary()

Print helpful output for private repository scaffold.

#_trigger_monorepo_sync

python
def _trigger_monorepo_sync(no_commit=False)

If the current directory is inside a monorepo workspace, run sync.

Uses a subprocess so that sys.exit() calls inside sync don't kill scaffold. Failures are silently ignored -- sync is best-effort after scaffold.

When no_commit is True, propagates --no-commit to the sync call so a single user invocation with --no-commit produces zero commits.

#run_cmd

python
def run_cmd(registry, args, flags)

Init command handler.

Scaffolds release infrastructure (CI, publish workflows, changelog, etc.) from templates.

#_extract_top_level_block

python
def _extract_top_level_block(lines, key)

Extract a top-level YAML block (e.g., 'permissions:', 'env:') from template lines.

Returns (block_lines, remaining_lines) where block_lines are the key + its indented children, and remaining_lines are everything else.

#_parse_permissions

python
def _parse_permissions(block_lines)

Parse permission key-value pairs from a permissions block.

Returns a dict like {"contents": "write", "id-token": "write"}.

#_parse_env

python
def _parse_env(block_lines)

Parse env key-value pairs from an env block.

Returns a list of (key, full_line) tuples to preserve formatting. Keys are used for deduplication; full lines are used for output.

#_merge_permissions

python
def _merge_permissions(perm_dicts)

Merge multiple permission dicts, choosing the most permissive value for each key.

Permission escalation order: read < write.

#_extract_jobs_section

python
def _extract_jobs_section(lines)

Extract the content under the 'jobs:' key from template lines.

Returns lines starting from the first job definition (the indented content after 'jobs:'), not including the 'jobs:' line itself.

#_generate_merged_publish

python
def _generate_merged_publish(targets, template_vars)

Generate a merged publish.yml from individual target publish templates.

Reads each target's publish.yml.tpl, extracts jobs/permissions/env, and composes a single workflow with all jobs merged.

#_merge_template_vars

python
def _merge_template_vars(registries_list, primary, target_paths)

Build a merged template vars dict with namespaced keys from all targets.

The primary target's vars are included un-namespaced (as the base). Every target's vars are also included with a namespace prefix: {target_name}.{key} so templates can reference target-specific values like {{pypi.minRequiredPython}}.

target_paths is a dict mapping target name to its directory path.

#_plan_merged_publish

python
def _plan_merged_publish(publish_target, merged_content, force, update)

Compute a plan for the merged publish workflow (analysis only).

#run_cmd_multi

python
def run_cmd_multi(registries_list, args, flags)

Scaffold for multiple registries with a merged publish workflow.

Uses the primary registry for template vars and CI, then writes a merged publish.yml that contains jobs for all detected registries.