Skip to content

Commit c6c794a

Browse files
committed
fix(yok): record hierarchical subcommands on registerCommand
Only requireCommand populated hierarchicalCommands, so a hierarchical command registered directly (without a require step) never routed through its parent name and was missing from the children listing. The push is guarded because the legacy flow reaches registerCommand for a name that requireCommand already recorded.
1 parent e46e697 commit c6c794a

2 files changed

Lines changed: 84 additions & 1 deletion

File tree

lib/common/yok.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,28 @@ export class Yok extends Injector implements IInjector {
239239
this.register(this.createCommandName(name), resolver);
240240

241241
if (commands.length > 1) {
242-
this.createHierarchicalCommand(commands[0]);
242+
const parentCommandName = commands[0];
243+
const subCommandName = _.tail(commands).join(
244+
CommandsDelimiters.HierarchicalCommand,
245+
);
246+
247+
if (!this.hierarchicalCommands[parentCommandName]) {
248+
this.hierarchicalCommands[parentCommandName] = [];
249+
}
250+
251+
// Guarded: the legacy flow reaches here twice for one command —
252+
// requireCommand records the subcommand, then the required module
253+
// registers itself through this method.
254+
if (
255+
!_.includes(
256+
this.hierarchicalCommands[parentCommandName],
257+
subCommandName,
258+
)
259+
) {
260+
this.hierarchicalCommands[parentCommandName].push(subCommandName);
261+
}
262+
263+
this.createHierarchicalCommand(parentCommandName);
243264
}
244265
});
245266
}

test/command-registration.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { assert } from "chai";
2+
import { Yok } from "../lib/common/yok";
3+
4+
const noopCommandFactory = () => ({
5+
execute: async (): Promise<void> => undefined,
6+
});
7+
8+
describe("yok: command registration", () => {
9+
let injector: Yok;
10+
11+
beforeEach(() => {
12+
injector = new Yok();
13+
});
14+
15+
describe("registerCommand with a hierarchical name", () => {
16+
it("records the subcommand under its parent", () => {
17+
injector.registerCommand("dev|test", noopCommandFactory);
18+
19+
assert.deepStrictEqual(injector.getChildrenCommandsNames("dev"), [
20+
"test",
21+
]);
22+
});
23+
24+
it("routes arguments through the parent name", () => {
25+
injector.registerCommand("dev|test", noopCommandFactory);
26+
27+
const built = injector.buildHierarchicalCommand("dev", ["test", "extra"]);
28+
29+
assert.deepStrictEqual(built, {
30+
commandName: "dev|test",
31+
remainingArguments: ["extra"],
32+
});
33+
});
34+
35+
it("synthesizes a dispatcher for the parent", () => {
36+
injector.registerCommand("dev|test", noopCommandFactory);
37+
38+
const parent = injector.resolveCommand("dev");
39+
40+
assert.isTrue((<any>parent).isHierarchicalCommand);
41+
});
42+
43+
it("records each sibling once, including default commands", () => {
44+
injector.registerCommand("dev|*test", noopCommandFactory);
45+
injector.registerCommand("dev|lint", noopCommandFactory);
46+
47+
assert.deepStrictEqual(injector.getChildrenCommandsNames("dev"), [
48+
"*test",
49+
"lint",
50+
]);
51+
});
52+
53+
it("does not duplicate a subcommand already recorded by requireCommand", () => {
54+
injector.requireCommand("dev|test", "some-file");
55+
injector.registerCommand("dev|test", noopCommandFactory);
56+
57+
assert.deepStrictEqual(injector.getChildrenCommandsNames("dev"), [
58+
"test",
59+
]);
60+
});
61+
});
62+
});

0 commit comments

Comments
 (0)