Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions .github/scripts/gen-docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { REPO_RESULTS, type RepoResult } from "../../src/engine/orchestrate.js";
import { MERGE_RESULT } from "../../src/flows/deliver.js";
import { SNAPSHOT_RESULTS } from "../../src/flows/snapshot.js";
import type { SectionDocs } from "../../src/sections/contract/docs.js";
import {
type SectionMeta,
Expand Down Expand Up @@ -239,8 +240,12 @@ const RESULT_DISPLAY: Record<RepoResult, "any mode" | "multi-repo only"> = {

const WORST_OF = "; worst-of across targets in multi-repo mode";
const CAN_ALSO_APPEAR = " can also appear";
/** The merge-mode result, a value outside RepoResult, closes the enumeration. */
/** The merge- and snapshot-mode results, values outside RepoResult, close the enumeration. */
const MERGE_ONLY = `; \`${MERGE_RESULT}\` in mode: merge`;
const SNAPSHOT_ONLY = `; ${[...SNAPSHOT_RESULTS]
.reverse()
.map((value) => `\`${value}\``)
.join(" / ")} in mode: snapshot`;

export function renderOutputsList(results: readonly RepoResult[]): string {
const ordered = (Object.keys(RESULT_DISPLAY) as RepoResult[]).filter((value) =>
Expand All @@ -254,7 +259,7 @@ export function renderOutputsList(results: readonly RepoResult[]): string {
const lead = `${anyMode.join(" / ")}${WORST_OF}`;
const withMulti =
multiOnly.length === 0 ? lead : `${lead}, where ${multiOnly.join(" and ")}${CAN_ALSO_APPEAR}`;
return `${withMulti}${MERGE_ONLY}`;
return `${withMulti}${MERGE_ONLY}${SNAPSHOT_ONLY}`;
}

export type TaggedOperation = Pick<SectionOperation, "role" | "grade" | "permission"> & {
Expand Down Expand Up @@ -337,7 +342,7 @@ function outputsListRegion(name: string, heading: string): GeneratedRegion {
body: new RegExp(
String.raw`^(?:\x60[a-z]+\x60(?: / \x60[a-z]+\x60)*${escapeRe(WORST_OF)}(?:, where ` +
String.raw`\x60[a-z]+\x60(?: and ` +
String.raw`\x60[a-z]+\x60)*${escapeRe(CAN_ALSO_APPEAR)})?(?:${escapeRe(MERGE_ONLY)})?)?$`,
String.raw`\x60[a-z]+\x60)*${escapeRe(CAN_ALSO_APPEAR)})?(?:${escapeRe(MERGE_ONLY)})?(?:${escapeRe(SNAPSHOT_ONLY)})?)?$`,
),
render: () => renderOutputsList(REPO_RESULTS),
};
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Apply declarative repository settings from `.github/settings.yml`: a loud, state

1. Create a [fine-grained PAT](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token) from the [pre-filled token form][pat-form] and save it as the `ADMIN_TOKEN` repository secret. The form starts with every repository permission the action can need (an organization owner adds Members: read by hand); the default `GITHUB_TOKEN` can never hold them.

2. Add `.github/settings.yml`. The first line gives editor autocomplete and hover docs:
2. Add `.github/settings.yml` (or start from a [snapshot](docs/operate/snapshot.md) of the live settings). The first line gives editor autocomplete and hover docs:

```yaml
# yaml-language-server: $schema=https://raw.githubusercontent.com/Vivswan/github-settings-as-code/v2/lib/settings.schema.json # x-release-please-major
Expand Down
60 changes: 46 additions & 14 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,17 @@ inputs:
default: ".github/settings.yml"
mode:
description: >-
apply (mutate), check (report drift, exit 1 on any), or merge (fold the
apply (mutate), check (report drift, exit 1 on any), merge (fold the
settings-file layers into one document written to merged-file, with no
token and no GitHub API call; merge reads only settings-file,
merged-file, and layering, ignores token, and rejects every other input
set to a non-default value, since each controls an apply or check run).
check makes no settings changes, though a private report may still be
delivered.
set to a non-default value, since each controls an apply or check run),
or snapshot (read the live settings of the target repositories back and
write each as a settings document to snapshot-file or under
snapshot-dir; nothing is written to GitHub, the document reaches only
the file, and every input that controls an apply, a check, or a merge is
rejected). check makes no settings changes, though a private report may
still be delivered.
required: false
default: "apply"
merged-file:
Expand All @@ -55,6 +59,34 @@ inputs:
apply or check.
required: false
default: ""
snapshot-file:
description: >-
mode: snapshot only, and exactly one of snapshot-file and snapshot-dir
is required there: the path one repository's live settings are written
to as a settings document (parent directories are created). The target
is the repository input, defaulting to the current repository, so it
cannot be combined with repos or repos-dir. The header pins the schema,
names the repository and the moment, and lists every section note;
secret values GitHub never reveals become $NAME references to export
before an apply. Must not be .github/settings.yml, the file apply and
check read: the snapshot would overwrite the document you author, so
write it beside that file and copy it over deliberately. Fails when set
in apply, check, or merge.
required: false
default: ""
snapshot-dir:
description: >-
mode: snapshot only, and exactly one of snapshot-file and snapshot-dir
is required there: the directory the multi-repo targets' live settings
are written under, one <owner>/<name>.yml per target (the repos-dir
layout, so the directory can later serve as a repos-dir). The targets
come from repos and repos-dir exactly as in a multi-repo apply,
discovery filters included; defaults-file does not apply. Must be
disjoint from the repos-dir (not the same directory, not above it, not
below it): the snapshots would overwrite the central files or be read
back as central files. Fails when set in apply, check, or merge.
required: false
default: ""
on-missing-permission:
description: >-
fail (default) or warn. Under warn, sections the token cannot access are
Expand All @@ -72,10 +104,10 @@ inputs:
default: ""
sections:
description: >-
Optional comma-separated allowlist of sections to process. apply and
check only: mode: merge writes every section its layers declare, so the
allowlist belongs on the step that runs the merged document and fails
the merge when set.
Optional comma-separated allowlist of sections to process. apply, check,
and snapshot only: mode: merge writes every section its layers declare,
so the allowlist belongs on the step that runs the merged document and
fails the merge when set.
required: false
default: ""
api-version:
Expand Down Expand Up @@ -219,18 +251,18 @@ outputs:
result:
description: >-
applied | partial | clean | drift | failed (worst-of across all targets
in multi-repo mode, where skipped can also appear), or merged in mode:
merge.
in multi-repo mode, where skipped can also appear), merged in mode:
merge, or snapshot | partial | failed in mode: snapshot.
skipped-sections:
description: >-
Comma-separated sections skipped for missing permissions (deduped union
across targets in multi-repo mode).
repos-result:
description: >-
Multi-repo mode only: JSON map of owner/name to {result, source,
skippedSections}. A redacted private target is keyed by its "private
repository #N" placeholder instead of its slug. Empty in single-repo
mode.
Multi-repo mode (and the snapshot-dir form of mode: snapshot) only: JSON
map of owner/name to {result, source, skippedSections}. A redacted
private target is keyed by its "private repository #N" placeholder
instead of its slug. Empty in single-repo mode.
# END GENERATED: action-outputs

