From 8f0634f000b92d1064431d3cdaad6e50bed9ecf1 Mon Sep 17 00:00:00 2001 From: amalej Date: Tue, 9 Dec 2025 23:01:08 +0800 Subject: [PATCH 1/2] fix issue where tool does not work with firebase-tools v15 --- CHANGELOG.md | 5 +++++ src/firebase-cmd.ts | 13 +++++++----- src/module-loader.js | 48 +++++++++++++++++++++++++++++++++++++++++--- src/onfire-cli.ts | 12 ++++++----- 4 files changed, 65 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 050fcdc..97c3023 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ +Unreleased + +- Fixed issue where Onfire CLI cannot parse commands on firebase-tools v15 + - This is due to the lazy loading feature preventing all commands from being loaded + v1.3.2 - Added feature to list folders/files in current path to make it easier to use diff --git a/src/firebase-cmd.ts b/src/firebase-cmd.ts index 1f46fda..076361e 100644 --- a/src/firebase-cmd.ts +++ b/src/firebase-cmd.ts @@ -7,6 +7,7 @@ import { spawnSync, } from "child_process"; import spawn from "cross-spawn"; +import { writeFileSync } from "fs"; const COMMAND_TIMEOUT = 10000; @@ -251,12 +252,15 @@ export class FirebaseCommands { }); child.on("exit", () => { - if (childBufferString === "ENOENT") { - throw new Error( - `Firebase Tools module not found. Install using 'npm install -g firebase-tools'` + this.loadModuleChildProcess?.kill(); + this.loadModuleChildProcess = null; + if (childBufferString.includes("ERROR__")) { + const message = childBufferString.replace( + "ERROR__", + "Error loading module: " ); + throw new Error(message); } - this.loadModuleChildProcess = null; try { res(JSON.parse(childBufferString)); } catch (_) { @@ -266,7 +270,6 @@ export class FirebaseCommands { }); child.send(`${rootPath}/firebase-tools`); - // this.loadModuleChildProcess.kill(); }); // await new Promise((res, rej) => setTimeout(res, 5000)); diff --git a/src/module-loader.js b/src/module-loader.js index 6a2d7bd..4e39494 100644 --- a/src/module-loader.js +++ b/src/module-loader.js @@ -15,12 +15,54 @@ function stringify(obj) { return str; } +function determineCommandList(map, parentKey = null, depth = 0) { + if (depth > 10) return [] + const commandList = [] + if (map) { + for (let [key, value] of Object.entries(map)) { + if (key === "load") continue + if (typeof value?.load === "function") { + if (!parentKey) { + commandList.push(key) + } else { + commandList.push([parentKey, key].join(":")) + } + } + + if (!parentKey) { + commandList.push(...determineCommandList(value, key, depth + 1)) + } else { + commandList.push(...determineCommandList(value, [parentKey, key].join(":"), depth + 1)) + } + } + } + + return commandList.map((e) => e.toLowerCase()) +} + process.on('message', function (path) { try { const client = require(path); // Load the Firebase Tools module - const configStr = stringify(client) // Removes circular reference and convert to string + let configStr = stringify(client) // Removes circular reference and convert to string + + const clientObj = JSON.parse(configStr) + if (clientObj.cli.commands.length === 0) { + /** + * Due to firebase-tools v15 introducing lazy laoding of commands + * we would need to run `getCommand([command_name])` for each command + * to register each command + */ + const commands = determineCommandList(client) + for (let command of commands) { + require(path).getCommand(command) + } + + const clientWithLoadedCommands = require(path); + configStr = stringify(clientWithLoadedCommands) + } + process.send(configStr); // Send to main process - } catch (_) { - process.send('ENOENT'); // Send to main process + } catch (err) { + process.send(`ERROR__${err}`); // Send to main process } }); diff --git a/src/onfire-cli.ts b/src/onfire-cli.ts index 38d473e..e360ad6 100644 --- a/src/onfire-cli.ts +++ b/src/onfire-cli.ts @@ -1034,6 +1034,8 @@ export class OnFireCLI extends CommandLineInterface { } else { this.firebaseCommands = cmdConfig; } + this.attachCustomCommands(); + this.savedConfig["firebaseCommands"] = this.firebaseCommands; } else { this.firebaseCommands = this.savedConfig["firebaseCommands"]; this.firebaseCli @@ -1041,16 +1043,16 @@ export class OnFireCLI extends CommandLineInterface { .then((firebaseCommands) => { if (firebaseCommands !== null) { this.firebaseCommands = firebaseCommands; - this.savedConfig["firebaseCommands"] = this.firebaseCommands; this.attachCustomCommands(); + this.savedConfig["firebaseCommands"] = this.firebaseCommands; + } else { + throw new Error("Could not load firebase-tools commands"); } }) - .catch((_) => { - // No action needed since we can use the cached firebaseCommands + .catch((err) => { + throw err; }); } - this.attachCustomCommands(); - this.savedConfig["firebaseCommands"] = this.firebaseCommands; } async init() { From 5976ad1ea04473eae9b974a0b19565707d57c770 Mon Sep 17 00:00:00 2001 From: amalej Date: Tue, 9 Dec 2025 23:28:10 +0800 Subject: [PATCH 2/2] fix failing tests --- tests/onfire-cli.test.ts | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/tests/onfire-cli.test.ts b/tests/onfire-cli.test.ts index 7f78a15..7fa05ef 100644 --- a/tests/onfire-cli.test.ts +++ b/tests/onfire-cli.test.ts @@ -73,7 +73,7 @@ class MockOnFireCLI extends OnFireCLI { } const appdistribution = { - distributeLen: 10, + distributeLen: 20, testers: { addLen: 5, removeLen: 5, @@ -94,14 +94,14 @@ describe("Test loading of Firebase commands", () => { it("Should load 'appdistribution:distribute' command", async () => { const firebaseCommands = onfireCLI._getFirebaseCommands(); expect(firebaseCommands["appdistribution:distribute"].description).toEqual( - "upload a release binary" + "upload a release binary and optionally distribute it to testers and run automated tests" ); }); it("Should load 'appdistribution:testers:add' command", async () => { const firebaseCommands = onfireCLI._getFirebaseCommands(); expect(firebaseCommands["appdistribution:testers:add"].description).toEqual( - "add testers to project (and possibly group)" + "add testers to project (and App Distribution group, if specified via flag)" ); }); @@ -109,7 +109,9 @@ describe("Test loading of Firebase commands", () => { const firebaseCommands = onfireCLI._getFirebaseCommands(); expect( firebaseCommands["appdistribution:testers:remove"].description - ).toEqual("remove testers from a project (or group)"); + ).toEqual( + "remove testers from a project (or App Distribution group, if specified via flag)" + ); }); it("Should load 'exit' command", async () => { @@ -147,6 +149,15 @@ describe("Run simple commands", () => { "--testers-file", "--groups", "--groups-file", + "--test-devices", + "--test-devices-file", + "--test-username", + "--test-password", + "--test-password-file", + "--test-username-resource", + "--test-password-resource", + "--test-case-ids", + "--test-case-ids-file", "--project", ]); }); @@ -471,9 +482,9 @@ describe("Test getting rendering list", () => { expect(renderMessage.length).toEqual(5); }); - it("Should show that the selected command in index [0] is 'appdistribution:distribute -> upload a release binary'", () => { + it("Should show that the selected command in index [0] is 'appdistribution:distribute -> upload a release binary and optionally distribute it to testers and run automated tests'", () => { const renderMessage = onfireCLI._getCommandsToRender(); - const cmdLabel = `-> upload a release binary`; + const cmdLabel = `-> upload a release binary and optionally distribute it to testers and run automated tests`; expect(renderMessage[0]).toEqual( `${cli._textCyan(cli._textBold(">"))} ${cli._textGreen( cli._textBold("appdistribution:distribute") @@ -481,10 +492,10 @@ describe("Test getting rendering list", () => { ); }); - it("Should show that the unselected command in index [1] is 'appdistribution:testers:add -> add testers to project (and possibly group)'", () => { + it("Should show that the unselected command in index [2] is 'appdistribution:testers:add -> add testers to project (and App Distribution group, if specified via flag)'", () => { const renderMessage = onfireCLI._getCommandsToRender(); - const cmdLabel = `-> add testers to project (and possibly group)`; - expect(renderMessage[1]).toEqual( + const cmdLabel = `-> add testers to project (and App Distribution group, if specified via flag)`; + expect(renderMessage[2]).toEqual( ` ${cli._textBold("appdistribution:testers:add")} ${cmdLabel}\x1b[K` ); }); @@ -542,15 +553,6 @@ describe("Test getting rendering list", () => { )} ${cli._textGreen(cmdLabel)}\x1b[K` ); }); - - it("Should show that the unselected option in index [1] is 'experimental:functions:shell -> launch full Node shell with emulated functions. (Alias for `firebase functions:shell.)'", () => { - const renderMessage = onfireCLI._getCommandsToRender(); - const cmdLabel = - "-> launch full Node shell with emulated functions. (Alias for `firebase functions:shell.)"; - expect(renderMessage[1]).toEqual( - ` ${cli._textBold("experimental:functions:shell")} ${cmdLabel}\x1b[K` - ); - }); }); });