You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 4561d2c
Browse filesBrowse the repository at this point in the historyBrowse files
settings-as-code-release
committed
fix(cli): derive list flags from the input declarations, refuse the artifact channel, drop the snapshot placeholders
Which inputs are lists is now one declaration: InputDecl gains `list`, parseConfig reads such inputs only through its list() port (a name without the attribute cannot be split), and the CLI lets exactly those flags repeat. A complete Record over every input runs one repeated-argv case per name, so a new list input cannot be missed.
The snapshot and init subcommands are gone until the snapshot mode exists; no build could run them. `--private-report artifact` is refused before parsing (the upload needs the Actions runner) and report-public-key is not a flag. The no-token and not-a-slug problems name --token, --repository, and the environment variables; the --repository help says the flag is required outside Actions.
Tests assert whole outcomes: exact option sets and descriptions per subcommand, the fold's own shape in the merged file, the whole --json object with exit code and stderr, and where an unnamed summary goes. The package smoke gains a failing control (validate on an invalid file exits 1). The redacting stream honors the target's backpressure, with a guard test seen failing on the old code; the bin's empty declaration file is removed after the build; docs and AGENTS.md trimmed to what the code cannot show.
- `lib/settings.schema.json` is the COMMITTED JSON Schema for settings.yml, generated from the zod slices in `src/sections/<key>/schema.ts` as composed into the settings document by `src/schema.ts`. Each slice is ONE declaration that produces its config type (z.infer), the section's tolerant runtime shape (the section module derives `loosen(<slice>)` itself; the factory-minted families - secrets, variables, the two setup sections, and the list sections on `listSection` - get it from their shared factory in `src/sections/shared/`, which does that derivation once), and its part of the published schema (`.meta({id})` names its definitions; the descriptions come from the `schema:` map in the section's `<key>.docs.yml`, keyed `Definition.field`, which the generator attaches and fails on when a site or a key is unmatched); `src/schema.ts` adds only the document-level wrappers and pins each property to its slice with a lockstep type. Regenerate the schema with `bun run build:schema` after a schema-affecting `src/` change - CI's schema-check job fails when it drifts. `lib/index.js`, the bundled entrypoint the action runs (node24), is NOT committed on main: every workflow that executes the action builds it first (`bun run build:bundle`), the e2e runner builds it to a temp path, and a release builds and commits it on a packaged child of the release commit, which is what every `vX.Y.Z` tag and the moving major point at (main itself stays source-only). `lib/` is exempt from the typography check (third-party unicode in the bundle; the schema descriptions are checked at source) and excluded from [biome](https://biomejs.dev).
38
38
- `lib/pkg/` is the npm library build: `bun run build:lib` (tsdown, configured in `tsdown.config.ts`) bundles `src/index.ts` into one ESM `index.js` plus one `index.d.ts` with the runtime dependencies external; like `lib/index.js` it is built, never committed. `package.json` publishes it as `@vivswan/github-settings-as-code` through its `exports` map (`.` to the build, `./settings.schema.json` to the committed schema, `./package.json` to itself; `files` ships only those plus LICENSE.md and README.md), and `test/package-json.test.ts` pins the manifest's field set and holds its `version` equal to `.release-please-manifest.json` (release-please's json extra-file rewrites it). The package-smoke job in `checks.yml` (`bun .github/scripts/package-smoke.ts`) builds the library, runs publint and attw, installs the packed tarball into a fresh consumer, and imports it under Node and under tsc with skipLibCheck off, so a declaration that leaks a devDependency type or a type the emitter cannot name fails there; a type a section module exposes must therefore be exported from its home module. The library reference is docs/reference/library.md.
39
-
-`src/cli.ts` and `src/cli/` are the command line, a second CONSUMER of the public entry with the action's one edge, `../index.js`:
40
-
a capability the CLI needs is exported from `src/index.ts` first, and `test/cli/inputs.test.ts` pins its argv-to-config equal to the action's env-to-config. Guide: docs/start/cli.md.
39
+
-`src/cli/` is the command line, a consumer of the public entry like the action: a capability it needs is exported from `src/index.ts` first, never imported across. Guide: docs/start/cli.md.
41
40
- The import layering of `src/` is declared in `architecture.yml` and enforced by `bun run lint:arch`, so a new cross-layer import is a deliberate edit to that file; the module map in `docs/reference/architecture.md` is generated from it.
42
41
- The apply/check engine layout: `src/main.ts` is the thin bundled entrypoint; `src/index.ts` is the public entry (declaration-only re-exports, pinned by test/library/public-surface.test.ts); `src/action/` is the GitHub Actions layer and a CONSUMER of that entry: its only import outside itself is `../index.js`, the one edge `architecture.yml` grants it; `src/flows/` holds the run flows the action and a library caller share (single, multi, merge, delivery, redaction); `src/io.ts` is the import-free Io output port; `src/types.ts` holds the leaf type vocabulary; `src/engine/` is the per-repo pipeline (orchestrate, validate, merge, diff, secret references); `src/github/` is the REST client, pagination, and repo-file fetch; `src/discovery/` resolves multi-repo targets; `src/report/` composes and delivers the private report. Under `src/sections/`, each settings section is one self-contained DIRECTORY `src/sections/<key>/`: `index.ts` (the `SectionModule` - key, PAT grant advice, the loose shape derived from the slice, handler), `schema.ts` (the zod slice), `mock.ts` (the e2e mock fragment), `generators.ts` (the fuzz fragment; the secret and variable factory families share theirs in `test/e2e/generators.ts` instead), `<key>.test.ts` (absent where a shared factory's suite under `test/sections/` covers the section), and `scenarios/`. Beside the section directories sit `contract/` (the layered section contract: permissions/endpoints/graphql/module/errors/requests/live), `shared/` (the cross-section engines and factories, plus the schema helpers), and `registry.ts` (the single registration point). All GitHub API list calls must go through `listAll()` (bare-array endpoints) or `listAllEnveloped()` (endpoints that wrap the list in a `{total_count, <key>: []}` envelope), both backed by the single page loop in `src/github/paginate.ts`; errors through `call()`/`throwFor()` so the permission policy (`on-missing-permission`, `required-sections`) works.
43
42
- Adding a section: create `src/sections/<key>/` with the six pieces above plus its `<key>.docs.yml` (the Sections table cells for docs/reference/sections.md, the COVERAGE.md prose, and the schema descriptions the generators render; the docs loader fails without it), compose the `SettingsFile` property from the slice and add the key to `SECTION_KEYS` in `src/schema.ts`, and add one line in `src/sections/registry.ts` - the compiler flags each forgotten step (the registry's mapped type, the mock fragment registration, the slice composition pin, and the generator aggregation are all keyed by SectionKey).
Every flag of the action's `apply` and `check` inputs is a flag here, spelled the same way; `--repos`, `--repos-dir`, and `--defaults-file` switch to [multi-repo mode](../operate/multi-repo.md), and the discovery filters follow.
57
+
Every flag of the action's `apply` and `check` inputs is a flag here, spelled the same way, with two exceptions below. `--repos` or `--repos-dir` switches to [multi-repo mode](../operate/multi-repo.md); `--defaults-file` and the discovery filters apply there, as in the action.
58
+
Outside GitHub Actions there is no `GITHUB_REPOSITORY`, so single-repo runs need `--repository`.
62
59
63
60
### merge
64
61
@@ -90,6 +87,8 @@ The subcommand is the action's `mode` input. Every other input of that mode is a
90
87
the [inputs reference](../reference/inputs.md) lists each one with its default and meaning, and `gsac <command> --help` prints the same descriptions.
91
88
A list input (`--settings-file` under merge, `--repos`, `--exclude`, `--topics`, `--affiliation`, `--sections`, `--required-sections`) takes a comma-separated value or the flag repeated; repeating any other value flag, `--token` and `--summary` included, is an error naming it.
92
89
90
+
Two inputs have no command-line form: `--private-report artifact` is refused (the artifact upload needs the Actions runner; `issue`, `issue-on-failure`, and `none` work), and `report-public-key`, which only that channel reads, is not a flag.
91
+
93
92
Four flags are the command line's own:
94
93
95
94
| Flag | Does |
@@ -117,5 +116,5 @@ With `--json` the outputs are one object instead, and stdout carries nothing els
117
116
118
117
The exit codes are the action's: `check` exits 1 on drift or failure, `apply` and `merge` exit 1 on failure only, `validate` exits 1 on an invalid file, `permissions` exits 0 for a valid file.
119
118
A flag the mode does not read is unknown to that subcommand and exits 1 with the parser's message, where the action rejects the same input by name;
120
-
a value the mode refuses (a filter without `--repos "*"`, a `--repository` that is not a slug) fails with the action's own message.
119
+
a value the mode refuses (a filter without `--repos "*"`, a `--repository` that is not a slug) fails with the action's own message, reworded where the action's remedy names the workflow step.
121
120
The token passed on the command line is masked as `***` wherever a line would echo it.
return"cannot call the GitHub API: no token was provided. Pass --token, or export GITHUB_TOKEN";
129
+
case"input-repository-not-slug":
130
+
return`cannot target a repository: "${problem.value}" is not an owner/name slug. Pass --repository owner/name (inside GitHub Actions, GITHUB_REPOSITORY supplies it)`;
131
+
default:
132
+
returndescribeProblem(problem);
133
+
}
134
+
}
135
+
136
+
/** The one report channel a terminal cannot serve: the artifact upload needs the Actions runner. */
137
+
exportconstARTIFACT_UNSUPPORTED=
138
+
'"--private-report artifact" uploads through the Actions artifact service, which the command line has no access to. '+
139
+
'Use "--private-report issue" or "issue-on-failure" (a report on each private target repository), or "none"';
140
+
141
+
/** End a run on a problem the library has no code for, exactly as failRun ends one. */
0 commit comments