Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 291c8aa

Browse files
authored
fix(hook): regenerate subcommand wrapper when new entry added to existing program (sassman#89)
## Summary - Adding a second subcommand alias under the same program (e.g. `c:t` after `c:l` already exists) would not appear in the shell until the next session - The hook skipped reload because it only tracked program names (`c`) in `_AM_PROJECT_ALIASES` — adding a new entry looked identical to the existing state - Fix: also store individual subcommand keys (`c:l`, `c:t`) in `_AM_PROJECT_ALIASES` so change detection is precise; unloading still operates only on program wrapper names (filtered by absence of `:`) ## Test plan - [x] `am add c:t "cargo test"` in a project with `c:l` already defined — `c t` should work immediately without reopening the shell - [x] `am l` still shows both entries after adding - [x] Leaving and re-entering the project directory still loads/unloads the `c` wrapper correctly --------- Signed-off-by: Sven Kanoldt <[email protected]>
1 parent 0f5510f commit 291c8aa

1 file changed

Lines changed: 53 additions & 4 deletions

File tree

crates/am/src/hook.rs

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,13 @@ pub fn generate_hook_with_security(
5959
// repeating warnings. It is passed in explicitly rather than read from the
6060
// environment so that callers (e.g. tests) can control it independently.
6161

62-
// Helper: generate unalias commands for previously loaded aliases
62+
// Helper: unalias only shell-level names (no `:` — subcommand keys like `c:l`
63+
// are tracked for change detection but are not themselves shell functions).
6364
let unload_prev = |lines: &mut Vec<String>| {
6465
for name in &prev {
65-
lines.push(shell_impl.unalias(name));
66+
if !name.contains(':') {
67+
lines.push(shell_impl.unalias(name));
68+
}
6669
}
6770
};
6871

@@ -93,16 +96,25 @@ pub fn generate_hook_with_security(
9396

9497
let subcmd_groups =
9598
crate::subcommand::group_by_program(&project.subcommands);
99+
100+
// all_names tracks both the shell-level wrapper names (e.g. `c`) and
101+
// the individual subcommand keys (e.g. `c:l`, `c:t`). Wrapper names
102+
// are used to unload old functions; subcommand keys make change
103+
// detection precise — adding c:t when c:l already exists would
104+
// otherwise appear identical (both produce program name `c`).
96105
let subcmd_program_names: Vec<String> =
97106
subcmd_groups.keys().cloned().collect();
107+
let subcmd_keys: Vec<String> =
108+
project.subcommands.keys().cloned().collect();
98109

99110
let mut all_names: Vec<String> = names.clone();
100111
all_names.extend(subcmd_program_names.clone());
112+
all_names.extend(subcmd_keys);
101113
all_names.sort();
102114
all_names.dedup();
103115

104-
// If the same aliases are already loaded, skip entirely.
105-
// The hash check guarantees commands haven't changed either.
116+
// If the exact same set of aliases and subcommand keys is already
117+
// loaded, skip entirely — nothing changed.
106118
if all_names.len() == prev.len()
107119
&& all_names.iter().zip(&prev).all(|(a, b)| a == b)
108120
{
@@ -535,6 +547,43 @@ mod tests {
535547
);
536548
}
537549

550+
#[test]
551+
fn test_hook_picks_up_new_subcommand_added_to_existing_program() {
552+
// Regression: when a second subcommand is added under the same program (e.g. c:t after
553+
// c:l), the hook was incorrectly skipping the reload because the set of *program names*
554+
// hadn't changed ("c" was already in _AM_PROJECT_ALIASES). The wrapper function must
555+
// be regenerated whenever the file content changes.
556+
let mut t = TestBed::new()
557+
.with_aliases("[subcommands]\n\"c:l\" = [\"clippy\"]\n")
558+
.with_security_trusted()
559+
.setup();
560+
561+
let cwd = t.root();
562+
563+
// First run: load c:l, c wrapper is emitted
564+
let (output, _) = t.run(&Shells::Fish, &cwd, None);
565+
assert!(
566+
output.contains("function c"),
567+
"first run should emit c wrapper"
568+
);
569+
assert!(output.contains("clippy"));
570+
571+
// Add c:t — the .aliases file changes, but program name `c` stays the same
572+
t.update_aliases("[subcommands]\n\"c:l\" = [\"clippy\"]\n\"c:t\" = [\"test\"]\n");
573+
574+
// Second run: prev="c" (already loaded), but file has new content
575+
let (output, _) = t.run(&Shells::Fish, &cwd, Some("c"));
576+
assert!(
577+
output.contains("function c"),
578+
"hook must re-emit c wrapper after new subcommand added, got: {output}"
579+
);
580+
assert!(output.contains("test"), "updated wrapper must include c:t");
581+
assert!(
582+
output.contains("clippy"),
583+
"updated wrapper must still include c:l"
584+
);
585+
}
586+
538587
#[test]
539588
fn test_hook_with_project_subcommands() {
540589
let mut t = TestBed::new()

0 commit comments

Comments
 (0)