Repository navigation
Conversation
File size check0 over a hard cap (fails), 2 warning(s).
Split the file, wrap the line, shorten or exempt the comment, or list the path in 5 managed file(s) skipped; repo-platform owns them. |
d45b6fa to
a3d764a
Compare
33d86eb to
f9b7e3d
Compare
379c9b1 to
6ea8aa1
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Overlapping masked values can expose a secret suffix in CLI output.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds a packaged Node CLI that exposes the library's apply, check, merge, validate, and permissions workflows.
Changes:
- Adds CLI parsing, execution, output formatting, and secret redaction.
- Publishes
github-settings-as-codeandgsacbinaries. - Adds CLI tests, package smoke coverage, architecture rules, and documentation.
File summaries
| File | Description |
|---|---|
src/cli.ts |
Adds the executable entrypoint. |
src/cli/program.ts |
Defines commands and global options. |
src/cli/commands.ts |
Connects commands to library flows. |
src/cli/inputs.ts |
Maps action inputs to CLI flags. |
src/cli/io.ts |
Implements terminal output and masking. |
src/flows/inputs.ts |
Declares and parses list inputs. |
tsdown.config.ts |
Builds library and CLI entries. |
package.json |
Adds binaries and runtime dependencies. |
bun.lock |
Locks new CLI dependencies. |
architecture.yml |
Adds the CLI architecture layer. |
AGENTS.md |
Documents CLI layering guidance. |
.github/scripts/package-smoke.ts |
Smoke-tests the packaged CLI. |
.github/scripts/changed-sections.ts |
Makes CLI changes select all sections. |
test/scripts/changed-sections.test.ts |
Tests shebang import scanning. |
test/package-json.test.ts |
Pins binary and dependency metadata. |
test/cli/streams.ts |
Adds in-memory test streams. |
test/cli/program.test.ts |
Tests command behavior and exits. |
test/cli/io.test.ts |
Tests output routing and masking. |
test/cli/inputs.test.ts |
Tests argv-to-config parity. |
test/cli/backpressure.test.ts |
Tests stream backpressure behavior. |
test/docs/readme.test.ts |
Includes the CLI guide link. |
test/docs/guides.test.ts |
Requires the CLI guide. |
README.md |
Introduces terminal usage. |
docs/start/cli.md |
Documents commands, flags, and output. |
docs/reference/library.md |
Links library users to the CLI. |
docs/reference/architecture.md |
Documents the CLI layer. |
docs/README.md |
Adds CLI documentation navigation. |
Review details
- Files reviewed: 26/27 changed files
- Comments generated: 1
- Review effort level: Balanced (auto)
Note
Copilot is running an experiment and ran this review at Balanced.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
🔵 Needs a closer look
Overlapping masked token values can leak a token suffix in CLI output.
Review details
Suppressed comments (1)
src/cli/io.ts:65
- Overlapping masked values can expose the suffix of a token because shorter values are replaced first. For
--token ghp_abc --token ghp_abcdef, the masks are registered in that order, so Commander's echoed second value becomes***definstead of***. Replace longer masks first so one masked value cannot partially consume another.
for (const value of registry.masked()) {
out = out.replaceAll(value, "***");
}
- Files reviewed: 26/27 changed files
- Comments generated: 0 new
- Review effort level: Balanced (auto)
Note
Copilot is running an experiment and ran this review at Balanced.
There was a problem hiding this comment.
🟡 Changes recommended
Check and apply incorrectly treat repeated --settings-file flags as accumulative before rejecting them later.
Get a fresh assessment by requesting another Copilot review.
Review details
- Files reviewed: 27/28 changed files
- Comments generated: 1
- Review effort level: Balanced (auto)
Note
Copilot is running an experiment and ran this review at Balanced.
There was a problem hiding this comment.
🟢 Approval recommended
The implementation, packaging, documentation, and comprehensive tests are consistent with the stated CLI behavior.
Review details
- Files reviewed: 27/28 changed files
- Comments generated: 0 new
- Review effort level: Balanced (auto)
Note
Copilot is running an experiment and ran this review at Balanced.
The npm package gains a command line built by tsdown from src/cli.ts to lib/pkg/cli.js, with the bin names github-settings-as-code and gsac. It is a second consumer of the public entry: the new cli layer in architecture.yml has the action's one edge, src/index.ts. check, apply, and merge mirror the action's modes: the subcommand is the mode input and every other input the mode reads is a --kebab-case flag generated from INPUT_DECLS, so the help text and the inputs reference share one declaration. The read port is built from commander's parsed options and handed to the library's parseConfig, and test/cli/inputs.test.ts pins argv-to-config equal to a record-backed port shaped like the runner's, over every RunConfig arm and the rejections. validate and permissions read a settings file alone; snapshot and init name the snapshot mode this library build does not carry and exit 1. The Io over consola prints annotations to stderr as level: message, log lines to stdout, outputs as name=value lines or one JSON object under --json, and appends the summary to --summary's file; every channel is masked, and the token is registered before anything can echo it. Packaging: bin in package.json, the three runtime dependencies pinned, a second tsdown entry, the package smoke running the installed gsac --help and validate under Node, and the changed-sections selector listing the cli paths. The smoke's consumers read validateSettings as the Result it returns on this base. Docs: docs/start/cli.md, a CLI section in the library reference, the README paragraph and table row, and the AGENTS.md bullet for the layer.
…en the README cap The help usage line names github-settings-as-code (gsac in the description) instead of commander's default "cli". A repeated flag now accumulates only for the inputs parseConfig splits (LIST_INPUTS, pinned against the library's own reading of each pair); every other value flag, --token and --summary included, rejects a second value with commander's message naming the flag, so no argv can form a joined value the action never receives. The README line budget is 101, one over the file.
…rtifact 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.
…ument, assert the full refusal line failRun takes a describe parameter defaulting to describeProblem, so the command line hands it describeCliProblem instead of re-implementing the failure tail; the artifact channel's refusal is an input-unsupported-value problem whose allowed list is derived from the channels. The merge test compares the whole merged document the fold writes, and the single-value refusal test asserts the exact stderr line.
…secrets leave no fragment Replacing one masked value after another left a remainder whenever two values overlapped in the text: a prefix of a longer token printed ***DEF, and "ABC" with "BCD" across "ABCD" printed ***D. The redactor now locates every occurrence of every masked value in the original text, merges overlapping or touching ranges, and replaces each merged range with one ***, for both streams and the summary file.
There was a problem hiding this comment.
🟢 Approval recommended
The CLI behavior and packaging are comprehensively tested; only a minor crash-message issue remains.
Review details
- Files reviewed: 27/28 changed files
- Comments generated: 1
- Review effort level: Balanced (auto)
Note
Copilot is running an experiment and ran this review at Balanced.
…nted, not at re-running The unexpected-error line asked for a --verbose re-run even when the stack was already on the terminal. The remedy now depends on the flag: without it, re-run for the stack; with it, the stack above is the report to file. Both branches are pinned over the same crash.
There was a problem hiding this comment.
🔵 Needs a closer look
Bare invocation silently succeeds, and file-only read failures recommend an unavailable input.
Review details
Suppressed comments (2)
Previously missed (2) — in code that hasn't changed since the last review.
src/cli/program.ts:114
- Invoking the binary without a subcommand exits successfully without doing anything. For
gsac(orgsac --json), Commander runs no action,exitCoderemains 0, and the user gets no usage or error output. Add a root action that prints help and returns a nonzero code when no subcommand was selected.
src/cli/commands.ts:72 - The file-only commands point users to an option they do not accept when a file cannot be read. For
gsac validate missing.yml, this role producesset the "settings-file" input if it lives elsewhere, butvalidateandpermissionsonly accept the positional<file>. Render unreadable-file problems for these commands with advice to correct the positional path instead.
- Files reviewed: 27/28 changed files
- Comments generated: 0 new
- Review effort level: Balanced (auto)
Note
Copilot is running an experiment and ran this review at Balanced.
…shared parser Main's CLI (#152) is total over the parsed modes: its dispatcher and per-mode flag split are typed over RunConfig and MODES, so the snapshot arm needs a home. The dispatcher concludes a snapshot config the way the action does, inputsForMode derives each mode's flags from the exported per-mode input lists (MERGE_ONLY_INPUTS and SNAPSHOT_ONLY_INPUTS now exported), and the program gains the snapshot subcommand with its guide row. The README folds its two closing sentences into one to stay inside the front-door line budget.
…shared parser Main's CLI (#152) is total over the parsed modes: its dispatcher and per-mode flag split are typed over RunConfig and MODES, so the snapshot arm needs a home. The dispatcher concludes a snapshot config the way the action does, inputsForMode derives each mode's flags from the exported per-mode input lists (MERGE_ONLY_INPUTS and SNAPSHOT_ONLY_INPUTS now exported), and the program gains the snapshot subcommand with its guide row. The README folds its two closing sentences into one to stay inside the front-door line budget.
…shared parser Main's CLI (#152) is total over the parsed modes: its dispatcher and per-mode flag split are typed over RunConfig and MODES, so the snapshot arm needs a home. The dispatcher concludes a snapshot config the way the action does, inputsForMode derives each mode's flags from the exported per-mode input lists (MERGE_ONLY_INPUTS and SNAPSHOT_ONLY_INPUTS now exported), and the program gains the snapshot subcommand with its guide row.
…shared parser Main's CLI (#152) is total over the parsed modes: its dispatcher and per-mode flag split are typed over RunConfig and MODES, so the snapshot arm needs a home. The dispatcher concludes a snapshot config the way the action does, inputsForMode derives each mode's flags from the exported per-mode input lists (MERGE_ONLY_INPUTS and SNAPSHOT_ONLY_INPUTS now exported), and the program gains the snapshot subcommand with its guide row.
…shared parser Main's CLI (#152) is total over the parsed modes: its dispatcher and per-mode flag split are typed over RunConfig and MODES, so the snapshot arm needs a home. The dispatcher concludes a snapshot config the way the action does, inputsForMode derives each mode's flags from the exported per-mode input lists (MERGE_ONLY_INPUTS and SNAPSHOT_ONLY_INPUTS now exported), and the program gains the snapshot subcommand with its guide row.
github-settings-as-codeandgsac, built by tsdown fromsrc/cli.tstolib/pkg/cli.js, plain Node 22+.check,apply, andmergeare the action's modes: the subcommand is themodeinput, every other input of that mode is a--flaggenerated fromINPUT_DECLS, and the library'sparseConfigderives the config. A test pins argv-to-config equal to the action's env-to-config over every RunConfig arm.validate <file>andpermissions <file>read a settings file alone, no token.--token(orGITHUB_TOKEN),--json(one object of outputs on stdout),--summary <file>,--verbose. Exit codes are the action's.src/index.ts: the newclilayer inarchitecture.ymlhas the action's one edge.docs/start/cli.md.Technical details
check--<input> <value>(--repository,--settings-file,--on-missing-permission,--required-sections,--sections,--api-version,--repos,--repos-dir,--defaults-file,--private-repos,--private-report,--visibility,--archived,--forks,--exclude,--topics,--affiliation)applymerge--settings-file(repeatable or comma list),--merged-file,--layeringvalidate <file>permissions <file>--token,--json,--summary <file>,--verboseInputDecl.listinsrc/flows/inputs.ts, read by parseConfig'slist()port (a name without it cannot be split) and by the CLI (a list flag repeats and accumulates; any other value flag,--tokenand--summaryincluded, refuses a second value). A completeRecord<InputName, ...>test runs one repeated-argv case per input.--private-report artifactis refused before parsing (the artifact upload needs the Actions runner);report-public-keyis not a flag.input-token-missingandinput-repository-not-slugare reworded with the terminal's remedy; the--repositoryhelp says the flag is required outside Actions.src/cli/io.ts) is consola with one reporter: annotations and the debug trace on stderr aslevel: message, logs on stdout (stderr under--json), outputs collected and flushed asname=valuelines or one JSON object. Every writer, commander's messages included, goes through one redacting stream boundary registered with the token before the parse; the stream honors the target's backpressure.src/cli/commands.tsends every run where the action does (concludeRun,concludeMerge, or the failure tailfailRunproduces).index.jsandcli.jsimport.lib/pkg/cli.jskeeps the shebang and is chmod +x by tsdown; its empty.d.tsis removed after the build.node_modules/.bin/gsac --help(asserting every subcommand),gsac validateon a valid file (exit 0), and on an invalid file (exit 1, the control)..github/scripts/changed-sections.tslistssrc/cli/andsrc/cli.tsas all-selecting, and its import scanner tolerates the shebang line.snapshotandinitarrive with the snapshot mode (task 77).Reviewer note (Copilot): report a finding only with a concrete input and the wrong output it produces.