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:
- Standalone CLI (
generate-settings.js, mirroring full-sync.js) — writes generated files to the local filesystem.
- 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:
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.
Summary
Today safe-settings applies declarative YAML config to GitHub. This proposal adds the reverse: generate
repos/<name>.yml,suborgs/<name>.yml, andsettings.ymlfrom the current state of an org/repo. This helps teams onboard onto safe-settings without hand-authoring config for existing repositories.Motivation
Proposed delivery
Two entry points sharing one extraction core:
generate-settings.js, mirroringfull-sync.js) — writes generated files to the local filesystem.repository_dispatchevent (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.ymlinstead of replacing it.Design
A new
lib/settingsGenerator.jsdefines an extractor registry: section → "read current state".find()method, then sanitize API-only fields (id,node_id,url, timestamps, read-only props).repository(repos.get) andbranches(list protected branches +getBranchProtection).js-yaml, optionally validated againstschema/settings.json.Scope behavior:
source_typereporepos/<repo>.ymlorgsettings.ymlcustom-property(suborg)suborgs/<value>.ymlOpen 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). Asuborgpropertiesselector 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
validatoris excluded (meta plugin).branches/rulesetsis the riskiest area; generated output will be gated by a nop round-trip test.Verification plan
.samplelogic (mocked octokit).repos/<name>.ymlthrough safe-settings nop mode → expect zero diffs..samplefile whenoverwrite: false).Looking for community feedback before/while implementing, particularly on the sub-org intersection approach and the
repository_dispatchpayload shape.