Skip to content

Feature proposal: Generate safe-settings YAML from existing GitHub configuration (reverse sync) #994

@decyjphr

Description

@decyjphr

Summary

Today safe-settings applies declarative YAML config to GitHub. This proposal adds the reverse: generate repos/<name>.yml, suborgs/<name>.yml, and settings.yml from the current state of an org/repo. This helps teams onboard onto safe-settings without hand-authoring config for existing repositories.

Motivation

  • Onboarding existing orgs/repos requires manually transcribing current settings into YAML.
  • A generator bootstraps an accurate baseline config, which can then be refined and committed.
  • The generated files can be validated by feeding them back through safe-settings in nop (dry-run) mode — expecting zero diffs.

Proposed delivery

Two entry points sharing one extraction core:

  1. Standalone CLI (generate-settings.js, mirroring full-sync.js) — writes generated files to the local filesystem.
  2. App trigger via a repository_dispatch event (event_type: safe-settings-generate) — opens a PR against the admin repo with the generated file(s).

Client payload:

{
  "event_type": "safe-settings-generate",
  "client_payload": {
    "source_type": "repo | custom-property | org",
    "source_value": "<repo-name> | <custom-property-value> | <org>",
    "overwrite": false
  }
}

When overwrite: false (default) and the target file already exists, the generator writes <name>.sample.yml instead of replacing it.

Design

A new lib/settingsGenerator.js defines an extractor registry: section → "read current state".

  • Diffable plugins (labels, collaborators, teams, milestones, autolinks, environments, custom_properties, variables, rulesets) reuse each plugin's existing find() method, then sanitize API-only fields (id, node_id, url, timestamps, read-only props).
  • Non-diffable sections need custom reads: repository (repos.get) and branches (list protected branches + getBranchProtection).
  • Output serialized with js-yaml, optionally validated against schema/settings.json.

Scope behavior:

source_type What we can extract Output file
repo All repo-level plugins repos/<repo>.yml
org Org rulesets + custom repository roles only settings.yml
custom-property (suborg) Repo-level settings common to all matching repos suborgs/<value>.yml

Open question: how to derive sub-org settings

Sub-orgs represent settings common to a collection of repos. The proposed approach: discover all repos matching the custom property value, extract each repo's config, then reduce by intersection — keep only settings identical across all matching repos (arrays matched by MergeDeep.NAME_FIELDS). A suborgproperties selector is prepended. Alternatives considered: union (risks conflicts) or a single representative repo. Feedback welcome on the intersection strategy and whether to also report per-repo differences.

Scope boundaries

  • Secrets are never read (no secrets plugin exists); only API-readable values are emitted, with names/placeholders where values are unreadable.
  • validator is excluded (meta plugin).
  • Round-trip fidelity for branches/rulesets is the riskiest area; generated output will be gated by a nop round-trip test.

Verification plan

  • Unit tests per extractor + intersection + overwrite/.sample logic (mocked octokit).
  • Round-trip: feed generated repos/<name>.yml through safe-settings nop mode → expect zero diffs.
  • Manual dispatch produces a PR (or .sample file when overwrite: false).

Looking for community feedback before/while implementing, particularly on the sub-org intersection approach and the repository_dispatch payload shape.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions