Skip to content

Commit 2d9e86c

Browse files
committed
fix(cli): menu infinite loop when picking sign-in (or anything with argv)
Picking 'Sign in to MIOSA' (or any non-docs option) caused the menu to re-print indefinitely. Root cause in src/commands/menu.ts: await program.parseAsync(['node', 'miosa', ...chosen.argv], { from: 'user' }); With { from: 'user' } Commander expects argv WITHOUT the node/script prefix — just bare user-typed args. Passing ['node', 'miosa', 'login'] made Commander treat 'node' as the first positional, find no matching subcommand, and fall through to THIS file's default program.action() handler again — which IS the menu. The menu re-rendered, the user picked again, parseAsync mis-parsed again, ad infinitum. The docs option happened to work because it has an early return that never touches parseAsync. Fix: - parseAsync(chosen.argv, { from: 'user' }) — correct encoding - module-level menuActive flag — defense-in-depth guard that prints help if the action is somehow re-entered Bumped to 1.0.8.
1 parent cd18ad4 commit 2d9e86c

3 files changed

Lines changed: 33 additions & 6 deletions

File tree

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@miosa/cli",
3-
"version": "1.0.7",
3+
"version": "1.0.8",
44
"description": "MIOSA platform CLI — projects, sandboxes, deploys, databases, more",
55
"type": "module",
66
"bin": {

src/commands/menu.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,33 @@ function buildChoices(authenticated: boolean): MenuChoice[] {
6161
];
6262
}
6363

64+
// Module-level guard. Prevents the default `program.action()` from
65+
// re-entering itself if `parseAsync` for the chosen subcommand somehow
66+
// falls through to the default action again (e.g. a typo in the picker
67+
// argv, a Commander parsing edge case). The previous version of this
68+
// file had exactly that recursion bug — the user picked "Sign in to
69+
// MIOSA", a malformed `parseAsync` call re-triggered the default
70+
// action, and the menu re-printed itself on every cycle.
71+
let menuActive = false;
72+
6473
export function register(program: Command): void {
6574
program.action(async () => {
6675
// We only run the menu when Commander dispatched here because no
6776
// subcommand matched. If argv is longer than "node miosa", the user
6877
// typed something that Commander has already handled (or errored on).
6978
if (process.argv.length > 2) return;
7079

80+
// Hard reentry guard — see comment above. If `menuActive` is true
81+
// we're being called recursively from inside our own parseAsync
82+
// re-invocation, which means the chosen argv didn't match any
83+
// subcommand. Bail and print help so the user sees SOMETHING rather
84+
// than the menu looping forever.
85+
if (menuActive) {
86+
program.help();
87+
return;
88+
}
89+
menuActive = true;
90+
7191
// Non-TTY → fall back to default help text. Prompting would hang.
7292
if (!process.stdin.isTTY) {
7393
program.help();
@@ -122,9 +142,16 @@ export function register(program: Command): void {
122142
// Re-invoke Commander with the chosen argv. This goes through the
123143
// normal command lifecycle (option parsing, help, error handling) —
124144
// we don't need to duplicate any of that here.
145+
//
146+
// CRITICAL: with `{ from: "user" }` Commander expects argv WITHOUT
147+
// the node/script prefix — just the bare user-typed args. The prior
148+
// version passed ["node", "miosa", ...chosen.argv] here, which
149+
// Commander treated as user-typed positional args. It found no
150+
// matching subcommand for "node", fell through to THIS default
151+
// action again, and the menu re-printed in an infinite loop. The
152+
// bug only affected non-empty argv branches; "Read the docs" had
153+
// an early return and worked, which is exactly what the user saw.
125154
console.log();
126-
await program.parseAsync(["node", "miosa", ...chosen.argv], {
127-
from: "user",
128-
});
155+
await program.parseAsync(chosen.argv, { from: "user" });
129156
});
130157
}

0 commit comments

Comments
 (0)