Skip to content

Commit c4045a4

Browse files
Refactor command loading to be lazy
- Updated `src/commands/index.ts` to make `loadCommand` return a lazy runner that only `require`s and registers the command when executed or explicitly loaded. - Updated `src/index.ts` to handle lazy loading in the catch-all command handler by traversing the `client` object and loading the matching command before re-parsing arguments. - Updated `src/bin/cli.ts` to load all commands when no arguments are provided (global help), ensuring the help text is fully populated. - Added cycle detection to the `loadAll` logic to handle circular references in the `client` object.
1 parent 8211843 commit c4045a4

File tree

3 files changed

+62
-10
lines changed

3 files changed

+62
-10
lines changed

src/bin/cli.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,19 @@ export function cli(pkg: any) {
110110
if (!handlePreviewToggles(args)) {
111111
// determine if there are any arguments. if not, display help
112112
if (!args.length) {
113+
const seen = new Set();
114+
const loadAll = (obj: any) => {
115+
if (seen.has(obj)) return;
116+
seen.add(obj);
117+
for (const [key, value] of Object.entries(obj)) {
118+
if (typeof value === "function" && (value as any).load) {
119+
(value as any).load();
120+
} else if (typeof value === "object" && value !== null && !Array.isArray(value) && key !== "cli") {
121+
loadAll(value);
122+
}
123+
}
124+
};
125+
loadAll(client);
113126
client.cli.help();
114127
} else {
115128
cmd = client.cli.parse(process.argv);

src/commands/index.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,36 @@
11
import * as experiments from "../experiments";
2+
import { Command } from "../command";
3+
24
/**
35
* Loads all commands for our parser.
46
*/
57
export function load(client: any): any {
68
function loadCommand(name: string) {
79
const t0 = process.hrtime.bigint();
8-
const { command: cmd } = require(`./${name}`);
9-
cmd.register(client);
10-
const t1 = process.hrtime.bigint();
11-
const diffMS = (t1 - t0) / BigInt(1e6);
12-
if (diffMS > 75) {
13-
// NOTE: logger.debug doesn't work since it's not loaded yet. Comment out below to debug.
14-
// console.error(`Loading ${name} took ${diffMS}ms`);
15-
}
1610

17-
return cmd.runner();
11+
const load = () => {
12+
const { command: cmd } = require(`./${name}`);
13+
cmd.register(client);
14+
return cmd.runner();
15+
};
16+
17+
const runner = async (...args: any[]) => {
18+
const run = load();
19+
return run(...args);
20+
};
21+
22+
// Store the load function on the runner so we can trigger it without running.
23+
(runner as any).load = () => {
24+
const tStart = process.hrtime.bigint();
25+
require(`./${name}`).command.register(client);
26+
const tEnd = process.hrtime.bigint();
27+
const diff = (tEnd - tStart) / BigInt(1e6);
28+
if (diff > 75) {
29+
// console.error(`Loading ${name} took ${diff}ms`);
30+
}
31+
};
32+
33+
return runner;
1834
}
1935

2036
const t0 = process.hrtime.bigint();

src/index.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,32 @@ const RENAMED_COMMANDS: Record<string, string> = {
7575

7676
// Default handler, this is called when no other command action matches.
7777
program.action((_, args) => {
78+
const cmd = args[0];
79+
const keys = cmd.split(":");
80+
let obj = client;
81+
let hit = true;
82+
for (const key of keys) {
83+
if (!obj || typeof obj !== "object") {
84+
hit = false;
85+
break;
86+
}
87+
const nextKey = Object.keys(obj).find((k) => k.toLowerCase() === key.toLowerCase());
88+
if (!nextKey) {
89+
hit = false;
90+
break;
91+
}
92+
obj = (obj as any)[nextKey];
93+
}
94+
95+
if (hit && typeof obj === "function" && (obj as any).load) {
96+
(obj as any).load();
97+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
98+
(client.cli as any).parse(process.argv);
99+
return;
100+
}
101+
78102
useConsoleLoggers();
79103

80-
const cmd = args[0];
81104
logger.error(clc.bold(clc.red("Error:")), clc.bold(cmd), "is not a Firebase command");
82105

83106
if (RENAMED_COMMANDS[cmd]) {

0 commit comments

Comments
 (0)