refactor(commands): extract getRootOpts() helper#89
refactor(commands): extract getRootOpts() helper#89qorexdev wants to merge 1 commit intolinearis-oss:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR refactors CLI command handlers to stop relying on a fragile fixed command.parent!.parent!.opts() chain by introducing a getRootOpts(command) helper that walks the Commander parent tree to the root before reading options.
Changes:
- Added
getRootOpts(command: Command)helper insrc/common/context.tsto retrieve root command options safely. - Updated command action handlers across multiple command modules to call
getRootOpts(command)instead of hardcoded parent navigation. - Updated unit tests’
context.jsmocks to preserve the real module exports while overriding specific functions.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/commands/issues.test.ts | Updates context.js mock to keep real exports while stubbing createContext. |
| tests/unit/commands/auth.test.ts | Updates context.js mock to keep real exports while stubbing createGraphQLClient. |
| src/common/context.ts | Adds getRootOpts() helper for resolving root Commander options. |
| src/commands/users.ts | Uses getRootOpts(command) when creating context. |
| src/commands/teams.ts | Uses getRootOpts(command) when creating context. |
| src/commands/projects.ts | Uses getRootOpts(command) when creating context. |
| src/commands/milestones.ts | Uses getRootOpts(command) across milestone actions. |
| src/commands/labels.ts | Uses getRootOpts(command) when creating context. |
| src/commands/issues.ts | Uses getRootOpts(command) across issue actions. |
| src/commands/files.ts | Uses getRootOpts(command) to obtain root opts for API token resolution. |
| src/commands/documents.ts | Uses getRootOpts(command) to obtain root opts before creating context. |
| src/commands/cycles.ts | Uses getRootOpts(command) across cycle actions. |
| src/commands/comments.ts | Uses getRootOpts(command) when creating context. |
| src/commands/auth.ts | Uses getRootOpts(command) to resolve root opts for auth flows. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let current: Command | null = command; | ||
| while (current?.parent) { |
There was a problem hiding this comment.
getRootOpts declares current as Command | null and then returns current.opts(). With tsconfig.json strict: true, current remains possibly null after the loop (because current = current.parent can assign a nullable type), so this should fail type-checking/build. Consider making current non-nullable (e.g., initialize as let current = command; and loop while current.parent), or assert non-null at the end after proving it via the loop condition.
| let current: Command | null = command; | |
| while (current?.parent) { | |
| let current = command; | |
| while (current.parent) { |
iamfj
left a comment
There was a problem hiding this comment.
Clean refactoring — does exactly what #61 asked for, no more no less. All 25 parent!.parent! chains replaced, zero remaining. The helper is well-placed, well-named, and the mock updates in the test files are done correctly with importOriginal.
Two small suggestions (non-blocking):
-
Typing simplification — see inline comment. The
Command | nullannotation is unnecessary and could be simplified. -
Unit test for
getRootOpts()— it's a new exported function incommon/. It's tested indirectly through command tests, but a small dedicated test intests/unit/common/would be a nice addition — especially the edge case where the command is already the root. Happy to merge as-is and add it as a follow-up.
Nice first contribution 👍
| export type { CommandOptions }; | ||
|
|
||
| export function getRootOpts(command: Command): CommandOptions { | ||
| let current: Command | null = command; |
There was a problem hiding this comment.
Nit: current doesn't need to be Command | null — it starts as command (non-null) and the loop only assigns current.parent when it's truthy. You can simplify to:
let current = command;
while (current.parent) {
current = current.parent;
}This drops the optional chaining and keeps the type as Command throughout — no null narrowing needed, and current.opts() is unambiguously safe.
Replaces all 25 instances of
command.parent!.parent!.opts()with agetRootOpts(command)helper that walks the Commander tree to the root.Eliminates non-null assertions and makes command nesting changes safe.
Closes #61