runs:
Expand Down
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ The documentation for GitHub Settings as Code, in five groups. Start here if the
| Use the engine from your own code | [reference/library.md](reference/library.md) |
| Run check, apply, or validate from a terminal | [start/cli.md](start/cli.md) |
| Detect drift on a schedule | [operate/check-mode.md](operate/check-mode.md) |
| Write a repository's live settings to a file | [operate/snapshot.md](operate/snapshot.md) |
| Fold several settings files into one | [operate/layering.md](operate/layering.md) |
| Manage a fleet from one repository | [operate/multi-repo.md](operate/multi-repo.md) |
| Keep private targets out of public logs | [operate/private-repositories.md](operate/private-repositories.md) |
Expand Down Expand Up @@ -50,6 +51,7 @@ The documentation for GitHub Settings as Code, in five groups. Start here if the
## operate: day-to-day operation

- [Check mode](operate/check-mode.md): drift detection on a schedule, exit codes, and what a "cannot verify" note is telling you.
- [Snapshot mode](operate/snapshot.md): `mode: snapshot` writes the live settings as a settings file, the `$NAME` placeholders secrets become, the round trip and its exceptions, and the per-repo directory form.
- [Layering settings files](operate/layering.md): `mode: merge` folds an ordered list of files into one document, the rules of the fold, the `_layering` directive, and the two-step workflow.
- [Multi-repo mode](operate/multi-repo.md): manage a fleet from one admin repository with per-repo files, discovery, and a defaults-file fallback for repositories without a file.
- [Private repositories](operate/private-repositories.md): the redaction that keeps private targets out of public logs, and the private-report channels.
Expand Down
2 changes: 1 addition & 1 deletion docs/operate/layering.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Mappings merged key by key, the two label lists unioned by name, and `labels` ca
| `merged-file` | Required: where the merged document is written (parent directories are created). It must not name one of the `settings-file` layers, compared as resolved paths: the run refuses that, naming the layer's position, because the merge would overwrite the layer and the next run would fold the merged document as one |
| `layering` | `merge` (default) or `replace`: the run-wide default for the keyed list sections, see below |
| `token` | Ignored: a merge makes no GitHub API call, so a token a workflow sets on every step does no harm |
| `repository`, `repos`, `repos-dir`, `defaults-file`, `visibility`, `archived`, `forks`, `exclude`, `topics`, `affiliation`, `sections`, `required-sections`, `on-missing-permission`, `api-version`, `private-repos`, `private-report`, `report-public-key` | Rejected when set to a non-default value: a merge addresses no repository, fleet, or report, calls no API, and writes every section its layers declare; a `sections` allowlist belongs on the step that runs the merged document |
| `repository`, `repos`, `repos-dir`, `defaults-file`, `snapshot-file`, `snapshot-dir`, `visibility`, `archived`, `forks`, `exclude`, `topics`, `affiliation`, `sections`, `required-sections`, `on-missing-permission`, `api-version`, `private-repos`, `private-report`, `report-public-key` | Rejected when set to a non-default value: a merge addresses no repository, fleet, or report, calls no API, and writes every section its layers declare; a `sections` allowlist belongs on the step that runs the merged document |

The step ends with `result: merged` and exit 0, or exit 1 with an error naming the layer that was refused.

Expand Down
Loading
Loading