From f255eb6cfef5668bb37fab9e20f12c4a30198e43 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Tue, 23 Jan 2024 21:19:12 +0000 Subject: [PATCH 01/29] feat(events): implement initial phase - Writers Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- packages/imperative/src/config/src/Config.ts | 3 + .../src/config/src/api/ConfigLayers.ts | 1 + .../src/config/src/api/ConfigSecure.ts | 2 + packages/imperative/src/events/index.ts | 14 +++ .../src/events/src/ImperativeEvent.ts | 72 ++++++++++++ .../events/src/ImperativeEventConstants.ts | 23 ++++ .../src/events/src/ImperativeEventManager.ts | 106 ++++++++++++++++++ .../events/src/doc/IImperativeEventParms.ts | 31 +++++ .../imperative/src/events/src/doc/index.ts | 12 ++ 9 files changed, 264 insertions(+) create mode 100644 packages/imperative/src/events/index.ts create mode 100644 packages/imperative/src/events/src/ImperativeEvent.ts create mode 100644 packages/imperative/src/events/src/ImperativeEventConstants.ts create mode 100644 packages/imperative/src/events/src/ImperativeEventManager.ts create mode 100644 packages/imperative/src/events/src/doc/IImperativeEventParms.ts create mode 100644 packages/imperative/src/events/src/doc/index.ts diff --git a/packages/imperative/src/config/src/Config.ts b/packages/imperative/src/config/src/Config.ts index 7f9a211590..f3c15d4d47 100644 --- a/packages/imperative/src/config/src/Config.ts +++ b/packages/imperative/src/config/src/Config.ts @@ -31,6 +31,7 @@ import { ConfigUtils } from "./ConfigUtils"; import { IConfigSchemaInfo } from "./doc/IConfigSchema"; import { JsUtils } from "../../utilities/src/JsUtils"; import { IConfigMergeOpts } from "./doc/IConfigMergeOpts"; +import { ImperativeEventManager } from "../../events/src/ImperativeEventManager"; /** * Enum used by Config class to maintain order of config layers @@ -229,6 +230,7 @@ export class Config { this.api.layers.write(currLayer); } } + ImperativeEventManager.writeEvent("onConfigChanged"); } catch (e) { if (e instanceof ImperativeError) { throw e; @@ -512,6 +514,7 @@ export class Config { const schemaInfo = this.getSchemaInfo(); if (schemaObj != null && (schemaInfo.local || schemaInfo.original.startsWith("./"))) { fs.writeFileSync(schemaInfo.resolved, JSONC.stringify(schemaObj, null, ConfigConstants.INDENT)); + ImperativeEventManager.writeEvent("onSchemaChanged"); } } diff --git a/packages/imperative/src/config/src/api/ConfigLayers.ts b/packages/imperative/src/config/src/api/ConfigLayers.ts index 3e90e8337c..7ba098e5b8 100644 --- a/packages/imperative/src/config/src/api/ConfigLayers.ts +++ b/packages/imperative/src/config/src/api/ConfigLayers.ts @@ -18,6 +18,7 @@ import { ConfigConstants } from "../ConfigConstants"; import { IConfigLayer } from "../doc/IConfigLayer"; import { ConfigApi } from "./ConfigApi"; import { IConfig } from "../doc/IConfig"; +import { ImperativeEventManager } from "../../../events/src/ImperativeEventManager"; /** * API Class for manipulating config layers. diff --git a/packages/imperative/src/config/src/api/ConfigSecure.ts b/packages/imperative/src/config/src/api/ConfigSecure.ts index be89ddc39b..b2f8a08409 100644 --- a/packages/imperative/src/config/src/api/ConfigSecure.ts +++ b/packages/imperative/src/config/src/api/ConfigSecure.ts @@ -20,6 +20,7 @@ import { ConfigConstants } from "../ConfigConstants"; import { IConfigProfile } from "../doc/IConfigProfile"; import { CredentialManagerFactory } from "../../../security"; import { ConfigUtils } from "../ConfigUtils"; +import { ImperativeEventManager } from "../../../events/src/ImperativeEventManager"; /** * API Class for manipulating config layers. @@ -129,6 +130,7 @@ export class ConfigSecure extends ConfigApi { */ public async directSave() { await this.mConfig.mVault.save(ConfigConstants.SECURE_ACCT, JSONC.stringify(this.mConfig.mSecure)); + ImperativeEventManager.writeEvent("onVaultChanged"); } // _______________________________________________________________________ diff --git a/packages/imperative/src/events/index.ts b/packages/imperative/src/events/index.ts new file mode 100644 index 0000000000..c762fd8c39 --- /dev/null +++ b/packages/imperative/src/events/index.ts @@ -0,0 +1,14 @@ +/* +* This program and the accompanying materials are made available under the terms of the +* Eclipse Public License v2.0 which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Copyright Contributors to the Zowe Project. +* +*/ + +export * from "./src/doc"; +export * from "./src/ImperativeEvent"; +export * from "./src/ImperativeEventConstants"; diff --git a/packages/imperative/src/events/src/ImperativeEvent.ts b/packages/imperative/src/events/src/ImperativeEvent.ts new file mode 100644 index 0000000000..c385ec5a05 --- /dev/null +++ b/packages/imperative/src/events/src/ImperativeEvent.ts @@ -0,0 +1,72 @@ +/* +* This program and the accompanying materials are made available under the terms of the +* Eclipse Public License v2.0 which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Copyright Contributors to the Zowe Project. +* +*/ + +import { randomUUID } from "crypto"; +import { IImperativeEventParms } from "./doc"; +import { ImperativeEventType } from "./ImperativeEventConstants"; + +/** + * + * @export + * @class ImperativeEvent + */ +export class ImperativeEvent { + /** + * The ID of the event + * @private + * @type {string} + * @memberof ImperativeEvent + */ + private mEventID: string; + + /** + * The application ID that caused this event + * @private + * @type {string} + * @memberof ImperativeEvent + */ + private mAppID: string + + /** + * The time of the event created with new Date().toISOString() (ISO String) + * @private + * @type {string} + * @memberof ImperativeEvent + */ + private mEventTime: string + + /** + * The type of event that occurred + * @private + * @type {string} + * @memberof ImperativeEvent + */ + private mEventType: ImperativeEventType + + constructor(parms: IImperativeEventParms) { + this.mEventTime = new Date().toISOString(); + this.mEventID = randomUUID(); + this.mAppID = parms.appName; + this.mEventType = parms.eventType; + } + + public get eventTime(): string { + return this.mEventTime + } + + public get eventType(): ImperativeEventType { + return this.mEventType; + } + + public get appName(): string { + return this.mAppID; + } +} diff --git a/packages/imperative/src/events/src/ImperativeEventConstants.ts b/packages/imperative/src/events/src/ImperativeEventConstants.ts new file mode 100644 index 0000000000..18e7968d6b --- /dev/null +++ b/packages/imperative/src/events/src/ImperativeEventConstants.ts @@ -0,0 +1,23 @@ +/* +* This program and the accompanying materials are made available under the terms of the +* Eclipse Public License v2.0 which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Copyright Contributors to the Zowe Project. +* +*/ + +export const ImperativeUserEvents = [ + "onVaultChanged" +] as const; +export type ImperativeUserEventType = typeof ImperativeUserEvents[number]; + +export const ImperativeZoweEvents = [ + "onConfigChanged", + "onSchemaChanged" +] as const; +export type ImperativeZoweEventType = typeof ImperativeZoweEvents[number]; + +export type ImperativeEventType = ImperativeUserEventType | ImperativeZoweEventType; diff --git a/packages/imperative/src/events/src/ImperativeEventManager.ts b/packages/imperative/src/events/src/ImperativeEventManager.ts new file mode 100644 index 0000000000..1893059342 --- /dev/null +++ b/packages/imperative/src/events/src/ImperativeEventManager.ts @@ -0,0 +1,106 @@ +/* +* This program and the accompanying materials are made available under the terms of the +* Eclipse Public License v2.0 which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Copyright Contributors to the Zowe Project. +* +*/ + +import { homedir, EOL} from "os"; +import * as fs from "fs"; +import { ImperativeConfig } from "../../utilities/src/ImperativeConfig"; +import { join } from "path"; +import { ImperativeError } from "../../error"; +import { ImperativeEventType, ImperativeUserEvents, ImperativeZoweEvents } from "./ImperativeEventConstants"; +import { ImperativeEvent } from "./ImperativeEvent"; + +export class ImperativeEventManager { + + /** + * ZOWE HOME directory to search for system wide ImperativeEvents like `configChanged` + */ + public static getZoweEventDir(): string { + return join(ImperativeConfig.instance.cliHome, ".events"); + } + + /** + * USER HOME directory to search for user specific ImperativeEvents like `vaultChanged` + */ + public static getUserEventDir(): string { + return join(homedir(), ".zowe", ".events"); + } + + /** + * Check to see if the directory exists, otherwise, create it : ) + * @param directoryPath Zowe or User path where we will write the events + */ + private static ensureEventsDirExists(directoryPath: string) { + try { + if (!fs.existsSync(directoryPath)) { + fs.mkdirSync(directoryPath); + } + } catch (err) { + throw new ImperativeError({ msg: `Unable to create '.events' directory. Path: ${directoryPath}`, causeErrors: err }); + } + } + + /** + * Check to see if the given event is a User event + * @param eventType A string representing the type of event + * @returns True if it is a user event, false otherwise + */ + public static isUserEvent(eventType: string): boolean { + return ImperativeUserEvents.indexOf(eventType as any) >= 0; + } + + /** + * Check to see if the given event is a Zowe event + * @param eventType A string representing the type of event + * @returns True if it is a zowe event, false otherwise + */ + public static isZoweEvent(eventType: string): boolean { + return ImperativeZoweEvents.indexOf(eventType as any) >= 0; + } + + /** + * Simple method to write the events to disk + * @param eventType The type of event to write + * @internal We do not want to make this function accessible to any application developers + */ + public static writeEvent(eventType: ImperativeEventType) { + const theEvent = new ImperativeEvent({ appName: ImperativeConfig.instance.callerPackageJson?.name, eventType }); + + let dir: string; + if (ImperativeEventManager.isUserEvent(eventType)) { + dir = ImperativeEventManager.getUserEventDir(); + } else if (ImperativeEventManager.isZoweEvent(eventType)) { + dir = ImperativeEventManager.getZoweEventDir(); + } else { + throw new ImperativeError({ msg: `Unable to determine the type of event. Event: ${eventType}` }); + } + + ImperativeEventManager.ensureEventsDirExists(dir); + fs.writeFileSync(join(dir, theEvent.eventType), `${EOL}${eventType}${EOL}${theEvent.eventTime}${EOL}${theEvent.appName}${EOL}${EOL}`); + } + + public static registerAction(eventType: ImperativeEventType, callback: (...args: any[]) => any) { + + } + + /** + * Phase 1 + * Implement the writers + * These are the functions that are embedded in every config.save (for `configUpdated`) or secure.save (for vaultUpdated) + * + * Phase 2 + * Implement file watchers in a subscription pattern + * These are the methods that create the nodejs file watchers + * They should have a callback to perform an accion when the event is triggered + * + * Phase 3 + * Investigate how to turn these file watchers into vscode.Dispoable + */ +} diff --git a/packages/imperative/src/events/src/doc/IImperativeEventParms.ts b/packages/imperative/src/events/src/doc/IImperativeEventParms.ts new file mode 100644 index 0000000000..5405af680d --- /dev/null +++ b/packages/imperative/src/events/src/doc/IImperativeEventParms.ts @@ -0,0 +1,31 @@ +/* +* This program and the accompanying materials are made available under the terms of the +* Eclipse Public License v2.0 which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Copyright Contributors to the Zowe Project. +* +*/ +import { ImperativeEventType } from "../ImperativeEventConstants"; + +/** + * Imperative Standard Event + * @export + * @interface IImperativeEventParms + */ +export interface IImperativeEventParms { + /** + * The name of the application to be used to generate a unique ID for the event + * @type {string} + * @memberof IImperativeEventParms + */ + appName: string; + /** + * The type of imperative event that occurred + * @type {ImperativeEventType} + * @memberof IImperativeEventParms + */ + eventType: ImperativeEventType +} diff --git a/packages/imperative/src/events/src/doc/index.ts b/packages/imperative/src/events/src/doc/index.ts new file mode 100644 index 0000000000..9b5a4c8d1f --- /dev/null +++ b/packages/imperative/src/events/src/doc/index.ts @@ -0,0 +1,12 @@ +/* +* This program and the accompanying materials are made available under the terms of the +* Eclipse Public License v2.0 which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Copyright Contributors to the Zowe Project. +* +*/ + +export * from "./IImperativeEventParms"; From b6b056c1c6f09a8703c0772365d240734d4acab6 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Tue, 23 Jan 2024 21:31:22 +0000 Subject: [PATCH 02/29] forgot to export and to strip internal Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- packages/imperative/src/events/index.ts | 1 + packages/imperative/src/index.ts | 1 + packages/imperative/tsconfig.json | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/imperative/src/events/index.ts b/packages/imperative/src/events/index.ts index c762fd8c39..dc02a1f72e 100644 --- a/packages/imperative/src/events/index.ts +++ b/packages/imperative/src/events/index.ts @@ -12,3 +12,4 @@ export * from "./src/doc"; export * from "./src/ImperativeEvent"; export * from "./src/ImperativeEventConstants"; +export * from "./src/ImperativeEventManager"; diff --git a/packages/imperative/src/index.ts b/packages/imperative/src/index.ts index 334969ff98..08493bc401 100644 --- a/packages/imperative/src/index.ts +++ b/packages/imperative/src/index.ts @@ -13,6 +13,7 @@ export * from "./cmd"; export * from "./config"; export * from "./console"; export * from "./constants"; +export * from "./events"; export * from "./error"; export * from "./expect"; export * from "./imperative"; diff --git a/packages/imperative/tsconfig.json b/packages/imperative/tsconfig.json index fdfdb95a1d..511d8c8e52 100644 --- a/packages/imperative/tsconfig.json +++ b/packages/imperative/tsconfig.json @@ -1,7 +1,8 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "outDir": "./lib" + "outDir": "./lib", + "stripInternal": true }, "include": [ "./src" From a1f1b7140780ab44c06931e8dc25677f40ee5e12 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Wed, 24 Jan 2024 17:47:35 +0000 Subject: [PATCH 03/29] Add the ability to write custom events Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- .../src/config/src/api/ConfigLayers.ts | 1 - .../src/events/src/ImperativeEvent.ts | 46 +++---- .../events/src/ImperativeEventConstants.ts | 6 +- .../src/events/src/ImperativeEventManager.ts | 119 +++++++++++------- .../events/src/doc/IImperativeEventParms.ts | 8 +- 5 files changed, 106 insertions(+), 74 deletions(-) diff --git a/packages/imperative/src/config/src/api/ConfigLayers.ts b/packages/imperative/src/config/src/api/ConfigLayers.ts index 7ba098e5b8..3e90e8337c 100644 --- a/packages/imperative/src/config/src/api/ConfigLayers.ts +++ b/packages/imperative/src/config/src/api/ConfigLayers.ts @@ -18,7 +18,6 @@ import { ConfigConstants } from "../ConfigConstants"; import { IConfigLayer } from "../doc/IConfigLayer"; import { ConfigApi } from "./ConfigApi"; import { IConfig } from "../doc/IConfig"; -import { ImperativeEventManager } from "../../../events/src/ImperativeEventManager"; /** * API Class for manipulating config layers. diff --git a/packages/imperative/src/events/src/ImperativeEvent.ts b/packages/imperative/src/events/src/ImperativeEvent.ts index c385ec5a05..d51e2462a6 100644 --- a/packages/imperative/src/events/src/ImperativeEvent.ts +++ b/packages/imperative/src/events/src/ImperativeEvent.ts @@ -19,54 +19,54 @@ import { ImperativeEventType } from "./ImperativeEventConstants"; * @class ImperativeEvent */ export class ImperativeEvent { - /** + /** * The ID of the event * @private * @type {string} * @memberof ImperativeEvent */ - private mEventID: string; + private mEventID: string; - /** + /** * The application ID that caused this event * @private * @type {string} * @memberof ImperativeEvent */ - private mAppID: string + private mAppID: string; - /** + /** * The time of the event created with new Date().toISOString() (ISO String) * @private * @type {string} * @memberof ImperativeEvent */ - private mEventTime: string + private mEventTime: string; - /** + /** * The type of event that occurred * @private * @type {string} * @memberof ImperativeEvent */ - private mEventType: ImperativeEventType + private mEventType: ImperativeEventType | string; - constructor(parms: IImperativeEventParms) { - this.mEventTime = new Date().toISOString(); - this.mEventID = randomUUID(); - this.mAppID = parms.appName; - this.mEventType = parms.eventType; - } + constructor(parms: IImperativeEventParms) { + this.mEventTime = new Date().toISOString(); + this.mEventID = randomUUID(); + this.mAppID = parms.appName; + this.mEventType = parms.eventType; + } - public get eventTime(): string { - return this.mEventTime - } + public get eventTime(): string { + return this.mEventTime; + } - public get eventType(): ImperativeEventType { - return this.mEventType; - } + public get eventType(): ImperativeEventType | string { + return this.mEventType; + } - public get appName(): string { - return this.mAppID; - } + public get appName(): string { + return this.mAppID; + } } diff --git a/packages/imperative/src/events/src/ImperativeEventConstants.ts b/packages/imperative/src/events/src/ImperativeEventConstants.ts index 18e7968d6b..f788893b72 100644 --- a/packages/imperative/src/events/src/ImperativeEventConstants.ts +++ b/packages/imperative/src/events/src/ImperativeEventConstants.ts @@ -10,13 +10,13 @@ */ export const ImperativeUserEvents = [ - "onVaultChanged" + "onVaultChanged" ] as const; export type ImperativeUserEventType = typeof ImperativeUserEvents[number]; export const ImperativeZoweEvents = [ - "onConfigChanged", - "onSchemaChanged" + "onConfigChanged", + "onSchemaChanged" ] as const; export type ImperativeZoweEventType = typeof ImperativeZoweEvents[number]; diff --git a/packages/imperative/src/events/src/ImperativeEventManager.ts b/packages/imperative/src/events/src/ImperativeEventManager.ts index 1893059342..fe48444e39 100644 --- a/packages/imperative/src/events/src/ImperativeEventManager.ts +++ b/packages/imperative/src/events/src/ImperativeEventManager.ts @@ -9,7 +9,7 @@ * */ -import { homedir, EOL} from "os"; +import { homedir, EOL } from "os"; import * as fs from "fs"; import { ImperativeConfig } from "../../utilities/src/ImperativeConfig"; import { join } from "path"; @@ -19,78 +19,111 @@ import { ImperativeEvent } from "./ImperativeEvent"; export class ImperativeEventManager { - /** + /** * ZOWE HOME directory to search for system wide ImperativeEvents like `configChanged` */ - public static getZoweEventDir(): string { - return join(ImperativeConfig.instance.cliHome, ".events"); - } + public static getZoweEventDir(): string { + return join(ImperativeConfig.instance.cliHome, ".events"); + } - /** + /** * USER HOME directory to search for user specific ImperativeEvents like `vaultChanged` */ - public static getUserEventDir(): string { - return join(homedir(), ".zowe", ".events"); - } + public static getUserEventDir(): string { + return join(homedir(), ".zowe", ".events"); + } - /** + /** * Check to see if the directory exists, otherwise, create it : ) * @param directoryPath Zowe or User path where we will write the events */ - private static ensureEventsDirExists(directoryPath: string) { - try { - if (!fs.existsSync(directoryPath)) { - fs.mkdirSync(directoryPath); - } - } catch (err) { - throw new ImperativeError({ msg: `Unable to create '.events' directory. Path: ${directoryPath}`, causeErrors: err }); + private static ensureEventsDirExists(directoryPath: string) { + try { + if (!fs.existsSync(directoryPath)) { + fs.mkdirSync(directoryPath); + } + } catch (err) { + throw new ImperativeError({ msg: `Unable to create '.events' directory. Path: ${directoryPath}`, causeErrors: err }); + } } - } - /** + /** * Check to see if the given event is a User event * @param eventType A string representing the type of event * @returns True if it is a user event, false otherwise */ - public static isUserEvent(eventType: string): boolean { - return ImperativeUserEvents.indexOf(eventType as any) >= 0; - } + public static isUserEvent(eventType: string): boolean { + return ImperativeUserEvents.indexOf(eventType as any) >= 0; + } - /** + /** * Check to see if the given event is a Zowe event * @param eventType A string representing the type of event * @returns True if it is a zowe event, false otherwise */ - public static isZoweEvent(eventType: string): boolean { - return ImperativeZoweEvents.indexOf(eventType as any) >= 0; - } + public static isZoweEvent(eventType: string): boolean { + return ImperativeZoweEvents.indexOf(eventType as any) >= 0; + } - /** + /** + * Check to see if the given event is a Custom event + * @param eventType A string representing the type of event + * @returns True if it is not a zowe or a user event, false otherwise + */ + public static isCustomEvent(eventType: string): boolean { + return !ImperativeEventManager.isUserEvent(eventType) && !ImperativeEventManager.isZoweEvent(eventType); + } + + /** * Simple method to write the events to disk * @param eventType The type of event to write * @internal We do not want to make this function accessible to any application developers */ - public static writeEvent(eventType: ImperativeEventType) { - const theEvent = new ImperativeEvent({ appName: ImperativeConfig.instance.callerPackageJson?.name, eventType }); - - let dir: string; - if (ImperativeEventManager.isUserEvent(eventType)) { - dir = ImperativeEventManager.getUserEventDir(); - } else if (ImperativeEventManager.isZoweEvent(eventType)) { - dir = ImperativeEventManager.getZoweEventDir(); - } else { - throw new ImperativeError({ msg: `Unable to determine the type of event. Event: ${eventType}` }); + public static writeEvent(eventType: ImperativeEventType) { + const theEvent = new ImperativeEvent({ appName: ImperativeConfig.instance.callerPackageJson?.name, eventType }); + + let dir: string; + if (ImperativeEventManager.isUserEvent(eventType)) { + dir = ImperativeEventManager.getUserEventDir(); + } else if (ImperativeEventManager.isZoweEvent(eventType)) { + dir = ImperativeEventManager.getZoweEventDir(); + } else { + throw new ImperativeError({ msg: `Unable to determine the type of event. Event: ${eventType}` }); + } + + ImperativeEventManager.ensureEventsDirExists(dir); + fs.writeFileSync(join(dir, theEvent.eventType), `${EOL}${eventType}${EOL}${theEvent.eventTime}${EOL}${theEvent.appName}${EOL}${EOL}`); } - ImperativeEventManager.ensureEventsDirExists(dir); - fs.writeFileSync(join(dir, theEvent.eventType), `${EOL}${eventType}${EOL}${theEvent.eventTime}${EOL}${theEvent.appName}${EOL}${EOL}`); - } + /** + * Simple method to write the events to disk + * @param eventType The type of event to write + */ + public static writeCustomEvent(eventType: string, isUserSpecific: boolean = false) { + const theEvent = new ImperativeEvent({ appName: ImperativeConfig.instance.callerPackageJson?.name, eventType }); - public static registerAction(eventType: ImperativeEventType, callback: (...args: any[]) => any) { + let dir: string; + if (ImperativeEventManager.isCustomEvent(eventType)) { + if (isUserSpecific) { + dir = ImperativeEventManager.getUserEventDir(); + } else { + dir = ImperativeEventManager.getZoweEventDir(); + } + } else { + throw new ImperativeError({ msg: `Operation not allowed. Event is considered protected. Event: ${eventType}` }); + } - } + ImperativeEventManager.ensureEventsDirExists(dir); + fs.writeFileSync(join(dir, theEvent.eventType), `${EOL}${eventType}${EOL}${theEvent.eventTime}${EOL}${theEvent.appName}${EOL}${EOL}`); + } + + public static registerAction(eventType: ImperativeEventType, callback: (...args: any[]) => any) { + if (eventType) { + callback(); + } + } - /** + /** * Phase 1 * Implement the writers * These are the functions that are embedded in every config.save (for `configUpdated`) or secure.save (for vaultUpdated) diff --git a/packages/imperative/src/events/src/doc/IImperativeEventParms.ts b/packages/imperative/src/events/src/doc/IImperativeEventParms.ts index 5405af680d..692e58098d 100644 --- a/packages/imperative/src/events/src/doc/IImperativeEventParms.ts +++ b/packages/imperative/src/events/src/doc/IImperativeEventParms.ts @@ -16,16 +16,16 @@ import { ImperativeEventType } from "../ImperativeEventConstants"; * @interface IImperativeEventParms */ export interface IImperativeEventParms { - /** + /** * The name of the application to be used to generate a unique ID for the event * @type {string} * @memberof IImperativeEventParms */ - appName: string; - /** + appName: string; + /** * The type of imperative event that occurred * @type {ImperativeEventType} * @memberof IImperativeEventParms */ - eventType: ImperativeEventType + eventType: ImperativeEventType | string } From 81be315bcbd1653dc75285b58079c5e3e4d55ab8 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Thu, 25 Jan 2024 15:15:42 +0000 Subject: [PATCH 04/29] ensure that imperative utilities are initialized Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- .../imperative/src/config/src/ProfileInfo.ts | 15 +-- .../src/events/src/ImperativeEvent.ts | 53 +++++---- .../events/src/ImperativeEventConstants.ts | 3 +- .../src/events/src/ImperativeEventManager.ts | 104 +++++++++++------- .../events/src/doc/IImperativeEventParms.ts | 23 ++-- .../security/src/CredentialManagerOverride.ts | 3 + 6 files changed, 125 insertions(+), 76 deletions(-) diff --git a/packages/imperative/src/config/src/ProfileInfo.ts b/packages/imperative/src/config/src/ProfileInfo.ts index 63509f3ce5..33f4440c73 100644 --- a/packages/imperative/src/config/src/ProfileInfo.ts +++ b/packages/imperative/src/config/src/ProfileInfo.ts @@ -183,7 +183,7 @@ export class ProfileInfo { this.mCredentials = new ProfileCredentials(this, profInfoOpts); // do enough Imperative stuff to let imperative utilities work - this.initImpUtils(); + this.mImpLogger = ProfileInfo.initImpUtils(this.mAppName); } /** @@ -1133,26 +1133,27 @@ export class ProfileInfo { /** * Perform a rudimentary initialization of some Imperative utilities. * We must do this because VSCode apps do not typically call imperative.init. + * @internal */ - private initImpUtils() { + public static initImpUtils(appName: string) { // create a rudimentary ImperativeConfig if it has not been initialized if (ImperativeConfig.instance.loadedConfig == null) { let homeDir: string = null; - const envVarPrefix = this.mAppName.toUpperCase(); + const envVarPrefix = appName.toUpperCase(); const envVarNm = envVarPrefix + EnvironmentalVariableSettings.CLI_HOME_SUFFIX; if (process.env[envVarNm] === undefined) { // use OS home directory - homeDir = path.join(os.homedir(), "." + this.mAppName.toLowerCase()); + homeDir = path.join(os.homedir(), "." + appName.toLowerCase()); } else { // use the available environment variable homeDir = path.normalize(process.env[envVarNm]); } ImperativeConfig.instance.loadedConfig = { - name: this.mAppName, + name: appName, defaultHome: homeDir, envVariablePrefix: envVarPrefix }; - ImperativeConfig.instance.rootCommandName = this.mAppName; + ImperativeConfig.instance.rootCommandName = appName; } // initialize logging @@ -1162,7 +1163,7 @@ export class ProfileInfo { ); Logger.initLogger(loggingConfig); } - this.mImpLogger = Logger.getImperativeLogger(); + return Logger.getImperativeLogger(); } /** diff --git a/packages/imperative/src/events/src/ImperativeEvent.ts b/packages/imperative/src/events/src/ImperativeEvent.ts index d51e2462a6..af69bbd17c 100644 --- a/packages/imperative/src/events/src/ImperativeEvent.ts +++ b/packages/imperative/src/events/src/ImperativeEvent.ts @@ -20,42 +20,51 @@ import { ImperativeEventType } from "./ImperativeEventConstants"; */ export class ImperativeEvent { /** - * The ID of the event - * @private - * @type {string} - * @memberof ImperativeEvent - */ + * The ID of the event + * @private + * @type {string} + * @memberof ImperativeEvent + */ private mEventID: string; /** - * The application ID that caused this event - * @private - * @type {string} - * @memberof ImperativeEvent - */ + * The application ID that caused this event + * @private + * @type {string} + * @memberof ImperativeEvent + */ private mAppID: string; /** - * The time of the event created with new Date().toISOString() (ISO String) - * @private - * @type {string} - * @memberof ImperativeEvent - */ + * The time of the event created with new Date().toISOString() (ISO String) + * @private + * @type {string} + * @memberof ImperativeEvent + */ private mEventTime: string; /** - * The type of event that occurred - * @private - * @type {string} - * @memberof ImperativeEvent - */ + * The type of event that occurred + * @private + * @type {string} + * @memberof ImperativeEvent + */ private mEventType: ImperativeEventType | string; + /** + * toString overload to be called automatically on string concatenation + * @returns string representation of the imperative event + */ + public toString = () : string => { + return `Type: ${this.eventType} \t| Time: ${this.eventTime} \t| App: ${this.appName} \t| ID: ${this.eventId}`; + }; + constructor(parms: IImperativeEventParms) { this.mEventTime = new Date().toISOString(); this.mEventID = randomUUID(); this.mAppID = parms.appName; this.mEventType = parms.eventType; + parms.logger.debug("ImperativeEvent: " + this); } public get eventTime(): string { @@ -69,4 +78,8 @@ export class ImperativeEvent { public get appName(): string { return this.mAppID; } + + public get eventId() : string { + return this.mEventID; + } } diff --git a/packages/imperative/src/events/src/ImperativeEventConstants.ts b/packages/imperative/src/events/src/ImperativeEventConstants.ts index f788893b72..4a6d873d43 100644 --- a/packages/imperative/src/events/src/ImperativeEventConstants.ts +++ b/packages/imperative/src/events/src/ImperativeEventConstants.ts @@ -16,7 +16,8 @@ export type ImperativeUserEventType = typeof ImperativeUserEvents[number]; export const ImperativeZoweEvents = [ "onConfigChanged", - "onSchemaChanged" + "onSchemaChanged", + "onCredentialManagerChanged" ] as const; export type ImperativeZoweEventType = typeof ImperativeZoweEvents[number]; diff --git a/packages/imperative/src/events/src/ImperativeEventManager.ts b/packages/imperative/src/events/src/ImperativeEventManager.ts index fe48444e39..420e58487d 100644 --- a/packages/imperative/src/events/src/ImperativeEventManager.ts +++ b/packages/imperative/src/events/src/ImperativeEventManager.ts @@ -16,27 +16,30 @@ import { join } from "path"; import { ImperativeError } from "../../error"; import { ImperativeEventType, ImperativeUserEvents, ImperativeZoweEvents } from "./ImperativeEventConstants"; import { ImperativeEvent } from "./ImperativeEvent"; +import { Logger } from "../../logger"; +import { ProfileInfo } from "../../config"; +import { LoggerManager } from "../../logger/src/LoggerManager"; export class ImperativeEventManager { /** - * ZOWE HOME directory to search for system wide ImperativeEvents like `configChanged` - */ + * ZOWE HOME directory to search for system wide ImperativeEvents like `configChanged` + */ public static getZoweEventDir(): string { return join(ImperativeConfig.instance.cliHome, ".events"); } /** - * USER HOME directory to search for user specific ImperativeEvents like `vaultChanged` - */ + * USER HOME directory to search for user specific ImperativeEvents like `vaultChanged` + */ public static getUserEventDir(): string { return join(homedir(), ".zowe", ".events"); } /** - * Check to see if the directory exists, otherwise, create it : ) - * @param directoryPath Zowe or User path where we will write the events - */ + * Check to see if the directory exists, otherwise, create it : ) + * @param directoryPath Zowe or User path where we will write the events + */ private static ensureEventsDirExists(directoryPath: string) { try { if (!fs.existsSync(directoryPath)) { @@ -48,39 +51,59 @@ export class ImperativeEventManager { } /** - * Check to see if the given event is a User event - * @param eventType A string representing the type of event - * @returns True if it is a user event, false otherwise - */ + * Helper method to initialize the event + * @param eventType The type of event to initialize + * @returns The initialized ImperativeEvent + */ + private static initializeEvent(eventType: string): ImperativeEvent { + let logger: Logger; + if (ImperativeConfig.instance.loadedConfig == null || LoggerManager.instance.isLoggerInit === false) { + logger = ProfileInfo.initImpUtils("zowe"); + } + logger = logger ?? Logger.getImperativeLogger(); + const appName = ImperativeConfig.instance.callerPackageJson?.name; + if (logger == null || appName == null) { + throw new ImperativeError({ + msg: `Unable to initialize the Imperative utilities to emit this event. Event: ${eventType} \t| App: ${appName}` + }); + } + return new ImperativeEvent({ appName, eventType, logger }); + } + + /** + * Check to see if the given event is a User event + * @param eventType A string representing the type of event + * @returns True if it is a user event, false otherwise + */ public static isUserEvent(eventType: string): boolean { return ImperativeUserEvents.indexOf(eventType as any) >= 0; } /** - * Check to see if the given event is a Zowe event - * @param eventType A string representing the type of event - * @returns True if it is a zowe event, false otherwise - */ + * Check to see if the given event is a Zowe event + * @param eventType A string representing the type of event + * @returns True if it is a zowe event, false otherwise + */ public static isZoweEvent(eventType: string): boolean { return ImperativeZoweEvents.indexOf(eventType as any) >= 0; } /** - * Check to see if the given event is a Custom event - * @param eventType A string representing the type of event - * @returns True if it is not a zowe or a user event, false otherwise - */ + * Check to see if the given event is a Custom event + * @param eventType A string representing the type of event + * @returns True if it is not a zowe or a user event, false otherwise + */ public static isCustomEvent(eventType: string): boolean { return !ImperativeEventManager.isUserEvent(eventType) && !ImperativeEventManager.isZoweEvent(eventType); } /** - * Simple method to write the events to disk - * @param eventType The type of event to write - * @internal We do not want to make this function accessible to any application developers - */ + * Simple method to write the events to disk + * @param eventType The type of event to write + * @internal We do not want to make this function accessible to any application developers + */ public static writeEvent(eventType: ImperativeEventType) { - const theEvent = new ImperativeEvent({ appName: ImperativeConfig.instance.callerPackageJson?.name, eventType }); + const theEvent = ImperativeEventManager.initializeEvent(eventType); let dir: string; if (ImperativeEventManager.isUserEvent(eventType)) { @@ -96,11 +119,11 @@ export class ImperativeEventManager { } /** - * Simple method to write the events to disk - * @param eventType The type of event to write - */ + * Simple method to write the events to disk + * @param eventType The type of event to write + */ public static writeCustomEvent(eventType: string, isUserSpecific: boolean = false) { - const theEvent = new ImperativeEvent({ appName: ImperativeConfig.instance.callerPackageJson?.name, eventType }); + const theEvent = ImperativeEventManager.initializeEvent(eventType); let dir: string; if (ImperativeEventManager.isCustomEvent(eventType)) { @@ -124,16 +147,17 @@ export class ImperativeEventManager { } /** - * Phase 1 - * Implement the writers - * These are the functions that are embedded in every config.save (for `configUpdated`) or secure.save (for vaultUpdated) - * - * Phase 2 - * Implement file watchers in a subscription pattern - * These are the methods that create the nodejs file watchers - * They should have a callback to perform an accion when the event is triggered - * - * Phase 3 - * Investigate how to turn these file watchers into vscode.Dispoable - */ + * Phase 1 + * Implement the writers + * These are the functions that are embedded in every config.save, secure.save, or other methods + * + * Phase 2 + * Implement file watchers in a subscription pattern + * These are the methods that create the nodejs file watchers + * They should have a callback to perform an action when the event is triggered + * + * Phase 3 + * Investigate how to turn these file watchers into vscode.Disposable + * + */ } diff --git a/packages/imperative/src/events/src/doc/IImperativeEventParms.ts b/packages/imperative/src/events/src/doc/IImperativeEventParms.ts index 692e58098d..49ef6875c4 100644 --- a/packages/imperative/src/events/src/doc/IImperativeEventParms.ts +++ b/packages/imperative/src/events/src/doc/IImperativeEventParms.ts @@ -8,6 +8,7 @@ * Copyright Contributors to the Zowe Project. * */ +import { Logger } from "../../../logger"; import { ImperativeEventType } from "../ImperativeEventConstants"; /** @@ -17,15 +18,21 @@ import { ImperativeEventType } from "../ImperativeEventConstants"; */ export interface IImperativeEventParms { /** - * The name of the application to be used to generate a unique ID for the event - * @type {string} - * @memberof IImperativeEventParms - */ + * The name of the application to be used to generate a unique ID for the event + * @type {string} + * @memberof IImperativeEventParms + */ appName: string; /** - * The type of imperative event that occurred - * @type {ImperativeEventType} - * @memberof IImperativeEventParms - */ + * The type of imperative event that occurred + * @type {ImperativeEventType} + * @memberof IImperativeEventParms + */ eventType: ImperativeEventType | string + /** + * The logger to use when logging the imperative event that occurred + * @type {Logger} + * @memberof IImperativeEventParms + */ + logger: Logger; } diff --git a/packages/imperative/src/security/src/CredentialManagerOverride.ts b/packages/imperative/src/security/src/CredentialManagerOverride.ts index 60459512be..9977f9a85f 100644 --- a/packages/imperative/src/security/src/CredentialManagerOverride.ts +++ b/packages/imperative/src/security/src/CredentialManagerOverride.ts @@ -16,6 +16,7 @@ import { ICredentialManagerNameMap } from "./doc/ICredentialManagerNameMap"; import { ImperativeConfig } from "../../utilities"; import { ImperativeError } from "../../error"; import { ISettingsFile } from "../../settings/src/doc/ISettingsFile"; +import { ImperativeEventManager } from "../../events"; /** * This class provides access to the known set of credential manager overrides @@ -132,6 +133,7 @@ export class CredentialManagerOverride { settings.json.overrides.CredentialManager = newCredMgrName; try { writeJsonSync(settings.fileName, settings.json, {spaces: 2}); + ImperativeEventManager.writeEvent("onCredentialManagerChanged"); } catch (error) { throw new ImperativeError({ msg: "Unable to write settings file = " + settings.fileName + @@ -186,6 +188,7 @@ export class CredentialManagerOverride { settings.json.overrides.CredentialManager = this.DEFAULT_CRED_MGR_NAME; try { writeJsonSync(settings.fileName, settings.json, {spaces: 2}); + ImperativeEventManager.writeEvent("onCredentialManagerChanged"); } catch (error) { throw new ImperativeError({ msg: "Unable to write settings file = " + settings.fileName + From b6503a4178f4fefd9adfc6806c1602d072773976 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Fri, 26 Jan 2024 16:21:36 +0000 Subject: [PATCH 05/29] Refactor: changed class names, event names, event format to JSON Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- packages/imperative/src/config/src/Config.ts | 6 +- .../src/config/src/api/ConfigSecure.ts | 4 +- packages/imperative/src/events/index.ts | 2 +- .../src/events/src/ImperativeEvent.ts | 25 +- .../src/events/src/ImperativeEventEmitter.ts | 240 ++++++++++++++++++ .../src/events/src/ImperativeEventManager.ts | 163 ------------ .../events/src/doc/IImperativeEventJson.ts | 42 +++ .../src/doc/IImperativeRegisteredAction.ts | 23 ++ .../imperative/src/events/src/doc/index.ts | 2 + .../security/src/CredentialManagerOverride.ts | 6 +- 10 files changed, 339 insertions(+), 174 deletions(-) create mode 100644 packages/imperative/src/events/src/ImperativeEventEmitter.ts delete mode 100644 packages/imperative/src/events/src/ImperativeEventManager.ts create mode 100644 packages/imperative/src/events/src/doc/IImperativeEventJson.ts create mode 100644 packages/imperative/src/events/src/doc/IImperativeRegisteredAction.ts diff --git a/packages/imperative/src/config/src/Config.ts b/packages/imperative/src/config/src/Config.ts index f3c15d4d47..a607531d72 100644 --- a/packages/imperative/src/config/src/Config.ts +++ b/packages/imperative/src/config/src/Config.ts @@ -31,7 +31,7 @@ import { ConfigUtils } from "./ConfigUtils"; import { IConfigSchemaInfo } from "./doc/IConfigSchema"; import { JsUtils } from "../../utilities/src/JsUtils"; import { IConfigMergeOpts } from "./doc/IConfigMergeOpts"; -import { ImperativeEventManager } from "../../events/src/ImperativeEventManager"; +import { ImperativeEventEmitter } from "../../events/src/ImperativeEventEmitter"; /** * Enum used by Config class to maintain order of config layers @@ -230,7 +230,7 @@ export class Config { this.api.layers.write(currLayer); } } - ImperativeEventManager.writeEvent("onConfigChanged"); + ImperativeEventEmitter.emitEvent("onConfigChanged"); } catch (e) { if (e instanceof ImperativeError) { throw e; @@ -514,7 +514,7 @@ export class Config { const schemaInfo = this.getSchemaInfo(); if (schemaObj != null && (schemaInfo.local || schemaInfo.original.startsWith("./"))) { fs.writeFileSync(schemaInfo.resolved, JSONC.stringify(schemaObj, null, ConfigConstants.INDENT)); - ImperativeEventManager.writeEvent("onSchemaChanged"); + ImperativeEventEmitter.emitEvent("onSchemaChanged"); } } diff --git a/packages/imperative/src/config/src/api/ConfigSecure.ts b/packages/imperative/src/config/src/api/ConfigSecure.ts index b2f8a08409..6195c7c856 100644 --- a/packages/imperative/src/config/src/api/ConfigSecure.ts +++ b/packages/imperative/src/config/src/api/ConfigSecure.ts @@ -20,7 +20,7 @@ import { ConfigConstants } from "../ConfigConstants"; import { IConfigProfile } from "../doc/IConfigProfile"; import { CredentialManagerFactory } from "../../../security"; import { ConfigUtils } from "../ConfigUtils"; -import { ImperativeEventManager } from "../../../events/src/ImperativeEventManager"; +import { ImperativeEventEmitter } from "../../../events/src/ImperativeEventEmitter"; /** * API Class for manipulating config layers. @@ -130,7 +130,7 @@ export class ConfigSecure extends ConfigApi { */ public async directSave() { await this.mConfig.mVault.save(ConfigConstants.SECURE_ACCT, JSONC.stringify(this.mConfig.mSecure)); - ImperativeEventManager.writeEvent("onVaultChanged"); + ImperativeEventEmitter.emitEvent("onVaultChanged"); } // _______________________________________________________________________ diff --git a/packages/imperative/src/events/index.ts b/packages/imperative/src/events/index.ts index dc02a1f72e..8da687cf65 100644 --- a/packages/imperative/src/events/index.ts +++ b/packages/imperative/src/events/index.ts @@ -12,4 +12,4 @@ export * from "./src/doc"; export * from "./src/ImperativeEvent"; export * from "./src/ImperativeEventConstants"; -export * from "./src/ImperativeEventManager"; +export * from "./src/ImperativeEventEmitter"; diff --git a/packages/imperative/src/events/src/ImperativeEvent.ts b/packages/imperative/src/events/src/ImperativeEvent.ts index af69bbd17c..e3eaee0d54 100644 --- a/packages/imperative/src/events/src/ImperativeEvent.ts +++ b/packages/imperative/src/events/src/ImperativeEvent.ts @@ -10,7 +10,7 @@ */ import { randomUUID } from "crypto"; -import { IImperativeEventParms } from "./doc"; +import { IImperativeEventJson, IImperativeEventParms } from "./doc"; import { ImperativeEventType } from "./ImperativeEventConstants"; /** @@ -51,14 +51,35 @@ export class ImperativeEvent { */ private mEventType: ImperativeEventType | string; + /** + * Indicator of user-specific (if true) or shared (if false) events + * The ImperativeEventEmitter is responsible for setting this value on all events + * @default false We assume that all events are shared unless the ImperativeEventEmitter says otherwise + */ + public isUserSpecific: boolean = false; + /** * toString overload to be called automatically on string concatenation * @returns string representation of the imperative event */ - public toString = () : string => { + public toString = (): string => { return `Type: ${this.eventType} \t| Time: ${this.eventTime} \t| App: ${this.appName} \t| ID: ${this.eventId}`; }; + /** + * toJson helper method to be called for emitting or logging imperative events + * @returns JSON representation of the imperative event + */ + public toJson = (): IImperativeEventJson => { + return { + time: this.eventTime, + type: this.eventType, + source: this.appName, + id: this.eventId, + user: this.isUserSpecific, + }; + }; + constructor(parms: IImperativeEventParms) { this.mEventTime = new Date().toISOString(); this.mEventID = randomUUID(); diff --git a/packages/imperative/src/events/src/ImperativeEventEmitter.ts b/packages/imperative/src/events/src/ImperativeEventEmitter.ts new file mode 100644 index 0000000000..735a2cb485 --- /dev/null +++ b/packages/imperative/src/events/src/ImperativeEventEmitter.ts @@ -0,0 +1,240 @@ +/* +* This program and the accompanying materials are made available under the terms of the +* Eclipse Public License v2.0 which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Copyright Contributors to the Zowe Project. +* +*/ + +import { homedir } from "os"; +import * as fs from "fs"; +import { ImperativeConfig } from "../../utilities/src/ImperativeConfig"; +import { join } from "path"; +import { ImperativeError } from "../../error"; +import { ImperativeEventType, ImperativeUserEvents, ImperativeZoweEvents as ImperativeSharedEvents } from "./ImperativeEventConstants"; +import { ImperativeEvent } from "./ImperativeEvent"; +import { Logger } from "../../logger"; +import { ProfileInfo } from "../../config"; +import { LoggerManager } from "../../logger/src/LoggerManager"; +import { IImperativeRegisteredAction } from "./doc/IImperativeRegisteredAction"; + +export class ImperativeEventEmitter { + /** + * ZOWE HOME directory to search for system wide ImperativeEvents like `configChanged` + */ + public static getSharedEventDir(): string { + return join(ImperativeConfig.instance.cliHome, ".events"); + } + + /** + * USER HOME directory to search for user specific ImperativeEvents like `vaultChanged` + */ + public static getUserEventDir(): string { + return join(homedir(), ".zowe", ".events"); + } + + /** + * Check to see if the directory exists, otherwise, create it : ) + * @param directoryPath Zowe or User path where we will write the events + */ + private static ensureEventsDirExists(directoryPath: string) { + try { + if (!fs.existsSync(directoryPath)) { + fs.mkdirSync(directoryPath); + } + } catch (err) { + throw new ImperativeError({ msg: `Unable to create '.events' directory. Path: ${directoryPath}`, causeErrors: err }); + } + } + + /** + * Helper method to initialize the event + * @param eventType The type of event to initialize + * @returns The initialized ImperativeEvent + */ + private static initializeEvent(eventType: string): ImperativeEvent { + if (ImperativeConfig.instance.loadedConfig == null || LoggerManager.instance.isLoggerInit === false) { + ProfileInfo.initImpUtils("zowe"); + } + + let appName = ImperativeConfig.instance.loadedConfig?.name; + const logger = Logger.getImperativeLogger(); + + try { + appName = ImperativeConfig.instance.callerPackageJson.name; + } catch (e) { + logger.error(`Unable to get the source from the callerPackageJson. \n${e}`); + } + + if (appName == null) { + throw new ImperativeError({ + msg: `Unable to initialize the Imperative utilities to emit this event. Event: ${eventType} \t| App: ${appName}` + }); + } + return new ImperativeEvent({ appName, eventType, logger }); + } + + /** + * Check to see if the given event is a User event + * @param eventType A string representing the type of event + * @returns True if it is a user event, false otherwise + */ + public static isUserEvent(eventType: string): boolean { + return !!ImperativeUserEvents.find((e) => e === eventType); + } + + /** + * Check to see if the given event is a shared event + * @param eventType A string representing the type of event + * @returns True if it is a shared event, false otherwise + */ + public static isSharedEvent(eventType: string): boolean { + return !!ImperativeSharedEvents.find((e) => e === eventType); + } + + /** + * Check to see if the given event is a Custom event + * @param eventType A string representing the type of event + * @returns True if it is not a zowe or a user event, false otherwise + */ + public static isCustomEvent(eventType: string): boolean { + return !ImperativeEventEmitter.isUserEvent(eventType) && !ImperativeEventEmitter.isSharedEvent(eventType); + } + + /** + * Simple method to write the events to disk + * @param eventType The type of event to write + * @internal We do not want to make this function accessible to any application developers + */ + public static emitEvent(eventType: ImperativeEventType) { + const theEvent = ImperativeEventEmitter.initializeEvent(eventType); + + let dir: string; + if (ImperativeEventEmitter.isUserEvent(eventType)) { + dir = ImperativeEventEmitter.getUserEventDir(); + theEvent.isUserSpecific = true; + } else if (ImperativeEventEmitter.isSharedEvent(eventType)) { + dir = ImperativeEventEmitter.getSharedEventDir(); + } else { + throw new ImperativeError({ msg: `Unable to determine the type of event. Event: ${eventType}` }); + } + + ImperativeEventEmitter.writeEvent(dir, theEvent); + } + + /** + * Simple method to write the events to disk + * @param eventType The type of event to write + */ + public static emitCustomEvent(eventType: string) { //, isUserSpecific: boolean = false) { + const theEvent = ImperativeEventEmitter.initializeEvent(eventType); + + let dir: string; + if (ImperativeEventEmitter.isCustomEvent(eventType)) { + // TODO: Allow for user specific custom events (this applies everywhere we call `isCustomEvent`) + dir = ImperativeEventEmitter.getSharedEventDir(); + } else { + throw new ImperativeError({ msg: `Operation not allowed. Event is considered protected. Event: ${eventType}` }); + } + + ImperativeEventEmitter.writeEvent(dir, theEvent); + } + + /** + * Helper method to write contents out to disk + * @param location directory to write the file (i.e. emit the event) + * @param event the event to be written/emitted + * @internal + */ + private static writeEvent(location: string, event: ImperativeEvent) { + const eventPath = join(location, event.eventType); + const eventJson = { ...event.toJson(), loc: location }; + + ImperativeEventEmitter.ensureEventsDirExists(location); + fs.writeFileSync(eventPath, JSON.stringify(eventJson, null, 2)); + } + + /** + * Method to register your custom actions based on when the given event is emitted + * @param eventType Type of event to register + * @param callback Action to be registered to the given event + */ + public static registerAction(eventType: string, callback: (...args: any[]) => any): IImperativeRegisteredAction { + if (ImperativeConfig.instance.loadedConfig == null || LoggerManager.instance.isLoggerInit === false) { + ProfileInfo.initImpUtils("zowe"); + } + // TODO: Make sure that there is only one watcher per (application:event) combination + /* + private static mInstance: ImperativeEventEmitter; + private mAppName: string; + private logger: Logger; + public static initialize(applicationName: string, logger?: Logger) { + mAppName = applicationName; + logger = logger ?? Logger.getImperativeLogger(); + } + public static get instance(): ImperativeEventEmitter { + if (this.mInstance == null) { + this.mInstance = new ImperativeEventEmitter(); + } + + return this.mInstance; + } + */ + + return ImperativeEventEmitter.nodejsImplementation(eventType, callback); + //return ImperativeEventEmitter.chokidarImplementation(eventType, callback); + } + + private static nodejsImplementation(eventType: string, callback: (...args: any[]) => any): IImperativeRegisteredAction { + const logger = Logger.getImperativeLogger(); + let dir: string; + if (ImperativeEventEmitter.isUserEvent(eventType)) { + dir = ImperativeEventEmitter.getUserEventDir(); + } else if (ImperativeEventEmitter.isSharedEvent(eventType)) { + dir = ImperativeEventEmitter.getSharedEventDir(); + } else if (ImperativeEventEmitter.isCustomEvent(eventType)) { + dir = ImperativeEventEmitter.getSharedEventDir(); + } + + const watcher = fs.watch(join(dir, eventType), (event: "rename" | "change", filename: string) => { + console.log("ImperativeEventEmitter: ", event, filename); + logger.debug(`ImperativeEventEmitter: Event "${event}" emitted: ${eventType}`); + callback(); + }); + + return { close: watcher.close }; + } + + /** + * Implementation with the chokidar package + * @note This has not be tested yet + * @note You may have to install the package locally in order to try this out + */ + private static chokidarImplementation(eventType: ImperativeEventType, callback: (...args: any[]) => any): IImperativeRegisteredAction { + const chokidar = require("chokidar"); + const logger = Logger.getImperativeLogger(); + let dir: string; + if (ImperativeEventEmitter.isUserEvent(eventType)) { + dir = ImperativeEventEmitter.getUserEventDir(); + } else if (ImperativeEventEmitter.isSharedEvent(eventType)) { + dir = ImperativeEventEmitter.getSharedEventDir(); + } else if (ImperativeEventEmitter.isCustomEvent(eventType)) { + dir = ImperativeEventEmitter.getSharedEventDir(); + } + + const watcher = chokidar.watch(join(dir, eventType)); + watcher.on("change", (filePath: string) => { + console.log("ImperativeEventEmitter: ", "change", filePath); + logger.debug(`ImperativeEventEmitter: Event "change" emitted: ${eventType}`); + callback(); + }); + return { + close: () => { + watcher.unwatch(join(dir, eventType)); + } + }; + } +} diff --git a/packages/imperative/src/events/src/ImperativeEventManager.ts b/packages/imperative/src/events/src/ImperativeEventManager.ts deleted file mode 100644 index 420e58487d..0000000000 --- a/packages/imperative/src/events/src/ImperativeEventManager.ts +++ /dev/null @@ -1,163 +0,0 @@ -/* -* This program and the accompanying materials are made available under the terms of the -* Eclipse Public License v2.0 which accompanies this distribution, and is available at -* https://www.eclipse.org/legal/epl-v20.html -* -* SPDX-License-Identifier: EPL-2.0 -* -* Copyright Contributors to the Zowe Project. -* -*/ - -import { homedir, EOL } from "os"; -import * as fs from "fs"; -import { ImperativeConfig } from "../../utilities/src/ImperativeConfig"; -import { join } from "path"; -import { ImperativeError } from "../../error"; -import { ImperativeEventType, ImperativeUserEvents, ImperativeZoweEvents } from "./ImperativeEventConstants"; -import { ImperativeEvent } from "./ImperativeEvent"; -import { Logger } from "../../logger"; -import { ProfileInfo } from "../../config"; -import { LoggerManager } from "../../logger/src/LoggerManager"; - -export class ImperativeEventManager { - - /** - * ZOWE HOME directory to search for system wide ImperativeEvents like `configChanged` - */ - public static getZoweEventDir(): string { - return join(ImperativeConfig.instance.cliHome, ".events"); - } - - /** - * USER HOME directory to search for user specific ImperativeEvents like `vaultChanged` - */ - public static getUserEventDir(): string { - return join(homedir(), ".zowe", ".events"); - } - - /** - * Check to see if the directory exists, otherwise, create it : ) - * @param directoryPath Zowe or User path where we will write the events - */ - private static ensureEventsDirExists(directoryPath: string) { - try { - if (!fs.existsSync(directoryPath)) { - fs.mkdirSync(directoryPath); - } - } catch (err) { - throw new ImperativeError({ msg: `Unable to create '.events' directory. Path: ${directoryPath}`, causeErrors: err }); - } - } - - /** - * Helper method to initialize the event - * @param eventType The type of event to initialize - * @returns The initialized ImperativeEvent - */ - private static initializeEvent(eventType: string): ImperativeEvent { - let logger: Logger; - if (ImperativeConfig.instance.loadedConfig == null || LoggerManager.instance.isLoggerInit === false) { - logger = ProfileInfo.initImpUtils("zowe"); - } - logger = logger ?? Logger.getImperativeLogger(); - const appName = ImperativeConfig.instance.callerPackageJson?.name; - if (logger == null || appName == null) { - throw new ImperativeError({ - msg: `Unable to initialize the Imperative utilities to emit this event. Event: ${eventType} \t| App: ${appName}` - }); - } - return new ImperativeEvent({ appName, eventType, logger }); - } - - /** - * Check to see if the given event is a User event - * @param eventType A string representing the type of event - * @returns True if it is a user event, false otherwise - */ - public static isUserEvent(eventType: string): boolean { - return ImperativeUserEvents.indexOf(eventType as any) >= 0; - } - - /** - * Check to see if the given event is a Zowe event - * @param eventType A string representing the type of event - * @returns True if it is a zowe event, false otherwise - */ - public static isZoweEvent(eventType: string): boolean { - return ImperativeZoweEvents.indexOf(eventType as any) >= 0; - } - - /** - * Check to see if the given event is a Custom event - * @param eventType A string representing the type of event - * @returns True if it is not a zowe or a user event, false otherwise - */ - public static isCustomEvent(eventType: string): boolean { - return !ImperativeEventManager.isUserEvent(eventType) && !ImperativeEventManager.isZoweEvent(eventType); - } - - /** - * Simple method to write the events to disk - * @param eventType The type of event to write - * @internal We do not want to make this function accessible to any application developers - */ - public static writeEvent(eventType: ImperativeEventType) { - const theEvent = ImperativeEventManager.initializeEvent(eventType); - - let dir: string; - if (ImperativeEventManager.isUserEvent(eventType)) { - dir = ImperativeEventManager.getUserEventDir(); - } else if (ImperativeEventManager.isZoweEvent(eventType)) { - dir = ImperativeEventManager.getZoweEventDir(); - } else { - throw new ImperativeError({ msg: `Unable to determine the type of event. Event: ${eventType}` }); - } - - ImperativeEventManager.ensureEventsDirExists(dir); - fs.writeFileSync(join(dir, theEvent.eventType), `${EOL}${eventType}${EOL}${theEvent.eventTime}${EOL}${theEvent.appName}${EOL}${EOL}`); - } - - /** - * Simple method to write the events to disk - * @param eventType The type of event to write - */ - public static writeCustomEvent(eventType: string, isUserSpecific: boolean = false) { - const theEvent = ImperativeEventManager.initializeEvent(eventType); - - let dir: string; - if (ImperativeEventManager.isCustomEvent(eventType)) { - if (isUserSpecific) { - dir = ImperativeEventManager.getUserEventDir(); - } else { - dir = ImperativeEventManager.getZoweEventDir(); - } - } else { - throw new ImperativeError({ msg: `Operation not allowed. Event is considered protected. Event: ${eventType}` }); - } - - ImperativeEventManager.ensureEventsDirExists(dir); - fs.writeFileSync(join(dir, theEvent.eventType), `${EOL}${eventType}${EOL}${theEvent.eventTime}${EOL}${theEvent.appName}${EOL}${EOL}`); - } - - public static registerAction(eventType: ImperativeEventType, callback: (...args: any[]) => any) { - if (eventType) { - callback(); - } - } - - /** - * Phase 1 - * Implement the writers - * These are the functions that are embedded in every config.save, secure.save, or other methods - * - * Phase 2 - * Implement file watchers in a subscription pattern - * These are the methods that create the nodejs file watchers - * They should have a callback to perform an action when the event is triggered - * - * Phase 3 - * Investigate how to turn these file watchers into vscode.Disposable - * - */ -} diff --git a/packages/imperative/src/events/src/doc/IImperativeEventJson.ts b/packages/imperative/src/events/src/doc/IImperativeEventJson.ts new file mode 100644 index 0000000000..6f60d1f82f --- /dev/null +++ b/packages/imperative/src/events/src/doc/IImperativeEventJson.ts @@ -0,0 +1,42 @@ +/* +* This program and the accompanying materials are made available under the terms of the +* Eclipse Public License v2.0 which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Copyright Contributors to the Zowe Project. +* +*/ + +/** + * Imperative Event JSON representation + * @export + * @interface IImperativeEventJson + */ +export interface IImperativeEventJson { + /** + * The time in which the event occurred + */ + time: string; + /** + * The type of event that occurred + */ + type: string; + /** + * The application name that triggered the event + */ + source: string; + /** + * The ID of the event that occurred + */ + id?: string; + /** + * The location in which the event was emitted (User vs Shared) + */ + loc?: string; + /** + * The indicator of user-specific (if true) or shared (if false) events + */ + user?: boolean; +} diff --git a/packages/imperative/src/events/src/doc/IImperativeRegisteredAction.ts b/packages/imperative/src/events/src/doc/IImperativeRegisteredAction.ts new file mode 100644 index 0000000000..3f06b3f821 --- /dev/null +++ b/packages/imperative/src/events/src/doc/IImperativeRegisteredAction.ts @@ -0,0 +1,23 @@ +/* +* This program and the accompanying materials are made available under the terms of the +* Eclipse Public License v2.0 which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Copyright Contributors to the Zowe Project. +* +*/ + +/** + * Imperative Registered Action + * @export + * @interface IImperativeRegisteredAction + */ +export interface IImperativeRegisteredAction { + /** + * The method to dispose of the registered action + * @memberof IImperativeRegisteredAction + */ + close(): void; +} diff --git a/packages/imperative/src/events/src/doc/index.ts b/packages/imperative/src/events/src/doc/index.ts index 9b5a4c8d1f..76e16b6493 100644 --- a/packages/imperative/src/events/src/doc/index.ts +++ b/packages/imperative/src/events/src/doc/index.ts @@ -10,3 +10,5 @@ */ export * from "./IImperativeEventParms"; +export * from "./IImperativeRegisteredAction"; +export * from "./IImperativeEventJson"; diff --git a/packages/imperative/src/security/src/CredentialManagerOverride.ts b/packages/imperative/src/security/src/CredentialManagerOverride.ts index 9977f9a85f..9087048352 100644 --- a/packages/imperative/src/security/src/CredentialManagerOverride.ts +++ b/packages/imperative/src/security/src/CredentialManagerOverride.ts @@ -16,7 +16,7 @@ import { ICredentialManagerNameMap } from "./doc/ICredentialManagerNameMap"; import { ImperativeConfig } from "../../utilities"; import { ImperativeError } from "../../error"; import { ISettingsFile } from "../../settings/src/doc/ISettingsFile"; -import { ImperativeEventManager } from "../../events"; +import { ImperativeEventEmitter } from "../../events"; /** * This class provides access to the known set of credential manager overrides @@ -133,7 +133,7 @@ export class CredentialManagerOverride { settings.json.overrides.CredentialManager = newCredMgrName; try { writeJsonSync(settings.fileName, settings.json, {spaces: 2}); - ImperativeEventManager.writeEvent("onCredentialManagerChanged"); + ImperativeEventEmitter.emitEvent("onCredentialManagerChanged"); } catch (error) { throw new ImperativeError({ msg: "Unable to write settings file = " + settings.fileName + @@ -188,7 +188,7 @@ export class CredentialManagerOverride { settings.json.overrides.CredentialManager = this.DEFAULT_CRED_MGR_NAME; try { writeJsonSync(settings.fileName, settings.json, {spaces: 2}); - ImperativeEventManager.writeEvent("onCredentialManagerChanged"); + ImperativeEventEmitter.emitEvent("onCredentialManagerChanged"); } catch (error) { throw new ImperativeError({ msg: "Unable to write settings file = " + settings.fileName + From 3072c159fde470cd717655cdc1b0295921f720ec Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Wed, 20 Mar 2024 20:33:33 +0000 Subject: [PATCH 06/29] add an idea for singleton approach Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- packages/imperative/src/events/src/ImperativeEventEmitter.ts | 3 +++ .../imperative/src/events/src/doc/IImperativeEventParms.ts | 1 + 2 files changed, 4 insertions(+) diff --git a/packages/imperative/src/events/src/ImperativeEventEmitter.ts b/packages/imperative/src/events/src/ImperativeEventEmitter.ts index 735a2cb485..292ca54d15 100644 --- a/packages/imperative/src/events/src/ImperativeEventEmitter.ts +++ b/packages/imperative/src/events/src/ImperativeEventEmitter.ts @@ -182,6 +182,9 @@ export class ImperativeEventEmitter { return this.mInstance; } + + // (new imperative.ImperativeEventEmitter("onVaultChanged")).instance.registerAction(() => {}) + // The instance should prevent multiple actions from being registered */ return ImperativeEventEmitter.nodejsImplementation(eventType, callback); diff --git a/packages/imperative/src/events/src/doc/IImperativeEventParms.ts b/packages/imperative/src/events/src/doc/IImperativeEventParms.ts index 49ef6875c4..24ecce8530 100644 --- a/packages/imperative/src/events/src/doc/IImperativeEventParms.ts +++ b/packages/imperative/src/events/src/doc/IImperativeEventParms.ts @@ -8,6 +8,7 @@ * Copyright Contributors to the Zowe Project. * */ + import { Logger } from "../../../logger"; import { ImperativeEventType } from "../ImperativeEventConstants"; From 328de2f2c15bb11f59575da3cdb27dd70ba1a491 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Thu, 21 Mar 2024 20:58:51 +0000 Subject: [PATCH 07/29] MVP Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- packages/imperative/src/config/src/Config.ts | 3 -- .../events/src/ImperativeEventConstants.ts | 32 +++++++++++--- .../src/events/src/ImperativeEventEmitter.ts | 43 ++----------------- 3 files changed, 31 insertions(+), 47 deletions(-) diff --git a/packages/imperative/src/config/src/Config.ts b/packages/imperative/src/config/src/Config.ts index 89d8fc3103..57585f9823 100644 --- a/packages/imperative/src/config/src/Config.ts +++ b/packages/imperative/src/config/src/Config.ts @@ -31,7 +31,6 @@ import { ConfigUtils } from "./ConfigUtils"; import { IConfigSchemaInfo } from "./doc/IConfigSchema"; import { JsUtils } from "../../utilities/src/JsUtils"; import { IConfigMergeOpts } from "./doc/IConfigMergeOpts"; -import { ImperativeEventEmitter } from "../../events/src/ImperativeEventEmitter"; /** * Enum used by Config class to maintain order of config layers @@ -230,7 +229,6 @@ export class Config { this.api.layers.write(currLayer); } } - ImperativeEventEmitter.emitEvent("onConfigChanged"); } catch (e) { if (e instanceof ImperativeError) { throw e; @@ -516,7 +514,6 @@ export class Config { const schemaInfo = this.getSchemaInfo(); if (schemaObj != null && (schemaInfo.local || schemaInfo.original.startsWith("./"))) { fs.writeFileSync(schemaInfo.resolved, JSONC.stringify(schemaObj, null, ConfigConstants.INDENT)); - ImperativeEventEmitter.emitEvent("onSchemaChanged"); } } diff --git a/packages/imperative/src/events/src/ImperativeEventConstants.ts b/packages/imperative/src/events/src/ImperativeEventConstants.ts index 4a6d873d43..636e834376 100644 --- a/packages/imperative/src/events/src/ImperativeEventConstants.ts +++ b/packages/imperative/src/events/src/ImperativeEventConstants.ts @@ -14,11 +14,33 @@ export const ImperativeUserEvents = [ ] as const; export type ImperativeUserEventType = typeof ImperativeUserEvents[number]; -export const ImperativeZoweEvents = [ - "onConfigChanged", - "onSchemaChanged", +export const ImperativeSharedEvents = [ "onCredentialManagerChanged" ] as const; -export type ImperativeZoweEventType = typeof ImperativeZoweEvents[number]; +export type ImperativeSharedEventType = typeof ImperativeSharedEvents[number]; -export type ImperativeEventType = ImperativeUserEventType | ImperativeZoweEventType; +export type ImperativeEventType = ImperativeUserEventType | ImperativeSharedEventType; + +/** + * TODO: + * - Implement onGlobalConfigChanged as a shared event + * - Implement project-level config-changed as a shared event + * - These events should have their own directory structure to support multiple projects + * - $ZOWE_CLI_HOME/.zowe/.events/project-id/onConfigChanged + * - Implement onGlobalSchemaChanged as a shared event + * - Implement project-level schema-changed as a shared event + * - These events should have their own directory structure to support multiple projects + * - $ZOWE_CLI_HOME/.zowe/.events/project-id/onSchemaChanged + * + * + * - Implement CustomSharedEvents + * - These events should have their own directory structure to avoid conflicts between apps + * - $ZOWE_CLI_HOME/.zowe/.events// + * - Implement CustomUserEvents + * - These events should have their own directory structure to avoid conflicts between apps + * - ~/.zowe/.events// + * + * + * Edge cases: + * - What if the `path/to/.events` directory gets renamed or moved? (fs.watch stops notifying apps) + */ \ No newline at end of file diff --git a/packages/imperative/src/events/src/ImperativeEventEmitter.ts b/packages/imperative/src/events/src/ImperativeEventEmitter.ts index 292ca54d15..ebb0e335be 100644 --- a/packages/imperative/src/events/src/ImperativeEventEmitter.ts +++ b/packages/imperative/src/events/src/ImperativeEventEmitter.ts @@ -14,7 +14,7 @@ import * as fs from "fs"; import { ImperativeConfig } from "../../utilities/src/ImperativeConfig"; import { join } from "path"; import { ImperativeError } from "../../error"; -import { ImperativeEventType, ImperativeUserEvents, ImperativeZoweEvents as ImperativeSharedEvents } from "./ImperativeEventConstants"; +import { ImperativeEventType, ImperativeUserEvents, ImperativeSharedEvents } from "./ImperativeEventConstants"; import { ImperativeEvent } from "./ImperativeEvent"; import { Logger } from "../../logger"; import { ProfileInfo } from "../../config"; @@ -99,6 +99,7 @@ export class ImperativeEventEmitter { * Check to see if the given event is a Custom event * @param eventType A string representing the type of event * @returns True if it is not a zowe or a user event, false otherwise + * @internal Not implemented in the MVP */ public static isCustomEvent(eventType: string): boolean { return !ImperativeEventEmitter.isUserEvent(eventType) && !ImperativeEventEmitter.isSharedEvent(eventType); @@ -128,6 +129,7 @@ export class ImperativeEventEmitter { /** * Simple method to write the events to disk * @param eventType The type of event to write + * @internal Not implemented in the MVP */ public static emitCustomEvent(eventType: string) { //, isUserSpecific: boolean = false) { const theEvent = ImperativeEventEmitter.initializeEvent(eventType); @@ -147,7 +149,7 @@ export class ImperativeEventEmitter { * Helper method to write contents out to disk * @param location directory to write the file (i.e. emit the event) * @param event the event to be written/emitted - * @internal + * @internal We do not want developers writing events directly, they should use the `emit...` methods */ private static writeEvent(location: string, event: ImperativeEvent) { const eventPath = join(location, event.eventType); @@ -186,12 +188,6 @@ export class ImperativeEventEmitter { // (new imperative.ImperativeEventEmitter("onVaultChanged")).instance.registerAction(() => {}) // The instance should prevent multiple actions from being registered */ - - return ImperativeEventEmitter.nodejsImplementation(eventType, callback); - //return ImperativeEventEmitter.chokidarImplementation(eventType, callback); - } - - private static nodejsImplementation(eventType: string, callback: (...args: any[]) => any): IImperativeRegisteredAction { const logger = Logger.getImperativeLogger(); let dir: string; if (ImperativeEventEmitter.isUserEvent(eventType)) { @@ -203,41 +199,10 @@ export class ImperativeEventEmitter { } const watcher = fs.watch(join(dir, eventType), (event: "rename" | "change", filename: string) => { - console.log("ImperativeEventEmitter: ", event, filename); logger.debug(`ImperativeEventEmitter: Event "${event}" emitted: ${eventType}`); callback(); }); return { close: watcher.close }; } - - /** - * Implementation with the chokidar package - * @note This has not be tested yet - * @note You may have to install the package locally in order to try this out - */ - private static chokidarImplementation(eventType: ImperativeEventType, callback: (...args: any[]) => any): IImperativeRegisteredAction { - const chokidar = require("chokidar"); - const logger = Logger.getImperativeLogger(); - let dir: string; - if (ImperativeEventEmitter.isUserEvent(eventType)) { - dir = ImperativeEventEmitter.getUserEventDir(); - } else if (ImperativeEventEmitter.isSharedEvent(eventType)) { - dir = ImperativeEventEmitter.getSharedEventDir(); - } else if (ImperativeEventEmitter.isCustomEvent(eventType)) { - dir = ImperativeEventEmitter.getSharedEventDir(); - } - - const watcher = chokidar.watch(join(dir, eventType)); - watcher.on("change", (filePath: string) => { - console.log("ImperativeEventEmitter: ", "change", filePath); - logger.debug(`ImperativeEventEmitter: Event "change" emitted: ${eventType}`); - callback(); - }); - return { - close: () => { - watcher.unwatch(join(dir, eventType)); - } - }; - } } From f0f17ed30f688e56c129f519dae738534941aa31 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Wed, 27 Mar 2024 12:48:57 +0000 Subject: [PATCH 08/29] fix: prevent multiple watchers for the same event Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- packages/imperative/src/config/src/Config.ts | 4 + .../src/config/src/api/ConfigSecure.ts | 3 +- .../events/src/ImperativeEventConstants.ts | 25 +-- .../src/events/src/ImperativeEventEmitter.ts | 149 ++++++++++-------- .../src/doc/IImperativeEventEmitterOpts.ts | 26 +++ .../imperative/src/events/src/doc/index.ts | 1 + .../security/src/CredentialManagerOverride.ts | 6 +- 7 files changed, 132 insertions(+), 82 deletions(-) create mode 100644 packages/imperative/src/events/src/doc/IImperativeEventEmitterOpts.ts diff --git a/packages/imperative/src/config/src/Config.ts b/packages/imperative/src/config/src/Config.ts index 57585f9823..decda488ff 100644 --- a/packages/imperative/src/config/src/Config.ts +++ b/packages/imperative/src/config/src/Config.ts @@ -31,6 +31,8 @@ import { ConfigUtils } from "./ConfigUtils"; import { IConfigSchemaInfo } from "./doc/IConfigSchema"; import { JsUtils } from "../../utilities/src/JsUtils"; import { IConfigMergeOpts } from "./doc/IConfigMergeOpts"; +import { ImperativeEventEmitter } from "../../events"; +import { Logger } from "../../logger"; /** * Enum used by Config class to maintain order of config layers @@ -153,6 +155,8 @@ export class Config { myNewConfig.mVault = opts.vault; myNewConfig.mSecure = {}; + ImperativeEventEmitter.initialize(app, { logger:Logger.getAppLogger() }); + // Populate configuration file layers await myNewConfig.reload(opts); diff --git a/packages/imperative/src/config/src/api/ConfigSecure.ts b/packages/imperative/src/config/src/api/ConfigSecure.ts index abb8b7f5e6..85c68de98e 100644 --- a/packages/imperative/src/config/src/api/ConfigSecure.ts +++ b/packages/imperative/src/config/src/api/ConfigSecure.ts @@ -21,6 +21,7 @@ import { IConfigProfile } from "../doc/IConfigProfile"; import { CredentialManagerFactory } from "../../../security"; import { ConfigUtils } from "../ConfigUtils"; import { ImperativeEventEmitter } from "../../../events/src/ImperativeEventEmitter"; +import { ImperativeUserEvents } from "../../../events"; /** * API Class for manipulating config layers. @@ -131,7 +132,7 @@ export class ConfigSecure extends ConfigApi { */ public async directSave() { await this.mConfig.mVault.save(ConfigConstants.SECURE_ACCT, JSONC.stringify(this.mConfig.mSecure)); - ImperativeEventEmitter.emitEvent("onVaultChanged"); + ImperativeEventEmitter.instance.emitEvent(ImperativeUserEvents.ON_VAULT_CHANGED); } // _______________________________________________________________________ diff --git a/packages/imperative/src/events/src/ImperativeEventConstants.ts b/packages/imperative/src/events/src/ImperativeEventConstants.ts index 636e834376..2e861ac653 100644 --- a/packages/imperative/src/events/src/ImperativeEventConstants.ts +++ b/packages/imperative/src/events/src/ImperativeEventConstants.ts @@ -9,17 +9,24 @@ * */ -export const ImperativeUserEvents = [ - "onVaultChanged" -] as const; -export type ImperativeUserEventType = typeof ImperativeUserEvents[number]; +export enum ImperativeUserEvents { + ON_VAULT_CHANGED = "onVaultChanged" +} +export enum ImperativeSharedEvents { + ON_CREDENTIAL_MANAGER_CHANGED = "onCredentialManagerChanged" +} -export const ImperativeSharedEvents = [ - "onCredentialManagerChanged" -] as const; -export type ImperativeSharedEventType = typeof ImperativeSharedEvents[number]; +export type ImperativeEventType = ImperativeUserEvents | ImperativeSharedEvents; -export type ImperativeEventType = ImperativeUserEventType | ImperativeSharedEventType; +// export const ImperativeUserEvents = [ +// "onVaultChanged" +// ] as const; +// export type ImperativeUserEventType = typeof ImperativeUserEvents; +// export const ImperativeSharedEvents = [ +// "onCredentialManagerChanged" +// ] as const; +// export type ImperativeSharedEventType = typeof ImperativeSharedEvents[number]; +// export type ImperativeEventType = ImperativeUserEventType | ImperativeSharedEventType; /** * TODO: diff --git a/packages/imperative/src/events/src/ImperativeEventEmitter.ts b/packages/imperative/src/events/src/ImperativeEventEmitter.ts index ebb0e335be..18fc044a1c 100644 --- a/packages/imperative/src/events/src/ImperativeEventEmitter.ts +++ b/packages/imperative/src/events/src/ImperativeEventEmitter.ts @@ -20,19 +20,41 @@ import { Logger } from "../../logger"; import { ProfileInfo } from "../../config"; import { LoggerManager } from "../../logger/src/LoggerManager"; import { IImperativeRegisteredAction } from "./doc/IImperativeRegisteredAction"; +import { IImperativeEventEmitterOpts } from "./doc/IImperativeEventEmitterOpts"; export class ImperativeEventEmitter { + private static mInstance: ImperativeEventEmitter; + private static initialized = false; + public appName: string; + public logger: Logger; + private subscriptions: Map; + + public static initialize(appName?: string, options?: IImperativeEventEmitterOpts) { + if (this.initialized) { + throw new ImperativeError({msg: "Only one instance of the Imperative Event Emitter is allowed"}); + } + this.initialized = true; + ImperativeEventEmitter.instance.appName = appName; + ImperativeEventEmitter.instance.logger = options.logger ?? Logger.getImperativeLogger(); + } + public static get instance(): ImperativeEventEmitter { + if (this.mInstance == null) { + this.mInstance = new ImperativeEventEmitter(); + } + return this.mInstance; + } + /** * ZOWE HOME directory to search for system wide ImperativeEvents like `configChanged` */ - public static getSharedEventDir(): string { + public getSharedEventDir(): string { return join(ImperativeConfig.instance.cliHome, ".events"); } /** * USER HOME directory to search for user specific ImperativeEvents like `vaultChanged` */ - public static getUserEventDir(): string { + public getUserEventDir(): string { return join(homedir(), ".zowe", ".events"); } @@ -40,7 +62,7 @@ export class ImperativeEventEmitter { * Check to see if the directory exists, otherwise, create it : ) * @param directoryPath Zowe or User path where we will write the events */ - private static ensureEventsDirExists(directoryPath: string) { + private ensureEventsDirExists(directoryPath: string) { try { if (!fs.existsSync(directoryPath)) { fs.mkdirSync(directoryPath); @@ -55,26 +77,12 @@ export class ImperativeEventEmitter { * @param eventType The type of event to initialize * @returns The initialized ImperativeEvent */ - private static initializeEvent(eventType: string): ImperativeEvent { + private initializeEvent(eventType: ImperativeEventType | string): ImperativeEvent { if (ImperativeConfig.instance.loadedConfig == null || LoggerManager.instance.isLoggerInit === false) { ProfileInfo.initImpUtils("zowe"); } - let appName = ImperativeConfig.instance.loadedConfig?.name; - const logger = Logger.getImperativeLogger(); - - try { - appName = ImperativeConfig.instance.callerPackageJson.name; - } catch (e) { - logger.error(`Unable to get the source from the callerPackageJson. \n${e}`); - } - - if (appName == null) { - throw new ImperativeError({ - msg: `Unable to initialize the Imperative utilities to emit this event. Event: ${eventType} \t| App: ${appName}` - }); - } - return new ImperativeEvent({ appName, eventType, logger }); + return new ImperativeEvent({ appName: this.appName, eventType, logger: this.logger }); } /** @@ -82,8 +90,8 @@ export class ImperativeEventEmitter { * @param eventType A string representing the type of event * @returns True if it is a user event, false otherwise */ - public static isUserEvent(eventType: string): boolean { - return !!ImperativeUserEvents.find((e) => e === eventType); + public isUserEvent(eventType: string): eventType is ImperativeEventType { + return Object.values(ImperativeUserEvents).includes(eventType); } /** @@ -91,8 +99,8 @@ export class ImperativeEventEmitter { * @param eventType A string representing the type of event * @returns True if it is a shared event, false otherwise */ - public static isSharedEvent(eventType: string): boolean { - return !!ImperativeSharedEvents.find((e) => e === eventType); + public isSharedEvent(eventType: string): eventType is ImperativeEventType { + return Object.values(ImperativeSharedEvents).includes(eventType); } /** @@ -101,8 +109,8 @@ export class ImperativeEventEmitter { * @returns True if it is not a zowe or a user event, false otherwise * @internal Not implemented in the MVP */ - public static isCustomEvent(eventType: string): boolean { - return !ImperativeEventEmitter.isUserEvent(eventType) && !ImperativeEventEmitter.isSharedEvent(eventType); + public isCustomEvent(eventType: string): eventType is ImperativeEventType { + return !this.isUserEvent(eventType) && !this.isSharedEvent(eventType); } /** @@ -110,20 +118,20 @@ export class ImperativeEventEmitter { * @param eventType The type of event to write * @internal We do not want to make this function accessible to any application developers */ - public static emitEvent(eventType: ImperativeEventType) { - const theEvent = ImperativeEventEmitter.initializeEvent(eventType); + public emitEvent(eventType: ImperativeEventType) { + const theEvent = this.initializeEvent(eventType); let dir: string; - if (ImperativeEventEmitter.isUserEvent(eventType)) { - dir = ImperativeEventEmitter.getUserEventDir(); + if (this.isUserEvent(eventType)) { + dir = this.getUserEventDir(); theEvent.isUserSpecific = true; - } else if (ImperativeEventEmitter.isSharedEvent(eventType)) { - dir = ImperativeEventEmitter.getSharedEventDir(); + } else if (this.isSharedEvent(eventType)) { + dir = this.getSharedEventDir(); } else { throw new ImperativeError({ msg: `Unable to determine the type of event. Event: ${eventType}` }); } - ImperativeEventEmitter.writeEvent(dir, theEvent); + this.writeEvent(dir, theEvent); } /** @@ -131,18 +139,18 @@ export class ImperativeEventEmitter { * @param eventType The type of event to write * @internal Not implemented in the MVP */ - public static emitCustomEvent(eventType: string) { //, isUserSpecific: boolean = false) { - const theEvent = ImperativeEventEmitter.initializeEvent(eventType); + public emitCustomEvent(eventType: ImperativeEventType) { //, isUserSpecific: boolean = false) { + const theEvent = this.initializeEvent(eventType); let dir: string; - if (ImperativeEventEmitter.isCustomEvent(eventType)) { + if (this.isCustomEvent(eventType)) { // TODO: Allow for user specific custom events (this applies everywhere we call `isCustomEvent`) - dir = ImperativeEventEmitter.getSharedEventDir(); + dir = this.getSharedEventDir(); } else { throw new ImperativeError({ msg: `Operation not allowed. Event is considered protected. Event: ${eventType}` }); } - ImperativeEventEmitter.writeEvent(dir, theEvent); + this.writeEvent(dir, theEvent); } /** @@ -151,11 +159,11 @@ export class ImperativeEventEmitter { * @param event the event to be written/emitted * @internal We do not want developers writing events directly, they should use the `emit...` methods */ - private static writeEvent(location: string, event: ImperativeEvent) { + private writeEvent(location: string, event: ImperativeEvent) { const eventPath = join(location, event.eventType); const eventJson = { ...event.toJson(), loc: location }; - ImperativeEventEmitter.ensureEventsDirExists(location); + this.ensureEventsDirExists(location); fs.writeFileSync(eventPath, JSON.stringify(eventJson, null, 2)); } @@ -164,45 +172,48 @@ export class ImperativeEventEmitter { * @param eventType Type of event to register * @param callback Action to be registered to the given event */ - public static registerAction(eventType: string, callback: (...args: any[]) => any): IImperativeRegisteredAction { + public subscribe(eventType: string, callback: Function): IImperativeRegisteredAction { if (ImperativeConfig.instance.loadedConfig == null || LoggerManager.instance.isLoggerInit === false) { ProfileInfo.initImpUtils("zowe"); } - // TODO: Make sure that there is only one watcher per (application:event) combination - /* - private static mInstance: ImperativeEventEmitter; - private mAppName: string; - private logger: Logger; - public static initialize(applicationName: string, logger?: Logger) { - mAppName = applicationName; - logger = logger ?? Logger.getImperativeLogger(); - } - public static get instance(): ImperativeEventEmitter { - if (this.mInstance == null) { - this.mInstance = new ImperativeEventEmitter(); - } - - return this.mInstance; - } + if (this.subscriptions == null) { + this.subscriptions = new Map(); + } - // (new imperative.ImperativeEventEmitter("onVaultChanged")).instance.registerAction(() => {}) - // The instance should prevent multiple actions from being registered - */ const logger = Logger.getImperativeLogger(); let dir: string; - if (ImperativeEventEmitter.isUserEvent(eventType)) { - dir = ImperativeEventEmitter.getUserEventDir(); - } else if (ImperativeEventEmitter.isSharedEvent(eventType)) { - dir = ImperativeEventEmitter.getSharedEventDir(); - } else if (ImperativeEventEmitter.isCustomEvent(eventType)) { - dir = ImperativeEventEmitter.getSharedEventDir(); + if (this.isUserEvent(eventType)) { + dir = this.getUserEventDir(); + } else if (this.isSharedEvent(eventType)) { + dir = this.getSharedEventDir(); + } else if (this.isCustomEvent(eventType)) { + dir = this.getSharedEventDir(); } - const watcher = fs.watch(join(dir, eventType), (event: "rename" | "change", filename: string) => { - logger.debug(`ImperativeEventEmitter: Event "${event}" emitted: ${eventType}`); - callback(); - }); + if (dir == null) { + throw new ImperativeError({msg: "Unable to identify the type of event"}); + } + const setupWatcher = (callbacks: Function[] = []): fs.FSWatcher => { + const watcher = fs.watch(join(dir, eventType), (event: "rename" | "change", filename: string) => { + logger.debug(`ImperativeEventEmitter: Event "${event}" emitted: ${eventType}`); + callbacks.forEach(cb => cb()); + callback(); + }); + this.subscriptions.set(eventType, [watcher, [...callbacks, callback]]); + return watcher; + }; + + let watcher: fs.FSWatcher; + if (this.subscriptions.get(eventType) != null) { + // throw new ImperativeError({msg: "Only one subscription per event is allowed"}); + const [watcherToClose, callbacks] = this.subscriptions.get(eventType); + watcherToClose.removeAllListeners(eventType).close(); + + watcher = setupWatcher(callbacks); + } else { + watcher = setupWatcher(); + } return { close: watcher.close }; } } diff --git a/packages/imperative/src/events/src/doc/IImperativeEventEmitterOpts.ts b/packages/imperative/src/events/src/doc/IImperativeEventEmitterOpts.ts new file mode 100644 index 0000000000..2f9a20c223 --- /dev/null +++ b/packages/imperative/src/events/src/doc/IImperativeEventEmitterOpts.ts @@ -0,0 +1,26 @@ +/* +* This program and the accompanying materials are made available under the terms of the +* Eclipse Public License v2.0 which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Copyright Contributors to the Zowe Project. +* +*/ + +import { Logger } from "../../../logger"; + +/** + * Imperative standard event emitter options + * @export + * @interface IImperativeEventEmitterOpts + */ +export interface IImperativeEventEmitterOpts { + /** + * The logger to use when logging the imperative event that occurred + * @type {Logger} + * @memberof IImperativeEventEmitterOpts + */ + logger?: Logger; +} diff --git a/packages/imperative/src/events/src/doc/index.ts b/packages/imperative/src/events/src/doc/index.ts index 76e16b6493..22eb9c922e 100644 --- a/packages/imperative/src/events/src/doc/index.ts +++ b/packages/imperative/src/events/src/doc/index.ts @@ -9,6 +9,7 @@ * */ +export * from "./IImperativeEventEmitterOpts"; export * from "./IImperativeEventParms"; export * from "./IImperativeRegisteredAction"; export * from "./IImperativeEventJson"; diff --git a/packages/imperative/src/security/src/CredentialManagerOverride.ts b/packages/imperative/src/security/src/CredentialManagerOverride.ts index 9087048352..ca66b4a64e 100644 --- a/packages/imperative/src/security/src/CredentialManagerOverride.ts +++ b/packages/imperative/src/security/src/CredentialManagerOverride.ts @@ -16,7 +16,7 @@ import { ICredentialManagerNameMap } from "./doc/ICredentialManagerNameMap"; import { ImperativeConfig } from "../../utilities"; import { ImperativeError } from "../../error"; import { ISettingsFile } from "../../settings/src/doc/ISettingsFile"; -import { ImperativeEventEmitter } from "../../events"; +import { ImperativeEventEmitter, ImperativeSharedEvents } from "../../events"; /** * This class provides access to the known set of credential manager overrides @@ -133,7 +133,7 @@ export class CredentialManagerOverride { settings.json.overrides.CredentialManager = newCredMgrName; try { writeJsonSync(settings.fileName, settings.json, {spaces: 2}); - ImperativeEventEmitter.emitEvent("onCredentialManagerChanged"); + ImperativeEventEmitter.instance.emitEvent(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); } catch (error) { throw new ImperativeError({ msg: "Unable to write settings file = " + settings.fileName + @@ -188,7 +188,7 @@ export class CredentialManagerOverride { settings.json.overrides.CredentialManager = this.DEFAULT_CRED_MGR_NAME; try { writeJsonSync(settings.fileName, settings.json, {spaces: 2}); - ImperativeEventEmitter.emitEvent("onCredentialManagerChanged"); + ImperativeEventEmitter.instance.emitEvent(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); } catch (error) { throw new ImperativeError({ msg: "Unable to write settings file = " + settings.fileName + From 4f0fcf792c4a54f8ad01a81b92cc4481408fc4ae Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Wed, 27 Mar 2024 13:55:16 +0000 Subject: [PATCH 09/29] fix: logger of undefined Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- packages/imperative/src/events/src/ImperativeEventEmitter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/imperative/src/events/src/ImperativeEventEmitter.ts b/packages/imperative/src/events/src/ImperativeEventEmitter.ts index 18fc044a1c..d170618858 100644 --- a/packages/imperative/src/events/src/ImperativeEventEmitter.ts +++ b/packages/imperative/src/events/src/ImperativeEventEmitter.ts @@ -35,7 +35,7 @@ export class ImperativeEventEmitter { } this.initialized = true; ImperativeEventEmitter.instance.appName = appName; - ImperativeEventEmitter.instance.logger = options.logger ?? Logger.getImperativeLogger(); + ImperativeEventEmitter.instance.logger = options?.logger ?? Logger.getImperativeLogger(); } public static get instance(): ImperativeEventEmitter { if (this.mInstance == null) { From 7730aba8a3344230661722ad9bcf09605f2adcc7 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:22:54 +0000 Subject: [PATCH 10/29] fix: reuse the logger Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- packages/imperative/src/events/src/ImperativeEventEmitter.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/imperative/src/events/src/ImperativeEventEmitter.ts b/packages/imperative/src/events/src/ImperativeEventEmitter.ts index d170618858..a3875d3782 100644 --- a/packages/imperative/src/events/src/ImperativeEventEmitter.ts +++ b/packages/imperative/src/events/src/ImperativeEventEmitter.ts @@ -180,7 +180,6 @@ export class ImperativeEventEmitter { this.subscriptions = new Map(); } - const logger = Logger.getImperativeLogger(); let dir: string; if (this.isUserEvent(eventType)) { dir = this.getUserEventDir(); @@ -196,7 +195,7 @@ export class ImperativeEventEmitter { const setupWatcher = (callbacks: Function[] = []): fs.FSWatcher => { const watcher = fs.watch(join(dir, eventType), (event: "rename" | "change", filename: string) => { - logger.debug(`ImperativeEventEmitter: Event "${event}" emitted: ${eventType}`); + this.logger.debug(`ImperativeEventEmitter: Event "${event}" emitted: ${eventType}`); callbacks.forEach(cb => cb()); callback(); }); From 4ee31c70443a2a1099feabdde7d939dc07e23fa4 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Wed, 27 Mar 2024 13:46:55 -0400 Subject: [PATCH 11/29] fixing ENOENT error upon initial subscription to event Signed-off-by: Amber Torrise --- .../src/events/src/ImperativeEventEmitter.ts | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/imperative/src/events/src/ImperativeEventEmitter.ts b/packages/imperative/src/events/src/ImperativeEventEmitter.ts index a3875d3782..d2b25345be 100644 --- a/packages/imperative/src/events/src/ImperativeEventEmitter.ts +++ b/packages/imperative/src/events/src/ImperativeEventEmitter.ts @@ -60,7 +60,7 @@ export class ImperativeEventEmitter { /** * Check to see if the directory exists, otherwise, create it : ) - * @param directoryPath Zowe or User path where we will write the events + * @param directoryPath Zowe or User dir where we will write the events */ private ensureEventsDirExists(directoryPath: string) { try { @@ -72,6 +72,21 @@ export class ImperativeEventEmitter { } } + /** + * Check to see if the file path exists, otherwise, create it : ) + * @param filePath Zowe or User path where we will write the events + */ + private ensureEventFileExists(filePath: string) { + try { + if (!fs.existsSync(filePath)) { + fs.closeSync(fs.openSync(filePath, 'w')); + + } + } catch (err) { + throw new ImperativeError({ msg: `Unable to create file path: ${filePath}`, causeErrors: err }); + } + } + /** * Helper method to initialize the event * @param eventType The type of event to initialize @@ -193,7 +208,10 @@ export class ImperativeEventEmitter { throw new ImperativeError({msg: "Unable to identify the type of event"}); } + this.ensureEventsDirExists(dir); //ensure .events exist + const setupWatcher = (callbacks: Function[] = []): fs.FSWatcher => { + this.ensureEventFileExists(join(dir, eventType)); const watcher = fs.watch(join(dir, eventType), (event: "rename" | "change", filename: string) => { this.logger.debug(`ImperativeEventEmitter: Event "${event}" emitted: ${eventType}`); callbacks.forEach(cb => cb()); From dca31e4ed3e1804fc302aa7a27cc7d125e08ccb1 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Wed, 27 Mar 2024 14:39:18 -0400 Subject: [PATCH 12/29] adding method to unsubscribe from events Signed-off-by: Amber Torrise --- .../src/events/src/ImperativeEventEmitter.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/imperative/src/events/src/ImperativeEventEmitter.ts b/packages/imperative/src/events/src/ImperativeEventEmitter.ts index d2b25345be..0e7658b60f 100644 --- a/packages/imperative/src/events/src/ImperativeEventEmitter.ts +++ b/packages/imperative/src/events/src/ImperativeEventEmitter.ts @@ -233,4 +233,13 @@ export class ImperativeEventEmitter { } return { close: watcher.close }; } -} + + /** + * Method to unsubscribe from custom and regular events + * @param eventType Type of registered event + */ + public unsubscribe(eventType: string): void { + const [watcherToClose, _callbacks] = this.subscriptions.get(eventType); + watcherToClose.removeAllListeners(eventType).close(); + } +} \ No newline at end of file From b77a0ee3d5f09cc2f14c7486917b779b78368955 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Wed, 27 Mar 2024 14:54:10 -0400 Subject: [PATCH 13/29] removing subscription history upon deletion Signed-off-by: Amber Torrise --- packages/imperative/src/events/src/ImperativeEventEmitter.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/imperative/src/events/src/ImperativeEventEmitter.ts b/packages/imperative/src/events/src/ImperativeEventEmitter.ts index 0e7658b60f..0359153cb3 100644 --- a/packages/imperative/src/events/src/ImperativeEventEmitter.ts +++ b/packages/imperative/src/events/src/ImperativeEventEmitter.ts @@ -241,5 +241,6 @@ export class ImperativeEventEmitter { public unsubscribe(eventType: string): void { const [watcherToClose, _callbacks] = this.subscriptions.get(eventType); watcherToClose.removeAllListeners(eventType).close(); + this.subscriptions.delete(eventType); } } \ No newline at end of file From f2fe2609361f1a2cecd230282deb70d701510966 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Wed, 27 Mar 2024 20:46:55 +0000 Subject: [PATCH 14/29] fix: allow only one event per registration Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- .../src/events/src/ImperativeEventEmitter.ts | 64 +++++++++++++------ 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/packages/imperative/src/events/src/ImperativeEventEmitter.ts b/packages/imperative/src/events/src/ImperativeEventEmitter.ts index 0359153cb3..28aac03531 100644 --- a/packages/imperative/src/events/src/ImperativeEventEmitter.ts +++ b/packages/imperative/src/events/src/ImperativeEventEmitter.ts @@ -21,6 +21,7 @@ import { ProfileInfo } from "../../config"; import { LoggerManager } from "../../logger/src/LoggerManager"; import { IImperativeRegisteredAction } from "./doc/IImperativeRegisteredAction"; import { IImperativeEventEmitterOpts } from "./doc/IImperativeEventEmitterOpts"; +import { IImperativeEventJson } from "./doc"; export class ImperativeEventEmitter { private static mInstance: ImperativeEventEmitter; @@ -28,6 +29,7 @@ export class ImperativeEventEmitter { public appName: string; public logger: Logger; private subscriptions: Map; + private eventTimes: Map; public static initialize(appName?: string, options?: IImperativeEventEmitterOpts) { if (this.initialized) { @@ -152,9 +154,8 @@ export class ImperativeEventEmitter { /** * Simple method to write the events to disk * @param eventType The type of event to write - * @internal Not implemented in the MVP */ - public emitCustomEvent(eventType: ImperativeEventType) { //, isUserSpecific: boolean = false) { + public emitCustomEvent(eventType: string) { //, isUserSpecific: boolean = false) { const theEvent = this.initializeEvent(eventType); let dir: string; @@ -182,19 +183,13 @@ export class ImperativeEventEmitter { fs.writeFileSync(eventPath, JSON.stringify(eventJson, null, 2)); } + /** - * Method to register your custom actions based on when the given event is emitted - * @param eventType Type of event to register - * @param callback Action to be registered to the given event + * Obtain the directory of the event + * @param eventType The type of event to be emitted + * @returns The directory to where this event will be emitted */ - public subscribe(eventType: string, callback: Function): IImperativeRegisteredAction { - if (ImperativeConfig.instance.loadedConfig == null || LoggerManager.instance.isLoggerInit === false) { - ProfileInfo.initImpUtils("zowe"); - } - if (this.subscriptions == null) { - this.subscriptions = new Map(); - } - + public getEventDir(eventType: string): string { let dir: string; if (this.isUserEvent(eventType)) { dir = this.getUserEventDir(); @@ -208,14 +203,40 @@ export class ImperativeEventEmitter { throw new ImperativeError({msg: "Unable to identify the type of event"}); } + return dir; + } + + /** + * Method to register your custom actions based on when the given event is emitted + * @param eventType Type of event to register + * @param callback Action to be registered to the given event + */ + public subscribe(eventType: string, callback: Function): IImperativeRegisteredAction { + if (ImperativeConfig.instance.loadedConfig == null || LoggerManager.instance.isLoggerInit === false) { + ProfileInfo.initImpUtils("zowe"); + } + if (this.subscriptions == null) { + this.subscriptions = new Map(); + this.eventTimes = new Map(); + } + + const dir = this.getEventDir(eventType); this.ensureEventsDirExists(dir); //ensure .events exist const setupWatcher = (callbacks: Function[] = []): fs.FSWatcher => { this.ensureEventFileExists(join(dir, eventType)); const watcher = fs.watch(join(dir, eventType), (event: "rename" | "change", filename: string) => { - this.logger.debug(`ImperativeEventEmitter: Event "${event}" emitted: ${eventType}`); - callbacks.forEach(cb => cb()); - callback(); + // Node.JS triggers this event 3 times + const eventContents = fs.readFileSync(join(this.getEventDir(eventType), filename)).toString(); + const timeOfEvent = eventContents.length === 0 ? "" : (JSON.parse(eventContents) as IImperativeEventJson).time; + + if (this.eventTimes.get(eventType) !== timeOfEvent) { + this.logger.debug(`ImperativeEventEmitter: Event "${event}" emitted: ${eventType}`); + // Promise.all([...callbacks, callback]) + callbacks.forEach(cb => cb()); + callback(); + this.eventTimes.set(eventType, timeOfEvent); + } }); this.subscriptions.set(eventType, [watcher, [...callbacks, callback]]); return watcher; @@ -239,8 +260,13 @@ export class ImperativeEventEmitter { * @param eventType Type of registered event */ public unsubscribe(eventType: string): void { - const [watcherToClose, _callbacks] = this.subscriptions.get(eventType); - watcherToClose.removeAllListeners(eventType).close(); - this.subscriptions.delete(eventType); + if (this.subscriptions.has(eventType)) { + const [watcherToClose, _callbacks] = this.subscriptions.get(eventType); + watcherToClose.removeAllListeners(eventType).close(); + this.subscriptions.delete(eventType); + } + if (this.eventTimes.has(eventType)) { + this.eventTimes.delete(eventType); + } } } \ No newline at end of file From c13a40b440d11ae35db498e09ebe7d86d648904b Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Tue, 2 Apr 2024 15:09:49 -0400 Subject: [PATCH 15/29] outline for tests Signed-off-by: Amber Torrise --- .../ImperativeEventEmitter.unit.test.ts | 25 +++++++++++++++++++ .../events/src/ImperativeEventConstants.ts | 1 + 2 files changed, 26 insertions(+) create mode 100644 packages/imperative/src/events/__tests__/ImperativeEventEmitter.unit.test.ts diff --git a/packages/imperative/src/events/__tests__/ImperativeEventEmitter.unit.test.ts b/packages/imperative/src/events/__tests__/ImperativeEventEmitter.unit.test.ts new file mode 100644 index 0000000000..d547daec96 --- /dev/null +++ b/packages/imperative/src/events/__tests__/ImperativeEventEmitter.unit.test.ts @@ -0,0 +1,25 @@ +// shared (~/.zowe/.events//) +// it should create a file to watch on instantiation of the subscription +// it should generate a notification when credential manager is changed +// it should generate a notification when onGlobalConfigChanged is changed +// should generate .event files under a folder with the project name ~/.zowe/.events// +// it should generate multiple notifications(3) if multiple subscriptions(3) to the same event +// unsubscribing from subscriptions should not affect another user's subscriptions + +// custom (~/.zowe/.events//) +// it should create a file to watch on instantiation of the subscription +// it should generate a notification when a custom event occurs +// should generate .event files under a folder with the app name ~/.zowe/.events// +// it should generate multiple notifications(3) if multiple subscription events(3) + +// user +// it should create a file to watch on instantiation of the subscription +// it should generate a notification when vault is changed +// it should generate a notification when configuration is changed +// it should generate a notification when schema is changed +// it should generate multiple notifications(3) if multiple subscriptions(3) to the same event + +// event emission/file +// event should be written to file with all required properties in IImperativeEventJson +// event details should be written to the correct event file +// deleting a subscription should result in the deletion of the corresponding event file only \ No newline at end of file diff --git a/packages/imperative/src/events/src/ImperativeEventConstants.ts b/packages/imperative/src/events/src/ImperativeEventConstants.ts index 2e861ac653..498561fbd6 100644 --- a/packages/imperative/src/events/src/ImperativeEventConstants.ts +++ b/packages/imperative/src/events/src/ImperativeEventConstants.ts @@ -50,4 +50,5 @@ export type ImperativeEventType = ImperativeUserEvents | ImperativeSharedEvents; * * Edge cases: * - What if the `path/to/.events` directory gets renamed or moved? (fs.watch stops notifying apps) + * - (amber) I think this is something we should just let people know about. ie warn them NOT to mess w .zowe/events */ \ No newline at end of file From 59095c5ecfa44803cbdc9dbbca070ec6d1fe9b26 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Sat, 6 Apr 2024 18:11:12 -0400 Subject: [PATCH 16/29] fix: properly mock eventemitter in old unit tests Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- .../__tests__/CommandProcessor.unit.test.ts | 1 + .../config/__tests__/Config.api.unit.test.ts | 2 + .../__tests__/Config.secure.unit.test.ts | 4 ++ .../src/config/__tests__/Config.unit.test.ts | 3 + .../__tests__/ConfigAutoStore.unit.test.ts | 2 + .../ProfileInfo.TeamConfig.unit.test.ts | 2 + .../src/config/src/ConfigBuilder.ts | 2 +- .../imperative/src/config/src/ConfigUtils.ts | 48 +++++++++++++++- .../imperative/src/config/src/ProfileInfo.ts | 42 +------------- .../src/config/src/api/ConfigSecure.ts | 2 +- .../ImperativeEventEmitter.unit.test.ts | 55 +++++++++++-------- .../events/src/ImperativeEventConstants.ts | 52 ++++++++---------- .../src/events/src/ImperativeEventEmitter.ts | 40 +++++--------- .../cmd/import/import.handler.unit.test.ts | 1 + .../config/cmd/init/init.handler.unit.test.ts | 1 + .../cmd/secure/secure.handler.unit.test.ts | 5 +- .../config/cmd/set/set.handler.unit.test.ts | 6 +- .../BaseAuthHandler.config.unit.test.ts | 5 +- .../ConnectionPropsForSessCfg.unit.test.ts | 2 + .../CredentialManagerOverride.unit.test.ts | 5 ++ 20 files changed, 157 insertions(+), 123 deletions(-) diff --git a/packages/imperative/src/cmd/__tests__/CommandProcessor.unit.test.ts b/packages/imperative/src/cmd/__tests__/CommandProcessor.unit.test.ts index aaaa738106..104dddc311 100644 --- a/packages/imperative/src/cmd/__tests__/CommandProcessor.unit.test.ts +++ b/packages/imperative/src/cmd/__tests__/CommandProcessor.unit.test.ts @@ -29,6 +29,7 @@ import { join } from "path"; jest.mock("../src/syntax/SyntaxValidator"); jest.mock("../src/utils/SharedOptions"); jest.mock("../../utilities/src/ImperativeConfig"); +jest.mock("../../events/src/ImperativeEventEmitter"); // Persist the original definitions of process.write const ORIGINAL_STDOUT_WRITE = process.stdout.write; diff --git a/packages/imperative/src/config/__tests__/Config.api.unit.test.ts b/packages/imperative/src/config/__tests__/Config.api.unit.test.ts index af72547cbb..8130b143c0 100644 --- a/packages/imperative/src/config/__tests__/Config.api.unit.test.ts +++ b/packages/imperative/src/config/__tests__/Config.api.unit.test.ts @@ -19,6 +19,8 @@ import { IConfig } from "../src/doc/IConfig"; import { IConfigLayer } from "../src/doc/IConfigLayer"; import { IConfigProfile } from "../src/doc/IConfigProfile"; +jest.mock("../../events/src/ImperativeEventEmitter"); + const MY_APP = "my_app"; const mergeConfig: IConfig = { diff --git a/packages/imperative/src/config/__tests__/Config.secure.unit.test.ts b/packages/imperative/src/config/__tests__/Config.secure.unit.test.ts index b189683cf7..619b0fb087 100644 --- a/packages/imperative/src/config/__tests__/Config.secure.unit.test.ts +++ b/packages/imperative/src/config/__tests__/Config.secure.unit.test.ts @@ -18,6 +18,7 @@ import { Config } from "../src/Config"; import { IConfig } from "../src/doc/IConfig"; import { IConfigSecure } from "../src/doc/IConfigSecure"; import { IConfigVault } from "../src/doc/IConfigVault"; +import { ImperativeEventEmitter } from "../../events"; const MY_APP = "my_app"; @@ -46,6 +47,9 @@ describe("Config secure tests", () => { }); beforeEach(() => { + jest.spyOn(ImperativeEventEmitter, "initialize").mockImplementation(); + Object.defineProperty(ImperativeEventEmitter, "instance", { value: { emitEvent: jest.fn() }}); + mockSecureLoad = jest.fn(); mockSecureSave = jest.fn(); mockVault = { diff --git a/packages/imperative/src/config/__tests__/Config.unit.test.ts b/packages/imperative/src/config/__tests__/Config.unit.test.ts index 708e480243..abfb7480d5 100644 --- a/packages/imperative/src/config/__tests__/Config.unit.test.ts +++ b/packages/imperative/src/config/__tests__/Config.unit.test.ts @@ -19,6 +19,9 @@ import { ConfigConstants } from "../src/ConfigConstants"; import * as JSONC from "comment-json"; import { ConfigLayers, ConfigSecure } from "../src/api"; + +jest.mock("../../events/src/ImperativeEventEmitter"); + const MY_APP = "my_app"; describe("Config tests", () => { diff --git a/packages/imperative/src/config/__tests__/ConfigAutoStore.unit.test.ts b/packages/imperative/src/config/__tests__/ConfigAutoStore.unit.test.ts index a861671f60..73a22e7908 100644 --- a/packages/imperative/src/config/__tests__/ConfigAutoStore.unit.test.ts +++ b/packages/imperative/src/config/__tests__/ConfigAutoStore.unit.test.ts @@ -10,6 +10,8 @@ */ jest.mock("../../logger/src/LoggerUtils"); +jest.mock("../../events/src/ImperativeEventEmitter"); + import { AbstractAuthHandler } from "../../imperative"; import { SessConstants } from "../../rest"; import { ImperativeConfig } from "../../utilities"; diff --git a/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts b/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts index 333b03cb94..2478faa253 100644 --- a/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts +++ b/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts @@ -34,6 +34,8 @@ import { ConfigProfiles } from "../src/api"; import { IExtendersJsonOpts } from "../src/doc/IExtenderOpts"; import { ConfigSchema } from "../src/ConfigSchema"; +jest.mock("../../events/src/ImperativeEventEmitter"); + const testAppNm = "ProfInfoApp"; const testEnvPrefix = testAppNm.toUpperCase(); const profileTypes = ["zosmf", "tso", "base", "dummy"]; diff --git a/packages/imperative/src/config/src/ConfigBuilder.ts b/packages/imperative/src/config/src/ConfigBuilder.ts index ef907a58ff..e430c002da 100644 --- a/packages/imperative/src/config/src/ConfigBuilder.ts +++ b/packages/imperative/src/config/src/ConfigBuilder.ts @@ -16,7 +16,7 @@ import { IImperativeConfig } from "../../imperative"; import { Config } from "./Config"; import { IConfig } from "./doc/IConfig"; import { IConfigBuilderOpts } from "./doc/IConfigBuilderOpts"; -import { CredentialManagerFactory } from "../../security"; +import { CredentialManagerFactory } from "../../security/src/CredentialManagerFactory"; import { IConfigConvertResult } from "./doc/IConfigConvertResult"; import { ICommandProfileTypeConfiguration } from "../../cmd"; diff --git a/packages/imperative/src/config/src/ConfigUtils.ts b/packages/imperative/src/config/src/ConfigUtils.ts index 84dcbe7ae6..583676a0f8 100644 --- a/packages/imperative/src/config/src/ConfigUtils.ts +++ b/packages/imperative/src/config/src/ConfigUtils.ts @@ -9,13 +9,18 @@ * */ -import { normalize as pathNormalize } from "path"; +import { homedir as osHomedir } from "os"; +import { normalize as pathNormalize, join as pathJoin } from "path"; import { existsSync as fsExistsSync } from "fs"; -import { CredentialManagerFactory } from "../../security"; +import { CredentialManagerFactory } from "../../security/src/CredentialManagerFactory"; import { ICommandArguments } from "../../cmd"; import { ImperativeConfig } from "../../utilities"; import { ImperativeError } from "../../error"; +import { LoggerManager } from "../../logger/src/LoggerManager"; +import { LoggingConfigurer } from "../../imperative/src/LoggingConfigurer"; +import { Logger } from "../../logger/src/Logger"; +import { EnvironmentalVariableSettings } from "../../imperative/src/env/EnvironmentalVariableSettings"; export class ConfigUtils { /** @@ -114,4 +119,43 @@ export class ConfigUtils { additionalDetails: details }); } + + + // _______________________________________________________________________ + /** + * Perform a rudimentary initialization of some Imperative utilities. + * We must do this because VSCode apps do not typically call imperative.init. + * @internal + */ + public static initImpUtils(appName: string) { + // create a rudimentary ImperativeConfig if it has not been initialized + if (ImperativeConfig.instance.loadedConfig == null) { + let homeDir: string = null; + const envVarPrefix = appName.toUpperCase(); + const envVarNm = envVarPrefix + EnvironmentalVariableSettings.CLI_HOME_SUFFIX; + if (process.env[envVarNm] === undefined) { + // use OS home directory + homeDir = pathJoin(osHomedir(), "." + appName.toLowerCase()); + } else { + // use the available environment variable + homeDir = pathNormalize(process.env[envVarNm]); + } + ImperativeConfig.instance.loadedConfig = { + name: appName, + defaultHome: homeDir, + envVariablePrefix: envVarPrefix + }; + ImperativeConfig.instance.rootCommandName = appName; + } + + // initialize logging + if (LoggerManager.instance.isLoggerInit === false) { + const loggingConfig = LoggingConfigurer.configureLogger( + ImperativeConfig.instance.cliHome, ImperativeConfig.instance.loadedConfig + ); + Logger.initLogger(loggingConfig); + } + return Logger.getImperativeLogger(); + } + } diff --git a/packages/imperative/src/config/src/ProfileInfo.ts b/packages/imperative/src/config/src/ProfileInfo.ts index 871f46370f..91d65a45c7 100644 --- a/packages/imperative/src/config/src/ProfileInfo.ts +++ b/packages/imperative/src/config/src/ProfileInfo.ts @@ -37,12 +37,9 @@ import { ICommandProfileProperty, ICommandArguments } from "../../cmd"; import { IProfileLoaded, IProfileProperty, IProfileSchema } from "../../profiles"; // for imperative operations -import { EnvironmentalVariableSettings } from "../../imperative/src/env/EnvironmentalVariableSettings"; -import { LoggingConfigurer } from "../../imperative/src/LoggingConfigurer"; import { CliUtils, ImperativeConfig } from "../../utilities"; import { ImperativeExpect } from "../../expect"; import { Logger, LoggerUtils } from "../../logger"; -import { LoggerManager } from "../../logger/src/LoggerManager"; import { IOptionsForAddConnProps, ISession, Session, SessConstants, ConnectionPropsForSessCfg } from "../../rest"; @@ -180,7 +177,7 @@ export class ProfileInfo { this.mCredentials = new ProfileCredentials(this, profInfoOpts); // do enough Imperative stuff to let imperative utilities work - this.mImpLogger = ProfileInfo.initImpUtils(this.mAppName); + this.mImpLogger = ConfigUtils.initImpUtils(this.mAppName); } /** @@ -969,43 +966,6 @@ export class ProfileInfo { } } - // _______________________________________________________________________ - /** - * Perform a rudimentary initialization of some Imperative utilities. - * We must do this because VSCode apps do not typically call imperative.init. - * @internal - */ - public static initImpUtils(appName: string) { - // create a rudimentary ImperativeConfig if it has not been initialized - if (ImperativeConfig.instance.loadedConfig == null) { - let homeDir: string = null; - const envVarPrefix = appName.toUpperCase(); - const envVarNm = envVarPrefix + EnvironmentalVariableSettings.CLI_HOME_SUFFIX; - if (process.env[envVarNm] === undefined) { - // use OS home directory - homeDir = path.join(os.homedir(), "." + appName.toLowerCase()); - } else { - // use the available environment variable - homeDir = path.normalize(process.env[envVarNm]); - } - ImperativeConfig.instance.loadedConfig = { - name: appName, - defaultHome: homeDir, - envVariablePrefix: envVarPrefix - }; - ImperativeConfig.instance.rootCommandName = appName; - } - - // initialize logging - if (LoggerManager.instance.isLoggerInit === false) { - const loggingConfig = LoggingConfigurer.configureLogger( - ImperativeConfig.instance.cliHome, ImperativeConfig.instance.loadedConfig - ); - Logger.initLogger(loggingConfig); - } - return Logger.getImperativeLogger(); - } - /** * Load any profile schema objects found on disk and cache them. For team * config, we check each config layer and load its schema JSON if there is diff --git a/packages/imperative/src/config/src/api/ConfigSecure.ts b/packages/imperative/src/config/src/api/ConfigSecure.ts index 85c68de98e..765a8d430a 100644 --- a/packages/imperative/src/config/src/api/ConfigSecure.ts +++ b/packages/imperative/src/config/src/api/ConfigSecure.ts @@ -21,7 +21,7 @@ import { IConfigProfile } from "../doc/IConfigProfile"; import { CredentialManagerFactory } from "../../../security"; import { ConfigUtils } from "../ConfigUtils"; import { ImperativeEventEmitter } from "../../../events/src/ImperativeEventEmitter"; -import { ImperativeUserEvents } from "../../../events"; +import { ImperativeUserEvents } from "../../../events/src/ImperativeEventConstants"; /** * API Class for manipulating config layers. diff --git a/packages/imperative/src/events/__tests__/ImperativeEventEmitter.unit.test.ts b/packages/imperative/src/events/__tests__/ImperativeEventEmitter.unit.test.ts index d547daec96..43f1095c34 100644 --- a/packages/imperative/src/events/__tests__/ImperativeEventEmitter.unit.test.ts +++ b/packages/imperative/src/events/__tests__/ImperativeEventEmitter.unit.test.ts @@ -1,25 +1,36 @@ -// shared (~/.zowe/.events//) -// it should create a file to watch on instantiation of the subscription -// it should generate a notification when credential manager is changed -// it should generate a notification when onGlobalConfigChanged is changed -// should generate .event files under a folder with the project name ~/.zowe/.events// -// it should generate multiple notifications(3) if multiple subscriptions(3) to the same event -// unsubscribing from subscriptions should not affect another user's subscriptions +/* +* This program and the accompanying materials are made available under the terms of the +* Eclipse Public License v2.0 which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Copyright Contributors to the Zowe Project. +* +*/ -// custom (~/.zowe/.events//) -// it should create a file to watch on instantiation of the subscription -// it should generate a notification when a custom event occurs -// should generate .event files under a folder with the app name ~/.zowe/.events// -// it should generate multiple notifications(3) if multiple subscription events(3) +describe("Event Emitter", () => { + describe("Base structure and emission", () => { + it("should be written to a file with all required properties in IImperativeEventJson", () => {}); + it("should write details to the correct event file", () => {}); + it("should not delete event files when unsubscribing", () => {}); + }); -// user -// it should create a file to watch on instantiation of the subscription -// it should generate a notification when vault is changed -// it should generate a notification when configuration is changed -// it should generate a notification when schema is changed -// it should generate multiple notifications(3) if multiple subscriptions(3) to the same event + describe("Shared Events", () => { + it("should create an event file upon first subscription if the file does not exist", () => {}); + it("should trigger subscriptions for all instances watching for onCredentialManagerChanged", () => {}); + it("should not affect subscriptions from another instance when unsubscribing from events", () => {}); + }); -// event emission/file -// event should be written to file with all required properties in IImperativeEventJson -// event details should be written to the correct event file -// deleting a subscription should result in the deletion of the corresponding event file only \ No newline at end of file + describe("User Events", () => { + it("should create an event file upon first subscription if the file does not exist", () => {}); + it("should trigger subscriptions for all instances watching for onVaultChanged", () => {}); + it("should not affect subscriptions from another instance when unsubscribing from events", () => {}); + }); + + describe("Custom Events", () => { + it("should create an event file upon first subscription if the file does not exist", () => {}); + it("should trigger subscriptions for all instances watching for onMyCustomEvent", () => {}); + it("should not affect subscriptions from another instance when unsubscribing from events", () => {}); + }); +}); diff --git a/packages/imperative/src/events/src/ImperativeEventConstants.ts b/packages/imperative/src/events/src/ImperativeEventConstants.ts index 498561fbd6..0fd35d70af 100644 --- a/packages/imperative/src/events/src/ImperativeEventConstants.ts +++ b/packages/imperative/src/events/src/ImperativeEventConstants.ts @@ -18,37 +18,33 @@ export enum ImperativeSharedEvents { export type ImperativeEventType = ImperativeUserEvents | ImperativeSharedEvents; -// export const ImperativeUserEvents = [ -// "onVaultChanged" -// ] as const; -// export type ImperativeUserEventType = typeof ImperativeUserEvents; -// export const ImperativeSharedEvents = [ -// "onCredentialManagerChanged" -// ] as const; -// export type ImperativeSharedEventType = typeof ImperativeSharedEvents[number]; -// export type ImperativeEventType = ImperativeUserEventType | ImperativeSharedEventType; - /** * TODO: - * - Implement onGlobalConfigChanged as a shared event - * - Implement project-level config-changed as a shared event - * - These events should have their own directory structure to support multiple projects - * - $ZOWE_CLI_HOME/.zowe/.events/project-id/onConfigChanged - * - Implement onGlobalSchemaChanged as a shared event - * - Implement project-level schema-changed as a shared event - * - These events should have their own directory structure to support multiple projects - * - $ZOWE_CLI_HOME/.zowe/.events/project-id/onSchemaChanged - * + * The following list of event types will only be implemented upon request * - * - Implement CustomSharedEvents - * - These events should have their own directory structure to avoid conflicts between apps - * - $ZOWE_CLI_HOME/.zowe/.events// - * - Implement CustomUserEvents - * - These events should have their own directory structure to avoid conflicts between apps - * - ~/.zowe/.events// + * Shared events: + * Global: + * - $ZOWE_CLI_HOME/.events/onConfigChanged + * - $ZOWE_CLI_HOME/.events/onSchemaChanged + * Project: + * - $ZOWE_CLI_HOME/.events//onConfigChanged + * - $ZOWE_CLI_HOME/.events//onSchemaChanged * + * User events: + * Global: + * - ~/.zowe/.events/onUserConfigChanged + * Project: + * - ~/.zowe/.events//onUserConfigChanged * - * Edge cases: - * - What if the `path/to/.events` directory gets renamed or moved? (fs.watch stops notifying apps) - * - (amber) I think this is something we should just let people know about. ie warn them NOT to mess w .zowe/events + * Custom events: + * Shared: + * Global: + * - $ZOWE_CLI_HOME/.events// + * Project: + * - $ZOWE_CLI_HOME/.events/// + * User: + * Global: + * - ~/.zowe/.events// + * Project: + * - ~/.zowe/.events/// */ \ No newline at end of file diff --git a/packages/imperative/src/events/src/ImperativeEventEmitter.ts b/packages/imperative/src/events/src/ImperativeEventEmitter.ts index 28aac03531..943eb07ea6 100644 --- a/packages/imperative/src/events/src/ImperativeEventEmitter.ts +++ b/packages/imperative/src/events/src/ImperativeEventEmitter.ts @@ -9,19 +9,17 @@ * */ -import { homedir } from "os"; import * as fs from "fs"; -import { ImperativeConfig } from "../../utilities/src/ImperativeConfig"; +import { homedir } from "os"; import { join } from "path"; -import { ImperativeError } from "../../error"; +import { ImperativeConfig } from "../../utilities/src/ImperativeConfig"; +import { ImperativeError } from "../../error/src/ImperativeError"; import { ImperativeEventType, ImperativeUserEvents, ImperativeSharedEvents } from "./ImperativeEventConstants"; import { ImperativeEvent } from "./ImperativeEvent"; -import { Logger } from "../../logger"; -import { ProfileInfo } from "../../config"; +import { Logger } from "../../logger/src/Logger"; import { LoggerManager } from "../../logger/src/LoggerManager"; -import { IImperativeRegisteredAction } from "./doc/IImperativeRegisteredAction"; -import { IImperativeEventEmitterOpts } from "./doc/IImperativeEventEmitterOpts"; -import { IImperativeEventJson } from "./doc"; +import { IImperativeRegisteredAction, IImperativeEventEmitterOpts, IImperativeEventJson } from "./doc"; +import { ConfigUtils } from "../../config/src/ConfigUtils"; export class ImperativeEventEmitter { private static mInstance: ImperativeEventEmitter; @@ -82,7 +80,6 @@ export class ImperativeEventEmitter { try { if (!fs.existsSync(filePath)) { fs.closeSync(fs.openSync(filePath, 'w')); - } } catch (err) { throw new ImperativeError({ msg: `Unable to create file path: ${filePath}`, causeErrors: err }); @@ -96,7 +93,7 @@ export class ImperativeEventEmitter { */ private initializeEvent(eventType: ImperativeEventType | string): ImperativeEvent { if (ImperativeConfig.instance.loadedConfig == null || LoggerManager.instance.isLoggerInit === false) { - ProfileInfo.initImpUtils("zowe"); + ConfigUtils.initImpUtils("zowe"); } return new ImperativeEvent({ appName: this.appName, eventType, logger: this.logger }); @@ -154,13 +151,13 @@ export class ImperativeEventEmitter { /** * Simple method to write the events to disk * @param eventType The type of event to write + * @internal We won't support custom events as part of the MVP */ public emitCustomEvent(eventType: string) { //, isUserSpecific: boolean = false) { const theEvent = this.initializeEvent(eventType); let dir: string; if (this.isCustomEvent(eventType)) { - // TODO: Allow for user specific custom events (this applies everywhere we call `isCustomEvent`) dir = this.getSharedEventDir(); } else { throw new ImperativeError({ msg: `Operation not allowed. Event is considered protected. Event: ${eventType}` }); @@ -190,20 +187,13 @@ export class ImperativeEventEmitter { * @returns The directory to where this event will be emitted */ public getEventDir(eventType: string): string { - let dir: string; if (this.isUserEvent(eventType)) { - dir = this.getUserEventDir(); + return this.getUserEventDir(); } else if (this.isSharedEvent(eventType)) { - dir = this.getSharedEventDir(); - } else if (this.isCustomEvent(eventType)) { - dir = this.getSharedEventDir(); - } - - if (dir == null) { - throw new ImperativeError({msg: "Unable to identify the type of event"}); + return this.getSharedEventDir(); } - return dir; + return this.getSharedEventDir(); } /** @@ -213,7 +203,7 @@ export class ImperativeEventEmitter { */ public subscribe(eventType: string, callback: Function): IImperativeRegisteredAction { if (ImperativeConfig.instance.loadedConfig == null || LoggerManager.instance.isLoggerInit === false) { - ProfileInfo.initImpUtils("zowe"); + ConfigUtils.initImpUtils("zowe"); } if (this.subscriptions == null) { this.subscriptions = new Map(); @@ -228,14 +218,14 @@ export class ImperativeEventEmitter { const watcher = fs.watch(join(dir, eventType), (event: "rename" | "change", filename: string) => { // Node.JS triggers this event 3 times const eventContents = fs.readFileSync(join(this.getEventDir(eventType), filename)).toString(); - const timeOfEvent = eventContents.length === 0 ? "" : (JSON.parse(eventContents) as IImperativeEventJson).time; + const eventTime = eventContents.length === 0 ? "" : (JSON.parse(eventContents) as IImperativeEventJson).time; - if (this.eventTimes.get(eventType) !== timeOfEvent) { + if (this.eventTimes.get(eventType) !== eventTime) { this.logger.debug(`ImperativeEventEmitter: Event "${event}" emitted: ${eventType}`); // Promise.all([...callbacks, callback]) callbacks.forEach(cb => cb()); callback(); - this.eventTimes.set(eventType, timeOfEvent); + this.eventTimes.set(eventType, eventTime); } }); this.subscriptions.set(eventType, [watcher, [...callbacks, callback]]); diff --git a/packages/imperative/src/imperative/__tests__/config/cmd/import/import.handler.unit.test.ts b/packages/imperative/src/imperative/__tests__/config/cmd/import/import.handler.unit.test.ts index 671bab25e8..17bac7fa95 100644 --- a/packages/imperative/src/imperative/__tests__/config/cmd/import/import.handler.unit.test.ts +++ b/packages/imperative/src/imperative/__tests__/config/cmd/import/import.handler.unit.test.ts @@ -23,6 +23,7 @@ import { expectedConfigObject, expectedSchemaObject } from "../../../../../../__tests__/__integration__/imperative/__tests__/__integration__/cli/config/__resources__/expectedObjects"; jest.mock("fs"); +jest.mock("../../../../../events/src/ImperativeEventEmitter"); const expectedConfigText = JSONC.stringify(expectedConfigObject, null, ConfigConstants.INDENT); const expectedConfigObjectWithoutSchema = lodash.omit(expectedConfigObject, "$schema"); diff --git a/packages/imperative/src/imperative/__tests__/config/cmd/init/init.handler.unit.test.ts b/packages/imperative/src/imperative/__tests__/config/cmd/init/init.handler.unit.test.ts index 83249761c7..d54d6f24a6 100644 --- a/packages/imperative/src/imperative/__tests__/config/cmd/init/init.handler.unit.test.ts +++ b/packages/imperative/src/imperative/__tests__/config/cmd/init/init.handler.unit.test.ts @@ -26,6 +26,7 @@ import { setupConfigToLoad } from "../../../../../../__tests__/src/TestUtil"; import { OverridesLoader } from "../../../../src/OverridesLoader"; jest.mock("fs"); +jest.mock("../../../../../events/src/ImperativeEventEmitter"); const getIHandlerParametersObject = (): IHandlerParameters => { const x: any = { diff --git a/packages/imperative/src/imperative/__tests__/config/cmd/secure/secure.handler.unit.test.ts b/packages/imperative/src/imperative/__tests__/config/cmd/secure/secure.handler.unit.test.ts index eaf068137e..89c4aa879c 100644 --- a/packages/imperative/src/imperative/__tests__/config/cmd/secure/secure.handler.unit.test.ts +++ b/packages/imperative/src/imperative/__tests__/config/cmd/secure/secure.handler.unit.test.ts @@ -9,7 +9,7 @@ * */ -import { IHandlerParameters, Logger } from "../../../../.."; +import { IHandlerParameters, ImperativeEventEmitter, Logger } from "../../../../.."; import { Config } from "../../../../../config/src/Config"; import { IConfig, IConfigOpts, IConfigProfile } from "../../../../../config"; import { ImperativeConfig } from "../../../../../utilities"; @@ -27,6 +27,8 @@ import * as fs from "fs"; import { SessConstants } from "../../../../../rest"; import { setupConfigToLoad } from "../../../../../../__tests__/src/TestUtil"; +jest.mock("../../../../../events/src/ImperativeEventEmitter"); + let readPromptSpy: any; const getIHandlerParametersObject = (): IHandlerParameters => { const x: any = { @@ -101,6 +103,7 @@ describe("Configuration Secure command handler", () => { }; beforeAll( async() => { + Object.defineProperty(ImperativeEventEmitter, "instance", { value: { emitEvent: jest.fn() }, configurable: true}); keytarGetPasswordSpy = jest.spyOn(keytar, "getPassword"); keytarSetPasswordSpy = jest.spyOn(keytar, "setPassword"); keytarDeletePasswordSpy = jest.spyOn(keytar, "deletePassword"); diff --git a/packages/imperative/src/imperative/__tests__/config/cmd/set/set.handler.unit.test.ts b/packages/imperative/src/imperative/__tests__/config/cmd/set/set.handler.unit.test.ts index d060bcb3f4..08613f6777 100644 --- a/packages/imperative/src/imperative/__tests__/config/cmd/set/set.handler.unit.test.ts +++ b/packages/imperative/src/imperative/__tests__/config/cmd/set/set.handler.unit.test.ts @@ -9,7 +9,7 @@ * */ -import { IHandlerParameters } from "../../../../.."; +import { IHandlerParameters, ImperativeEventEmitter } from "../../../../.."; import { Config } from "../../../../../config/src/Config"; import { IConfigOpts } from "../../../../../config"; import { ImperativeConfig } from "../../../../../utilities"; @@ -26,6 +26,9 @@ import * as lodash from "lodash"; import * as fs from "fs"; import { setupConfigToLoad } from "../../../../../../__tests__/src/TestUtil"; +jest.mock("../../../../../events/src/ImperativeEventEmitter"); + + const getIHandlerParametersObject = (): IHandlerParameters => { const x: any = { response: { @@ -95,6 +98,7 @@ describe("Configuration Set command handler", () => { }; beforeAll( async() => { + Object.defineProperty(ImperativeEventEmitter, "instance", { value: { emitEvent: jest.fn() }, configurable: true}); keytarGetPasswordSpy = jest.spyOn(keytar, "getPassword"); keytarSetPasswordSpy = jest.spyOn(keytar, "setPassword"); keytarDeletePasswordSpy = jest.spyOn(keytar, "deletePassword"); diff --git a/packages/imperative/src/imperative/src/auth/__tests__/BaseAuthHandler.config.unit.test.ts b/packages/imperative/src/imperative/src/auth/__tests__/BaseAuthHandler.config.unit.test.ts index 5ba1bd2e25..cf13d6e108 100644 --- a/packages/imperative/src/imperative/src/auth/__tests__/BaseAuthHandler.config.unit.test.ts +++ b/packages/imperative/src/imperative/src/auth/__tests__/BaseAuthHandler.config.unit.test.ts @@ -10,6 +10,8 @@ */ jest.mock("../../../../logger/src/LoggerUtils"); +jest.mock("../../../../events/src/ImperativeEventEmitter"); + import * as fs from "fs"; import * as path from "path"; import * as lodash from "lodash"; @@ -20,7 +22,7 @@ import { Config } from "../../../../config"; import { IConfigSecure } from "../../../../config/src/doc/IConfigSecure"; import FakeAuthHandler from "./__data__/FakeAuthHandler"; import { CredentialManagerFactory } from "../../../../security"; -import { ImperativeError } from "../../../.."; +import { ImperativeError, ImperativeEventEmitter } from "../../../.."; const MY_APP = "my_app"; @@ -36,6 +38,7 @@ describe("BaseAuthHandler config", () => { let fakeConfig: Config; beforeAll(() => { + Object.defineProperty(ImperativeEventEmitter, "instance", { value: { emitEvent: jest.fn() }}); Object.defineProperty(CredentialManagerFactory, "initialized", { get: () => true }); Object.defineProperty(ImperativeConfig, "instance", { get: () => ({ diff --git a/packages/imperative/src/rest/__tests__/session/ConnectionPropsForSessCfg.unit.test.ts b/packages/imperative/src/rest/__tests__/session/ConnectionPropsForSessCfg.unit.test.ts index 9943614d70..732b90ae1d 100644 --- a/packages/imperative/src/rest/__tests__/session/ConnectionPropsForSessCfg.unit.test.ts +++ b/packages/imperative/src/rest/__tests__/session/ConnectionPropsForSessCfg.unit.test.ts @@ -10,6 +10,8 @@ */ jest.mock("../../../logger/src/LoggerUtils"); +jest.mock("../../../events/src/ImperativeEventEmitter"); + import { ConnectionPropsForSessCfg } from "../../src/session/ConnectionPropsForSessCfg"; import { CliUtils } from "../../../utilities/src/CliUtils"; import { ImperativeError } from "../../../error"; diff --git a/packages/imperative/src/security/__tests__/CredentialManagerOverride.unit.test.ts b/packages/imperative/src/security/__tests__/CredentialManagerOverride.unit.test.ts index 2433cc66bb..0a7a6f71d1 100644 --- a/packages/imperative/src/security/__tests__/CredentialManagerOverride.unit.test.ts +++ b/packages/imperative/src/security/__tests__/CredentialManagerOverride.unit.test.ts @@ -17,6 +17,10 @@ import { ICredentialManagerNameMap } from "../src/doc/ICredentialManagerNameMap" import { ImperativeConfig } from "../../utilities"; import { ImperativeError } from "../../error"; import { ISettingsFile } from "../../settings/src/doc/ISettingsFile"; +import { ImperativeEventEmitter } from "../../events"; + + +jest.mock("../../events/src/ImperativeEventEmitter"); describe("CredentialManagerOverride", () => { let mockImpConfig: any; @@ -28,6 +32,7 @@ describe("CredentialManagerOverride", () => { cliHome: __dirname }; jest.spyOn(ImperativeConfig, "instance", "get").mockReturnValue(mockImpConfig); + Object.defineProperty(ImperativeEventEmitter, "instance", { value: { emitEvent: jest.fn() }, configurable: true}); expectedSettings = { fileName: path.join(mockImpConfig.cliHome, "settings", "imperative.json"), From 4800dac334f3fb132828f429fade21be88d658bf Mon Sep 17 00:00:00 2001 From: zFernand0 Date: Mon, 8 Apr 2024 21:52:34 +0000 Subject: [PATCH 17/29] test: half-way through tests Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- .../ImperativeEventEmitter.unit.test.ts | 103 ++++++++++++++- .../src/events/src/ImperativeEvent.ts | 10 +- .../src/events/src/ImperativeEventEmitter.ts | 119 ++++++++---------- .../events/src/doc/IImperativeEventParms.ts | 6 + 4 files changed, 168 insertions(+), 70 deletions(-) diff --git a/packages/imperative/src/events/__tests__/ImperativeEventEmitter.unit.test.ts b/packages/imperative/src/events/__tests__/ImperativeEventEmitter.unit.test.ts index 43f1095c34..d92679e072 100644 --- a/packages/imperative/src/events/__tests__/ImperativeEventEmitter.unit.test.ts +++ b/packages/imperative/src/events/__tests__/ImperativeEventEmitter.unit.test.ts @@ -9,10 +9,109 @@ * */ +import * as fs from "fs"; +import { join } from "path"; +import { homedir } from "os"; +import { ImperativeEventEmitter, ImperativeSharedEvents, ImperativeUserEvents, Logger } from "../.."; + describe("Event Emitter", () => { + const iee = ImperativeEventEmitter; + const sharedDir = join(__dirname, ".zowe", ".events"); + const userDir = join(homedir(), ".zowe", ".events"); + let fsWriteFileSync: jest.SpyInstance; + + beforeEach(() => { + jest.restoreAllMocks(); + (iee as any).initialized = undefined; + process.env["ZOWE_CLI_HOME"] = join(__dirname, ".zowe"); + jest.spyOn(fs, "existsSync").mockImplementation(jest.fn()); + jest.spyOn(fs, "mkdirSync").mockImplementation(jest.fn()); + fsWriteFileSync = jest.spyOn(fs, "writeFileSync").mockImplementation(jest.fn()); + }); + describe("Base structure and emission", () => { - it("should be written to a file with all required properties in IImperativeEventJson", () => {}); - it("should write details to the correct event file", () => {}); + it("should only allow for one instance of the event emitter", () => { + jest.spyOn(Logger, "getImperativeLogger").mockReturnValue("the logger" as any); + iee.initialize("test"); + let caughtError: any; + try { + iee.initialize("dummy"); + } catch (err) { + caughtError = err; + } + expect(caughtError).toBeDefined(); + expect(caughtError.message).toContain("Only one instance"); + expect(iee.instance.appName).toEqual("test"); + expect(iee.instance.logger).toEqual("the logger"); + }); + + it("should determine the type of event", () => { + iee.initialize("test"); + expect(iee.instance.isUserEvent("dummy")).toBe(false); + expect(iee.instance.isUserEvent(ImperativeUserEvents.ON_VAULT_CHANGED)).toBe(true); + expect(iee.instance.isSharedEvent("dummy")).toBe(false); + expect(iee.instance.isSharedEvent(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED)).toBe(true); + + expect(iee.instance.isCustomEvent(ImperativeUserEvents.ON_VAULT_CHANGED)).toBe(false); + expect(iee.instance.isCustomEvent(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED)).toBe(false); + expect(iee.instance.isCustomEvent("dummy")).toBe(true); + }); + + it("should determine the correct directory based on the event", () => { + iee.initialize("test"); + expect(iee.instance.getEventDir("dummy")).toEqual(sharedDir); + expect(iee.instance.getEventDir(ImperativeUserEvents.ON_VAULT_CHANGED)).toEqual(userDir); + expect(iee.instance.getEventDir(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED)).toEqual(sharedDir); + delete process.env["ZOWE_CLI_HOME"]; + }); + + it("should not allow all kinds of events to be emitted", () => { + iee.initialize("zowe"); + expect(iee.instance.appName).toEqual("zowe"); + + const processError = (eventType: string, msg: string, isCustomEvent = true) => { + let caughtError: any; + try { + iee.instance[(isCustomEvent ? "emitCustomEvent" : "emitEvent")](eventType as any); + } catch(err) { + caughtError = err; + } + expect(caughtError).toBeDefined(); + expect(caughtError.message).toContain(msg); + } + + const aMsg = "Unable to determine the type of event."; + const bMsg = "Operation not allowed. Event is considered protected"; + + // Application developers shouldn't be able to emit custom events from emitEvent, even though it is an internal method + processError("dummy", aMsg, false); + processError(ImperativeUserEvents.ON_VAULT_CHANGED, bMsg); + processError(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED, bMsg); + }); + + it("should write to a file with all required properties in IImperativeEventJson to the correct location", () => { + iee.initialize("zowe"); + expect(iee.instance.appName).toEqual("zowe"); + + const processEvent = (theEvent: any, isUser: boolean, isCustomEvent = false) => { + iee.instance[(isCustomEvent ? "emitCustomEvent" : "emitEvent")](theEvent); + const dir = isUser ? userDir : sharedDir; + expect(fs.existsSync).toHaveBeenCalledWith(dir); + expect(fs.mkdirSync).toHaveBeenCalledWith(dir); + expect(fsWriteFileSync.mock.calls[0][0]).toEqual(join(dir, theEvent)); + expect(JSON.parse(fsWriteFileSync.mock.calls[0][1])).toMatchObject({ + type: theEvent, + user: isUser, + loc: dir, + }); + fsWriteFileSync.mockClear(); + } + + processEvent(ImperativeUserEvents.ON_VAULT_CHANGED, true); + processEvent(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED, false); + processEvent("onSuperCustomEvent", false, true); + }); + it("should not delete event files when unsubscribing", () => {}); }); diff --git a/packages/imperative/src/events/src/ImperativeEvent.ts b/packages/imperative/src/events/src/ImperativeEvent.ts index e3eaee0d54..d8417295ed 100644 --- a/packages/imperative/src/events/src/ImperativeEvent.ts +++ b/packages/imperative/src/events/src/ImperativeEvent.ts @@ -53,10 +53,11 @@ export class ImperativeEvent { /** * Indicator of user-specific (if true) or shared (if false) events - * The ImperativeEventEmitter is responsible for setting this value on all events - * @default false We assume that all events are shared unless the ImperativeEventEmitter says otherwise + * @private + * @type {boolean} + * @memberof ImperativeEvent */ - public isUserSpecific: boolean = false; + private isUserEvent: boolean; /** * toString overload to be called automatically on string concatenation @@ -76,7 +77,7 @@ export class ImperativeEvent { type: this.eventType, source: this.appName, id: this.eventId, - user: this.isUserSpecific, + user: this.isUserEvent, }; }; @@ -85,6 +86,7 @@ export class ImperativeEvent { this.mEventID = randomUUID(); this.mAppID = parms.appName; this.mEventType = parms.eventType; + this.isUserEvent = parms.isUser; parms.logger.debug("ImperativeEvent: " + this); } diff --git a/packages/imperative/src/events/src/ImperativeEventEmitter.ts b/packages/imperative/src/events/src/ImperativeEventEmitter.ts index 943eb07ea6..e1d3d37979 100644 --- a/packages/imperative/src/events/src/ImperativeEventEmitter.ts +++ b/packages/imperative/src/events/src/ImperativeEventEmitter.ts @@ -24,16 +24,21 @@ import { ConfigUtils } from "../../config/src/ConfigUtils"; export class ImperativeEventEmitter { private static mInstance: ImperativeEventEmitter; private static initialized = false; - public appName: string; - public logger: Logger; private subscriptions: Map; private eventTimes: Map; + public appName: string; + public logger: Logger; public static initialize(appName?: string, options?: IImperativeEventEmitterOpts) { if (this.initialized) { throw new ImperativeError({msg: "Only one instance of the Imperative Event Emitter is allowed"}); } this.initialized = true; + + if (ImperativeConfig.instance.loadedConfig == null || LoggerManager.instance.isLoggerInit === false) { + ConfigUtils.initImpUtils("zowe"); + } + ImperativeEventEmitter.instance.appName = appName; ImperativeEventEmitter.instance.logger = options?.logger ?? Logger.getImperativeLogger(); } @@ -44,20 +49,6 @@ export class ImperativeEventEmitter { return this.mInstance; } - /** - * ZOWE HOME directory to search for system wide ImperativeEvents like `configChanged` - */ - public getSharedEventDir(): string { - return join(ImperativeConfig.instance.cliHome, ".events"); - } - - /** - * USER HOME directory to search for user specific ImperativeEvents like `vaultChanged` - */ - public getUserEventDir(): string { - return join(homedir(), ".zowe", ".events"); - } - /** * Check to see if the directory exists, otherwise, create it : ) * @param directoryPath Zowe or User dir where we will write the events @@ -91,12 +82,22 @@ export class ImperativeEventEmitter { * @param eventType The type of event to initialize * @returns The initialized ImperativeEvent */ - private initializeEvent(eventType: ImperativeEventType | string): ImperativeEvent { - if (ImperativeConfig.instance.loadedConfig == null || LoggerManager.instance.isLoggerInit === false) { - ConfigUtils.initImpUtils("zowe"); - } + private initEvent(eventType: ImperativeEventType | string): ImperativeEvent { + return new ImperativeEvent({ appName: this.appName, eventType, isUser: this.isUserEvent(eventType), logger: this.logger }); + } - return new ImperativeEvent({ appName: this.appName, eventType, logger: this.logger }); + /** + * Helper method to write contents out to disk + * @param location directory to write the file (i.e. emit the event) + * @param event the event to be written/emitted + * @internal We do not want developers writing events directly, they should use the `emit...` methods + */ + private writeEvent(location: string, event: ImperativeEvent) { + const eventPath = join(location, event.eventType); + const eventJson = { ...event.toJson(), loc: location }; + + this.ensureEventsDirExists(location); + fs.writeFileSync(eventPath, JSON.stringify(eventJson, null, 2)); } /** @@ -128,72 +129,62 @@ export class ImperativeEventEmitter { } /** - * Simple method to write the events to disk - * @param eventType The type of event to write - * @internal We do not want to make this function accessible to any application developers + * ZOWE HOME directory to search for system wide ImperativeEvents like `configChanged` */ - public emitEvent(eventType: ImperativeEventType) { - const theEvent = this.initializeEvent(eventType); + public getSharedEventDir(): string { + return join(ImperativeConfig.instance.cliHome, ".events"); + } + + /** + * USER HOME directory to search for user specific ImperativeEvents like `vaultChanged` + */ + public getUserEventDir(): string { + return join(homedir(), ".zowe", ".events"); + } - let dir: string; + /** + * Obtain the directory of the event + * @param eventType The type of event to be emitted + * @returns The directory to where this event will be emitted + */ + public getEventDir(eventType: string): string { if (this.isUserEvent(eventType)) { - dir = this.getUserEventDir(); - theEvent.isUserSpecific = true; + return this.getUserEventDir(); } else if (this.isSharedEvent(eventType)) { - dir = this.getSharedEventDir(); - } else { - throw new ImperativeError({ msg: `Unable to determine the type of event. Event: ${eventType}` }); + return this.getSharedEventDir(); } - this.writeEvent(dir, theEvent); + return this.getSharedEventDir(); } /** * Simple method to write the events to disk * @param eventType The type of event to write - * @internal We won't support custom events as part of the MVP + * @internal We do not want to make this function accessible to any application developers */ - public emitCustomEvent(eventType: string) { //, isUserSpecific: boolean = false) { - const theEvent = this.initializeEvent(eventType); + public emitEvent(eventType: ImperativeEventType) { + const theEvent = this.initEvent(eventType); - let dir: string; if (this.isCustomEvent(eventType)) { - dir = this.getSharedEventDir(); - } else { - throw new ImperativeError({ msg: `Operation not allowed. Event is considered protected. Event: ${eventType}` }); + throw new ImperativeError({ msg: `Unable to determine the type of event. Event: ${eventType}` }); } - this.writeEvent(dir, theEvent); + this.writeEvent(this.getEventDir(eventType), theEvent); } /** - * Helper method to write contents out to disk - * @param location directory to write the file (i.e. emit the event) - * @param event the event to be written/emitted - * @internal We do not want developers writing events directly, they should use the `emit...` methods + * Simple method to write the events to disk + * @param eventType The type of event to write + * @internal We won't support custom events as part of the MVP */ - private writeEvent(location: string, event: ImperativeEvent) { - const eventPath = join(location, event.eventType); - const eventJson = { ...event.toJson(), loc: location }; - - this.ensureEventsDirExists(location); - fs.writeFileSync(eventPath, JSON.stringify(eventJson, null, 2)); - } - + public emitCustomEvent(eventType: string) { //, isUserSpecific: boolean = false) { + const theEvent = this.initEvent(eventType); - /** - * Obtain the directory of the event - * @param eventType The type of event to be emitted - * @returns The directory to where this event will be emitted - */ - public getEventDir(eventType: string): string { - if (this.isUserEvent(eventType)) { - return this.getUserEventDir(); - } else if (this.isSharedEvent(eventType)) { - return this.getSharedEventDir(); + if (!this.isCustomEvent(eventType)) { + throw new ImperativeError({ msg: `Operation not allowed. Event is considered protected. Event: ${eventType}` }); } - return this.getSharedEventDir(); + this.writeEvent(this.getSharedEventDir(), theEvent); } /** diff --git a/packages/imperative/src/events/src/doc/IImperativeEventParms.ts b/packages/imperative/src/events/src/doc/IImperativeEventParms.ts index 24ecce8530..e66efa5d4c 100644 --- a/packages/imperative/src/events/src/doc/IImperativeEventParms.ts +++ b/packages/imperative/src/events/src/doc/IImperativeEventParms.ts @@ -30,6 +30,12 @@ export interface IImperativeEventParms { * @memberof IImperativeEventParms */ eventType: ImperativeEventType | string + /** + * Specifies whether this is a user event or not + * @type {ImperativeEventType} + * @memberof IImperativeEventParms + */ + isUser: boolean /** * The logger to use when logging the imperative event that occurred * @type {Logger} From 5816bbd0fac574161e8edc39a97afdb31914ab50 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Fri, 12 Apr 2024 21:15:09 +0000 Subject: [PATCH 18/29] test: finalize unit-tests on event emitter Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- ...ImperativeEventEmitter.integration.test.ts | 38 ++++ .../ImperativeEventEmitter.unit.test.ts | 167 +++++++++++++++--- .../src/events/src/ImperativeEventEmitter.ts | 78 ++++---- 3 files changed, 225 insertions(+), 58 deletions(-) create mode 100644 packages/imperative/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts diff --git a/packages/imperative/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts b/packages/imperative/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts new file mode 100644 index 0000000000..f5154f35e7 --- /dev/null +++ b/packages/imperative/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts @@ -0,0 +1,38 @@ +/* +* This program and the accompanying materials are made available under the terms of the +* Eclipse Public License v2.0 which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Copyright Contributors to the Zowe Project. +* +*/ + +import { ImperativeEventEmitter } from "../../src"; + +describe("Event Emitter", () => { + const iee = ImperativeEventEmitter; + + beforeEach(() => { + jest.restoreAllMocks(); + }); + + describe("Shared Events", () => { + it("should create an event file upon first subscription if the file does not exist", () => { }); + it("should trigger subscriptions for all instances watching for onCredentialManagerChanged", () => { }); + it("should not affect subscriptions from another instance when unsubscribing from events", () => { }); + }); + + describe("User Events", () => { + it("should create an event file upon first subscription if the file does not exist", () => { }); + it("should trigger subscriptions for all instances watching for onVaultChanged", () => { }); + it("should not affect subscriptions from another instance when unsubscribing from events", () => { }); + }); + + describe("Custom Events", () => { + it("should create an event file upon first subscription if the file does not exist", () => { }); + it("should trigger subscriptions for all instances watching for onMyCustomEvent", () => { }); + it("should not affect subscriptions from another instance when unsubscribing from events", () => { }); + }); +}); diff --git a/packages/imperative/src/events/__tests__/ImperativeEventEmitter.unit.test.ts b/packages/imperative/src/events/__tests__/ImperativeEventEmitter.unit.test.ts index d92679e072..247494f378 100644 --- a/packages/imperative/src/events/__tests__/ImperativeEventEmitter.unit.test.ts +++ b/packages/imperative/src/events/__tests__/ImperativeEventEmitter.unit.test.ts @@ -18,7 +18,10 @@ describe("Event Emitter", () => { const iee = ImperativeEventEmitter; const sharedDir = join(__dirname, ".zowe", ".events"); const userDir = join(homedir(), ".zowe", ".events"); - let fsWriteFileSync: jest.SpyInstance; + let spyFsWriteFileSync: jest.SpyInstance; + let allCallbacks: Function[]; + let removeAllListeners: jest.SpyInstance; + let closeWatcher = jest.fn(); beforeEach(() => { jest.restoreAllMocks(); @@ -26,7 +29,16 @@ describe("Event Emitter", () => { process.env["ZOWE_CLI_HOME"] = join(__dirname, ".zowe"); jest.spyOn(fs, "existsSync").mockImplementation(jest.fn()); jest.spyOn(fs, "mkdirSync").mockImplementation(jest.fn()); - fsWriteFileSync = jest.spyOn(fs, "writeFileSync").mockImplementation(jest.fn()); + jest.spyOn(fs, "openSync").mockImplementation(jest.fn()); + jest.spyOn(fs, "closeSync").mockImplementation(jest.fn()); + jest.spyOn(fs, "openSync").mockImplementation(jest.fn()); + spyFsWriteFileSync = jest.spyOn(fs, "writeFileSync").mockImplementation(jest.fn()); + allCallbacks = []; + removeAllListeners = jest.fn().mockReturnValue({ close: closeWatcher }); + jest.spyOn(fs, "watch").mockImplementation((_event: string | any, cb: Function | any) => { + allCallbacks.push(cb); + return { close: jest.fn(), removeAllListeners } as any; + }); }); describe("Base structure and emission", () => { @@ -35,7 +47,7 @@ describe("Event Emitter", () => { iee.initialize("test"); let caughtError: any; try { - iee.initialize("dummy"); + iee.initialize("dummy"); } catch (err) { caughtError = err; } @@ -73,12 +85,12 @@ describe("Event Emitter", () => { let caughtError: any; try { iee.instance[(isCustomEvent ? "emitCustomEvent" : "emitEvent")](eventType as any); - } catch(err) { + } catch (err) { caughtError = err; } expect(caughtError).toBeDefined(); expect(caughtError.message).toContain(msg); - } + }; const aMsg = "Unable to determine the type of event."; const bMsg = "Operation not allowed. Event is considered protected"; @@ -94,42 +106,147 @@ describe("Event Emitter", () => { expect(iee.instance.appName).toEqual("zowe"); const processEvent = (theEvent: any, isUser: boolean, isCustomEvent = false) => { + // Emit the event iee.instance[(isCustomEvent ? "emitCustomEvent" : "emitEvent")](theEvent); + const dir = isUser ? userDir : sharedDir; expect(fs.existsSync).toHaveBeenCalledWith(dir); expect(fs.mkdirSync).toHaveBeenCalledWith(dir); - expect(fsWriteFileSync.mock.calls[0][0]).toEqual(join(dir, theEvent)); - expect(JSON.parse(fsWriteFileSync.mock.calls[0][1])).toMatchObject({ + expect(spyFsWriteFileSync.mock.calls[0][0]).toEqual(join(dir, theEvent)); + expect(JSON.parse(spyFsWriteFileSync.mock.calls[0][1])).toMatchObject({ type: theEvent, user: isUser, loc: dir, }); - fsWriteFileSync.mockClear(); - } + spyFsWriteFileSync.mockClear(); + }; processEvent(ImperativeUserEvents.ON_VAULT_CHANGED, true); processEvent(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED, false); processEvent("onSuperCustomEvent", false, true); }); - it("should not delete event files when unsubscribing", () => {}); - }); + it("should fail to emit, subscribe or unsubscribe if the emitter has not been initialized", () => { + const getError = (shouldThrow: any) => { + let caughtError: any; + try { + shouldThrow(); + } catch (err) { + caughtError = err; + } + return caughtError ?? { message: "THIS METHOD DID NOT THROW AN ERROR" }; + }; + + const cbs = [ + // Emitting should fail if IEE is not initialized + () => { iee.instance.emitEvent("dummy" as any); }, + () => { iee.instance.emitEvent(ImperativeUserEvents.ON_VAULT_CHANGED); }, + () => { iee.instance.emitEvent(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); }, + () => { iee.instance.emitCustomEvent("dummy"); }, + () => { iee.instance.emitCustomEvent(ImperativeUserEvents.ON_VAULT_CHANGED); }, + () => { iee.instance.emitCustomEvent(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); }, + + // Subscribing should fail if IEE is not initialized + () => { iee.instance.subscribe("dummy", jest.fn); }, + () => { iee.instance.subscribe(ImperativeUserEvents.ON_VAULT_CHANGED, jest.fn); }, + () => { iee.instance.subscribe(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED, jest.fn); }, + () => { iee.instance.unsubscribe("dummy"); }, + () => { iee.instance.unsubscribe(ImperativeUserEvents.ON_VAULT_CHANGED); }, + () => { iee.instance.unsubscribe(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); }, + ]; + cbs.forEach(cb => { + expect((getError(cb)).message).toContain("You must initialize the instance"); + }); + }); - describe("Shared Events", () => { - it("should create an event file upon first subscription if the file does not exist", () => {}); - it("should trigger subscriptions for all instances watching for onCredentialManagerChanged", () => {}); - it("should not affect subscriptions from another instance when unsubscribing from events", () => {}); - }); + it("should surface errors if unable to create event files or directories", () => { + iee.initialize("zowe"); - describe("User Events", () => { - it("should create an event file upon first subscription if the file does not exist", () => {}); - it("should trigger subscriptions for all instances watching for onVaultChanged", () => {}); - it("should not affect subscriptions from another instance when unsubscribing from events", () => {}); - }); + jest.spyOn(fs, "mkdirSync").mockImplementationOnce(() => { throw "DIR"; }); + + const theEvent = ImperativeUserEvents.ON_VAULT_CHANGED; + try { + iee.instance.subscribe(theEvent, jest.fn); + } catch (err) { + expect(err.message).toContain("Unable to create '.events' directory."); + } + expect(fs.existsSync).toHaveBeenCalledWith(userDir); + expect(fs.mkdirSync).toHaveBeenCalledWith(userDir); + + jest.spyOn(fs, "closeSync").mockImplementation(() => { throw "FILE"; }); - describe("Custom Events", () => { - it("should create an event file upon first subscription if the file does not exist", () => {}); - it("should trigger subscriptions for all instances watching for onMyCustomEvent", () => {}); - it("should not affect subscriptions from another instance when unsubscribing from events", () => {}); + try { + iee.instance.subscribe(theEvent, jest.fn); + } catch (err) { + expect(err.message).toContain("Unable to create event file."); + } + expect(fs.existsSync).toHaveBeenCalledWith(join(userDir, theEvent)); + expect(fs.openSync).toHaveBeenCalledWith(join(userDir, theEvent), "w"); + expect(fs.closeSync).toHaveBeenCalled(); + }); + + it("should subscribe even when the onEventFile or the events directory do not exist", () => { + iee.initialize("zowe"); + expect(iee.instance.appName).toEqual("zowe"); + + const processSubcription = (theEvent: any, isUser: boolean) => { + const dir = isUser ? userDir : sharedDir; + const cbSpy = jest.fn(); + iee.instance.subscribe(theEvent, cbSpy); + + // Ensure the directory is created + expect(fs.existsSync).toHaveBeenCalledWith(dir); + expect(fs.mkdirSync).toHaveBeenCalledWith(dir); + + // Ensure the file is created + expect(fs.existsSync).toHaveBeenCalledWith(join(dir, theEvent)); + expect(fs.openSync).toHaveBeenCalledWith(join(dir, theEvent), "w"); + expect(fs.closeSync).toHaveBeenCalled(); + }; + + processSubcription(ImperativeUserEvents.ON_VAULT_CHANGED, true); + processSubcription(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED, false); + }); + + it("should trigger all callbacks when subscribed event is emitted", () => { + jest.spyOn(ImperativeEventEmitter.prototype, "emitEvent").mockImplementation((theEvent: any) => { + (iee.instance as any).subscriptions.get(theEvent)[1].forEach((cb: any) => cb()); + }); + jest.spyOn(fs, "readFileSync").mockReturnValue("{\"time\":\"123456\"}"); + + iee.initialize("zowe"); + expect(iee.instance.appName).toEqual("zowe"); + + const processEmission = (theEvent: any, isCustomEvent = false) => { + const cbSpy = jest.fn().mockReturnValue("test"); + const numberOfCalls = Math.floor(Math.random() * 20); + let i = numberOfCalls; + while(i-- > 0) { + iee.instance.subscribe(theEvent, cbSpy); + } + + iee.instance[(isCustomEvent ? "emitCustomEvent" : "emitEvent")](theEvent); + expect(cbSpy).toHaveBeenCalledTimes(numberOfCalls); + }; + + processEmission(ImperativeUserEvents.ON_VAULT_CHANGED); + processEmission(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); + }); + + it("should unsubscribe from events successfully", () => { + iee.initialize("zowe"); + + const dummyMap = { + has: () => (true), + delete: jest.fn(), + get: () => ([{ removeAllListeners }, jest.fn()]) + }; + // Mocked map of subscriptions + (iee.instance as any).subscriptions = dummyMap; + (iee.instance as any).eventTimes = dummyMap; + + iee.instance.unsubscribe("dummy"); + expect(closeWatcher).toHaveBeenCalled(); + }); }); }); diff --git a/packages/imperative/src/events/src/ImperativeEventEmitter.ts b/packages/imperative/src/events/src/ImperativeEventEmitter.ts index e1d3d37979..ba261f03d7 100644 --- a/packages/imperative/src/events/src/ImperativeEventEmitter.ts +++ b/packages/imperative/src/events/src/ImperativeEventEmitter.ts @@ -45,10 +45,21 @@ export class ImperativeEventEmitter { public static get instance(): ImperativeEventEmitter { if (this.mInstance == null) { this.mInstance = new ImperativeEventEmitter(); + this.mInstance.subscriptions = new Map(); + this.mInstance.eventTimes = new Map(); } return this.mInstance; } + /** + * Check to see if the Imperative Event Emitter instance has been initialized + */ + private ensureClassInitialized() { + if (!ImperativeEventEmitter.initialized) { + throw new ImperativeError({msg: "You must initialize the instance before using any of its methods."}); + } + } + /** * Check to see if the directory exists, otherwise, create it : ) * @param directoryPath Zowe or User dir where we will write the events @@ -73,7 +84,7 @@ export class ImperativeEventEmitter { fs.closeSync(fs.openSync(filePath, 'w')); } } catch (err) { - throw new ImperativeError({ msg: `Unable to create file path: ${filePath}`, causeErrors: err }); + throw new ImperativeError({ msg: `Unable to create event file. Path: ${filePath}`, causeErrors: err }); } } @@ -83,6 +94,7 @@ export class ImperativeEventEmitter { * @returns The initialized ImperativeEvent */ private initEvent(eventType: ImperativeEventType | string): ImperativeEvent { + this.ensureClassInitialized(); return new ImperativeEvent({ appName: this.appName, eventType, isUser: this.isUserEvent(eventType), logger: this.logger }); } @@ -100,6 +112,33 @@ export class ImperativeEventEmitter { fs.writeFileSync(eventPath, JSON.stringify(eventJson, null, 2)); } + /** + * Helper method to create watchers based on event strings and list of callbacks + * @param eventType type of event to which we will create a watcher for + * @param callbacks list of all callbacks for this watcher + * @returns The FSWatcher instance created + */ + private setupWatcher(eventType: string, callbacks: Function[] = []): fs.FSWatcher { + const dir = this.getEventDir(eventType); + this.ensureEventsDirExists(dir); //ensure .events exist + + this.ensureEventFileExists(join(dir, eventType)); + const watcher = fs.watch(join(dir, eventType), (event: "rename" | "change") => { + // Node.JS triggers this event 3 times + const eventContents = fs.readFileSync(join(this.getEventDir(eventType), eventType)).toString(); + const eventTime = eventContents.length === 0 ? "" : (JSON.parse(eventContents) as IImperativeEventJson).time; + + if (this.eventTimes.get(eventType) !== eventTime) { + this.logger.debug(`ImperativeEventEmitter: Event "${event}" emitted: ${eventType}`); + // Promise.all(callbacks) + callbacks.forEach(cb => cb()); + this.eventTimes.set(eventType, eventTime); + } + }); + this.subscriptions.set(eventType, [watcher, callbacks]); + return watcher; + }; + /** * Check to see if the given event is a User event * @param eventType A string representing the type of event @@ -193,45 +232,16 @@ export class ImperativeEventEmitter { * @param callback Action to be registered to the given event */ public subscribe(eventType: string, callback: Function): IImperativeRegisteredAction { - if (ImperativeConfig.instance.loadedConfig == null || LoggerManager.instance.isLoggerInit === false) { - ConfigUtils.initImpUtils("zowe"); - } - if (this.subscriptions == null) { - this.subscriptions = new Map(); - this.eventTimes = new Map(); - } - - const dir = this.getEventDir(eventType); - this.ensureEventsDirExists(dir); //ensure .events exist - - const setupWatcher = (callbacks: Function[] = []): fs.FSWatcher => { - this.ensureEventFileExists(join(dir, eventType)); - const watcher = fs.watch(join(dir, eventType), (event: "rename" | "change", filename: string) => { - // Node.JS triggers this event 3 times - const eventContents = fs.readFileSync(join(this.getEventDir(eventType), filename)).toString(); - const eventTime = eventContents.length === 0 ? "" : (JSON.parse(eventContents) as IImperativeEventJson).time; - - if (this.eventTimes.get(eventType) !== eventTime) { - this.logger.debug(`ImperativeEventEmitter: Event "${event}" emitted: ${eventType}`); - // Promise.all([...callbacks, callback]) - callbacks.forEach(cb => cb()); - callback(); - this.eventTimes.set(eventType, eventTime); - } - }); - this.subscriptions.set(eventType, [watcher, [...callbacks, callback]]); - return watcher; - }; + this.ensureClassInitialized(); let watcher: fs.FSWatcher; if (this.subscriptions.get(eventType) != null) { - // throw new ImperativeError({msg: "Only one subscription per event is allowed"}); const [watcherToClose, callbacks] = this.subscriptions.get(eventType); watcherToClose.removeAllListeners(eventType).close(); - watcher = setupWatcher(callbacks); + watcher = this.setupWatcher(eventType, [...callbacks, callback]); } else { - watcher = setupWatcher(); + watcher = this.setupWatcher(eventType, [callback]); } return { close: watcher.close }; } @@ -241,6 +251,8 @@ export class ImperativeEventEmitter { * @param eventType Type of registered event */ public unsubscribe(eventType: string): void { + this.ensureClassInitialized(); + if (this.subscriptions.has(eventType)) { const [watcherToClose, _callbacks] = this.subscriptions.get(eventType); watcherToClose.removeAllListeners(eventType).close(); From 7feae614a9229324ee4cb8edc3ccc856f8c1efe5 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Fri, 12 Apr 2024 21:29:28 +0000 Subject: [PATCH 19/29] lint: fix lint issues Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- .../src/events/__tests__/ImperativeEventEmitter.unit.test.ts | 2 +- packages/imperative/src/events/src/ImperativeEventEmitter.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/imperative/src/events/__tests__/ImperativeEventEmitter.unit.test.ts b/packages/imperative/src/events/__tests__/ImperativeEventEmitter.unit.test.ts index 247494f378..6a60d07253 100644 --- a/packages/imperative/src/events/__tests__/ImperativeEventEmitter.unit.test.ts +++ b/packages/imperative/src/events/__tests__/ImperativeEventEmitter.unit.test.ts @@ -21,7 +21,7 @@ describe("Event Emitter", () => { let spyFsWriteFileSync: jest.SpyInstance; let allCallbacks: Function[]; let removeAllListeners: jest.SpyInstance; - let closeWatcher = jest.fn(); + const closeWatcher = jest.fn(); beforeEach(() => { jest.restoreAllMocks(); diff --git a/packages/imperative/src/events/src/ImperativeEventEmitter.ts b/packages/imperative/src/events/src/ImperativeEventEmitter.ts index ba261f03d7..429cfd07be 100644 --- a/packages/imperative/src/events/src/ImperativeEventEmitter.ts +++ b/packages/imperative/src/events/src/ImperativeEventEmitter.ts @@ -137,7 +137,7 @@ export class ImperativeEventEmitter { }); this.subscriptions.set(eventType, [watcher, callbacks]); return watcher; - }; + } /** * Check to see if the given event is a User event From a600acdc71cf5f67b8f55c6d72ab66535aae0b43 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Mon, 22 Apr 2024 19:37:50 +0000 Subject: [PATCH 20/29] fix build by allowing other files in packages/imperative/__tests__/__integration__ Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- scripts/sampleCliTool.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/scripts/sampleCliTool.js b/scripts/sampleCliTool.js index 3e97f9f93e..da57095ed9 100644 --- a/scripts/sampleCliTool.js +++ b/scripts/sampleCliTool.js @@ -12,18 +12,22 @@ const childProcess = require("child_process"); const path = require("path"); const glob = require("glob"); +const isDir = (filePath) => require("fs").statSync(filePath).isDirectory(); process.chdir(__dirname + "/.."); const npmPrefix = path.join(process.cwd(), ".npm-global"); -function runAll(callback, parallel=false) { +function runAll(callback, parallel = false) { if (!parallel) { glob.sync("packages/imperative/__tests__/__integration__/*").forEach((dir) => { - const command = callback(dir); + const command = isDir(dir) ? callback(dir) : `echo 'File is not a directory: ${dir}'`; childProcess.execSync(command.command, { cwd: command.cwd, stdio: "inherit" }); }); } else { - require("concurrently")(glob.sync("packages/imperative/__tests__/__integration__/*").map((dir) => callback(dir))); + require("concurrently")(glob.sync("packages/imperative/__tests__/__integration__/*").map((dir) => { + if (isDir(dir)) return callback(dir); + else return `echo 'File is not a directory: ${dir}'`; + })); } } From 6402b924d1b469b34be61184de36f422cfc3ae69 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Mon, 22 Apr 2024 19:43:25 +0000 Subject: [PATCH 21/29] test: moved the integration tests odwn to events instead Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- .../ImperativeEventEmitter.integration.test.ts | 0 .../{ => __unit__}/ImperativeEventEmitter.unit.test.ts | 3 ++- scripts/sampleCliTool.js | 10 +++------- 3 files changed, 5 insertions(+), 8 deletions(-) rename packages/imperative/{ => src/events}/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts (100%) rename packages/imperative/src/events/__tests__/{ => __unit__}/ImperativeEventEmitter.unit.test.ts (99%) diff --git a/packages/imperative/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts b/packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts similarity index 100% rename from packages/imperative/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts rename to packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts diff --git a/packages/imperative/src/events/__tests__/ImperativeEventEmitter.unit.test.ts b/packages/imperative/src/events/__tests__/__unit__/ImperativeEventEmitter.unit.test.ts similarity index 99% rename from packages/imperative/src/events/__tests__/ImperativeEventEmitter.unit.test.ts rename to packages/imperative/src/events/__tests__/__unit__/ImperativeEventEmitter.unit.test.ts index 6a60d07253..8ba129dc1e 100644 --- a/packages/imperative/src/events/__tests__/ImperativeEventEmitter.unit.test.ts +++ b/packages/imperative/src/events/__tests__/__unit__/ImperativeEventEmitter.unit.test.ts @@ -12,7 +12,8 @@ import * as fs from "fs"; import { join } from "path"; import { homedir } from "os"; -import { ImperativeEventEmitter, ImperativeSharedEvents, ImperativeUserEvents, Logger } from "../.."; +import { Logger } from "../../../logger/src/Logger"; +import { ImperativeEventEmitter, ImperativeSharedEvents, ImperativeUserEvents } from "../.."; describe("Event Emitter", () => { const iee = ImperativeEventEmitter; diff --git a/scripts/sampleCliTool.js b/scripts/sampleCliTool.js index da57095ed9..3e97f9f93e 100644 --- a/scripts/sampleCliTool.js +++ b/scripts/sampleCliTool.js @@ -12,22 +12,18 @@ const childProcess = require("child_process"); const path = require("path"); const glob = require("glob"); -const isDir = (filePath) => require("fs").statSync(filePath).isDirectory(); process.chdir(__dirname + "/.."); const npmPrefix = path.join(process.cwd(), ".npm-global"); -function runAll(callback, parallel = false) { +function runAll(callback, parallel=false) { if (!parallel) { glob.sync("packages/imperative/__tests__/__integration__/*").forEach((dir) => { - const command = isDir(dir) ? callback(dir) : `echo 'File is not a directory: ${dir}'`; + const command = callback(dir); childProcess.execSync(command.command, { cwd: command.cwd, stdio: "inherit" }); }); } else { - require("concurrently")(glob.sync("packages/imperative/__tests__/__integration__/*").map((dir) => { - if (isDir(dir)) return callback(dir); - else return `echo 'File is not a directory: ${dir}'`; - })); + require("concurrently")(glob.sync("packages/imperative/__tests__/__integration__/*").map((dir) => callback(dir))); } } From dcd805de26c94f46790a83ee6a2298c81c8111e4 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Fri, 26 Apr 2024 15:56:41 +0000 Subject: [PATCH 22/29] refactor Imperative Event Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- ...ImperativeEventEmitter.integration.test.ts | 63 +++++++++- .../src/events/src/ImperativeEvent.ts | 34 +++-- .../src/events/src/ImperativeEventEmitter.ts | 119 ++++++++++-------- .../events/src/doc/IImperativeEventParms.ts | 2 +- 4 files changed, 149 insertions(+), 69 deletions(-) diff --git a/packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts b/packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts index f5154f35e7..8600e13370 100644 --- a/packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts +++ b/packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts @@ -9,17 +9,70 @@ * */ -import { ImperativeEventEmitter } from "../../src"; +import { ImperativeEventEmitter, ImperativeSharedEvents, ImperativeUserEvents } from "../../.."; +import { ITestEnvironment } from "../../../../__tests__/__src__/environment/doc/response/ITestEnvironment"; +import { TestLogger } from "../../../../__tests__/src/TestLogger"; +import * as TestUtil from "../../../../__tests__/src/TestUtil"; +import { SetupTestEnvironment } from "../../../../__tests__/__src__/environment/SetupTestEnvironment"; +import * as fs from "fs"; +import * as path from "path"; + +let TEST_ENVIRONMENT: ITestEnvironment; +const iee = ImperativeEventEmitter; +const iee_user = ImperativeUserEvents; +const iee_shared = ImperativeSharedEvents; +let cwd = ''; describe("Event Emitter", () => { - const iee = ImperativeEventEmitter; + const mainModule = process.mainModule; + const testLogger = TestLogger.getTestLogger(); + + beforeAll(async () => { + (process.mainModule as any) = { + filename: __filename + }; + + TEST_ENVIRONMENT = await SetupTestEnvironment.createTestEnv({ + cliHomeEnvVar: "ZOWE_CLI_HOME", + testName: "event_emitter" + }); + cwd = TEST_ENVIRONMENT.workingDir; + }); - beforeEach(() => { - jest.restoreAllMocks(); + afterAll(() => { + process.mainModule = mainModule; + TestUtil.rimraf(cwd); }); + const doesEventFileExists = (eventType: string) => { + const eventDir = iee.instance.getEventDir(eventType); + if (!fs.existsSync(eventDir)) return false; + if (fs.existsSync(path.join(eventDir, eventType))) return true; + return false; + }; + describe("Shared Events", () => { - it("should create an event file upon first subscription if the file does not exist", () => { }); + it("should create an event file upon first subscription if the file does not exist", () => { + iee.initialize("zowe", { logger: testLogger }); + + const theEvent = iee_shared.ON_CREDENTIAL_MANAGER_CHANGED; + + expect(doesEventFileExists(theEvent)).toBeFalsy(); + + const subSpy = jest.fn(); + iee.instance.subscribe(theEvent, subSpy); + + expect(subSpy).not.toHaveBeenCalled(); + expect(doesEventFileExists(theEvent)).toBeTruthy(); + + expect(iee.instance.getEventContents(theEvent)).toBeFalsy(); + + iee.instance.emitEvent(theEvent); + + expect(doesEventFileExists(theEvent)).toBeTruthy(); + expect(iee.instance.getEventContents(theEvent)).toBeTruthy(); + expect(subSpy).toHaveBeenCalled(); + }); it("should trigger subscriptions for all instances watching for onCredentialManagerChanged", () => { }); it("should not affect subscriptions from another instance when unsubscribing from events", () => { }); }); diff --git a/packages/imperative/src/events/src/ImperativeEvent.ts b/packages/imperative/src/events/src/ImperativeEvent.ts index d8417295ed..b06f046560 100644 --- a/packages/imperative/src/events/src/ImperativeEvent.ts +++ b/packages/imperative/src/events/src/ImperativeEvent.ts @@ -43,6 +43,14 @@ export class ImperativeEvent { */ private mEventTime: string; + /** + * The location of the event + * @private + * @type {string} + * @memberof ImperativeEvent + */ + private mEventLoc: string; + /** * The type of event that occurred * @private @@ -64,7 +72,7 @@ export class ImperativeEvent { * @returns string representation of the imperative event */ public toString = (): string => { - return `Type: ${this.eventType} \t| Time: ${this.eventTime} \t| App: ${this.appName} \t| ID: ${this.eventId}`; + return `Type: ${this.type} \t| Time: ${this.time} \t| App: ${this.appName} \t| ID: ${this.id}`; }; /** @@ -73,10 +81,11 @@ export class ImperativeEvent { */ public toJson = (): IImperativeEventJson => { return { - time: this.eventTime, - type: this.eventType, + time: this.time, + type: this.type, source: this.appName, - id: this.eventId, + id: this.id, + loc: this.location, user: this.isUserEvent, }; }; @@ -85,16 +94,25 @@ export class ImperativeEvent { this.mEventTime = new Date().toISOString(); this.mEventID = randomUUID(); this.mAppID = parms.appName; - this.mEventType = parms.eventType; + this.mEventType = parms.eventName; this.isUserEvent = parms.isUser; parms.logger.debug("ImperativeEvent: " + this); } - public get eventTime(): string { + public set location(location: string) { + // TODO: (edge-case) Test whether we need to re-assign the location (multiple times) of an already initialized event + this.mEventLoc ||= location; + } + + public get location(): string { + return this.mEventLoc; + } + + public get time(): string { return this.mEventTime; } - public get eventType(): ImperativeEventType | string { + public get type(): ImperativeEventType | string { return this.mEventType; } @@ -102,7 +120,7 @@ export class ImperativeEvent { return this.mAppID; } - public get eventId() : string { + public get id() : string { return this.mEventID; } } diff --git a/packages/imperative/src/events/src/ImperativeEventEmitter.ts b/packages/imperative/src/events/src/ImperativeEventEmitter.ts index 429cfd07be..bae85dd628 100644 --- a/packages/imperative/src/events/src/ImperativeEventEmitter.ts +++ b/packages/imperative/src/events/src/ImperativeEventEmitter.ts @@ -90,12 +90,12 @@ export class ImperativeEventEmitter { /** * Helper method to initialize the event - * @param eventType The type of event to initialize + * @param eventName The type of event to initialize * @returns The initialized ImperativeEvent */ - private initEvent(eventType: ImperativeEventType | string): ImperativeEvent { + private initEvent(eventName: ImperativeEventType | string): ImperativeEvent { this.ensureClassInitialized(); - return new ImperativeEvent({ appName: this.appName, eventType, isUser: this.isUserEvent(eventType), logger: this.logger }); + return new ImperativeEvent({ appName: this.appName, eventName, isUser: this.isUserEvent(eventName), logger: this.logger }); } /** @@ -105,66 +105,65 @@ export class ImperativeEventEmitter { * @internal We do not want developers writing events directly, they should use the `emit...` methods */ private writeEvent(location: string, event: ImperativeEvent) { - const eventPath = join(location, event.eventType); - const eventJson = { ...event.toJson(), loc: location }; + event.location = location; this.ensureEventsDirExists(location); - fs.writeFileSync(eventPath, JSON.stringify(eventJson, null, 2)); + fs.writeFileSync(join(location, event.type), JSON.stringify(event.toJson(), null, 2)); } /** * Helper method to create watchers based on event strings and list of callbacks - * @param eventType type of event to which we will create a watcher for + * @param eventName type of event to which we will create a watcher for * @param callbacks list of all callbacks for this watcher * @returns The FSWatcher instance created */ - private setupWatcher(eventType: string, callbacks: Function[] = []): fs.FSWatcher { - const dir = this.getEventDir(eventType); + private setupWatcher(eventName: string, callbacks: Function[] = []): fs.FSWatcher { + const dir = this.getEventDir(eventName); this.ensureEventsDirExists(dir); //ensure .events exist - this.ensureEventFileExists(join(dir, eventType)); - const watcher = fs.watch(join(dir, eventType), (event: "rename" | "change") => { + this.ensureEventFileExists(join(dir, eventName)); + const watcher = fs.watch(join(dir, eventName), (event: "rename" | "change") => { // Node.JS triggers this event 3 times - const eventContents = fs.readFileSync(join(this.getEventDir(eventType), eventType)).toString(); + const eventContents = this.getEventContents(eventName); const eventTime = eventContents.length === 0 ? "" : (JSON.parse(eventContents) as IImperativeEventJson).time; - if (this.eventTimes.get(eventType) !== eventTime) { - this.logger.debug(`ImperativeEventEmitter: Event "${event}" emitted: ${eventType}`); + if (this.eventTimes.get(eventName) !== eventTime) { + this.logger.debug(`ImperativeEventEmitter: Event "${event}" emitted: ${eventName}`); // Promise.all(callbacks) callbacks.forEach(cb => cb()); - this.eventTimes.set(eventType, eventTime); + this.eventTimes.set(eventName, eventTime); } }); - this.subscriptions.set(eventType, [watcher, callbacks]); + this.subscriptions.set(eventName, [watcher, callbacks]); return watcher; } /** * Check to see if the given event is a User event - * @param eventType A string representing the type of event + * @param eventName A string representing the type of event * @returns True if it is a user event, false otherwise */ - public isUserEvent(eventType: string): eventType is ImperativeEventType { - return Object.values(ImperativeUserEvents).includes(eventType); + public isUserEvent(eventName: string): eventName is ImperativeEventType { + return Object.values(ImperativeUserEvents).includes(eventName); } /** * Check to see if the given event is a shared event - * @param eventType A string representing the type of event + * @param eventName A string representing the type of event * @returns True if it is a shared event, false otherwise */ - public isSharedEvent(eventType: string): eventType is ImperativeEventType { - return Object.values(ImperativeSharedEvents).includes(eventType); + public isSharedEvent(eventName: string): eventName is ImperativeEventType { + return Object.values(ImperativeSharedEvents).includes(eventName); } /** * Check to see if the given event is a Custom event - * @param eventType A string representing the type of event + * @param eventName A string representing the type of event * @returns True if it is not a zowe or a user event, false otherwise * @internal Not implemented in the MVP */ - public isCustomEvent(eventType: string): eventType is ImperativeEventType { - return !this.isUserEvent(eventType) && !this.isSharedEvent(eventType); + public isCustomEvent(eventName: string): eventName is ImperativeEventType { + return !this.isUserEvent(eventName) && !this.isSharedEvent(eventName); } /** @@ -183,44 +182,54 @@ export class ImperativeEventEmitter { /** * Obtain the directory of the event - * @param eventType The type of event to be emitted + * @param eventName The type of event to be emitted * @returns The directory to where this event will be emitted */ - public getEventDir(eventType: string): string { - if (this.isUserEvent(eventType)) { + public getEventDir(eventName: string): string { + if (this.isUserEvent(eventName)) { return this.getUserEventDir(); - } else if (this.isSharedEvent(eventType)) { + } else if (this.isSharedEvent(eventName)) { return this.getSharedEventDir(); } return this.getSharedEventDir(); } + /** + * Obtain the contents of the event + * @param eventName The type of event to retrieve contents from + * @returns The contents of the event + * @internal + */ + public getEventContents(eventName: string): string { + return fs.readFileSync(join(this.getEventDir(eventName), eventName)).toString(); + } + /** * Simple method to write the events to disk - * @param eventType The type of event to write + * @param eventName The type of event to write * @internal We do not want to make this function accessible to any application developers */ - public emitEvent(eventType: ImperativeEventType) { - const theEvent = this.initEvent(eventType); + public emitEvent(eventName: ImperativeEventType) { + const theEvent = this.initEvent(eventName); - if (this.isCustomEvent(eventType)) { - throw new ImperativeError({ msg: `Unable to determine the type of event. Event: ${eventType}` }); + if (this.isCustomEvent(eventName)) { + throw new ImperativeError({ msg: `Unable to determine the type of event. Event: ${eventName}` }); } - this.writeEvent(this.getEventDir(eventType), theEvent); + this.writeEvent(this.getEventDir(eventName), theEvent); } /** * Simple method to write the events to disk - * @param eventType The type of event to write + * @param eventName The type of event to write * @internal We won't support custom events as part of the MVP */ - public emitCustomEvent(eventType: string) { //, isUserSpecific: boolean = false) { - const theEvent = this.initEvent(eventType); + public emitCustomEvent(eventName: string) { //, isUserSpecific: boolean = false) { + const theEvent = this.initEvent(eventName); - if (!this.isCustomEvent(eventType)) { - throw new ImperativeError({ msg: `Operation not allowed. Event is considered protected. Event: ${eventType}` }); + if (!this.isCustomEvent(eventName)) { + throw new ImperativeError({ msg: `Operation not allowed. Event is considered protected. Event: ${eventName}` }); } this.writeEvent(this.getSharedEventDir(), theEvent); @@ -228,38 +237,38 @@ export class ImperativeEventEmitter { /** * Method to register your custom actions based on when the given event is emitted - * @param eventType Type of event to register + * @param eventName Type of event to register * @param callback Action to be registered to the given event */ - public subscribe(eventType: string, callback: Function): IImperativeRegisteredAction { + public subscribe(eventName: string, callback: Function): IImperativeRegisteredAction { this.ensureClassInitialized(); let watcher: fs.FSWatcher; - if (this.subscriptions.get(eventType) != null) { - const [watcherToClose, callbacks] = this.subscriptions.get(eventType); - watcherToClose.removeAllListeners(eventType).close(); + if (this.subscriptions.get(eventName) != null) { + const [watcherToClose, callbacks] = this.subscriptions.get(eventName); + watcherToClose.removeAllListeners(eventName).close(); - watcher = this.setupWatcher(eventType, [...callbacks, callback]); + watcher = this.setupWatcher(eventName, [...callbacks, callback]); } else { - watcher = this.setupWatcher(eventType, [callback]); + watcher = this.setupWatcher(eventName, [callback]); } return { close: watcher.close }; } /** * Method to unsubscribe from custom and regular events - * @param eventType Type of registered event + * @param eventName Type of registered event */ - public unsubscribe(eventType: string): void { + public unsubscribe(eventName: string): void { this.ensureClassInitialized(); - if (this.subscriptions.has(eventType)) { - const [watcherToClose, _callbacks] = this.subscriptions.get(eventType); - watcherToClose.removeAllListeners(eventType).close(); - this.subscriptions.delete(eventType); + if (this.subscriptions.has(eventName)) { + const [watcherToClose, _callbacks] = this.subscriptions.get(eventName); + watcherToClose.removeAllListeners(eventName).close(); + this.subscriptions.delete(eventName); } - if (this.eventTimes.has(eventType)) { - this.eventTimes.delete(eventType); + if (this.eventTimes.has(eventName)) { + this.eventTimes.delete(eventName); } } } \ No newline at end of file diff --git a/packages/imperative/src/events/src/doc/IImperativeEventParms.ts b/packages/imperative/src/events/src/doc/IImperativeEventParms.ts index e66efa5d4c..2220a99349 100644 --- a/packages/imperative/src/events/src/doc/IImperativeEventParms.ts +++ b/packages/imperative/src/events/src/doc/IImperativeEventParms.ts @@ -29,7 +29,7 @@ export interface IImperativeEventParms { * @type {ImperativeEventType} * @memberof IImperativeEventParms */ - eventType: ImperativeEventType | string + eventName: ImperativeEventType | string /** * Specifies whether this is a user event or not * @type {ImperativeEventType} From 95304a3150fa93ddba29f6aa3775b28d457b9431 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Wed, 8 May 2024 13:28:38 +0000 Subject: [PATCH 23/29] chore: add teardown stage Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- ...-test-cli.test.logging.integration.test.ts | 5 +++- ...ImperativeEventEmitter.integration.test.ts | 22 +++++++++++----- .../src/events/src/ImperativeEventEmitter.ts | 26 ++++++++++++++----- 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/test/cli.imperative-test-cli.test.logging.integration.test.ts b/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/test/cli.imperative-test-cli.test.logging.integration.test.ts index 1a9fd55447..1bd4648387 100644 --- a/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/test/cli.imperative-test-cli.test.logging.integration.test.ts +++ b/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/test/cli.imperative-test-cli.test.logging.integration.test.ts @@ -265,7 +265,10 @@ describe("imperative-test-cli test logging command", () => { // Set the ENV var for the script const response = runCliScript(__dirname + "/__scripts__/test_logging_cmd.sh", - TEST_ENVIRONMENT.workingDir, [], { IMPERATIVE_TEST_CLI_IMPERATIVE_LOG_LEVEL: "OFF" }); + TEST_ENVIRONMENT.workingDir, [], { + IMPERATIVE_TEST_CLI_IMPERATIVE_LOG_LEVEL: "OFF", + IMPERATIVE_TEST_CLI_APP_LOG_LEVEL: "OFF" + }); expect(response.stderr.toString()).toBe(""); expect(response.status).toBe(0); expect(response.stdout.toString()).toMatchSnapshot(); diff --git a/packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts b/packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts index 8600e13370..86ae40f77f 100644 --- a/packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts +++ b/packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts @@ -19,8 +19,8 @@ import * as path from "path"; let TEST_ENVIRONMENT: ITestEnvironment; const iee = ImperativeEventEmitter; -const iee_user = ImperativeUserEvents; -const iee_shared = ImperativeSharedEvents; +const iee_u = ImperativeUserEvents; +const iee_s = ImperativeSharedEvents; let cwd = ''; describe("Event Emitter", () => { @@ -39,6 +39,14 @@ describe("Event Emitter", () => { cwd = TEST_ENVIRONMENT.workingDir; }); + beforeEach(() => { + iee.initialize("zowe", { logger: testLogger }); + }); + + afterEach(() => { + iee.teardown(); + }); + afterAll(() => { process.mainModule = mainModule; TestUtil.rimraf(cwd); @@ -53,25 +61,27 @@ describe("Event Emitter", () => { describe("Shared Events", () => { it("should create an event file upon first subscription if the file does not exist", () => { - iee.initialize("zowe", { logger: testLogger }); - - const theEvent = iee_shared.ON_CREDENTIAL_MANAGER_CHANGED; + const theEvent = iee_s.ON_CREDENTIAL_MANAGER_CHANGED; expect(doesEventFileExists(theEvent)).toBeFalsy(); + let testVar = "event not emitted"; const subSpy = jest.fn(); iee.instance.subscribe(theEvent, subSpy); + // iee.instance.subscribe(theEvent, (()=>{testVar = "event emitted";}).bind(this)); expect(subSpy).not.toHaveBeenCalled(); + // expect(testVar).toContain("not"); expect(doesEventFileExists(theEvent)).toBeTruthy(); expect(iee.instance.getEventContents(theEvent)).toBeFalsy(); iee.instance.emitEvent(theEvent); - expect(doesEventFileExists(theEvent)).toBeTruthy(); expect(iee.instance.getEventContents(theEvent)).toBeTruthy(); expect(subSpy).toHaveBeenCalled(); + // expect((iee.instance as any).subscriptions.get(theEvent)[1][0]).toHaveBeenCalled(); + // expect(testVar).not.toContain("not"); }); it("should trigger subscriptions for all instances watching for onCredentialManagerChanged", () => { }); it("should not affect subscriptions from another instance when unsubscribing from events", () => { }); diff --git a/packages/imperative/src/events/src/ImperativeEventEmitter.ts b/packages/imperative/src/events/src/ImperativeEventEmitter.ts index bae85dd628..294fd0df9c 100644 --- a/packages/imperative/src/events/src/ImperativeEventEmitter.ts +++ b/packages/imperative/src/events/src/ImperativeEventEmitter.ts @@ -39,9 +39,22 @@ export class ImperativeEventEmitter { ConfigUtils.initImpUtils("zowe"); } - ImperativeEventEmitter.instance.appName = appName; - ImperativeEventEmitter.instance.logger = options?.logger ?? Logger.getImperativeLogger(); + this.instance.appName = appName; + this.instance.logger = options?.logger ?? Logger.getImperativeLogger(); } + + public static teardown() { + if (!this.initialized) { + return; + } + + for (const sub of [...this.instance.subscriptions.keys()]) { + this.instance.unsubscribe(sub); + } + + this.initialized = false; + } + public static get instance(): ImperativeEventEmitter { if (this.mInstance == null) { this.mInstance = new ImperativeEventEmitter(); @@ -106,7 +119,6 @@ export class ImperativeEventEmitter { */ private writeEvent(location: string, event: ImperativeEvent) { event.location = location; - this.ensureEventsDirExists(location); fs.writeFileSync(join(location, event.type), JSON.stringify(event.toJson(), null, 2)); } @@ -119,9 +131,9 @@ export class ImperativeEventEmitter { */ private setupWatcher(eventName: string, callbacks: Function[] = []): fs.FSWatcher { const dir = this.getEventDir(eventName); - this.ensureEventsDirExists(dir); //ensure .events exist - + this.ensureEventsDirExists(dir); this.ensureEventFileExists(join(dir, eventName)); + const watcher = fs.watch(join(dir, eventName), (event: "rename" | "change") => { // Node.JS triggers this event 3 times const eventContents = this.getEventContents(eventName); @@ -202,7 +214,9 @@ export class ImperativeEventEmitter { * @internal */ public getEventContents(eventName: string): string { - return fs.readFileSync(join(this.getEventDir(eventName), eventName)).toString(); + const eventLoc = join(this.getEventDir(eventName), eventName); + if (!fs.existsSync(eventLoc)) return ""; + return fs.readFileSync(eventLoc).toString(); } /** From 932c6c6b19d48bea3664b5080aa8e16b1f89db99 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Wed, 8 May 2024 14:58:33 +0000 Subject: [PATCH 24/29] test: update integration test Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- packages/imperative/CHANGELOG.md | 4 ++++ .../ImperativeEventEmitter.integration.test.ts | 15 ++++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/imperative/CHANGELOG.md b/packages/imperative/CHANGELOG.md index d7186b2f62..c474d01fff 100644 --- a/packages/imperative/CHANGELOG.md +++ b/packages/imperative/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to the Imperative package will be documented in this file. +## Recent Changes + +- Enhancement: Add client-side event handling capabilities. [#1987](https://github.com/zowe/zowe-cli/pull/1987) + ## `8.0.0-next.202403272026` - BugFix: Resolved technical currency by updating `markdown-it` dependency. [#2107](https://github.com/zowe/zowe-cli/pull/2107) diff --git a/packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts b/packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts index 86ae40f77f..77dbb45333 100644 --- a/packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts +++ b/packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts @@ -9,7 +9,7 @@ * */ -import { ImperativeEventEmitter, ImperativeSharedEvents, ImperativeUserEvents } from "../../.."; +import { IImperativeEventJson, ImperativeEventEmitter, ImperativeSharedEvents, ImperativeUserEvents } from "../../.."; import { ITestEnvironment } from "../../../../__tests__/__src__/environment/doc/response/ITestEnvironment"; import { TestLogger } from "../../../../__tests__/src/TestLogger"; import * as TestUtil from "../../../../__tests__/src/TestUtil"; @@ -65,23 +65,24 @@ describe("Event Emitter", () => { expect(doesEventFileExists(theEvent)).toBeFalsy(); - let testVar = "event not emitted"; const subSpy = jest.fn(); iee.instance.subscribe(theEvent, subSpy); - // iee.instance.subscribe(theEvent, (()=>{testVar = "event emitted";}).bind(this)); expect(subSpy).not.toHaveBeenCalled(); - // expect(testVar).toContain("not"); expect(doesEventFileExists(theEvent)).toBeTruthy(); expect(iee.instance.getEventContents(theEvent)).toBeFalsy(); iee.instance.emitEvent(theEvent); + + (iee.instance as any).subscriptions.get(theEvent)[1][0](); // simulate FSWatcher called + expect(doesEventFileExists(theEvent)).toBeTruthy(); - expect(iee.instance.getEventContents(theEvent)).toBeTruthy(); + const eventDetails: IImperativeEventJson = JSON.parse(iee.instance.getEventContents(theEvent)); + expect(eventDetails.type).toEqual(theEvent); + expect(eventDetails.user).toBeFalsy(); + expect(subSpy).toHaveBeenCalled(); - // expect((iee.instance as any).subscriptions.get(theEvent)[1][0]).toHaveBeenCalled(); - // expect(testVar).not.toContain("not"); }); it("should trigger subscriptions for all instances watching for onCredentialManagerChanged", () => { }); it("should not affect subscriptions from another instance when unsubscribing from events", () => { }); From bd7e040010fea9fcbfccb15abe48172247e4c58f Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Mon, 13 May 2024 14:55:36 +0000 Subject: [PATCH 25/29] fix: address PR comments :yum: Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- .../ImperativeEventEmitter.integration.test.ts | 2 +- .../imperative/src/events/src/ImperativeEventEmitter.ts | 8 ++++---- .../imperative/src/events/src/doc/IImperativeEventJson.ts | 2 ++ .../src/events/src/doc/IImperativeEventParms.ts | 4 ++-- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts b/packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts index 77dbb45333..5c851d4cb5 100644 --- a/packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts +++ b/packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts @@ -19,7 +19,7 @@ import * as path from "path"; let TEST_ENVIRONMENT: ITestEnvironment; const iee = ImperativeEventEmitter; -const iee_u = ImperativeUserEvents; +// const iee_u = ImperativeUserEvents; const iee_s = ImperativeSharedEvents; let cwd = ''; diff --git a/packages/imperative/src/events/src/ImperativeEventEmitter.ts b/packages/imperative/src/events/src/ImperativeEventEmitter.ts index 294fd0df9c..b711df95dd 100644 --- a/packages/imperative/src/events/src/ImperativeEventEmitter.ts +++ b/packages/imperative/src/events/src/ImperativeEventEmitter.ts @@ -18,13 +18,13 @@ import { ImperativeEventType, ImperativeUserEvents, ImperativeSharedEvents } fro import { ImperativeEvent } from "./ImperativeEvent"; import { Logger } from "../../logger/src/Logger"; import { LoggerManager } from "../../logger/src/LoggerManager"; -import { IImperativeRegisteredAction, IImperativeEventEmitterOpts, IImperativeEventJson } from "./doc"; +import { IImperativeRegisteredAction, IImperativeEventEmitterOpts, IImperativeEventJson, ImperativeEventCallback } from "./doc"; import { ConfigUtils } from "../../config/src/ConfigUtils"; export class ImperativeEventEmitter { private static mInstance: ImperativeEventEmitter; private static initialized = false; - private subscriptions: Map; + private subscriptions: Map; private eventTimes: Map; public appName: string; public logger: Logger; @@ -129,7 +129,7 @@ export class ImperativeEventEmitter { * @param callbacks list of all callbacks for this watcher * @returns The FSWatcher instance created */ - private setupWatcher(eventName: string, callbacks: Function[] = []): fs.FSWatcher { + private setupWatcher(eventName: string, callbacks: ImperativeEventCallback[] = []): fs.FSWatcher { const dir = this.getEventDir(eventName); this.ensureEventsDirExists(dir); this.ensureEventFileExists(join(dir, eventName)); @@ -254,7 +254,7 @@ export class ImperativeEventEmitter { * @param eventName Type of event to register * @param callback Action to be registered to the given event */ - public subscribe(eventName: string, callback: Function): IImperativeRegisteredAction { + public subscribe(eventName: string, callback: ImperativeEventCallback): IImperativeRegisteredAction { this.ensureClassInitialized(); let watcher: fs.FSWatcher; diff --git a/packages/imperative/src/events/src/doc/IImperativeEventJson.ts b/packages/imperative/src/events/src/doc/IImperativeEventJson.ts index 6f60d1f82f..5f6e8177b9 100644 --- a/packages/imperative/src/events/src/doc/IImperativeEventJson.ts +++ b/packages/imperative/src/events/src/doc/IImperativeEventJson.ts @@ -40,3 +40,5 @@ export interface IImperativeEventJson { */ user?: boolean; } + +export type ImperativeEventCallback = () => void | Promise; diff --git a/packages/imperative/src/events/src/doc/IImperativeEventParms.ts b/packages/imperative/src/events/src/doc/IImperativeEventParms.ts index 2220a99349..3a9568b694 100644 --- a/packages/imperative/src/events/src/doc/IImperativeEventParms.ts +++ b/packages/imperative/src/events/src/doc/IImperativeEventParms.ts @@ -29,13 +29,13 @@ export interface IImperativeEventParms { * @type {ImperativeEventType} * @memberof IImperativeEventParms */ - eventName: ImperativeEventType | string + eventName: ImperativeEventType | string; /** * Specifies whether this is a user event or not * @type {ImperativeEventType} * @memberof IImperativeEventParms */ - isUser: boolean + isUser: boolean; /** * The logger to use when logging the imperative event that occurred * @type {Logger} From 44e7a8065008d97433c170959c1de95f38509590 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Mon, 13 May 2024 18:19:39 +0000 Subject: [PATCH 26/29] tests: fix unit tests and add new ones for teardown and getContents Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- .../ImperativeEventEmitter.unit.test.ts | 45 ++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/packages/imperative/src/events/__tests__/__unit__/ImperativeEventEmitter.unit.test.ts b/packages/imperative/src/events/__tests__/__unit__/ImperativeEventEmitter.unit.test.ts index 8ba129dc1e..62c8326f7a 100644 --- a/packages/imperative/src/events/__tests__/__unit__/ImperativeEventEmitter.unit.test.ts +++ b/packages/imperative/src/events/__tests__/__unit__/ImperativeEventEmitter.unit.test.ts @@ -148,9 +148,9 @@ describe("Event Emitter", () => { () => { iee.instance.emitCustomEvent(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); }, // Subscribing should fail if IEE is not initialized - () => { iee.instance.subscribe("dummy", jest.fn); }, - () => { iee.instance.subscribe(ImperativeUserEvents.ON_VAULT_CHANGED, jest.fn); }, - () => { iee.instance.subscribe(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED, jest.fn); }, + () => { iee.instance.subscribe("dummy", jest.fn()); }, + () => { iee.instance.subscribe(ImperativeUserEvents.ON_VAULT_CHANGED, jest.fn()); }, + () => { iee.instance.subscribe(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED, jest.fn()); }, () => { iee.instance.unsubscribe("dummy"); }, () => { iee.instance.unsubscribe(ImperativeUserEvents.ON_VAULT_CHANGED); }, () => { iee.instance.unsubscribe(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); }, @@ -167,7 +167,7 @@ describe("Event Emitter", () => { const theEvent = ImperativeUserEvents.ON_VAULT_CHANGED; try { - iee.instance.subscribe(theEvent, jest.fn); + iee.instance.subscribe(theEvent, jest.fn()); } catch (err) { expect(err.message).toContain("Unable to create '.events' directory."); } @@ -177,7 +177,7 @@ describe("Event Emitter", () => { jest.spyOn(fs, "closeSync").mockImplementation(() => { throw "FILE"; }); try { - iee.instance.subscribe(theEvent, jest.fn); + iee.instance.subscribe(theEvent, jest.fn()); } catch (err) { expect(err.message).toContain("Unable to create event file."); } @@ -249,5 +249,40 @@ describe("Event Emitter", () => { iee.instance.unsubscribe("dummy"); expect(closeWatcher).toHaveBeenCalled(); }); + + it("should teardown the Event Emitter instance successfully", () => { + expect((iee as any).initialized).toBeFalsy(); + iee.teardown() + expect((iee as any).initialized).toBeFalsy(); + + iee.initialize("zowe", {logger: jest.fn() as any}); + expect((iee as any).initialized).toBeTruthy(); + + const dummyMap = { + has: () => (true), + delete: jest.fn(), + keys: () => ["dummy"], + get: () => ([{ removeAllListeners }, jest.fn()]) + }; + // Mocked map of subscriptions + (iee.instance as any).subscriptions = dummyMap; + (iee.instance as any).eventTimes = dummyMap; + + iee.teardown(); + expect(closeWatcher).toHaveBeenCalled(); + expect((iee as any).initialized).toBeFalsy(); + }); + + it("should retrieve event contents successfully", () => { + jest.spyOn(fs, "existsSync").mockReturnValueOnce(false); + iee.initialize("zowe"); + let contents = iee.instance.getEventContents(ImperativeUserEvents.ON_VAULT_CHANGED); + expect(contents).toEqual(""); + + jest.spyOn(fs, "existsSync").mockReturnValueOnce(true); + jest.spyOn(fs, "readFileSync").mockReturnValueOnce("dummy"); + contents = iee.instance.getEventContents(ImperativeUserEvents.ON_VAULT_CHANGED); + expect(contents).toEqual("dummy"); + }); }); }); From 31b05910a3c74525d2948ac2bfccdbb1f8a9bdc0 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Mon, 13 May 2024 19:19:43 +0000 Subject: [PATCH 27/29] tests: forgot to mock fs module Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- .../__tests__/__unit__/ImperativeEventEmitter.unit.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/imperative/src/events/__tests__/__unit__/ImperativeEventEmitter.unit.test.ts b/packages/imperative/src/events/__tests__/__unit__/ImperativeEventEmitter.unit.test.ts index 62c8326f7a..eafc4662bc 100644 --- a/packages/imperative/src/events/__tests__/__unit__/ImperativeEventEmitter.unit.test.ts +++ b/packages/imperative/src/events/__tests__/__unit__/ImperativeEventEmitter.unit.test.ts @@ -15,6 +15,8 @@ import { homedir } from "os"; import { Logger } from "../../../logger/src/Logger"; import { ImperativeEventEmitter, ImperativeSharedEvents, ImperativeUserEvents } from "../.."; +jest.mock("fs"); + describe("Event Emitter", () => { const iee = ImperativeEventEmitter; const sharedDir = join(__dirname, ".zowe", ".events"); From 5e45fd81e5468c8038df5f8be686b989a60d1124 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Mon, 13 May 2024 19:22:26 +0000 Subject: [PATCH 28/29] chore: address PR feedback Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- .../ImperativeEventEmitter.integration.test.ts | 3 +-- .../__tests__/__unit__/ImperativeEventEmitter.unit.test.ts | 2 +- packages/imperative/src/events/src/ImperativeEventEmitter.ts | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts b/packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts index 5c851d4cb5..2b4dd4041e 100644 --- a/packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts +++ b/packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts @@ -9,7 +9,7 @@ * */ -import { IImperativeEventJson, ImperativeEventEmitter, ImperativeSharedEvents, ImperativeUserEvents } from "../../.."; +import { IImperativeEventJson, ImperativeEventEmitter, ImperativeSharedEvents } from "../../.."; import { ITestEnvironment } from "../../../../__tests__/__src__/environment/doc/response/ITestEnvironment"; import { TestLogger } from "../../../../__tests__/src/TestLogger"; import * as TestUtil from "../../../../__tests__/src/TestUtil"; @@ -19,7 +19,6 @@ import * as path from "path"; let TEST_ENVIRONMENT: ITestEnvironment; const iee = ImperativeEventEmitter; -// const iee_u = ImperativeUserEvents; const iee_s = ImperativeSharedEvents; let cwd = ''; diff --git a/packages/imperative/src/events/__tests__/__unit__/ImperativeEventEmitter.unit.test.ts b/packages/imperative/src/events/__tests__/__unit__/ImperativeEventEmitter.unit.test.ts index eafc4662bc..4421d8b41b 100644 --- a/packages/imperative/src/events/__tests__/__unit__/ImperativeEventEmitter.unit.test.ts +++ b/packages/imperative/src/events/__tests__/__unit__/ImperativeEventEmitter.unit.test.ts @@ -254,7 +254,7 @@ describe("Event Emitter", () => { it("should teardown the Event Emitter instance successfully", () => { expect((iee as any).initialized).toBeFalsy(); - iee.teardown() + iee.teardown(); expect((iee as any).initialized).toBeFalsy(); iee.initialize("zowe", {logger: jest.fn() as any}); diff --git a/packages/imperative/src/events/src/ImperativeEventEmitter.ts b/packages/imperative/src/events/src/ImperativeEventEmitter.ts index b711df95dd..e8ca554397 100644 --- a/packages/imperative/src/events/src/ImperativeEventEmitter.ts +++ b/packages/imperative/src/events/src/ImperativeEventEmitter.ts @@ -181,14 +181,14 @@ export class ImperativeEventEmitter { /** * ZOWE HOME directory to search for system wide ImperativeEvents like `configChanged` */ - public getSharedEventDir(): string { + private getSharedEventDir(): string { return join(ImperativeConfig.instance.cliHome, ".events"); } /** * USER HOME directory to search for user specific ImperativeEvents like `vaultChanged` */ - public getUserEventDir(): string { + private getUserEventDir(): string { return join(homedir(), ".zowe", ".events"); } From d68e05d84269d9a228eba29ac4b1b96ebe634314 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Tue, 14 May 2024 20:19:42 +0000 Subject: [PATCH 29/29] chore: update inteface for EventCallback Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- packages/imperative/src/events/src/doc/IImperativeEventJson.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/imperative/src/events/src/doc/IImperativeEventJson.ts b/packages/imperative/src/events/src/doc/IImperativeEventJson.ts index 5f6e8177b9..b433c49ec7 100644 --- a/packages/imperative/src/events/src/doc/IImperativeEventJson.ts +++ b/packages/imperative/src/events/src/doc/IImperativeEventJson.ts @@ -41,4 +41,4 @@ export interface IImperativeEventJson { user?: boolean; } -export type ImperativeEventCallback = () => void | Promise; +export type ImperativeEventCallback = () => void | PromiseLike;