From 3cbcd16a33a474bc8b023c56182dfea975b6b006 Mon Sep 17 00:00:00 2001 From: Pujal Date: Fri, 7 Mar 2025 10:18:36 -0500 Subject: [PATCH 01/12] updated unit tests Signed-off-by: Pujal --- .../src/cmd/src/yargs/YargsConfigurer.ts | 9 +++- .../utilities/NpmFunctions.unit.test.ts | 23 +++++++++- .../npm-interface/uninstall.unit.test.ts | 43 ++++++++++++++++++- .../src/plugins/utilities/NpmFunctions.ts | 12 ++++-- .../utilities/npm-interface/uninstall.ts | 18 ++++++-- 5 files changed, 92 insertions(+), 13 deletions(-) diff --git a/packages/imperative/src/cmd/src/yargs/YargsConfigurer.ts b/packages/imperative/src/cmd/src/yargs/YargsConfigurer.ts index 35135afb69..648d5e0c3c 100644 --- a/packages/imperative/src/cmd/src/yargs/YargsConfigurer.ts +++ b/packages/imperative/src/cmd/src/yargs/YargsConfigurer.ts @@ -86,8 +86,13 @@ export class YargsConfigurer { Logger.getImperativeLogger().debug("Root help complete."); }) .catch((rejected) => { - process.stderr.write("Internal Imperative Error: Root command help error occurred: " - + rejected.message + "\n"); + const daemonStream = ImperativeConfig.instance.daemonContext?.stream; + if(daemonStream) { + daemonStream.write(`Internal Imperative Error: Root command help error occurred: ${rejected.message}\n`); + } else { + process.stderr.write("Internal Imperative Error: Root command help error occurred: " + + rejected.message + "\n"); + } Logger.getImperativeLogger().error(`Root unexpected help error: ${inspect(rejected)}`); }); } else { diff --git a/packages/imperative/src/imperative/__tests__/plugins/utilities/NpmFunctions.unit.test.ts b/packages/imperative/src/imperative/__tests__/plugins/utilities/NpmFunctions.unit.test.ts index b0e39b5137..e738ce7d1c 100644 --- a/packages/imperative/src/imperative/__tests__/plugins/utilities/NpmFunctions.unit.test.ts +++ b/packages/imperative/src/imperative/__tests__/plugins/utilities/NpmFunctions.unit.test.ts @@ -15,7 +15,7 @@ import * as npmPackageArg from "npm-package-arg"; import * as pacote from "pacote"; import * as npmFunctions from "../../../src/plugins/utilities/NpmFunctions"; import { PMFConstants } from "../../../src/plugins/utilities/PMFConstants"; -import { ExecUtils } from "../../../../utilities"; +import { DaemonRequest, ExecUtils, ImperativeConfig } from "../../../../utilities"; jest.mock("cross-spawn"); jest.mock("jsonfile"); @@ -26,6 +26,7 @@ describe("NpmFunctions", () => { const npmCmd = npmFunctions.findNpmOnPath(); afterEach(() => { + jest.restoreAllMocks(); jest.clearAllMocks(); }); @@ -45,7 +46,25 @@ describe("NpmFunctions", () => { expect(spawnSyncSpy.mock.calls[0][1]).toEqual(expect.arrayContaining(["install", "samplePlugin"])); expect(spawnSyncSpy.mock.calls[0][1]).toEqual(expect.arrayContaining(["--prefix", "fakePrefix"])); expect(spawnSyncSpy.mock.calls[0][1]).toEqual(expect.arrayContaining(["--registry", fakeRegistry])); - expect(result).toBe(stdoutBuffer.toString()); + expect(result).toBe("true"); + }); + it("should write output to daemon stream if available", () => { + const writeMock = jest.fn().mockReturnValue("true"); + + jest.spyOn(ImperativeConfig, "instance", "get").mockReturnValue({ + envVariablePrefix: "MOCK_PREFIX", + cliHome: "/mock/home", + daemonContext: { + stream: { + write: writeMock + } + } + } as any); + + jest.spyOn(ExecUtils, "spawnAndGetOutput").mockReturnValue(Buffer.from("Install Succeeded")); + const result = npmFunctions.installPackages("samplePlugin", { prefix: "fakePrefix" }); + expect(writeMock).toHaveBeenCalledWith(DaemonRequest.create({ stderr: "Install Succeeded" })); + expect(result).toBe("true"); }); it("getRegistry should run npm config command", () => { diff --git a/packages/imperative/src/imperative/__tests__/plugins/utilities/npm-interface/uninstall.unit.test.ts b/packages/imperative/src/imperative/__tests__/plugins/utilities/npm-interface/uninstall.unit.test.ts index 5c396ccdbb..5acffa2129 100644 --- a/packages/imperative/src/imperative/__tests__/plugins/utilities/npm-interface/uninstall.unit.test.ts +++ b/packages/imperative/src/imperative/__tests__/plugins/utilities/npm-interface/uninstall.unit.test.ts @@ -26,7 +26,7 @@ import { findNpmOnPath } from "../../../../src/plugins/utilities/NpmFunctions"; import { uninstall } from "../../../../src/plugins/utilities/npm-interface"; import { ConfigSchema, ConfigUtils } from "../../../../../config"; import mockTypeConfig from "../../__resources__/typeConfiguration"; -import { ExecUtils } from "../../../../../utilities"; +import { DaemonRequest, ExecUtils, ImperativeConfig } from "../../../../../utilities"; import { IExtendersJsonOpts } from "../../../../../config/src/doc/IExtenderOpts"; import { updateAndGetRemovedTypes } from "../../../../src/plugins/utilities/npm-interface/uninstall"; @@ -50,6 +50,9 @@ describe("PMF: Uninstall Interface", () => { // This needs to be mocked before running uninstall jest.spyOn(Logger, "getImperativeLogger").mockReturnValue(new Logger(new Console())); }); + afterEach(() => { + jest.restoreAllMocks(); + }) afterAll(() => { jest.restoreAllMocks(); @@ -70,7 +73,7 @@ describe("PMF: Uninstall Interface", () => { "-g" ], { cwd : PMFConstants.instance.PMF_ROOT, - stdio: ["pipe", "pipe", process.stderr] + stdio: ["pipe", "pipe", "pipe"] } ); }; @@ -99,6 +102,8 @@ describe("PMF: Uninstall Interface", () => { }; describe("Basic uninstall", () => { + let writeMock: jest.Mock; + beforeEach(() => { mocks.spawnSync.mockReturnValue({ status: 0, @@ -129,6 +134,40 @@ describe("PMF: Uninstall Interface", () => { wasSpawnSyncCallValid(packageName); wasWriteFileSyncCallValid(); }); + it("should uninstall and check daemon stream if available", () => { + const writeMock = jest.fn(); + jest.spyOn(ImperativeConfig, "instance", "get").mockReturnValue({ + envVariablePrefix: "MOCK_PREFIX", + cliHome: "/mock/home", + daemonContext: { + stream: { + write: writeMock + } + } + } as any); + + const pluginJsonFile: IPluginJson = { + a: { + package: "a", + location: packageRegistry, + version: "3.2.1" + }, + plugin2: { + package: "plugin1", + location: packageRegistry, + version: "1.2.3" + } + }; + + mocks.readFileSync.mockReturnValue(pluginJsonFile); + + uninstall(packageName); + + wasSpawnSyncCallValid(packageName); + wasWriteFileSyncCallValid(); + + expect(writeMock).toHaveBeenCalled(); + }); it("should uninstall imperative-sample-plugin", () => { diff --git a/packages/imperative/src/imperative/src/plugins/utilities/NpmFunctions.ts b/packages/imperative/src/imperative/src/plugins/utilities/NpmFunctions.ts index b400464b07..fcaa1fac2b 100644 --- a/packages/imperative/src/imperative/src/plugins/utilities/NpmFunctions.ts +++ b/packages/imperative/src/imperative/src/plugins/utilities/NpmFunctions.ts @@ -16,7 +16,7 @@ import { StdioOptions } from "child_process"; import { readFileSync } from "jsonfile"; import * as npmPackageArg from "npm-package-arg"; import * as pacote from "pacote"; -import { ExecUtils } from "../../../../utilities"; +import { DaemonRequest, ExecUtils, ImperativeConfig } from "../../../../utilities"; import { INpmInstallArgs } from "../doc/INpmInstallArgs"; import { IPluginJsonObject } from "../doc/IPluginJsonObject"; import { INpmRegistryInfo } from "../doc/INpmRegistryInfo"; @@ -43,7 +43,7 @@ export function findNpmOnPath(): string { * */ export function installPackages(npmPackage: string, npmArgs: INpmInstallArgs): string { - const pipe: StdioOptions = ["pipe", "pipe", process.stderr]; + const pipe: StdioOptions = ["pipe", "pipe", "pipe"]; const args = ["install", npmPackage, "-g", "--legacy-peer-deps"]; for (const [k, v] of Object.entries(npmArgs)) { if (v != null) { @@ -55,8 +55,12 @@ export function installPackages(npmPackage: string, npmArgs: INpmInstallArgs): s cwd: PMFConstants.instance.PMF_ROOT, stdio: pipe }); - - return execOutput.toString(); + const daemonStream = ImperativeConfig.instance.daemonContext?.stream; + if (daemonStream != null) { + return (daemonStream.write(DaemonRequest.create({ stderr: execOutput.toString() }))).toString(); + } else { + return (process.stderr.write(execOutput.toString())).toString(); + } } /** diff --git a/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/uninstall.ts b/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/uninstall.ts index 90e001720d..e20387ee85 100644 --- a/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/uninstall.ts +++ b/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/uninstall.ts @@ -16,7 +16,7 @@ import { readFileSync, writeFileSync } from "jsonfile"; import { IPluginJson } from "../../doc/IPluginJson"; import { Logger } from "../../../../../logger"; import { ImperativeError } from "../../../../../error"; -import { ExecUtils, TextUtils } from "../../../../../utilities"; +import { DaemonRequest, ExecUtils, ImperativeConfig, TextUtils } from "../../../../../utilities"; import { StdioOptions } from "child_process"; import { findNpmOnPath } from "../NpmFunctions"; import { ConfigSchema, ConfigUtils } from "../../../../../config"; @@ -101,14 +101,16 @@ export function uninstall(packageName: string): void { try { // We need to capture stdout but apparently stderr also gives us a progress // bar from the npm install. - const pipe: StdioOptions = ["pipe", "pipe", process.stderr]; + const daemonStream = ImperativeConfig.instance.daemonContext?.stream; + const stderrBuffer: string[] = []; + const pipe: StdioOptions = ["pipe", "pipe", "pipe"]; // Perform the npm uninstall, somehow piping stdout and inheriting stderr gives // some form of a half-assed progress bar. This progress bar doesn't have any // formatting or colors but at least I can get the output of stdout right. (comment from install handler) iConsole.info("Uninstalling package...this may take some time."); - ExecUtils.spawnAndGetOutput(npmCmd, + const output = ExecUtils.spawnAndGetOutput(npmCmd, [ "uninstall", npmPackage, @@ -122,6 +124,9 @@ export function uninstall(packageName: string): void { stdio: pipe } ); + if(output) { + stderrBuffer.push(output.toString()); + } const installFolder = path.join(PMFConstants.instance.PLUGIN_HOME_LOCATION, npmPackage); if (fs.existsSync(installFolder)) { @@ -162,6 +167,13 @@ export function uninstall(packageName: string): void { }); iConsole.info("Plugin successfully uninstalled."); + + if(stderrBuffer.length > 0 && daemonStream) { + daemonStream.write(DaemonRequest.create({stderr: stderrBuffer.toString()})); + } + else { + process.stderr.write(stderrBuffer.join("")); + } } catch (e) { throw new ImperativeError({ msg: e.message, From b3b036060b08aaac92005474df35e0ab1cc5cf94 Mon Sep 17 00:00:00 2001 From: Pujal Date: Fri, 7 Mar 2025 10:35:55 -0500 Subject: [PATCH 02/12] updated changelog Signed-off-by: Pujal --- packages/imperative/CHANGELOG.md | 3 +++ .../plugins/utilities/npm-interface/uninstall.unit.test.ts | 5 ++--- .../src/imperative/src/plugins/utilities/NpmFunctions.ts | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/imperative/CHANGELOG.md b/packages/imperative/CHANGELOG.md index 0ab7b8fd87..5befec9e76 100644 --- a/packages/imperative/CHANGELOG.md +++ b/packages/imperative/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to the Imperative package will be documented in this file. +## Recent Changes +- BugFix: When in daemon mode, the user would not see imperative initialization errors, but now the errors are passed back to the user terminal window. + ## `8.14.1` - BugFix: Fixed help text example section's wrapping issue where the first line of the description is wrapped differently than the rest of the lines. [#1945] (https://github.com/zowe/zowe-cli/issues/1945). diff --git a/packages/imperative/src/imperative/__tests__/plugins/utilities/npm-interface/uninstall.unit.test.ts b/packages/imperative/src/imperative/__tests__/plugins/utilities/npm-interface/uninstall.unit.test.ts index 5acffa2129..cb0bf3942d 100644 --- a/packages/imperative/src/imperative/__tests__/plugins/utilities/npm-interface/uninstall.unit.test.ts +++ b/packages/imperative/src/imperative/__tests__/plugins/utilities/npm-interface/uninstall.unit.test.ts @@ -26,7 +26,7 @@ import { findNpmOnPath } from "../../../../src/plugins/utilities/NpmFunctions"; import { uninstall } from "../../../../src/plugins/utilities/npm-interface"; import { ConfigSchema, ConfigUtils } from "../../../../../config"; import mockTypeConfig from "../../__resources__/typeConfiguration"; -import { DaemonRequest, ExecUtils, ImperativeConfig } from "../../../../../utilities"; +import { ExecUtils, ImperativeConfig } from "../../../../../utilities"; import { IExtendersJsonOpts } from "../../../../../config/src/doc/IExtenderOpts"; import { updateAndGetRemovedTypes } from "../../../../src/plugins/utilities/npm-interface/uninstall"; @@ -52,7 +52,7 @@ describe("PMF: Uninstall Interface", () => { }); afterEach(() => { jest.restoreAllMocks(); - }) + }); afterAll(() => { jest.restoreAllMocks(); @@ -102,7 +102,6 @@ describe("PMF: Uninstall Interface", () => { }; describe("Basic uninstall", () => { - let writeMock: jest.Mock; beforeEach(() => { mocks.spawnSync.mockReturnValue({ diff --git a/packages/imperative/src/imperative/src/plugins/utilities/NpmFunctions.ts b/packages/imperative/src/imperative/src/plugins/utilities/NpmFunctions.ts index fcaa1fac2b..9fe092a393 100644 --- a/packages/imperative/src/imperative/src/plugins/utilities/NpmFunctions.ts +++ b/packages/imperative/src/imperative/src/plugins/utilities/NpmFunctions.ts @@ -57,9 +57,9 @@ export function installPackages(npmPackage: string, npmArgs: INpmInstallArgs): s }); const daemonStream = ImperativeConfig.instance.daemonContext?.stream; if (daemonStream != null) { - return (daemonStream.write(DaemonRequest.create({ stderr: execOutput.toString() }))).toString(); + return daemonStream.write(DaemonRequest.create({ stderr: execOutput.toString() })).toString(); } else { - return (process.stderr.write(execOutput.toString())).toString(); + return process.stderr.write(execOutput.toString()).toString(); } } From 0ec0d6d3e5c9cb1e15f6c9133eae01fc160dec36 Mon Sep 17 00:00:00 2001 From: Pujal Date: Fri, 7 Mar 2025 10:36:19 -0500 Subject: [PATCH 03/12] changelog update Signed-off-by: Pujal --- packages/imperative/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/imperative/CHANGELOG.md b/packages/imperative/CHANGELOG.md index 5befec9e76..ecd2cc2d2d 100644 --- a/packages/imperative/CHANGELOG.md +++ b/packages/imperative/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to the Imperative package will be documented in this file. ## Recent Changes + - BugFix: When in daemon mode, the user would not see imperative initialization errors, but now the errors are passed back to the user terminal window. ## `8.14.1` From 85af3e9baa0b909a9dd83b386d45e4446f385c4f Mon Sep 17 00:00:00 2001 From: Pujal Date: Mon, 10 Mar 2025 11:27:14 -0400 Subject: [PATCH 04/12] fixed failing integration tests Signed-off-by: Pujal --- .../__tests__/plugins/utilities/NpmFunctions.unit.test.ts | 2 +- .../src/imperative/src/plugins/utilities/NpmFunctions.ts | 2 +- .../src/plugins/utilities/npm-interface/uninstall.ts | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/imperative/src/imperative/__tests__/plugins/utilities/NpmFunctions.unit.test.ts b/packages/imperative/src/imperative/__tests__/plugins/utilities/NpmFunctions.unit.test.ts index e738ce7d1c..7d4555de56 100644 --- a/packages/imperative/src/imperative/__tests__/plugins/utilities/NpmFunctions.unit.test.ts +++ b/packages/imperative/src/imperative/__tests__/plugins/utilities/NpmFunctions.unit.test.ts @@ -46,7 +46,7 @@ describe("NpmFunctions", () => { expect(spawnSyncSpy.mock.calls[0][1]).toEqual(expect.arrayContaining(["install", "samplePlugin"])); expect(spawnSyncSpy.mock.calls[0][1]).toEqual(expect.arrayContaining(["--prefix", "fakePrefix"])); expect(spawnSyncSpy.mock.calls[0][1]).toEqual(expect.arrayContaining(["--registry", fakeRegistry])); - expect(result).toBe("true"); + expect(result).toBe(stdoutBuffer.toString()); }); it("should write output to daemon stream if available", () => { const writeMock = jest.fn().mockReturnValue("true"); diff --git a/packages/imperative/src/imperative/src/plugins/utilities/NpmFunctions.ts b/packages/imperative/src/imperative/src/plugins/utilities/NpmFunctions.ts index 9fe092a393..15d02b51d4 100644 --- a/packages/imperative/src/imperative/src/plugins/utilities/NpmFunctions.ts +++ b/packages/imperative/src/imperative/src/plugins/utilities/NpmFunctions.ts @@ -59,7 +59,7 @@ export function installPackages(npmPackage: string, npmArgs: INpmInstallArgs): s if (daemonStream != null) { return daemonStream.write(DaemonRequest.create({ stderr: execOutput.toString() })).toString(); } else { - return process.stderr.write(execOutput.toString()).toString(); + return execOutput.toString(); } } diff --git a/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/uninstall.ts b/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/uninstall.ts index e20387ee85..06fbc66de5 100644 --- a/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/uninstall.ts +++ b/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/uninstall.ts @@ -171,9 +171,7 @@ export function uninstall(packageName: string): void { if(stderrBuffer.length > 0 && daemonStream) { daemonStream.write(DaemonRequest.create({stderr: stderrBuffer.toString()})); } - else { - process.stderr.write(stderrBuffer.join("")); - } + } catch (e) { throw new ImperativeError({ msg: e.message, From d70a0533dfd8952995a2c49250883ecdcf6fb057 Mon Sep 17 00:00:00 2001 From: Pujal Date: Mon, 10 Mar 2025 14:58:10 -0400 Subject: [PATCH 05/12] updated unit tests Signed-off-by: Pujal --- .../yargs/YargsConfigurer.unit.test.ts | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/packages/imperative/src/cmd/__tests__/yargs/YargsConfigurer.unit.test.ts b/packages/imperative/src/cmd/__tests__/yargs/YargsConfigurer.unit.test.ts index 45ce0c0c45..88d22bfe95 100644 --- a/packages/imperative/src/cmd/__tests__/yargs/YargsConfigurer.unit.test.ts +++ b/packages/imperative/src/cmd/__tests__/yargs/YargsConfigurer.unit.test.ts @@ -17,6 +17,35 @@ jest.mock("yargs"); jest.mock("../../src/CommandProcessor"); describe("YargsConfigurer tests", () => { + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it("should write error message to daemon stream", async () => { + const rejectedError = new Error("Root command help error occurred"); + const writeMock = jest.fn(); + jest.spyOn(ImperativeConfig, "instance", "get").mockReturnValue({ + envVariablePrefix: "MOCK_PREFIX", + cliHome: "/mock/home", + daemonContext: { + stream: { + write: writeMock + } + } + } as any); + + const mockedYargs = require("yargs"); + const config = new YargsConfigurer({ name: "any", description: "any", type: "command", children: []}, + mockedYargs, undefined as any, { getHelpGenerator: jest.fn() }, undefined as any, "fake", "fake", "ZOWE", "fake"); + try { + config.configure(); + } catch (err) { + expect(writeMock).toHaveBeenCalledWith( + `Internal Imperative Error: Root command help error occurred: ${rejectedError.message}\n` + ); + } + }); it("should build a failure message", () => { const config = new YargsConfigurer({ name: "any", description: "any", type: "command", children: []}, From 13392f5406d193130d7cf70fa323578ff9b746e7 Mon Sep 17 00:00:00 2001 From: Pujal Date: Mon, 10 Mar 2025 21:50:55 -0400 Subject: [PATCH 06/12] updated unit tests Signed-off-by: Pujal --- .../yargs/YargsConfigurer.unit.test.ts | 43 ++++++++++++++----- .../src/cmd/src/yargs/YargsConfigurer.ts | 6 +-- 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/packages/imperative/src/cmd/__tests__/yargs/YargsConfigurer.unit.test.ts b/packages/imperative/src/cmd/__tests__/yargs/YargsConfigurer.unit.test.ts index 88d22bfe95..9c78503619 100644 --- a/packages/imperative/src/cmd/__tests__/yargs/YargsConfigurer.unit.test.ts +++ b/packages/imperative/src/cmd/__tests__/yargs/YargsConfigurer.unit.test.ts @@ -10,8 +10,9 @@ */ import { CommandProcessor } from "../../src/CommandProcessor"; -import { Constants, ImperativeConfig } from "../../.."; +import { Constants, DaemonRequest, ImperativeConfig } from "../../.."; import { YargsConfigurer } from "../../src/yargs/YargsConfigurer"; +import { stderr } from "process"; jest.mock("yargs"); jest.mock("../../src/CommandProcessor"); @@ -22,9 +23,9 @@ describe("YargsConfigurer tests", () => { jest.restoreAllMocks(); }); - it("should write error message to daemon stream", async () => { - const rejectedError = new Error("Root command help error occurred"); + it('should write to daemonStream if available', async () => { const writeMock = jest.fn(); + const mockedYargs = require("yargs"); jest.spyOn(ImperativeConfig, "instance", "get").mockReturnValue({ envVariablePrefix: "MOCK_PREFIX", cliHome: "/mock/home", @@ -34,18 +35,38 @@ describe("YargsConfigurer tests", () => { } } } as any); - - const mockedYargs = require("yargs"); + const rejectedError = new Error("Test error"); const config = new YargsConfigurer({ name: "any", description: "any", type: "command", children: []}, mockedYargs, undefined as any, { getHelpGenerator: jest.fn() }, undefined as any, "fake", "fake", "ZOWE", "fake"); - try { - config.configure(); - } catch (err) { - expect(writeMock).toHaveBeenCalledWith( - `Internal Imperative Error: Root command help error occurred: ${rejectedError.message}\n` - ); + config.configure(); + + const daemonStream = ImperativeConfig.instance.daemonContext?.stream; + if (daemonStream) { + daemonStream.write(DaemonRequest.create({ stderr:`Internal Imperative Error: Root command help error occurred: ${rejectedError.message}\n`})); + } + expect(writeMock).toHaveBeenCalled(); + expect(writeMock).toHaveBeenCalledWith(DaemonRequest.create({stderr: "Internal Imperative Error: Root command help error occurred: Test error\n"})); + }); + + it("should handle daemonStream when it's undefined", async () => { + ImperativeConfig.instance.daemonContext = {} as any; + + const rejectedError = new Error("Test error"); + const config = new YargsConfigurer( + { name: "any", description: "any", type: "command", children: [] }, + undefined as any, undefined as any, undefined as any, undefined as any, "fake", "fake", "ZOWE", "fake" + ); + + const stderrWriteSpy = jest.spyOn(process.stderr, "write").mockImplementation(); + + const daemonStream = ImperativeConfig.instance.daemonContext?.stream; + if (!daemonStream) { + process.stderr.write("Internal Imperative Error: Root command help error occurred: " + rejectedError.message + "\n"); } + + expect(stderrWriteSpy).toHaveBeenCalledWith("Internal Imperative Error: Root command help error occurred: Test error\n"); }); + it("should build a failure message", () => { const config = new YargsConfigurer({ name: "any", description: "any", type: "command", children: []}, diff --git a/packages/imperative/src/cmd/src/yargs/YargsConfigurer.ts b/packages/imperative/src/cmd/src/yargs/YargsConfigurer.ts index 648d5e0c3c..2473b33ff9 100644 --- a/packages/imperative/src/cmd/src/yargs/YargsConfigurer.ts +++ b/packages/imperative/src/cmd/src/yargs/YargsConfigurer.ts @@ -19,7 +19,7 @@ import { ICommandResponseParms } from "../doc/response/parms/ICommandResponsePar import { CommandProcessor } from "../CommandProcessor"; import { CommandUtils } from "../utils/CommandUtils"; import { IHelpGeneratorFactory } from "../help/doc/IHelpGeneratorFactory"; -import { ImperativeConfig } from "../../../utilities"; +import { DaemonRequest, ImperativeConfig } from "../../../utilities"; import { closest } from "fastest-levenshtein"; import { COMMAND_RESPONSE_FORMAT } from "../doc/response/api/processor/ICommandResponseApi"; @@ -80,7 +80,7 @@ export class YargsConfigurer { rootCommandName: this.rootCommandName, commandLine: this.commandLine, envVariablePrefix: this.envVariablePrefix, - promptPhrase: this.promptPhrase + promptPhrase: this.promptPhrase, }).invoke({ arguments: argv, silent: false, responseFormat: this.getResponseFormat(argv) }) .then((_response) => { Logger.getImperativeLogger().debug("Root help complete."); @@ -88,7 +88,7 @@ export class YargsConfigurer { .catch((rejected) => { const daemonStream = ImperativeConfig.instance.daemonContext?.stream; if(daemonStream) { - daemonStream.write(`Internal Imperative Error: Root command help error occurred: ${rejected.message}\n`); + daemonStream.write(DaemonRequest.create({ stderr:`Internal Imperative Error: Root command help error occurred: ${rejected.message}\n`})); } else { process.stderr.write("Internal Imperative Error: Root command help error occurred: " + rejected.message + "\n"); From 68f70074203367138e0a0d5fe8a2d63cef2e6e35 Mon Sep 17 00:00:00 2001 From: Pujal Date: Mon, 10 Mar 2025 21:56:01 -0400 Subject: [PATCH 07/12] small fixes Signed-off-by: Pujal --- .../src/cmd/__tests__/yargs/YargsConfigurer.unit.test.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/imperative/src/cmd/__tests__/yargs/YargsConfigurer.unit.test.ts b/packages/imperative/src/cmd/__tests__/yargs/YargsConfigurer.unit.test.ts index 9c78503619..10f654fed1 100644 --- a/packages/imperative/src/cmd/__tests__/yargs/YargsConfigurer.unit.test.ts +++ b/packages/imperative/src/cmd/__tests__/yargs/YargsConfigurer.unit.test.ts @@ -12,7 +12,6 @@ import { CommandProcessor } from "../../src/CommandProcessor"; import { Constants, DaemonRequest, ImperativeConfig } from "../../.."; import { YargsConfigurer } from "../../src/yargs/YargsConfigurer"; -import { stderr } from "process"; jest.mock("yargs"); jest.mock("../../src/CommandProcessor"); @@ -52,10 +51,6 @@ describe("YargsConfigurer tests", () => { ImperativeConfig.instance.daemonContext = {} as any; const rejectedError = new Error("Test error"); - const config = new YargsConfigurer( - { name: "any", description: "any", type: "command", children: [] }, - undefined as any, undefined as any, undefined as any, undefined as any, "fake", "fake", "ZOWE", "fake" - ); const stderrWriteSpy = jest.spyOn(process.stderr, "write").mockImplementation(); From 23a2cff835059098a309aaa6f8bc9b7b87b79239 Mon Sep 17 00:00:00 2001 From: Pujal Date: Wed, 12 Mar 2025 11:26:38 -0400 Subject: [PATCH 08/12] updated code coverage Signed-off-by: Pujal --- .../yargs/YargsConfigurer.unit.test.ts | 34 ++++++------------- 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/packages/imperative/src/cmd/__tests__/yargs/YargsConfigurer.unit.test.ts b/packages/imperative/src/cmd/__tests__/yargs/YargsConfigurer.unit.test.ts index 10f654fed1..5fa9949b6f 100644 --- a/packages/imperative/src/cmd/__tests__/yargs/YargsConfigurer.unit.test.ts +++ b/packages/imperative/src/cmd/__tests__/yargs/YargsConfigurer.unit.test.ts @@ -12,6 +12,7 @@ import { CommandProcessor } from "../../src/CommandProcessor"; import { Constants, DaemonRequest, ImperativeConfig } from "../../.."; import { YargsConfigurer } from "../../src/yargs/YargsConfigurer"; +import { mock } from "node:test"; jest.mock("yargs"); jest.mock("../../src/CommandProcessor"); @@ -19,47 +20,32 @@ jest.mock("../../src/CommandProcessor"); describe("YargsConfigurer tests", () => { afterEach(() => { + jest.clearAllMocks(); jest.restoreAllMocks(); }); it('should write to daemonStream if available', async () => { const writeMock = jest.fn(); + const rejectedError = new Error("Test error"); + const invokeSpy = jest.spyOn(CommandProcessor.prototype, "invoke").mockRejectedValue(rejectedError); + const stream = {write: writeMock}; const mockedYargs = require("yargs"); jest.spyOn(ImperativeConfig, "instance", "get").mockReturnValue({ envVariablePrefix: "MOCK_PREFIX", cliHome: "/mock/home", daemonContext: { - stream: { - write: writeMock - } + stream } } as any); - const rejectedError = new Error("Test error"); + const config = new YargsConfigurer({ name: "any", description: "any", type: "command", children: []}, mockedYargs, undefined as any, { getHelpGenerator: jest.fn() }, undefined as any, "fake", "fake", "ZOWE", "fake"); config.configure(); + const handler = mockedYargs.command.mock.calls[0][0].handler; - const daemonStream = ImperativeConfig.instance.daemonContext?.stream; - if (daemonStream) { - daemonStream.write(DaemonRequest.create({ stderr:`Internal Imperative Error: Root command help error occurred: ${rejectedError.message}\n`})); - } - expect(writeMock).toHaveBeenCalled(); - expect(writeMock).toHaveBeenCalledWith(DaemonRequest.create({stderr: "Internal Imperative Error: Root command help error occurred: Test error\n"})); - }); - - it("should handle daemonStream when it's undefined", async () => { - ImperativeConfig.instance.daemonContext = {} as any; - - const rejectedError = new Error("Test error"); - - const stderrWriteSpy = jest.spyOn(process.stderr, "write").mockImplementation(); - - const daemonStream = ImperativeConfig.instance.daemonContext?.stream; - if (!daemonStream) { - process.stderr.write("Internal Imperative Error: Root command help error occurred: " + rejectedError.message + "\n"); - } + await handler({_: []}); - expect(stderrWriteSpy).toHaveBeenCalledWith("Internal Imperative Error: Root command help error occurred: Test error\n"); + expect(invokeSpy).toHaveBeenCalled(); }); it("should build a failure message", () => { From c5f22c338c3a02719e7fe155f2be737c6db30383 Mon Sep 17 00:00:00 2001 From: Pujal Date: Wed, 12 Mar 2025 12:44:58 -0400 Subject: [PATCH 09/12] updated changelog Signed-off-by: Pujal --- packages/imperative/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/imperative/CHANGELOG.md b/packages/imperative/CHANGELOG.md index ecd2cc2d2d..1296bcf5df 100644 --- a/packages/imperative/CHANGELOG.md +++ b/packages/imperative/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to the Imperative package will be documented in this file. ## Recent Changes -- BugFix: When in daemon mode, the user would not see imperative initialization errors, but now the errors are passed back to the user terminal window. +- BugFix: When in daemon mode, the user would not see Imperative initialization errors, but now the errors are passed back to the user's terminal window. [#1875] (https://github.com/zowe/zowe-cli/issues/1875). ## `8.14.1` - BugFix: Fixed help text example section's wrapping issue where the first line of the description is wrapped differently than the rest of the lines. [#1945] (https://github.com/zowe/zowe-cli/issues/1945). From 9b82f08c45efd0d1f331b8ad87db977c29babf99 Mon Sep 17 00:00:00 2001 From: Pujal Gandhi <71276682+pujal0909@users.noreply.github.com> Date: Wed, 12 Mar 2025 15:03:34 -0400 Subject: [PATCH 10/12] Update packages/imperative/CHANGELOG.md Co-authored-by: Fernando Rijo Cedeno <37381190+zFernand0@users.noreply.github.com> Signed-off-by: Pujal Gandhi <71276682+pujal0909@users.noreply.github.com> --- packages/imperative/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/imperative/CHANGELOG.md b/packages/imperative/CHANGELOG.md index 68906de642..5d1c153833 100644 --- a/packages/imperative/CHANGELOG.md +++ b/packages/imperative/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to the Imperative package will be documented in this file. ## Recent Changes -- BugFix: When in daemon mode, the user would not see Imperative initialization errors, but now the errors are passed back to the user's terminal window. [#1875] (https://github.com/zowe/zowe-cli/issues/1875). +- BugFix: When in daemon mode, the user would not see Imperative initialization errors, but now the errors are passed back to the user's terminal window. [#1875](https://github.com/zowe/zowe-cli/issues/1875). - Enhancement: Added a favicon to the Web Help that displays in browser tabs. [#801] (https://github.com/zowe/zowe-cli/issues/801) ## `8.15.1` From d87a316e511814bd18abbedf359d755e2d93c630 Mon Sep 17 00:00:00 2001 From: Pujal Date: Wed, 12 Mar 2025 15:08:13 -0400 Subject: [PATCH 11/12] removed unused import and added unit test Signed-off-by: Pujal --- .../yargs/YargsConfigurer.unit.test.ts | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/imperative/src/cmd/__tests__/yargs/YargsConfigurer.unit.test.ts b/packages/imperative/src/cmd/__tests__/yargs/YargsConfigurer.unit.test.ts index 5fa9949b6f..1d81e877f4 100644 --- a/packages/imperative/src/cmd/__tests__/yargs/YargsConfigurer.unit.test.ts +++ b/packages/imperative/src/cmd/__tests__/yargs/YargsConfigurer.unit.test.ts @@ -10,9 +10,8 @@ */ import { CommandProcessor } from "../../src/CommandProcessor"; -import { Constants, DaemonRequest, ImperativeConfig } from "../../.."; +import { Constants, ImperativeConfig } from "../../.."; import { YargsConfigurer } from "../../src/yargs/YargsConfigurer"; -import { mock } from "node:test"; jest.mock("yargs"); jest.mock("../../src/CommandProcessor"); @@ -48,6 +47,29 @@ describe("YargsConfigurer tests", () => { expect(invokeSpy).toHaveBeenCalled(); }); + it('should write to daemonStream if available', async () => { + const rejectedError = new Error("Test error"); + const invokeSpy = jest.spyOn(CommandProcessor.prototype, "invoke").mockRejectedValue(rejectedError); + const mockedYargs = require("yargs"); + jest.spyOn(ImperativeConfig, "instance", "get").mockReturnValue({ + envVariablePrefix: "MOCK_PREFIX", + cliHome: "/mock/home", + daemonContext: { + stream: undefined + } + } as any); + + const config = new YargsConfigurer({ name: "any", description: "any", type: "command", children: []}, + mockedYargs, undefined as any, { getHelpGenerator: jest.fn() }, undefined as any, "fake", "fake", "ZOWE", "fake"); + config.configure(); + const handler = mockedYargs.command.mock.calls[0][0].handler; + + await handler({_: []}); + + expect(invokeSpy).toHaveBeenCalled(); + }); + + it("should build a failure message", () => { const config = new YargsConfigurer({ name: "any", description: "any", type: "command", children: []}, From e101f30e7e0d730bfd4b823c979a2da86f232217 Mon Sep 17 00:00:00 2001 From: Pujal Date: Wed, 12 Mar 2025 15:16:51 -0400 Subject: [PATCH 12/12] fixed lint errors Signed-off-by: Pujal --- .../src/cmd/__tests__/yargs/YargsConfigurer.unit.test.ts | 2 +- packages/imperative/src/cmd/src/yargs/YargsConfigurer.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/imperative/src/cmd/__tests__/yargs/YargsConfigurer.unit.test.ts b/packages/imperative/src/cmd/__tests__/yargs/YargsConfigurer.unit.test.ts index 1d81e877f4..d66cbd935a 100644 --- a/packages/imperative/src/cmd/__tests__/yargs/YargsConfigurer.unit.test.ts +++ b/packages/imperative/src/cmd/__tests__/yargs/YargsConfigurer.unit.test.ts @@ -47,7 +47,7 @@ describe("YargsConfigurer tests", () => { expect(invokeSpy).toHaveBeenCalled(); }); - it('should write to daemonStream if available', async () => { + it('should not write to daemonStream if not available', async () => { const rejectedError = new Error("Test error"); const invokeSpy = jest.spyOn(CommandProcessor.prototype, "invoke").mockRejectedValue(rejectedError); const mockedYargs = require("yargs"); diff --git a/packages/imperative/src/cmd/src/yargs/YargsConfigurer.ts b/packages/imperative/src/cmd/src/yargs/YargsConfigurer.ts index 2473b33ff9..7fe9c9900e 100644 --- a/packages/imperative/src/cmd/src/yargs/YargsConfigurer.ts +++ b/packages/imperative/src/cmd/src/yargs/YargsConfigurer.ts @@ -88,7 +88,8 @@ export class YargsConfigurer { .catch((rejected) => { const daemonStream = ImperativeConfig.instance.daemonContext?.stream; if(daemonStream) { - daemonStream.write(DaemonRequest.create({ stderr:`Internal Imperative Error: Root command help error occurred: ${rejected.message}\n`})); + daemonStream.write(DaemonRequest.create({ stderr:`Internal Imperative Error: Root command help ` + + `error occurred: ${rejected.message}\n`})); } else { process.stderr.write("Internal Imperative Error: Root command help error occurred: " + rejected.message + "\n");