diff --git a/__tests__/__packages__/cli-test-utils/package.json b/__tests__/__packages__/cli-test-utils/package.json index a83dddbe8d..db4696f778 100644 --- a/__tests__/__packages__/cli-test-utils/package.json +++ b/__tests__/__packages__/cli-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/cli-test-utils", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "description": "Test utilities package for Zowe CLI plug-ins", "author": "Zowe", "license": "EPL-2.0", @@ -43,7 +43,7 @@ "devDependencies": { "@types/js-yaml": "^4.0.9", "@types/uuid": "^10.0.0", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/imperative": "8.0.0-next.202407112150" }, "peerDependencies": { "@zowe/imperative": "^8.0.0-next" diff --git a/lerna.json b/lerna.json index 5c27a61411..f40da649e2 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "command": { "publish": { "ignoreChanges": [ diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 2e130f2b7f..71d16c2ec4 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -52,7 +52,7 @@ }, "__tests__/__packages__/cli-test-utils": { "name": "@zowe/cli-test-utils", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "license": "EPL-2.0", "dependencies": { "find-up": "^5.0.0", @@ -63,7 +63,7 @@ "devDependencies": { "@types/js-yaml": "^4.0.9", "@types/uuid": "^10.0.0", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/imperative": "8.0.0-next.202407112150" }, "peerDependencies": { "@zowe/imperative": "^8.0.0-next" @@ -16333,21 +16333,21 @@ }, "packages/cli": { "name": "@zowe/cli", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "hasInstallScript": true, "license": "EPL-2.0", "dependencies": { - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717", - "@zowe/provisioning-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zos-console-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zos-jobs-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zos-logs-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zos-tso-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zos-workflows-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150", + "@zowe/provisioning-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zos-console-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zos-jobs-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zos-logs-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zos-tso-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zos-workflows-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407112150", "find-process": "1.4.7", "lodash": "4.17.21", "minimatch": "9.0.5", @@ -16360,7 +16360,7 @@ "@types/diff": "^5.0.9", "@types/lodash": "^4.17.6", "@types/tar": "^6.1.11", - "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/cli-test-utils": "8.0.0-next.202407112150", "comment-json": "^4.2.3", "strip-ansi": "^6.0.1", "which": "^4.0.0" @@ -16369,7 +16369,7 @@ "node": ">=18.12.0" }, "optionalDependencies": { - "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407051717" + "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407112150" } }, "packages/cli/node_modules/brace-expansion": { @@ -16416,15 +16416,15 @@ }, "packages/core": { "name": "@zowe/core-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "license": "EPL-2.0", "dependencies": { "comment-json": "~4.2.3", "string-width": "^4.2.3" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "engines": { "node": ">=18.12.0" @@ -16435,7 +16435,7 @@ }, "packages/imperative": { "name": "@zowe/imperative", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "license": "EPL-2.0", "dependencies": { "@types/yargs": "^17.0.32", @@ -16488,7 +16488,7 @@ "@types/pacote": "^11.1.8", "@types/progress": "^2.0.7", "@types/stack-trace": "^0.0.33", - "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407112150", "concurrently": "^8.0.0", "cowsay": "^1.6.0", "deep-diff": "^1.0.0", @@ -16637,16 +16637,16 @@ }, "packages/provisioning": { "name": "@zowe/provisioning-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "license": "EPL-2.0", "dependencies": { "js-yaml": "^4.1.0" }, "devDependencies": { "@types/js-yaml": "^4.0.9", - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "engines": { "node": ">=18.12.0" @@ -16658,7 +16658,7 @@ }, "packages/secrets": { "name": "@zowe/secrets-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "hasInstallScript": true, "license": "EPL-2.0", "devDependencies": { @@ -16671,15 +16671,15 @@ }, "packages/workflows": { "name": "@zowe/zos-workflows-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "license": "EPL-2.0", "dependencies": { - "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407051717" + "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407112150" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "engines": { "node": ">=18.12.0" @@ -16691,12 +16691,12 @@ }, "packages/zosconsole": { "name": "@zowe/zos-console-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "license": "EPL-2.0", "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "engines": { "node": ">=18.12.0" @@ -16708,16 +16708,16 @@ }, "packages/zosfiles": { "name": "@zowe/zos-files-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "license": "EPL-2.0", "dependencies": { "minimatch": "^9.0.5" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717", - "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150", + "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407112150" }, "engines": { "node": ">=18.12.0" @@ -16749,15 +16749,15 @@ }, "packages/zosjobs": { "name": "@zowe/zos-jobs-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "license": "EPL-2.0", "dependencies": { - "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407051717" + "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407112150" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "engines": { "node": ">=18.12.0" @@ -16769,12 +16769,12 @@ }, "packages/zoslogs": { "name": "@zowe/zos-logs-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "license": "EPL-2.0", "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "engines": { "node": ">=18.12.0" @@ -16786,12 +16786,12 @@ }, "packages/zosmf": { "name": "@zowe/zosmf-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "license": "EPL-2.0", "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "engines": { "node": ">=18.12.0" @@ -16803,15 +16803,15 @@ }, "packages/zostso": { "name": "@zowe/zos-tso-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "license": "EPL-2.0", "dependencies": { - "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407051717" + "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407112150" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "engines": { "node": ">=18.12.0" @@ -16823,15 +16823,15 @@ }, "packages/zosuss": { "name": "@zowe/zos-uss-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "license": "EPL-2.0", "dependencies": { "ssh2": "^1.15.0" }, "devDependencies": { "@types/ssh2": "^1.11.19", - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "engines": { "node": ">=18.12.0" diff --git a/packages/cli/package.json b/packages/cli/package.json index 5762a5938d..70fa9b4896 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/cli", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "zoweVersion": "V3", "description": "Zowe CLI is a command line interface (CLI) that provides a simple and streamlined way to interact with IBM z/OS.", "author": "Zowe", @@ -58,17 +58,17 @@ "preshrinkwrap": "node ../../scripts/rewriteShrinkwrap.js" }, "dependencies": { - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717", - "@zowe/provisioning-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zos-console-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zos-jobs-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zos-logs-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zos-tso-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zos-workflows-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150", + "@zowe/provisioning-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zos-console-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zos-jobs-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zos-logs-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zos-tso-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zos-workflows-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407112150", "find-process": "1.4.7", "lodash": "4.17.21", "minimatch": "9.0.5", @@ -78,13 +78,13 @@ "@types/diff": "^5.0.9", "@types/lodash": "^4.17.6", "@types/tar": "^6.1.11", - "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/cli-test-utils": "8.0.0-next.202407112150", "comment-json": "^4.2.3", "strip-ansi": "^6.0.1", "which": "^4.0.0" }, "optionalDependencies": { - "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407051717" + "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407112150" }, "engines": { "node": ">=18.12.0" diff --git a/packages/core/package.json b/packages/core/package.json index 1c07861c3a..f970c2d6d6 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/core-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "description": "Core libraries shared by Zowe SDK packages", "author": "Zowe", "license": "EPL-2.0", @@ -49,8 +49,8 @@ "string-width": "^4.2.3" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "peerDependencies": { "@zowe/imperative": "^8.0.0-next" diff --git a/packages/imperative/CHANGELOG.md b/packages/imperative/CHANGELOG.md index d48ceb0e2d..0276b9183c 100644 --- a/packages/imperative/CHANGELOG.md +++ b/packages/imperative/CHANGELOG.md @@ -6,6 +6,16 @@ All notable changes to the Imperative package will be documented in this file. - Enhancement: Added the function ConfigUtils.formGlobOrProjProfileNm and modified the function ConfigBuilder.build so that the 'zowe config init' command now generates a base profile name of 'global_base' or 'project_base', depending on whether a global or project configuration file is being generated. Related to Zowe Explorer issue https://github.com/zowe/zowe-explorer-vscode/issues/2682. +## `8.0.0-next.202407112150` + +- Enhancement: Add client-side custom-event handling capabilities. [#2136](https://github.com/zowe/zowe-cli/pull/2136) +- Next-Breaking: Refactored the Imperative Event Emitter class. [#2136](https://github.com/zowe/zowe-cli/pull/2136) + - Removed the `ImperativeEventEmitter` class. + - Added an `EventProcessor` class to handle event listening and emitting. + - Added an `EventOperator` class to handle creation and deletion of `EventProcessors`. + - Added an `EventUtils` class to contain all common utility methods for the Client Event Handling capabilities. + - Added `IEmitter`, `IWatcher`, and `IEmitterAndWatcher` interfaces to expose what application developers should see. + ## `8.0.0-next.202407051717` - BugFix: V3 Breaking: Modified the ConvertV1Profiles.convert API to accept a new ProfileInfo option and initialize components sufficiently to enable VSCode apps to convert V1 profiles. [#2170](https://github.com/zowe/zowe-cli/issues/2170) @@ -84,7 +94,7 @@ All notable changes to the Imperative package will be documented in this file. ## `8.0.0-next.202405151329` -- Enhancement: Add client-side event handling capabilities. [#1987](https://github.com/zowe/zowe-cli/pull/1987) +- Enhancement: Add client-side event handling capabilities. [#1987](https://github.com/zowe/zowe-cli/issues/1987) ## `8.0.0-next.202405061946` diff --git a/packages/imperative/__tests__/__integration__/cmd/__tests__/integration/cli/invalid/Cmd.cli.invalid.profile-spec.integration.test.ts b/packages/imperative/__tests__/__integration__/cmd/__tests__/integration/cli/invalid/Cmd.cli.invalid.profile-spec.integration.test.ts index 3483295980..fb1f2c2c38 100644 --- a/packages/imperative/__tests__/__integration__/cmd/__tests__/integration/cli/invalid/Cmd.cli.invalid.profile-spec.integration.test.ts +++ b/packages/imperative/__tests__/__integration__/cmd/__tests__/integration/cli/invalid/Cmd.cli.invalid.profile-spec.integration.test.ts @@ -27,9 +27,9 @@ describe("cmd-cli invalid profile-spec", () => { it("should fail the command if the profile property is not supplied and the handler requests a profile", () => { const response = runCliScript(__dirname + "/__scripts__/profile-spec.sh", TEST_ENVIRONMENT.workingDir); - expect(response.status).toBe(1); expect(response.stdout.toString()).toBe(''); expect(response.stderr.toString()).toContain('Internal Error: No profiles of type "blah" were loaded for this command.'); + expect(response.status).toBe(1); expect(response.stderr.toString()).toContain('This error can occur for one of two reasons:'); expect(response.stderr.toString()).toContain('- The "profile" property on the command definition document ' + 'does NOT specify the requested profile type'); diff --git a/packages/imperative/__tests__/__integration__/cmd/__tests__/integration/cli/nested/Cmd.cli.nested.first-group.integration.test.ts b/packages/imperative/__tests__/__integration__/cmd/__tests__/integration/cli/nested/Cmd.cli.nested.first-group.integration.test.ts index c72b87ed24..ee593928fc 100644 --- a/packages/imperative/__tests__/__integration__/cmd/__tests__/integration/cli/nested/Cmd.cli.nested.first-group.integration.test.ts +++ b/packages/imperative/__tests__/__integration__/cmd/__tests__/integration/cli/nested/Cmd.cli.nested.first-group.integration.test.ts @@ -26,8 +26,8 @@ describe("cmd-cli nested first-group", () => { it("should display both groups and commands in the help", () => { const response = runCliScript(__dirname + "/__scripts__/first-group/first_group_help.sh", TEST_ENVIRONMENT.workingDir); - expect(response.status).toBe(0); expect(response.stderr.toString()).toBe(""); + expect(response.status).toBe(0); expect(response.stdout.toString()).toMatchSnapshot(); }); }); diff --git a/packages/imperative/__tests__/__integration__/cmd/__tests__/integration/cli/nested/Cmd.cli.nested.integration.test.ts b/packages/imperative/__tests__/__integration__/cmd/__tests__/integration/cli/nested/Cmd.cli.nested.integration.test.ts index 7297199cee..d82165e726 100644 --- a/packages/imperative/__tests__/__integration__/cmd/__tests__/integration/cli/nested/Cmd.cli.nested.integration.test.ts +++ b/packages/imperative/__tests__/__integration__/cmd/__tests__/integration/cli/nested/Cmd.cli.nested.integration.test.ts @@ -25,8 +25,8 @@ describe("cmd-cli nested", () => { it("should display both groups and commands in the help", () => { const response = runCliScript(__dirname + "/__scripts__/nested/nested_help.sh", TEST_ENVIRONMENT.workingDir); - expect(response.status).toBe(0); expect(response.stderr.toString()).toBe(""); + expect(response.status).toBe(0); expect(response.stdout.toString()).toMatchSnapshot(); }); }); diff --git a/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/auto-init/imperative.test.cli.config.auto-init.fruit.integration.subtest.ts b/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/auto-init/imperative.test.cli.config.auto-init.fruit.integration.subtest.ts index 4e9ad9c6fe..ffbf94c9ec 100644 --- a/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/auto-init/imperative.test.cli.config.auto-init.fruit.integration.subtest.ts +++ b/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/auto-init/imperative.test.cli.config.auto-init.fruit.integration.subtest.ts @@ -118,7 +118,7 @@ describe("cmd-cli config auto-init", () => { expect(response.status).toBe(0); expect(glob.sync("*.json", { cwd: TEST_ENVIRONMENT.workingDir })) - .toEqual(["imperative-test-cli.config.json", "imperative-test-cli.schema.json"]); + .toEqual(["extenders.json", "imperative-test-cli.config.json", "imperative-test-cli.schema.json"]); const configJson: IConfig = jsonfile.readFileSync(TEST_ENVIRONMENT.workingDir + "/imperative-test-cli.config.json"); expect(configJson.profiles.base_fruit).toBeDefined(); expect(configJson.profiles.base_fruit.properties.tokenValue).toBeUndefined(); @@ -135,7 +135,7 @@ describe("cmd-cli config auto-init", () => { expect(response.status).toBe(0); expect(glob.sync("*.json", { cwd: TEST_ENVIRONMENT.workingDir })) - .toEqual(["imperative-test-cli.config.user.json", "imperative-test-cli.schema.json"]); + .toEqual(["extenders.json", "imperative-test-cli.config.user.json", "imperative-test-cli.schema.json"]); const configJson: IConfig = jsonfile.readFileSync(TEST_ENVIRONMENT.workingDir + "/imperative-test-cli.config.user.json"); expect(configJson.profiles.base_fruit).toBeDefined(); expect(configJson.profiles.base_fruit.properties.tokenValue).toBeUndefined(); diff --git a/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/convert-profiles/cli.imperative-test-cli.config.convert-profiles.integration.subtest.ts b/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/convert-profiles/cli.imperative-test-cli.config.convert-profiles.integration.subtest.ts index 6263057a56..8025b02006 100644 --- a/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/convert-profiles/cli.imperative-test-cli.config.convert-profiles.integration.subtest.ts +++ b/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/convert-profiles/cli.imperative-test-cli.config.convert-profiles.integration.subtest.ts @@ -30,6 +30,7 @@ describe("imperative-test-cli config convert-profiles", () => { testName: "imperative_test_cli_test_config_convert_profiles_command" }); configJsonPath = path.join(process.env.IMPERATIVE_TEST_CLI_CLI_HOME as string, "imperative-test-cli.config.json"); + // jest.spyOn(EventOperator, "getZoweProcessor").mockReturnValue({emitZoweEvent: jest.fn()} as any); }); beforeEach(() => { diff --git a/packages/imperative/package.json b/packages/imperative/package.json index 7a76896ed4..952c7ac2c8 100644 --- a/packages/imperative/package.json +++ b/packages/imperative/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/imperative", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "description": "framework for building configurable CLIs", "author": "Zowe", "license": "EPL-2.0", @@ -93,7 +93,7 @@ "@types/pacote": "^11.1.8", "@types/progress": "^2.0.7", "@types/stack-trace": "^0.0.33", - "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407112150", "concurrently": "^8.0.0", "cowsay": "^1.6.0", "deep-diff": "^1.0.0", diff --git a/packages/imperative/src/cmd/__tests__/CommandProcessor.unit.test.ts b/packages/imperative/src/cmd/__tests__/CommandProcessor.unit.test.ts index 104dddc311..aaaa738106 100644 --- a/packages/imperative/src/cmd/__tests__/CommandProcessor.unit.test.ts +++ b/packages/imperative/src/cmd/__tests__/CommandProcessor.unit.test.ts @@ -29,7 +29,6 @@ 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 8130b143c0..af72547cbb 100644 --- a/packages/imperative/src/config/__tests__/Config.api.unit.test.ts +++ b/packages/imperative/src/config/__tests__/Config.api.unit.test.ts @@ -19,8 +19,6 @@ 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 bd0f795db6..72b632c70a 100644 --- a/packages/imperative/src/config/__tests__/Config.secure.unit.test.ts +++ b/packages/imperative/src/config/__tests__/Config.secure.unit.test.ts @@ -18,7 +18,6 @@ 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"; @@ -47,9 +46,6 @@ 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 abfb7480d5..1ee1928519 100644 --- a/packages/imperative/src/config/__tests__/Config.unit.test.ts +++ b/packages/imperative/src/config/__tests__/Config.unit.test.ts @@ -20,8 +20,6 @@ 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 71f0fa1f85..6890f5a87b 100644 --- a/packages/imperative/src/config/__tests__/ConfigAutoStore.unit.test.ts +++ b/packages/imperative/src/config/__tests__/ConfigAutoStore.unit.test.ts @@ -10,7 +10,6 @@ */ jest.mock("../../logger/src/LoggerUtils"); -jest.mock("../../events/src/ImperativeEventEmitter"); import { AbstractAuthHandler } from "../../imperative/src/auth/handlers/AbstractAuthHandler"; import { SessConstants } from "../../rest"; diff --git a/packages/imperative/src/config/__tests__/ConfigUtils.unit.test.ts b/packages/imperative/src/config/__tests__/ConfigUtils.unit.test.ts index 4a22a9172f..f064825c66 100644 --- a/packages/imperative/src/config/__tests__/ConfigUtils.unit.test.ts +++ b/packages/imperative/src/config/__tests__/ConfigUtils.unit.test.ts @@ -10,12 +10,20 @@ */ import * as fs from "fs"; - +import * as path from "path"; +import * as os from "os"; +import * as jsonfile from "jsonfile"; import { ConfigUtils } from "../../config/src/ConfigUtils"; import { CredentialManagerFactory } from "../../security"; import { ImperativeConfig } from "../../utilities"; +import { EnvironmentalVariableSettings } from "../../imperative/src/env/EnvironmentalVariableSettings"; +import { IExtendersJsonOpts } from "../src/doc/IExtenderOpts"; describe("Config Utils", () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + describe("coercePropValue", () => { it("should parse value when type is boolean", () => { expect(ConfigUtils.coercePropValue("false", "boolean")).toBe(false); @@ -250,4 +258,112 @@ describe("Config Utils", () => { expect(baseProfileName).toEqual("project_base"); }); }); + + describe("getZoweDir", () => { + const expectedLoadedConfig = { + name: "zowe", + defaultHome: path.join("z", "zowe"), + envVariablePrefix: "ZOWE" + }; + let defaultHome: string; + let envReadSpy: any; + let homeDirSpy: any; + let loadedConfigOrig: any; + + beforeAll(() => { + loadedConfigOrig = ImperativeConfig.instance.loadedConfig; + }); + + beforeEach(() => { + envReadSpy = jest.spyOn(EnvironmentalVariableSettings, "read").mockReturnValue({ + cliHome: { value: null } + } as any); + homeDirSpy = jest.spyOn(os, "homedir").mockReturnValue(expectedLoadedConfig.defaultHome); + ImperativeConfig.instance.loadedConfig = undefined as any; + defaultHome = path.join(expectedLoadedConfig.defaultHome, ".zowe"); + }); + + afterAll(() => { + ImperativeConfig.instance.loadedConfig = loadedConfigOrig; + envReadSpy.mockRestore(); + homeDirSpy.mockRestore(); + }); + + it("should return the ENV cliHome even if loadedConfig is set in the process", () => { + jest.spyOn(EnvironmentalVariableSettings, "read").mockReturnValue({ cliHome: { value: "test" } } as any); + expect(ImperativeConfig.instance.loadedConfig).toBeUndefined(); + expect(ConfigUtils.getZoweDir()).toEqual("test"); + expect(ImperativeConfig.instance.loadedConfig).toEqual({ ...expectedLoadedConfig, defaultHome }); + }); + + it("should return the defaultHome and set loadedConfig if undefined", () => { + expect(ImperativeConfig.instance.loadedConfig).toBeUndefined(); + expect(ConfigUtils.getZoweDir()).toEqual(defaultHome); + expect(ImperativeConfig.instance.loadedConfig).toEqual({ ...expectedLoadedConfig, defaultHome }); + }); + + it("should return the defaultHome and reset loadedConfig if defaultHome changes", () => { + expect(ImperativeConfig.instance.loadedConfig).toBeUndefined(); + ImperativeConfig.instance.loadedConfig = { ...expectedLoadedConfig, defaultHome: "test" }; + expect(ImperativeConfig.instance.loadedConfig?.defaultHome).toEqual("test"); + expect(ConfigUtils.getZoweDir()).toEqual(defaultHome); + expect(ImperativeConfig.instance.loadedConfig).toEqual({ ...expectedLoadedConfig, defaultHome }); + }); + + it("should return the defaultHome without resetting loadedConfig", () => { + expect(ImperativeConfig.instance.loadedConfig).toBeUndefined(); + ImperativeConfig.instance.loadedConfig = expectedLoadedConfig; + expect(ConfigUtils.getZoweDir()).toEqual(defaultHome); + expect(ImperativeConfig.instance.loadedConfig).toEqual({ ...expectedLoadedConfig, defaultHome }); + }); + }); + + const dummyExtJson: IExtendersJsonOpts = { + profileTypes: { + "test": { + from: ["Zowe Client App"] + } + } + }; + describe("readExtendersJsonFromDisk", () => { + // case 1: the JSON file doesn't exist at time of read + it("writes an empty extenders.json file if it doesn't exist on disk", async () => { + const writeFileSyncMock = jest.spyOn(jsonfile, "writeFileSync").mockImplementation(); + jest.spyOn(fs, "existsSync").mockReturnValueOnce(false); + ConfigUtils.readExtendersJson(); + expect(writeFileSyncMock).toHaveBeenCalled(); + }); + + // case 2: JSON file exists on-disk at time of read + it("reads extenders.json from disk if it exists", async () => { + const readFileSyncMock = jest.spyOn(jsonfile, "readFileSync").mockReturnValueOnce(dummyExtJson); + jest.spyOn(fs, "existsSync").mockReturnValueOnce(true); + const result = ConfigUtils.readExtendersJson(); + expect(readFileSyncMock).toHaveBeenCalled(); + expect(result).toEqual({ + profileTypes: { + "test": { + from: ["Zowe Client App"] + } + } + }); + }); + }); + + describe("writeExtendersJson", () => { + // case 1: Write operation is successful + it("returns true if written to disk successfully", async () => { + const writeFileSyncMock = jest.spyOn(jsonfile, "writeFileSync").mockImplementation(); + expect(ConfigUtils.writeExtendersJson(dummyExtJson)).toBe(true); + expect(writeFileSyncMock).toHaveBeenCalled(); + }); + + // case 2: Write operation is unsuccessful + it("returns false if it couldn't write to disk", async () => { + const writeFileSyncMock = jest.spyOn(jsonfile, "writeFileSync").mockImplementation(); + writeFileSyncMock.mockImplementation(() => { throw new Error(); }); + expect(ConfigUtils.writeExtendersJson(dummyExtJson)).toBe(false); + expect(writeFileSyncMock).toHaveBeenCalled(); + }); + }); }); 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 02a66ad0d0..5bd89d66bc 100644 --- a/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts +++ b/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts @@ -10,7 +10,6 @@ */ import * as fs from "fs"; -import * as os from "os"; import * as path from "path"; import * as jsonfile from "jsonfile"; import * as lodash from "lodash"; @@ -25,7 +24,6 @@ import { ProfLocType } from "../src/doc/IProfLoc"; import { IProfileSchema } from "../../profiles"; import { AbstractSession, SessConstants } from "../../rest"; import { ConfigAutoStore } from "../src/ConfigAutoStore"; -import { EnvironmentalVariableSettings } from "../../imperative/src/env/EnvironmentalVariableSettings"; import { ImperativeConfig } from "../../utilities/src/ImperativeConfig"; import { ImperativeError } from "../../error"; import { IProfInfoUpdatePropOpts } from "../src/doc/IProfInfoUpdatePropOpts"; @@ -33,13 +31,14 @@ import { ConfigUtils } from "../src/ConfigUtils"; import { ConfigProfiles } from "../src/api"; import { IExtendersJsonOpts } from "../src/doc/IExtenderOpts"; import { ConfigSchema } from "../src/ConfigSchema"; -import { Logger } from "../.."; +import { Logger } from "../../logger/src/Logger"; -jest.mock("../../events/src/ImperativeEventEmitter"); const testAppNm = "ProfInfoApp"; const testEnvPrefix = testAppNm.toUpperCase(); const profileTypes = ["zosmf", "tso", "base", "dummy"]; +const testDir = path.join(__dirname, "__resources__"); +const teamProjDir = path.join(testDir, testAppNm + "_team_config_proj"); function createNewProfInfo(newDir: string, opts?: IProfOpts): ProfileInfo { // create a new ProfileInfo in the desired directory @@ -54,8 +53,6 @@ describe("TeamConfig ProfileInfo tests", () => { const tsoName = "tsoProfName"; const tsoProfName = "LPAR1.tsoProfName"; const tsoJsonLoc = "profiles.LPAR1.profiles." + tsoName; - const testDir = path.join(__dirname, "__resources__"); - const teamProjDir = path.join(testDir, testAppNm + "_team_config_proj"); const userTeamProjDir = path.join(testDir, testAppNm + "_user_and_team_config_proj"); const teamHomeProjDir = path.join(testDir, testAppNm + "_home_team_config_proj"); const largeTeamProjDir = path.join(testDir, testAppNm + "_large_team_config_proj"); @@ -154,65 +151,6 @@ describe("TeamConfig ProfileInfo tests", () => { }); }); - describe("getZoweDir", () => { - const expectedLoadedConfig = { - name: "zowe", - defaultHome: path.join("z", "zowe"), - envVariablePrefix: "ZOWE" - }; - let defaultHome: string; - let envReadSpy: any; - let homeDirSpy: any; - let loadedConfigOrig: any; - - beforeAll(() => { - loadedConfigOrig = ImperativeConfig.instance.loadedConfig; - }); - - beforeEach(() => { - envReadSpy = jest.spyOn(EnvironmentalVariableSettings, "read").mockReturnValue({ - cliHome: { value: null } - } as any); - homeDirSpy = jest.spyOn(os, "homedir").mockReturnValue(expectedLoadedConfig.defaultHome); - ImperativeConfig.instance.loadedConfig = undefined as any; - defaultHome = path.join(expectedLoadedConfig.defaultHome, ".zowe"); - }); - - afterAll(() => { - ImperativeConfig.instance.loadedConfig = loadedConfigOrig; - envReadSpy.mockRestore(); - homeDirSpy.mockRestore(); - }); - - it("should return the ENV cliHome even if loadedConfig is set in the process", () => { - jest.spyOn(EnvironmentalVariableSettings, "read").mockReturnValue({ cliHome: { value: "test" } } as any); - expect(ImperativeConfig.instance.loadedConfig).toBeUndefined(); - expect(ProfileInfo.getZoweDir()).toEqual("test"); - expect(ImperativeConfig.instance.loadedConfig).toEqual({ ...expectedLoadedConfig, defaultHome }); - }); - - it("should return the defaultHome and set loadedConfig if undefined", () => { - expect(ImperativeConfig.instance.loadedConfig).toBeUndefined(); - expect(ProfileInfo.getZoweDir()).toEqual(defaultHome); - expect(ImperativeConfig.instance.loadedConfig).toEqual({ ...expectedLoadedConfig, defaultHome }); - }); - - it("should return the defaultHome and reset loadedConfig if defaultHome changes", () => { - expect(ImperativeConfig.instance.loadedConfig).toBeUndefined(); - ImperativeConfig.instance.loadedConfig = { ...expectedLoadedConfig, defaultHome: "test" }; - expect(ImperativeConfig.instance.loadedConfig?.defaultHome).toEqual("test"); - expect(ProfileInfo.getZoweDir()).toEqual(defaultHome); - expect(ImperativeConfig.instance.loadedConfig).toEqual({ ...expectedLoadedConfig, defaultHome }); - }); - - it("should return the defaultHome without resetting loadedConfig", () => { - expect(ImperativeConfig.instance.loadedConfig).toBeUndefined(); - ImperativeConfig.instance.loadedConfig = expectedLoadedConfig; - expect(ProfileInfo.getZoweDir()).toEqual(defaultHome); - expect(ImperativeConfig.instance.loadedConfig).toEqual({ ...expectedLoadedConfig, defaultHome }); - }); - }); - describe("createSession", () => { const profAttrs: IProfAttrs = { profName: "profName", @@ -542,6 +480,9 @@ describe("TeamConfig ProfileInfo tests", () => { }); describe("mergeArgsForProfile", () => { + beforeEach(() => { + (ImperativeConfig as any).mInstance = null; + }); afterEach(() => { delete process.env[envHost]; delete process.env[envPort]; @@ -919,6 +860,7 @@ describe("TeamConfig ProfileInfo tests", () => { it("should not look for secure properties in the global-layer base profile if it does not exist", async () => { process.env[testEnvPrefix + "_CLI_HOME"] = nestedTeamProjDir; const profInfo = createNewProfInfo(userTeamProjDir); + process.env[testEnvPrefix + "_CLI_HOME"] = nestedTeamProjDir; await profInfo.readProfilesFromDisk(); const profiles = profInfo.getAllProfiles(); const desiredProfile = "TEST001.first"; @@ -933,7 +875,7 @@ describe("TeamConfig ProfileInfo tests", () => { } expect(unexpectedError).toBeUndefined(); } else { - expect("Profile " + desiredProfile + "not found").toBeUndefined(); + expect("Profile " + desiredProfile + " not found").toBeUndefined(); } expect(mergedArgs.missingArgs.find(a => a.argName === "user")?.secure).toBeTruthy(); expect(mergedArgs.missingArgs.find(a => a.argName === "password")?.secure).toBeTruthy(); @@ -1213,7 +1155,7 @@ describe("TeamConfig ProfileInfo tests", () => { const storageSpy = jest.spyOn(ConfigAutoStore as any, "_storeSessCfgProps").mockResolvedValue(undefined); const profiles = [{ type: "test", schema: {} as any }]; ImperativeConfig.instance.loadedConfig.profiles = profiles; - ImperativeConfig.instance.loadedConfig.baseProfile = null; + ImperativeConfig.instance.loadedConfig.baseProfile = undefined; let caughtError; try { @@ -1343,11 +1285,11 @@ describe("TeamConfig ProfileInfo tests", () => { const mergedArgs = profInfo.mergeArgsForProfile(profAttrs); const userArg = mergedArgs.knownArgs.find((arg) => arg.argName === "user"); - expect(userArg.argValue).toBe("userNameBase"); + expect(userArg?.argValue).toBe("userNameBase"); expect(profInfo.loadSecureArg(userArg as IProfArgAttrs)).toBe("userNameBase"); const passwordArg = mergedArgs.knownArgs.find((arg) => arg.argName === "password"); - expect(passwordArg.argValue).toBe("passwordBase"); + expect(passwordArg?.argValue).toBe("passwordBase"); expect(profInfo.loadSecureArg(passwordArg as IProfArgAttrs)).toBe("passwordBase"); }); @@ -1358,10 +1300,10 @@ describe("TeamConfig ProfileInfo tests", () => { const mergedArgs = profInfo.mergeArgsForProfile(profAttrs, { getSecureVals: true }); const userArg = mergedArgs.knownArgs.find((arg) => arg.argName === "user"); - expect(userArg.argValue).toBe("userNameBase"); + expect(userArg?.argValue).toBe("userNameBase"); const passwordArg = mergedArgs.knownArgs.find((arg) => arg.argName === "password"); - expect(passwordArg.argValue).toBe("passwordBase"); + expect(passwordArg?.argValue).toBe("passwordBase"); }); it("should treat secure arg as plain text if loaded from environment variable", async () => { @@ -1388,7 +1330,7 @@ describe("TeamConfig ProfileInfo tests", () => { profInfo.loadSecureArg({ argName: "test", dataType: "string", - argValue: undefined, + argValue: undefined as any, argLoc: { locType: ProfLocType.DEFAULT } }); } catch (error) { @@ -1415,8 +1357,12 @@ describe("TeamConfig ProfileInfo tests", () => { const profAttrs = profInfo.getDefaultProfile("zosmf") as IProfAttrs; const osLocInfo = profInfo.getOsLocInfo(profAttrs); const expectedObjs = [ - { name: profAttrs.profName, path: profAttrs.profLoc.osLoc[0], user: false, global: false }, - { name: profAttrs.profName, path: profAttrs.profLoc.osLoc[0], user: false, global: true } + { name: profAttrs.profName, path: profAttrs.profLoc.osLoc?.[0], user: false, global: false }, + // TODO(zFernand0): Investigate why only the team project is present in the osLoc array + // Possible reason: global layer not loaded by getAllProfiles() + // ---- + // Reseting the loaded configuration in `getZoweDir` may be the root cause. + { name: profAttrs.profName, path: profAttrs.profLoc.osLoc?.[0], user: false, global: true } ]; expect(osLocInfo).toBeDefined(); expect(osLocInfo.length).toBe(expectedObjs.length); @@ -1468,58 +1414,6 @@ describe("TeamConfig ProfileInfo tests", () => { }; // begin schema management tests - describe("readExtendersJsonFromDisk", () => { - // case 1: the JSON file doesn't exist at time of read - it("writes an empty extenders.json file if it doesn't exist on disk", async () => { - const profInfo = createNewProfInfo(teamProjDir); - (profInfo as any).mExtendersJson = { profileTypes: {} }; - jest.spyOn(fs, "existsSync").mockReturnValueOnce(false); - ProfileInfo.readExtendersJsonFromDisk(); - expect(writeFileSyncMock).toHaveBeenCalled(); - }); - - // case 2: JSON file exists on-disk at time of read - it("reads extenders.json from disk if it exists", async () => { - const readFileSyncMock = jest.spyOn(jsonfile, "readFileSync").mockReturnValueOnce({ profileTypes: { - "test": { - from: ["Zowe Client App"] - } - } }); - const profInfo = createNewProfInfo(teamProjDir); - jest.spyOn(fs, "existsSync").mockReturnValueOnce(true); - (profInfo as any).mExtendersJson = ProfileInfo.readExtendersJsonFromDisk(); - expect(readFileSyncMock).toHaveBeenCalled(); - expect((profInfo as any).mExtendersJson).toEqual({ - profileTypes: { - "test": { - from: ["Zowe Client App"] - } - } - }); - }); - }); - - describe("writeExtendersJson", () => { - // case 1: Write operation is successful - it("returns true if written to disk successfully", async () => { - const profInfo = createNewProfInfo(teamProjDir); - (profInfo as any).mExtendersJson = { profileTypes: {} }; - await profInfo.readProfilesFromDisk({ homeDir: teamHomeProjDir }); - expect(ProfileInfo.writeExtendersJson((profInfo as any).mExtendersJson)).toBe(true); - expect(writeFileSyncMock).toHaveBeenCalled(); - }); - - // case 2: Write operation is unsuccessful - it("returns false if it couldn't write to disk", async () => { - const profInfo = createNewProfInfo(teamProjDir); - (profInfo as any).mExtendersJson = { profileTypes: {} }; - await profInfo.readProfilesFromDisk({ homeDir: teamHomeProjDir }); - writeFileSyncMock.mockImplementation(() => { throw new Error(); }); - expect(ProfileInfo.writeExtendersJson((profInfo as any).mExtendersJson)).toBe(false); - expect(writeFileSyncMock).toHaveBeenCalled(); - }); - }); - describe("updateSchemaAtLayer", () => { const getBlockMocks = () => { return { @@ -1671,7 +1565,7 @@ describe("TeamConfig ProfileInfo tests", () => { } as any); const updateSchemaAtLayerMock = jest.spyOn((ProfileInfo as any).prototype, "updateSchemaAtLayer") .mockReturnValue(expected.res.success); - const writeExtendersJsonMock = jest.spyOn(ProfileInfo, "writeExtendersJson").mockImplementation(); + const writeExtendersJsonMock = jest.spyOn(ConfigUtils, "writeExtendersJson").mockImplementation(); const res = profInfo.addProfileTypeToSchema("some-type", { ...testCase, sourceApp: "Zowe Client App" }); if (expected.res.success) { expect(updateSchemaAtLayerMock).toHaveBeenCalled(); diff --git a/packages/imperative/src/config/__tests__/__resources__/project.config.json b/packages/imperative/src/config/__tests__/__resources__/project.config.json index c2a1b3be7f..5a1f5b485f 100644 --- a/packages/imperative/src/config/__tests__/__resources__/project.config.json +++ b/packages/imperative/src/config/__tests__/__resources__/project.config.json @@ -43,4 +43,4 @@ }, "plugins": [], "autoStore": true -} +} \ No newline at end of file diff --git a/packages/imperative/src/config/src/Config.ts b/packages/imperative/src/config/src/Config.ts index 381ee06783..999f568960 100644 --- a/packages/imperative/src/config/src/Config.ts +++ b/packages/imperative/src/config/src/Config.ts @@ -31,8 +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"; -import { Logger } from "../../logger"; /** * Enum used by Config class to maintain order of config layers @@ -155,8 +153,6 @@ export class Config { myNewConfig.mVault = opts.vault; myNewConfig.mSecure = {}; - ImperativeEventEmitter.initialize(app, { logger:Logger.getAppLogger() }); - // Populate configuration file layers await myNewConfig.reload(opts); @@ -247,7 +243,7 @@ export class Config { * Get absolute file path for a config layer. * For project config files, We search up from our current directory and * ignore the Zowe hone directory (in case our current directory is under - * Zowe home.). For golbal config files we only retrieve config files + * Zowe home.). For global config files we only retrieve config files * from the Zowe home directory. * * @internal diff --git a/packages/imperative/src/config/src/ConfigUtils.ts b/packages/imperative/src/config/src/ConfigUtils.ts index 77ea57fe94..79b8640537 100644 --- a/packages/imperative/src/config/src/ConfigUtils.ts +++ b/packages/imperative/src/config/src/ConfigUtils.ts @@ -12,6 +12,7 @@ import { homedir as osHomedir } from "os"; import { normalize as pathNormalize, join as pathJoin } from "path"; import { existsSync as fsExistsSync } from "fs"; +import * as jsonfile from "jsonfile"; import { CredentialManagerFactory } from "../../security/src/CredentialManagerFactory"; import { ICommandArguments } from "../../cmd"; @@ -22,9 +23,62 @@ import { LoggingConfigurer } from "../../imperative/src/LoggingConfigurer"; import { Logger } from "../../logger/src/Logger"; import { EnvironmentalVariableSettings } from "../../imperative/src/env/EnvironmentalVariableSettings"; import { IConfigProfile } from "./doc/IConfigProfile"; - +import { IExtendersJsonOpts } from "./doc/IExtenderOpts"; export class ConfigUtils { + /** + * Retrieves the Zowe CLI home directory. In the situation Imperative has + * not initialized it we use a default value. + * @returns {string} - Returns the Zowe home directory + */ + public static getZoweDir(): string { + const defaultHome = pathJoin(osHomedir(), ".zowe"); + if (ImperativeConfig.instance.loadedConfig?.defaultHome !== defaultHome) { + ImperativeConfig.instance.loadedConfig = { + name: "zowe", + defaultHome, + envVariablePrefix: "ZOWE" + }; + } + return ImperativeConfig.instance.cliHome; + } + + /** + * Reads the `extenders.json` file from the CLI home directory. + * Called once in `readProfilesFromDisk` and cached to minimize I/O operations. + * @internal + * @throws If the extenders.json file cannot be created when it does not exist. + * @throws If the extenders.json file cannot be read. + */ + public static readExtendersJson(): IExtendersJsonOpts { + const cliHome = ImperativeConfig.instance.loadedConfig != null ? ImperativeConfig.instance.cliHome : ConfigUtils.getZoweDir(); + const extenderJsonPath = pathJoin(cliHome, "extenders.json"); + if (!fsExistsSync(extenderJsonPath)) { + jsonfile.writeFileSync(extenderJsonPath, { + profileTypes: {} + }, { spaces: 4 }); + return { profileTypes: {} }; + } else { + return jsonfile.readFileSync(extenderJsonPath); + } + } + + /** + * Attempts to write to the `extenders.json` file in the CLI home directory. + * @returns `true` if written successfully; `false` otherwise + * @internal + */ + public static writeExtendersJson(obj: IExtendersJsonOpts): boolean { + try { + const extenderJsonPath = pathJoin(ConfigUtils.getZoweDir(), "extenders.json"); + jsonfile.writeFileSync(extenderJsonPath, obj, { spaces: 4 }); + } catch (err) { + return false; + } + + return true; + } + /** * Coerces string property value to a boolean or number type. * @param value String value diff --git a/packages/imperative/src/config/src/ProfileInfo.ts b/packages/imperative/src/config/src/ProfileInfo.ts index f5204e242e..1230df3706 100644 --- a/packages/imperative/src/config/src/ProfileInfo.ts +++ b/packages/imperative/src/config/src/ProfileInfo.ts @@ -10,8 +10,6 @@ */ import * as fs from "fs"; -import * as os from "os"; -import * as path from "path"; import * as url from "url"; import * as jsonfile from "jsonfile"; import * as lodash from "lodash"; @@ -731,17 +729,10 @@ export class ProfileInfo { * Retrieves the Zowe CLI home directory. In the situation Imperative has * not initialized it we use a default value. * @returns {string} - Returns the Zowe home directory + * @deprecated Use ConfigUtils.getZoweDir() */ public static getZoweDir(): string { - const defaultHome = path.join(os.homedir(), ".zowe"); - if (ImperativeConfig.instance.loadedConfig?.defaultHome !== defaultHome) { - ImperativeConfig.instance.loadedConfig = { - name: "zowe", - defaultHome, - envVariablePrefix: "ZOWE" - }; - } - return ImperativeConfig.instance.cliHome; + return ConfigUtils.getZoweDir(); } /** @@ -838,7 +829,7 @@ export class ProfileInfo { }); } - this.mExtendersJson = ProfileInfo.readExtendersJsonFromDisk(); + this.mExtendersJson = ConfigUtils.readExtendersJson(); this.loadAllSchemas(); } @@ -1036,33 +1027,20 @@ export class ProfileInfo { * Reads the `extenders.json` file from the CLI home directory. * Called once in `readProfilesFromDisk` and cached to minimize I/O operations. * @internal + * @deprecated Please use `ConfigUtils.readExtendersJson` instead */ public static readExtendersJsonFromDisk(): IExtendersJsonOpts { - const extenderJsonPath = path.join(ImperativeConfig.instance.cliHome, "extenders.json"); - if (!fs.existsSync(extenderJsonPath)) { - jsonfile.writeFileSync(extenderJsonPath, { - profileTypes: {} - }, { spaces: 4 }); - return { profileTypes: {} }; - } else { - return jsonfile.readFileSync(extenderJsonPath); - } + return ConfigUtils.readExtendersJson(); } /** * Attempts to write to the `extenders.json` file in the CLI home directory. * @returns `true` if written successfully; `false` otherwise * @internal + * @deprecated Please use `ConfigUtils.writeExtendersJson` instead */ public static writeExtendersJson(obj: IExtendersJsonOpts): boolean { - try { - const extenderJsonPath = path.join(ImperativeConfig.instance.cliHome, "extenders.json"); - jsonfile.writeFileSync(extenderJsonPath, obj, { spaces: 4 }); - } catch (err) { - return false; - } - - return true; + return ConfigUtils.writeExtendersJson(obj); } /** @@ -1284,7 +1262,7 @@ export class ProfileInfo { // Update contents of extenders.json if it has changed if (wasGlobalUpdated && !lodash.isEqual(oldExtendersJson, this.mExtendersJson)) { - if (!ProfileInfo.writeExtendersJson(this.mExtendersJson)) { + if (!ConfigUtils.writeExtendersJson(this.mExtendersJson)) { return { success: true, // Even if we failed to update extenders.json, it was technically added to the schema cache. @@ -1608,7 +1586,7 @@ export class ProfileInfo { // _______________________________________________________________________ /** * Override values in a merged argument object with values found in - * environment variables. The choice to override enviroment variables is + * environment variables. The choice to override environment variables is * controlled by an option on the ProfileInfo constructor. * * @param mergedArgs @@ -1622,8 +1600,10 @@ export class ProfileInfo { private overrideWithEnv(mergedArgs: IProfMergedArg, profSchema?: IProfileSchema) { if (!this.mOverrideWithEnv) return; // Don't do anything + const envPrefix = this.mAppName.toUpperCase(); + // Do we expect to always read "ZOWE_OPT_" environmental variables or "APPNAME_OPT_"? + // Populate any missing options - const envPrefix = ImperativeConfig.instance.loadedConfig.envVariablePrefix; const envStart = envPrefix + "_OPT_"; for (const key in process.env) { if (key.startsWith(envStart)) { diff --git a/packages/imperative/src/config/src/api/ConfigSecure.ts b/packages/imperative/src/config/src/api/ConfigSecure.ts index 474b754290..7586cfaab1 100644 --- a/packages/imperative/src/config/src/api/ConfigSecure.ts +++ b/packages/imperative/src/config/src/api/ConfigSecure.ts @@ -20,8 +20,8 @@ import { ConfigConstants } from "../ConfigConstants"; import { IConfigProfile } from "../doc/IConfigProfile"; import { CredentialManagerFactory } from "../../../security"; import { ConfigUtils } from "../ConfigUtils"; -import { ImperativeEventEmitter } from "../../../events/src/ImperativeEventEmitter"; -import { ImperativeUserEvents } from "../../../events/src/ImperativeEventConstants"; +import { ZoweUserEvents } from "../../../events/src/EventConstants"; +import { EventOperator } from "../../../events/src/EventOperator"; /** * API Class for manipulating config layers. @@ -132,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.instance.emitEvent(ImperativeUserEvents.ON_VAULT_CHANGED); + EventOperator.getZoweProcessor().emitZoweEvent(ZoweUserEvents.ON_VAULT_CHANGED); } // _______________________________________________________________________ diff --git a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts new file mode 100644 index 0000000000..12c1741456 --- /dev/null +++ b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts @@ -0,0 +1,178 @@ +/* +* 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 { SetupTestEnvironment } from "../../../../__tests__/__src__/environment/SetupTestEnvironment"; +import { ConfigUtils, EventCallback, EventOperator, EventProcessor, EventUtils, IEventJson, ZoweSharedEvents } from "../../.."; +import * as fs from "fs"; +import * as path from "path"; +import { IExtendersJsonOpts } from "../../../config/src/doc/IExtenderOpts"; + +const zoweApp = "Zowe"; +const sampleApps = ["firstApp", "secondApp"]; +let zoweCliHome: string; + +/** + * ## Understanding Event Files + * | Zowe Event Dir | <...>/.zowe/.events/Zowe/ + * | Custom Event Dir | <...>/.zowe/.events/custApp/ + * + * ## Understanding Event Types + * - **Shared Events**: Zowe events that when triggered, notify all subscribed users. + * - **User Events**: Zowe events that are specific to a particular user or session. + * - **Custom Events**: Applications can define their own shared and user events. +*/ +describe("Event Operator and Processor", () => { + const sharedZoweEvent = ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED; + const customUserEvent = "onCustomUserEvent"; + beforeAll(async () => { + await SetupTestEnvironment.createTestEnv({ + cliHomeEnvVar: "ZOWE_CLI_HOME", + testName: "event_operator_and_processor" + }); + // have to reset because test environment doesn't add .zowe to ZOWE_CLI_HOME :( + process.env.ZOWE_CLI_HOME = path.join(process.env.ZOWE_CLI_HOME || '', ".zowe"); + zoweCliHome = process.env.ZOWE_CLI_HOME; + EventUtils.ensureEventsDirExists(zoweCliHome); + const extJson: IExtendersJsonOpts = ConfigUtils.readExtendersJson(); + sampleApps.forEach(app => extJson.profileTypes[app] = { from: [app] }); + ConfigUtils.writeExtendersJson(extJson); + }); + + beforeEach(() => { + jest.restoreAllMocks(); + }); + + describe("Zowe Events", () => { + it("should create an event file upon first subscription if file does not exist", async () => { + const setupWatcherSpy = jest.spyOn(EventUtils, "setupWatcher"); + const callback = jest.fn(); + const Watcher = EventOperator.getWatcher(zoweApp); + const Emitter = EventOperator.getZoweProcessor(); + + expect((Watcher as EventProcessor).subscribedEvents.get(sharedZoweEvent)).toBeFalsy(); + + // Subscribe to event + Watcher.subscribeShared(sharedZoweEvent, callback); + const eventDetails: IEventJson = (Watcher as any).subscribedEvents.get(sharedZoweEvent).toJson(); + + expect(callback).not.toHaveBeenCalled(); + expect(fs.existsSync(eventDetails.eventFilePath)).toBeTruthy(); + + // Emit event and trigger callback + Emitter.emitZoweEvent(sharedZoweEvent); + setupWatcherSpy.mock.calls.forEach(call => (call[2] as Function)()); // Mock event emission + + expect(eventDetails.eventName).toEqual(sharedZoweEvent); + expect(EventUtils.isSharedEvent(eventDetails.eventName)).toBeTruthy(); + expect(callback).toHaveBeenCalled(); + EventOperator.deleteProcessor(zoweApp); + }); + + it("should trigger subscriptions for all instances watching for ZoweSharedEvent", async () => { + const setupWatcherSpy = jest.spyOn(EventUtils, "setupWatcher"); + // create two watchers watching the same app (Zowe - default) + const firstWatcher = EventOperator.getWatcher(); + const secondWatcher = EventOperator.getWatcher(); + const Emitter = EventOperator.getZoweProcessor(); + + const firstCallback: EventCallback = jest.fn() as EventCallback; + const secondCallback: EventCallback = jest.fn() as EventCallback; + + // We expect no subscriptions yet + expect((firstWatcher as EventProcessor).subscribedEvents.get(sharedZoweEvent)).toBeFalsy(); + expect((secondWatcher as EventProcessor).subscribedEvents.get(sharedZoweEvent)).toBeFalsy(); + + // Subscribe to event + firstWatcher.subscribeShared(sharedZoweEvent, firstCallback); + secondWatcher.subscribeShared(sharedZoweEvent, secondCallback); + const firstEventDetails: IEventJson = (firstWatcher as any).subscribedEvents.get(sharedZoweEvent).toJson(); + const secondEventDetails: IEventJson = (secondWatcher as any).subscribedEvents.get(sharedZoweEvent).toJson(); + + // Emit event and trigger callbacks + Emitter.emitZoweEvent(sharedZoweEvent); + setupWatcherSpy.mock.calls.forEach(call => (call[2] as Function)()); + + expect(firstEventDetails.eventName).toEqual(sharedZoweEvent); + expect(secondEventDetails.eventName).toEqual(sharedZoweEvent); + expect(EventUtils.isSharedEvent(firstEventDetails.eventName)).toBeTruthy(); + expect(EventUtils.isSharedEvent(secondEventDetails.eventName)).toBeTruthy(); + expect(firstCallback).toHaveBeenCalled(); + expect(secondCallback).toHaveBeenCalled(); + EventOperator.deleteProcessor(zoweApp); + }); + + it("should not affect subscriptions from another instance when unsubscribing from events", async () => { + const setupWatcherSpy = jest.spyOn(EventUtils, "setupWatcher"); + // create two watchers watching the same app (Zowe) + // BUT because we are in the same process and can't actually simulate different processes, + // need to fake out unsubscription of secondWatcher by watching for the same event on another app + const firstWatcher = EventOperator.getWatcher(); + const secondWatcher = EventOperator.getWatcher(sampleApps[0]); + const Emitter = EventOperator.getZoweProcessor(); + + const firstCallback: EventCallback = jest.fn() as EventCallback; + const secondCallback: EventCallback = jest.fn() as EventCallback; + + expect((firstWatcher as EventProcessor).subscribedEvents.get(sharedZoweEvent)).toBeFalsy(); + expect((secondWatcher as EventProcessor).subscribedEvents.get(sharedZoweEvent)).toBeFalsy(); + + // Subscribe to event + firstWatcher.subscribeShared(sharedZoweEvent, firstCallback); + secondWatcher.subscribeShared(sharedZoweEvent, secondCallback); + + // unsubscribe! + secondWatcher.unsubscribe(sharedZoweEvent); + + expect((firstWatcher as any).subscribedEvents.get(sharedZoweEvent)).toBeTruthy(); + expect((secondWatcher as any).subscribedEvents.get(sharedZoweEvent)).toBeFalsy(); + + // Emit event and trigger callbacks + Emitter.emitZoweEvent(sharedZoweEvent); + setupWatcherSpy.mock.calls.forEach(call => { + if (call[0].appName === zoweApp) { (call[2] as Function)(); } + }); + + expect(firstCallback).toHaveBeenCalled(); + expect(secondCallback).not.toHaveBeenCalled(); + EventOperator.deleteProcessor(sampleApps[0]); + EventOperator.deleteProcessor(zoweApp); + }); + }); + + describe("Custom Events", () => { + it("should create an event file upon first subscription if file does not exist - specific to CustomUserEvent directory structure", + async () => { + const setupWatcherSpy = jest.spyOn(EventUtils, "setupWatcher"); + const callback = jest.fn(); + const Watcher = EventOperator.getWatcher(sampleApps[1]); + const Emitter = EventOperator.getEmitter(sampleApps[1]); + const eventDir = path.join(zoweCliHome, ".events", sampleApps[1]); //corresponds to emitter's event file + + expect((Watcher as EventProcessor).subscribedEvents.get(customUserEvent)).toBeFalsy(); + + // Subscribe to event + Watcher.subscribeUser(customUserEvent, callback); + const eventDetails: IEventJson = (Watcher as any).subscribedEvents.get(customUserEvent).toJson(); + expect(callback).not.toHaveBeenCalled(); + expect(fs.existsSync(eventDetails.eventFilePath)).toBeTruthy(); + + // Emit event and trigger callback + Emitter.emitEvent(customUserEvent); + setupWatcherSpy.mock.calls.forEach(call => (call[2] as Function)()); + + expect(eventDetails.eventName).toEqual(customUserEvent); + expect(eventDetails.eventFilePath).toContain(eventDir); + expect(callback).toHaveBeenCalled(); + expect(EventUtils.isUserEvent(eventDetails.eventName)).toBeFalsy(); //ensuring this custom event isnt a Zowe event + EventOperator.deleteProcessor(sampleApps[1]); + }); + }); +}); diff --git a/packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts b/packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts deleted file mode 100644 index f1f1d7f677..0000000000 --- a/packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts +++ /dev/null @@ -1,95 +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 { 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"; -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_s = ImperativeSharedEvents; -let cwd = ''; - -describe("Event Emitter", () => { - const testLogger = TestLogger.getTestLogger(); - - beforeAll(async () => { - TEST_ENVIRONMENT = await SetupTestEnvironment.createTestEnv({ - cliHomeEnvVar: "ZOWE_CLI_HOME", - testName: "event_emitter" - }); - cwd = TEST_ENVIRONMENT.workingDir; - }); - - beforeEach(() => { - iee.initialize("zowe", { logger: testLogger }); - }); - - afterEach(() => { - iee.teardown(); - }); - - afterAll(() => { - 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", () => { - const theEvent = iee_s.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); - - (iee.instance as any).subscriptions.get(theEvent)[1][0](); // simulate FSWatcher called - - expect(doesEventFileExists(theEvent)).toBeTruthy(); - const eventDetails: IImperativeEventJson = JSON.parse(iee.instance.getEventContents(theEvent)); - expect(eventDetails.type).toEqual(theEvent); - expect(eventDetails.user).toBeFalsy(); - - 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", () => { }); - }); - - 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__/__unit__/EventOperator.unit.test.ts b/packages/imperative/src/events/__tests__/__unit__/EventOperator.unit.test.ts new file mode 100644 index 0000000000..87063402ee --- /dev/null +++ b/packages/imperative/src/events/__tests__/__unit__/EventOperator.unit.test.ts @@ -0,0 +1,165 @@ +/* +* 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 { EventOperator } from '../../src/EventOperator'; +import { EventProcessor } from '../../src/EventProcessor'; +import { EventUtils, Logger } from '../../..'; +import { IProcessorTypes } from '../../src/doc'; +import { Event } from '../../..'; +import { EventTypes, ZoweUserEvents } from "../../src/EventConstants"; + +jest.mock('../../../logger'); +jest.mock('../../src/EventProcessor'); +jest.mock('../../src/Event'); + +const logger = Logger.getImperativeLogger(); +const appName = 'TestApp'; + +describe("EventOperator Unit Tests", () => { + beforeEach(() => { + jest.clearAllMocks(); + jest.spyOn(EventUtils, "getListOfApps").mockReturnValue(["Zowe", appName]); + const subs = EventProcessor.prototype.subscribedEvents = new Map(); + const dummyEvent: any = { subscriptions: [ { removeAllListeners: jest.fn().mockReturnValue({close: jest.fn()})} as any] } ; + subs.set("Zowe", dummyEvent); + }); + afterEach(() => { + EventOperator.deleteProcessor("Zowe"); + EventOperator.deleteProcessor(appName); + }); + afterAll(() => { + jest.restoreAllMocks(); + }); + + describe("processor tests", () => { + it("'createProcessor' should create a new 'EventProcessor' if not already existing", () => { + const type = IProcessorTypes.BOTH; + const processor = EventOperator.getProcessor(appName, logger); + + expect(EventProcessor).toHaveBeenCalledWith(appName, type, logger); + expect(processor).toBeInstanceOf(EventProcessor); + }); + + it("'getZoweProcessor' should return the Zowe processor instance", () => { + const processor = EventOperator.getZoweProcessor(); + + expect(EventProcessor).toHaveBeenCalledWith("Zowe", IProcessorTypes.BOTH, logger); + expect(processor).toBeInstanceOf(EventProcessor); + }); + + it('emitZoweEvent is called by a Zowe processor and emits a ZoweUserEvents', () => { + const processor = EventOperator.getZoweProcessor(); + const eventName = "onVaultChanged"; + const emitZoweEventSpy = jest.spyOn(processor, 'emitZoweEvent'); + + processor.emitZoweEvent(eventName); + + expect(emitZoweEventSpy).toHaveBeenCalledWith(eventName); + expect(Object.values(ZoweUserEvents)).toContain(eventName); + }); + + it("'getProcessor' should return a generic event processor", () => { + const processor = EventOperator.getProcessor(appName, logger); + + expect(EventProcessor).toHaveBeenCalledWith(appName, IProcessorTypes.BOTH, logger); + expect(processor).toBeInstanceOf(EventProcessor); + }); + + it("'deleteProcessor' should remove the correct event processor", () => { + const processor = new EventProcessor(appName, IProcessorTypes.BOTH); + processor.subscribedEvents = new Map([ + ['testEvent', { + eventTime: '', + eventName: 'testEvent', + eventType: EventTypes.SharedEvents, + appName: appName, + subscriptions: new Set([ + { + removeAllListeners: jest.fn(), + close: jest.fn() + } + ]) + } as unknown as Event] + ]); + + EventOperator.deleteProcessor(appName); + + expect(EventOperator['instances'].has(appName)).toBe(false); + }); + }); + + describe("watcher tests", () => { + it("'getWatcher' should return a Zowe watcher as the default", () => { + const processor = EventOperator.getWatcher(); + + expect(EventProcessor).toHaveBeenCalledWith("Zowe", IProcessorTypes.WATCHER, undefined); + expect(processor).toBeInstanceOf(EventProcessor); + }); + it("'getWatcher' should return a watcher-only event processor", () => { + const processor = EventOperator.getWatcher(appName, logger); + + expect(EventProcessor).toHaveBeenCalledWith(appName, IProcessorTypes.WATCHER, logger); + expect(processor).toBeInstanceOf(EventProcessor); + }); + it("'deleteWatcher' should remove the correct event processor", () => { + const processor = new EventProcessor(appName, IProcessorTypes.WATCHER); + processor.subscribedEvents = new Map([ + ['testEvent', { + eventTime: '', + eventName: 'testEvent', + eventType: EventTypes.UserEvents, + appName: appName, + subscriptions: new Set([ + { + removeAllListeners: jest.fn(), + close: jest.fn() + } + ]) + } as unknown as Event] + ]); + + EventOperator.deleteWatcher(appName); + + expect(EventOperator['instances'].has(appName)).toBe(false); + }); + }); + + describe("emitter tests", () => { + it("'getEmitter' should return an emitter-only event processor", () => { + const processor = EventOperator.getEmitter(appName, logger); + + expect(EventProcessor).toHaveBeenCalledWith(appName, IProcessorTypes.EMITTER, logger); + expect(processor).toBeInstanceOf(EventProcessor); + }); + + it("'deleteEmitter' should remove the correct event processor", () => { + const processor = new EventProcessor(appName, IProcessorTypes.EMITTER); + processor.subscribedEvents = new Map([ + ['testEvent', { + eventTime: '', + eventName: 'testEvent', + eventType: EventTypes.UserEvents, + appName: appName, + subscriptions: new Set([ + { + removeAllListeners: jest.fn(), + close: jest.fn() + } + ]) + } as unknown as Event] + ]); + + EventOperator.deleteEmitter(appName); + + expect(EventOperator['instances'].has(appName)).toBe(false); + }); + }); +}); \ No newline at end of file diff --git a/packages/imperative/src/events/__tests__/__unit__/EventProcessor.unit.test.ts b/packages/imperative/src/events/__tests__/__unit__/EventProcessor.unit.test.ts new file mode 100644 index 0000000000..e98756b116 --- /dev/null +++ b/packages/imperative/src/events/__tests__/__unit__/EventProcessor.unit.test.ts @@ -0,0 +1,182 @@ +/* +* 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 { EventProcessor } from '../../src/EventProcessor'; +import { EventUtils } from '../../src/EventUtils'; +import { IProcessorTypes } from '../../src/doc/IEventInstanceTypes'; +import { ImperativeError } from '../../../error/src/ImperativeError'; +import { Event } from '../../src/Event'; +import { EventTypes, ZoweSharedEvents, ZoweUserEvents } from '../../src/EventConstants'; +import { EventOperator } from '../../src/EventOperator'; + +jest.mock('../../../logger/src/Logger'); + +describe('EventProcessor Unit Tests', () => { + let createSubscriptionSpy: any; + let setupWatcherSpy: any; + const appName = 'TestApp'; + + beforeEach(() => { + jest.clearAllMocks(); + jest.spyOn(EventUtils, 'writeEvent').mockImplementation(jest.fn()); + createSubscriptionSpy = jest.spyOn(EventUtils, 'createSubscription').mockImplementation(jest.fn()); + setupWatcherSpy = jest.spyOn(EventUtils, 'setupWatcher').mockImplementation(jest.fn()); + + jest.spyOn(EventUtils, "getListOfApps").mockReturnValue(["Zowe", appName]); + const subs = EventProcessor.prototype.subscribedEvents = new Map(); + const dummyEvent: any = { subscriptions: [ { removeAllListeners: jest.fn().mockReturnValue({close: jest.fn()})} as any] } ; + subs.set("Zowe", dummyEvent); + }); + afterEach(() => { + EventOperator.deleteProcessor(appName); + }); + afterAll(() => { + jest.restoreAllMocks(); + }); + + describe('Constructor', () => { + it('initializes EventProcessor correctly', () => { + expect(EventOperator['instances'].get(appName)).toBeUndefined(); + + EventOperator.getProcessor(appName); + + expect(EventOperator['instances'].get(appName)).toBeDefined(); + }); + }); + + describe('Subscription Methods', () => { + it('"subscribeShared" throws error for emitter-only processor', () => { + const emitter = new EventProcessor(appName, IProcessorTypes.EMITTER); + expect(() => emitter.subscribeShared('fakeEventToSubscribeTo', () => {})).toThrow(ImperativeError); + }); + + it('"subscribeUser" throws error for emitter-only processor', () => { + const emitter = new EventProcessor(appName, IProcessorTypes.EMITTER); + expect(() => emitter.subscribeUser('fakeEventToSubscribeTo', () => {})).toThrow(ImperativeError); + }); + + it('"subscribeShared" correctly subscribes to shared events', () => { + const processor = new EventProcessor(appName, IProcessorTypes.BOTH); + const eventName = 'someEvent'; + const callbacks = [jest.fn()]; + + createSubscriptionSpy.mockReturnValue({ close: jest.fn() }); + + const disposable = processor.subscribeShared(eventName, callbacks); + + expect(createSubscriptionSpy).toHaveBeenCalledWith(processor, eventName, EventTypes.SharedEvents); + expect(setupWatcherSpy).toHaveBeenCalledWith(processor, eventName, callbacks); + expect(disposable).toBeDefined(); + }); + + it('"subscribeUser" correctly subscribes to user-specific events', () => { + const processor = new EventProcessor(appName, IProcessorTypes.BOTH); + const eventName = 'someEvent'; + const callbacks = [jest.fn()]; + + createSubscriptionSpy.mockReturnValue({ close: jest.fn() }); + + const disposable = processor.subscribeUser(eventName, callbacks); + + expect(createSubscriptionSpy).toHaveBeenCalledWith(processor, eventName, EventTypes.UserEvents); + expect(setupWatcherSpy).toHaveBeenCalledWith(processor, eventName, callbacks); + expect(disposable).toBeDefined(); + }); + }); + + describe('Emission Methods', () => { + it('"emitEvent" throws error for watcher-only processor', () => { + const watcher = new EventProcessor(appName, IProcessorTypes.WATCHER); + expect(() => watcher.emitEvent('someEvent')).toThrow(ImperativeError); + }); + + it('"emitEvent" throws error when emitting Zowe events', () => { + const emitter = new EventProcessor(appName, IProcessorTypes.EMITTER); + expect(() => emitter.emitEvent(ZoweUserEvents.ON_VAULT_CHANGED)).toThrow(ImperativeError); + expect(() => emitter.emitEvent(ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED)).toThrow(ImperativeError); + }); + + it('"emitZoweEvent" throws error for watcher-only processor', () => { + const watcher = new EventProcessor(appName, IProcessorTypes.WATCHER); + expect(() => watcher.emitZoweEvent('someEvent')).toThrow(ImperativeError); + }); + + it('"emitZoweEvent" throws error when emitting non Zowe events', () => { + const emitter = EventOperator.getZoweProcessor(); + expect(() => emitter.emitZoweEvent('someEvent')).toThrow(ImperativeError); + }); + + it('"emitEvent" updates event timestamp and writes event', () => { + const emitter = new EventProcessor(appName, IProcessorTypes.EMITTER); + const eventName = 'someEvent'; + const event = { eventTime: '', eventName, eventType: EventTypes.UserEvents, appName, subscriptions: new Set() } as unknown as Event; + + emitter.subscribedEvents.set(eventName, event); + emitter.emitEvent(eventName); + + expect(event.eventTime).not.toBe(''); + expect(EventUtils.writeEvent).toHaveBeenCalledWith(event); + }); + }); + + describe('Unsubscribe Methods', () => { + it('"unsubscribe" removes subscriptions and cleans up resources', () => { + const processor = new EventProcessor(appName, IProcessorTypes.BOTH); + processor.subscribedEvents = new Map([ + ['testEvent', { + eventTime: '', + eventName: 'testEvent', + eventType: EventTypes.UserEvents, + appName: appName, + subscriptions: new Set([ + { + removeAllListeners: jest.fn(), + close: jest.fn() + } + ]) + } as unknown as Event] + ]); + + processor.unsubscribe('testEvent'); + + expect(processor.subscribedEvents.has('testEvent')).toBe(false); + }); + it('subscription removed from a processor\'s subscribed events and resources are cleaned', () => { + const processor = new EventProcessor(appName, IProcessorTypes.BOTH); + const mockSubscription = { + removeAllListeners: jest.fn(), + close: jest.fn() + }; + + const event = { + eventTime: '', + eventName: 'testEvent', + eventType: EventTypes.UserEvents, + appName: appName, + subscriptions: new Set([mockSubscription]) + } as unknown as Event; + + processor.subscribedEvents = new Map([ + ['testEvent', event] + ]); + + expect(processor.subscribedEvents.has('testEvent')).toBe(true); + processor.unsubscribe('testEvent'); + expect(mockSubscription.removeAllListeners).toHaveBeenCalled(); + expect(mockSubscription.close).toHaveBeenCalled(); + expect(processor.subscribedEvents.has('testEvent')).toBe(false); + }); + it('"unsubscribe" throws error for emitter-only processor', () => { + const emitter = new EventProcessor(appName, IProcessorTypes.EMITTER); + expect(() => emitter.unsubscribe('someEvent')).toThrow(ImperativeError); + }); + }); +}); \ No newline at end of file diff --git a/packages/imperative/src/events/__tests__/__unit__/EventUtils.unit.test.ts b/packages/imperative/src/events/__tests__/__unit__/EventUtils.unit.test.ts new file mode 100644 index 0000000000..7967ecda59 --- /dev/null +++ b/packages/imperative/src/events/__tests__/__unit__/EventUtils.unit.test.ts @@ -0,0 +1,156 @@ +/* +* 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 { EventOperator } from '../../src/EventOperator'; +import { EventProcessor } from '../../src/EventProcessor'; +import { Logger } from '../../..'; +import { IProcessorTypes } from '../../src/doc'; +import { Event } from '../../..'; +import { EventTypes, ZoweUserEvents } from "../../src/EventConstants"; + +jest.mock('../../src/EventProcessor'); +jest.mock('../../../logger'); + +const logger = Logger.getImperativeLogger(); + +describe("EventOperator Unit Tests", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + afterAll(() => { + jest.restoreAllMocks(); + }); + + describe("processor tests", () => { + it("'createProcessor' should create a new 'EventProcessor' if not already existing", () => { + const appName = 'TestApp'; + const type = IProcessorTypes.BOTH; + const processor = EventOperator.getProcessor(appName, logger); + + expect(EventProcessor).toHaveBeenCalledWith(appName, type, logger); + expect(processor).toBeInstanceOf(EventProcessor); + }); + + it("'getZoweProcessor' should return the Zowe processor instance", () => { + const processor = EventOperator.getZoweProcessor(); + + expect(EventProcessor).toHaveBeenCalledWith("Zowe", IProcessorTypes.BOTH, logger); + expect(processor).toBeInstanceOf(EventProcessor); + }); + + it('emitZoweEvent is called by a Zowe processor and emits a ZoweUserEvents', () => { + const processor = EventOperator.getZoweProcessor(); + const eventName = "onVaultChanged"; + const emitZoweEventSpy = jest.spyOn(processor, 'emitZoweEvent'); + + processor.emitZoweEvent(eventName); + + expect(emitZoweEventSpy).toHaveBeenCalledWith(eventName); + expect(Object.values(ZoweUserEvents)).toContain(eventName); + }); + + it("'getProcessor' should return a generic event processor", () => { + const appName = 'GenericApp'; + const processor = EventOperator.getProcessor(appName, logger); + + expect(EventProcessor).toHaveBeenCalledWith(appName, IProcessorTypes.BOTH, logger); + expect(processor).toBeInstanceOf(EventProcessor); + }); + + it("'deleteProcessor' should remove the correct event processor", () => { + const appName = 'DeleteApp'; + const processor = new EventProcessor(appName, IProcessorTypes.BOTH); + processor.subscribedEvents = new Map([ + ['testEvent', { + eventTime: '', + eventName: 'testEvent', + eventType: EventTypes.SharedEvents, + appName: appName, + subscriptions: new Set([ + { + removeAllListeners: jest.fn(), + close: jest.fn() + } + ]) + } as unknown as Event] + ]); + + EventOperator.deleteProcessor(appName); + + expect(EventOperator['instances'].has(appName)).toBe(false); + }); + }); + + describe("watcher tests", () => { + it("'getWatcher' should return a watcher-only event processor", () => { + const appName = 'WatcherApp'; + const processor = EventOperator.getWatcher(appName, logger); + + expect(EventProcessor).toHaveBeenCalledWith(appName, IProcessorTypes.WATCHER, logger); + expect(processor).toBeInstanceOf(EventProcessor); + }); + it("'deleteWatcher' should remove the correct event processor", () => { + const appName = 'DeleteWatcher'; + const processor = new EventProcessor(appName, IProcessorTypes.WATCHER); + processor.subscribedEvents = new Map([ + ['testEvent', { + eventTime: '', + eventName: 'testEvent', + eventType: EventTypes.UserEvents, + appName: appName, + subscriptions: new Set([ + { + removeAllListeners: jest.fn(), + close: jest.fn() + } + ]) + } as unknown as Event] + ]); + + EventOperator.deleteWatcher(appName); + + expect(EventOperator['instances'].has(appName)).toBe(false); + }); + }); + + describe("emitter tests", () => { + it("'getEmitter' should return an emitter-only event processor", () => { + const appName = 'EmitterApp'; + const processor = EventOperator.getEmitter(appName, logger); + + expect(EventProcessor).toHaveBeenCalledWith(appName, IProcessorTypes.EMITTER, logger); + expect(processor).toBeInstanceOf(EventProcessor); + }); + + it("'deleteEmitter' should remove the correct event processor", () => { + const appName = 'DeleteEmitter'; + const processor = new EventProcessor(appName, IProcessorTypes.EMITTER); + processor.subscribedEvents = new Map([ + ['testEvent', { + eventTime: '', + eventName: 'testEvent', + eventType: EventTypes.UserEvents, + appName: appName, + subscriptions: new Set([ + { + removeAllListeners: jest.fn(), + close: jest.fn() + } + ]) + } as unknown as Event] + ]); + + EventOperator.deleteEmitter(appName); + + expect(EventOperator['instances'].has(appName)).toBe(false); + }); + }); +}); \ No newline at end of file diff --git a/packages/imperative/src/events/__tests__/__unit__/ImperativeEventEmitter.unit.test.ts b/packages/imperative/src/events/__tests__/__unit__/ImperativeEventEmitter.unit.test.ts deleted file mode 100644 index 9b4e3a57d5..0000000000 --- a/packages/imperative/src/events/__tests__/__unit__/ImperativeEventEmitter.unit.test.ts +++ /dev/null @@ -1,284 +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 * as fs from "fs"; -import { join } from "path"; -import { homedir } from "os"; -import { ImperativeEventEmitter, ImperativeSharedEvents, ImperativeUserEvents } from "../.."; - -jest.mock("fs"); - -describe("Event Emitter", () => { - const iee = ImperativeEventEmitter; - const sharedDir = join(__dirname, ".zowe", ".events"); - const userDir = join(homedir(), ".zowe", ".events"); - let spyFsWriteFileSync: jest.SpyInstance; - let allCallbacks: Function[]; - let removeAllListeners: jest.SpyInstance; - const closeWatcher = jest.fn(); - - 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()); - 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", () => { - it("should only allow for one instance of the event emitter", () => { - const mockLogger: any = { warn: jest.fn() as any }; - iee.initialize("test", { logger: mockLogger }); - iee.initialize("dummy", { logger: mockLogger }); - expect(mockLogger.warn).toHaveBeenCalledTimes(1); - expect(mockLogger.warn.mock.calls[0][0]).toContain("Only one instance"); - expect(iee.instance.appName).toEqual("test"); - expect(iee.instance.logger).toEqual(mockLogger); - }); - - 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) => { - // 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(spyFsWriteFileSync.mock.calls[0][0]).toEqual(join(dir, theEvent)); - expect(JSON.parse(spyFsWriteFileSync.mock.calls[0][1])).toMatchObject({ - type: theEvent, - user: isUser, - loc: dir, - }); - spyFsWriteFileSync.mockClear(); - }; - - processEvent(ImperativeUserEvents.ON_VAULT_CHANGED, true); - processEvent(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED, false); - processEvent("onSuperCustomEvent", false, true); - }); - - 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"); - }); - }); - - it("should surface errors if unable to create event files or directories", () => { - iee.initialize("zowe"); - - 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"; }); - - 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(); - }); - - 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"); - }); - }); -}); diff --git a/packages/imperative/src/events/index.ts b/packages/imperative/src/events/index.ts index 8da687cf65..0c3a6c5326 100644 --- a/packages/imperative/src/events/index.ts +++ b/packages/imperative/src/events/index.ts @@ -10,6 +10,8 @@ */ export * from "./src/doc"; -export * from "./src/ImperativeEvent"; -export * from "./src/ImperativeEventConstants"; -export * from "./src/ImperativeEventEmitter"; +export * from "./src/Event"; +export * from "./src/EventConstants"; +export * from "./src/EventProcessor"; +export * from "./src/EventOperator"; +export * from "./src/EventUtils"; \ No newline at end of file diff --git a/packages/imperative/src/events/src/Event.ts b/packages/imperative/src/events/src/Event.ts new file mode 100644 index 0000000000..d316e215c9 --- /dev/null +++ b/packages/imperative/src/events/src/Event.ts @@ -0,0 +1,66 @@ +/* +* 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 { FSWatcher } from "fs"; +import { EventTypes } from "./EventConstants"; +import { IEventJson } from "./doc"; + +/** + * Represents an event within the system, containing all necessary metadata + * and subscriptions related to the event. + */ +export class Event implements IEventJson { + public eventTime: string; + public eventName: string; + public eventType: EventTypes; + public appName: string; + public eventFilePath: string; + public subscriptions: FSWatcher[]; + + + /** + * Initializes a new instance of the Event class with specified properties. + * + * @param {IEventJson} params - The parameters to create the Event. + */ + constructor({ eventTime, eventName, eventType, appName, eventFilePath, subscriptions }: IEventJson) { + this.eventTime = eventTime; + this.eventName = eventName; + this.eventType = eventType ?? null; + this.appName = appName; + this.eventFilePath = eventFilePath; + this.subscriptions = subscriptions; + } + + /** + * Serializes the Event object to a JSON object for storage or transmission. + * + * @returns {Object} The JSON representation of the event. + */ + public toJson(): IEventJson { + return { + eventTime: this.eventTime, + eventName: this.eventName, + eventType: this.eventType, + appName: this.appName, + eventFilePath: this.eventFilePath, + }; + } + + /** + * Provides a string representation of the Event, useful for logging and debugging. + * + * @returns {string} A string detailing the event's significant information. + */ + public toString = (): string => { + return `Name: ${this.eventName} \t| Time: ${this.eventTime} \t| App: ${this.appName} \t| Type: ${this.eventType}`; + }; +} \ No newline at end of file diff --git a/packages/imperative/src/events/src/ImperativeEventConstants.ts b/packages/imperative/src/events/src/EventConstants.ts similarity index 84% rename from packages/imperative/src/events/src/ImperativeEventConstants.ts rename to packages/imperative/src/events/src/EventConstants.ts index 0fd35d70af..61641bdd20 100644 --- a/packages/imperative/src/events/src/ImperativeEventConstants.ts +++ b/packages/imperative/src/events/src/EventConstants.ts @@ -9,18 +9,21 @@ * */ -export enum ImperativeUserEvents { + +export enum ZoweUserEvents { ON_VAULT_CHANGED = "onVaultChanged" } -export enum ImperativeSharedEvents { + +export enum ZoweSharedEvents { ON_CREDENTIAL_MANAGER_CHANGED = "onCredentialManagerChanged" } -export type ImperativeEventType = ImperativeUserEvents | ImperativeSharedEvents; +export enum EventTypes { ZoweUserEvents, ZoweSharedEvents, SharedEvents, UserEvents } + +export type EventCallback = () => void | PromiseLike; /** - * TODO: - * The following list of event types will only be implemented upon request + * EXPECTED EVENT LOCATIONS: * * Shared events: * Global: diff --git a/packages/imperative/src/events/src/EventOperator.ts b/packages/imperative/src/events/src/EventOperator.ts new file mode 100644 index 0000000000..d2a69d6ce2 --- /dev/null +++ b/packages/imperative/src/events/src/EventOperator.ts @@ -0,0 +1,170 @@ +/* +* 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 { EventProcessor } from "./EventProcessor"; +import { IEmitter, IEmitterAndWatcher, IProcessorTypes, IWatcher } from "./doc/IEventInstanceTypes"; +import { Logger } from "../../logger"; + +/** + * @internal Interface for Zowe-specific event processing, combining emitter and watcher functionalities. + */ +interface IZoweProcessor extends IEmitterAndWatcher { + emitZoweEvent(eventName: string): void; +} + +/** + * ## Overview + * The `EventOperator` manages event processors and sets limits on their behavior. + * + * An `EventOperator` categorizes processors into 3 types: + * - **Watcher**: Listens for events and triggers callbacks when events occur. + * - **Emitter**: Emits events that other applications listen for. + * - **EmitterAndWatcher**: Combines the functionalities of both watcher and emitter. + * + * Applications use the `EventOperator` to obtain the appropriate event processor based on their needs. + * For example, an application might use a watcher to react to user actions and an emitter to notify other + * components of state changes. + * + * ### App Names and Processors + * Processors are tied to application names to prevent event collisions and to maintain a clear separation + * of event domains. Valid app names are defined in the list of extenders (formal plugin names or ZE extender names). + * + * ### Understanding Event Types + * - **Predefined Zowe Events**: Zowe provides a set of predefined shared and user events that can be watched. + * - **Custom Events**: Applications can define their own shared/user events, allowing for custom event-driven behavior. + * + * @export + * @class EventOperator + */ +export class EventOperator { + private static instances: Map = new Map(); + /** + * Creates an event processor for a specified application. + * + * @static + * @param {string} appName - The name of the application. + * @param {IProcessorTypes} type - The type of processor to create (emitter, watcher, or both). + * @param {Logger} [logger] - Optional logger instance for the processor. + * @returns {IZoweProcessor} A new or existing processor instance. + * @throws {ImperativeError} If the application name is not recognized. + */ + private static createProcessor(appName: string, type: IProcessorTypes, logger?: Logger): IZoweProcessor { + if (!this.instances.has(appName)) { + const newInstance = new EventProcessor(appName, type, logger); + this.instances.set(appName, newInstance); + } + const procInstance = this.instances.get(appName); + if (procInstance.processorType !== type) { + procInstance.processorType = IProcessorTypes.BOTH; + } + return procInstance; + } + /** + * Retrieves a Zowe-specific event processor. The purpose of this method is for internal + * Imperative APIs to get a properly initialized processor. This processor will be used + * when applications (like Zowe Explorer) call Imperative APIs that trigger events. For + * example, when the user updates credentials from Zowe Explorer, this processor could be + * used to emit an `OnVaultChanged` event. + * + * @internal Not meant to be called by application developers + * @static + * @returns {IZoweProcessor} The Zowe event processor instance. + * @throws {ImperativeError} If the application name is not recognized. + */ + public static getZoweProcessor(): IZoweProcessor { + return this.createProcessor("Zowe", IProcessorTypes.BOTH, Logger.getAppLogger()); + } + + /** + * Retrieves a generic event processor that can emit and watch events. + * + * @static + * @param {string} appName - The application name. + * @param {Logger} [logger] - Optional logger for the processor. + * @returns {IEmitterAndWatcher} An event processor capable of both emitting and watching. + * @throws {ImperativeError} If the application name is not recognized. + */ + public static getProcessor(appName: string, logger?: Logger): IEmitterAndWatcher { + return this.createProcessor(appName, IProcessorTypes.BOTH, logger); + } + + /** + * Retrieves a watcher-only event processor. + * + * @static + * @param {string} appName - The application name, defaults to "Zowe" if not specified. + * @param {Logger} [logger] - Optional logger for the processor. + * @returns {IWatcher} A watcher-only event processor. + * @throws {ImperativeError} If the application name is not recognized. + */ + public static getWatcher(appName: string = "Zowe", logger?: Logger): IWatcher { + return this.createProcessor(appName, IProcessorTypes.WATCHER, logger); + } + + /** + * Retrieves an emitter-only event processor. + * + * @static + * @param {string} appName - The application name. + * @param {Logger} [logger] - Optional logger for the processor. + * @returns {IEmitter} An emitter-only event processor. + * @throws {ImperativeError} If the application name is not recognized. + */ + public static getEmitter(appName: string, logger?: Logger): IEmitter { + return this.createProcessor(appName, IProcessorTypes.EMITTER, logger); + } + + /** + * Deletes a specific type of event processor (emitter). + * + * @static + * @param {string} appName - The application name associated with the emitter to be deleted. + */ + public static deleteEmitter(appName: string): void { + this.destroyProcessor(appName); + } + + /** + * Deletes a specific type of event processor (watcher). + * + * @static + * @param {string} appName - The application name associated with the watcher to be deleted. + */ + public static deleteWatcher(appName: string): void { + this.destroyProcessor(appName); + } + + /** + * Deletes an event processor, removing both its emitter and watcher capabilities. + * + * @static + * @param {string} appName - The application name whose processor is to be deleted. + */ + public static deleteProcessor(appName: string): void { + this.destroyProcessor(appName); + } + + /** + * Destroys a processor by removing all associated file watchers and cleaning up resources. + * + * @static + * @param {string} appName - The name of the application whose processor needs to be destroyed. + */ + private static destroyProcessor(appName: string): void { + const processor = this.instances.get(appName); + if (processor) { + processor.subscribedEvents.forEach((event, eventName) => { + event.subscriptions.forEach(subscription => subscription.removeAllListeners(eventName).close()); + }); + this.instances.delete(appName); + } + } +} \ No newline at end of file diff --git a/packages/imperative/src/events/src/EventProcessor.ts b/packages/imperative/src/events/src/EventProcessor.ts new file mode 100644 index 0000000000..f012da1f64 --- /dev/null +++ b/packages/imperative/src/events/src/EventProcessor.ts @@ -0,0 +1,189 @@ +/* +* 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/src/Logger"; +import { EventCallback, EventTypes } from "./EventConstants"; +import { ImperativeError } from "../../error/src/ImperativeError"; +import { Event } from "./Event"; +import { ConfigUtils } from "../../config/src/ConfigUtils"; +import { LoggerManager } from "../../logger/src/LoggerManager"; +import { ImperativeConfig } from "../../utilities"; +import { EventUtils } from "./EventUtils"; +import { IEventDisposable } from "./doc"; +import { IProcessorTypes } from "./doc/IEventInstanceTypes"; + +/** + * ## Overview + * Each EventProcessor manages its own subscriptions, handling the addition, emission, and removal of events. + * It uses a map where event names are keys, and values are Event objects that hold detailed event information and subscriptions. + * + * An `EventProcessor` handles three main functionalities: + * - **Subscribing to Events**: + * Registration of a callback function that will be executed when that event occurs. + * - **Emitting Events**: + * Notifying other applications or parts of the same application about certain actions or changes. + * - **Managing Event Subscriptions**: + * Mapping subscribed events and their corresponding callbacks, ensuring that events are properly handled and dispatched. + * + * ### Understanding Event Types + * - **Predefined Zowe Events**: + * Zowe provides a set of predefined shared and user events that can be watched. + * - **Custom Events**: + * Applications can define their own shared and user events, allowing for custom event-driven behavior. + * + * @export + * @class EventProcessor + */ +export class EventProcessor { + public subscribedEvents: Map = new Map(); + public eventTimes: Map; + public processorType: IProcessorTypes; + public appName: string; + public logger: Logger; + + /** + * Constructor initializes a new instance of EventProcessor. + * + * @param {string} appName - The application's name. + * @param {IProcessorTypes} type - The type of processor (Emitter, Watcher, or Both). + * @param {Logger} [logger] - Optional logger for recording events and errors. + * @throws {ImperativeError} If the application name is not recognized. + */ + public constructor(appName: string, type: IProcessorTypes, logger?: Logger) { + EventUtils.validateAppName(appName); + + this.subscribedEvents = new Map(); + this.eventTimes = new Map(); + this.appName = appName; + this.processorType = type; + + // Ensure correct environmental conditions to setup a custom logger, + // otherwise use default logger + if (ImperativeConfig.instance.loadedConfig == null || LoggerManager.instance.isLoggerInit === false) { + ConfigUtils.initImpUtils("zowe"); + } + this.logger = logger ?? Logger.getImperativeLogger(); + } + + private _subscribe(type: 'user' | 'shared', eventName: string, callbacks: EventCallback[] | EventCallback): IEventDisposable { + if (this.processorType === IProcessorTypes.EMITTER) { + throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}` }); + } + + let eventType; + if (type === "shared") { + eventType = EventUtils.isSharedEvent(eventName) ? EventTypes.ZoweSharedEvents : EventTypes.SharedEvents; + } else if (type === "user") { + eventType = EventUtils.isUserEvent(eventName) ? EventTypes.ZoweUserEvents : EventTypes.UserEvents; + } + + const disposable = EventUtils.createSubscription(this, eventName, eventType); + EventUtils.setupWatcher(this, eventName, callbacks); + return disposable; + } + /** + * Subscription to an event that will notify all subscribed users. + * + * @param {string} eventName - The name of the event to subscribe to. + * @param {EventCallback[] | EventCallback} callbacks - Callback functions to handle the event. + * @returns {IEventDisposable} - Object allowing management/cleanup of the subscription. + * @throws {ImperativeError} If the processor does not have the correct permissions to perform this action. + */ + public subscribeShared(eventName: string, callbacks: EventCallback[] | EventCallback): IEventDisposable { + return this._subscribe("shared", eventName, callbacks); + } + + /** + * Subscription to an event that will notify a single user. + * + * @param {string} eventName - The name of the event to subscribe to. + * @param {EventCallback[] | EventCallback} callbacks - Callback functions to handle the event. + * @returns {IEventDisposable} - Object allowing management/cleanup of the subscription. + * @throws {ImperativeError} If the processor does not have the correct permissions to perform this action. + */ + public subscribeUser(eventName: string, callbacks: EventCallback[] | EventCallback): IEventDisposable { + return this._subscribe("user", eventName, callbacks); + } + + /** + * Private method to emit the event + * @private + * @param eventName Event to be emitted + * @throws {ImperativeError} - If the event cannot be emitted. + */ + private emit(eventName: string) { + try { + const event = this.subscribedEvents.get(eventName) ?? EventUtils.createEvent(eventName, this.appName); + event.eventTime = new Date().toISOString(); + EventUtils.writeEvent(event); + } catch (err) { + throw new ImperativeError({ msg: `Error writing event: ${eventName}`, causeErrors: err }); + } + } + + /** + * Emits an event by updating its timestamp and writing event data. + * + * @param {string} eventName - The name of the event to emit. + * @throws {ImperativeError} If the processor does not have the correct permissions to perform this action. + * @throws {ImperativeError} - If the event cannot be emitted. + */ + public emitEvent(eventName: string): void { + if (this.processorType === IProcessorTypes.WATCHER) { + throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}` }); + } + if (EventUtils.isUserEvent(eventName) || EventUtils.isSharedEvent(eventName)) { + throw new ImperativeError({ msg: `Processor not allowed to emit Zowe events: ${eventName}` }); + } + this.emit(eventName); + } + + /** + * Specifically emits Zowe-related events, updating timestamps and handling data. + * + * @internal Internal Zowe emitter method + * @param {string} eventName - The name of the Zowe event to emit. + * @throws {ImperativeError} If the processor does not have the correct permissions to perform this action. + * @throws {ImperativeError} - If the event cannot be emitted. + */ + public emitZoweEvent(eventName: string): void { + if (this.appName !== "Zowe") { + throw new ImperativeError({ msg: `Processor does not have Zowe permissions: ${eventName}` }); + } + if (!EventUtils.isUserEvent(eventName) && !EventUtils.isSharedEvent(eventName)) { + throw new ImperativeError({ msg: `Invalid Zowe event: ${eventName}` }); + } + this.emit(eventName); + } + + /** + * Unsubscribes from an event, closing file watchers and cleaning up resources. + * + * @param {string} eventName - The name of the event to unsubscribe from. + * @throws {ImperativeError} If the processor does not have the correct permissions to perform this action. + * @throws {ImperativeError} - If unsubscribing fails. + */ + public unsubscribe(eventName: string): void { + if (this.processorType === IProcessorTypes.EMITTER) { + throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}` }); + } + const event = this.subscribedEvents.get(eventName); + if (event) { + event.subscriptions.forEach(subscription => { + subscription.removeAllListeners(eventName); + if (typeof subscription.close === 'function') { + subscription.close(); + } + }); + this.subscribedEvents.delete(eventName); + } + } +} \ No newline at end of file diff --git a/packages/imperative/src/events/src/EventUtils.ts b/packages/imperative/src/events/src/EventUtils.ts new file mode 100644 index 0000000000..7bf5a8e4f4 --- /dev/null +++ b/packages/imperative/src/events/src/EventUtils.ts @@ -0,0 +1,230 @@ +/* +* 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 { ImperativeError } from "../../error/src/ImperativeError"; +import { join } from "path"; +import { ZoweUserEvents, ZoweSharedEvents, EventTypes, EventCallback } from "./EventConstants"; +import * as fs from "fs"; +import { ConfigUtils } from "../../config/src/ConfigUtils"; +import { IEventDisposable, IEventJson } from "./doc"; +import { Event } from "./Event"; +import { EventProcessor } from "./EventProcessor"; +import { ImperativeConfig } from "../../utilities/src/ImperativeConfig"; + +/** + * A collection of helper functions related to event processing, including: + * - directory management + * - event type determination + * - subscription creation and setting callbacks + * - event writing + */ +export class EventUtils { + + /** + * Retrieves a list of supported applications from configuration. + * + * @static + * @returns {string[]} List of application names. + * @throws If the extenders.json file cannot be created when it does not exist. + * @throws If the extenders.json file cannot be read. + */ + public static getListOfApps(): string[] { + const extendersJson = ConfigUtils.readExtendersJson(); + return ["Zowe", ...Object.keys(extendersJson.profileTypes)]; + } + + /** + * Won't throw an error if user entered a valid appName + * + * @static + * @param {string} appName - The name of the application. + * @throws {ImperativeError} If the application name is not recognized. + */ + public static validateAppName(appName: string): void { + const appList = this.getListOfApps(); + if (appList.includes(appName)) return; + // Performing `appList.find(app => app.includes(appName))` will allow for "tags" (or suffixes) coming from `getListOfApps()` + // However, we do not want this behavior because it will allow partial application names to be used + // Hence why we should probably match the application name with the exact profileType in `extenders.json` + throw new ImperativeError({ + msg: `Application name not found: ${appName}. Please use an application name from the list:\n- ${appList.join("\n- ")}` + }); + } + + /** + * Determines if the specified event name is associated with a user event. + * + * @param {string} eventName - The name of the event. + * @return {boolean} True if it is a user event, otherwise false. + */ + public static isUserEvent(eventName: string): boolean { + return Object.values(ZoweUserEvents).includes(eventName); + } + + /** + * Determines if the specified event name is associated with a shared event. + * + * @param {string} eventName - The name of the event. + * @return {boolean} True if it is a shared event, otherwise false. + */ + public static isSharedEvent(eventName: string): boolean { + return Object.values(ZoweSharedEvents).includes(eventName); + } + + /** + * Retrieve the event contents form disk + * + * @internal This is not intended for application developers + * @param eventFilePath The path to the event file + * @returns The object representing the Event + * @throws {ImperativeError} If the contents of the event cannot be retrieved. + */ + public static getEventContents(eventFilePath: string): IEventJson { + try { + return JSON.parse(fs.readFileSync(eventFilePath).toString()); + } catch (err) { + throw new ImperativeError({ msg: `Unable to retrieve event contents: Path: ${eventFilePath}` }); + } + } + + /** + * Determines the directory path for storing event files based on the event type and application name. + * + * @param {string} appName - The name of the application. + * @return {string} The directory path. + * @throws {ImperativeError} If the application name is not recognized. + */ + public static getEventDir(appName: string): string { + this.validateAppName(appName); + return join(".events", appName); + } + + /** + * Ensures that the specified directory for storing event files exists, creating it if necessary. + * + * @param {string} directoryPath - The path to the directory. + * @throws {ImperativeError} If we are unable to create the '.events' directory. + */ + public static ensureEventsDirExists(directoryPath: string) { + try { + if (!fs.existsSync(directoryPath)) { + fs.mkdirSync(directoryPath, { mode: 0o750, recursive: true }); // user read/write/exec, group read/exec + } + } catch (err) { + throw new ImperativeError({ msg: `Unable to create '.events' directory. Path: ${directoryPath}`, causeErrors: err }); + } + } + + /** + * Ensures that the specified file path for storing event data exists, creating it if necessary. + * + * @param {string} filePath - The path to the file. + * @throws {ImperativeError} If we are unable to create the event file required for event emission. + */ + public static ensureFileExists(filePath: string) { + try { + // eslint-disable-next-line @typescript-eslint/no-magic-numbers + const fd = fs.openSync(filePath, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_RDWR, 0o640); + fs.closeSync(fd); + } catch (err) { + if (err.code !== 'EEXIST') { + throw new ImperativeError({ msg: `Unable to create event file. Path: ${filePath}`, causeErrors: err }); + } + } + } + + /** + * Create an event with minimal information + * + * @internal This is not intended for application developers + * @param eventName The name of the event. + * @param appName The name of the application. + * @returns The created event + * @throws {ImperativeError} If the application name is not recognized. + * @throws {ImperativeError} If we are unable to create the '.events' directory. + * @throws {ImperativeError} If we are unable to create the event file required for event emission. + */ + public static createEvent(eventName: string, appName: string): Event { + const zoweDir = ImperativeConfig.instance.loadedConfig != null ? ImperativeConfig.instance.cliHome : ConfigUtils.getZoweDir(); + const dir = join(zoweDir, EventUtils.getEventDir(appName)); + this.ensureEventsDirExists(dir); + + const filePath = join(dir, eventName); + this.ensureFileExists(filePath); + + return new Event({ + eventTime: new Date().toISOString(), + eventName: eventName, + appName: appName, + eventFilePath: filePath, + subscriptions: [], + }); + } + + /** + * Creates and registers a new event subscription for a specific event processor. + * + * @param {EventProcessor} eeInst - The event processor instance. + * @param {string} eventName - The name of the event. + * @param {EventTypes} eventType - The type of event. + * @return {IEventDisposable} An interface for managing the subscription. + * @throws {ImperativeError} If the application name is not recognized. + * @throws {ImperativeError} If we are unable to create the '.events' directory. + * @throws {ImperativeError} If we are unable to create the event file required for event emission. + */ + public static createSubscription(eeInst: EventProcessor, eventName: string, eventType: EventTypes): IEventDisposable { + const newEvent = EventUtils.createEvent(eventName, eeInst.appName); + newEvent.eventType = eventType; + eeInst.subscribedEvents.set(eventName, newEvent); + return { + close: () => eeInst.unsubscribe(eventName) + }; + } + + /** + * Sets up a file watcher for a specific event, triggering callbacks when the event file is updated. + * + * @param {EventProcessor} eeInst - The event processor instance. + * @param {string} eventName - The name of the event. + * @param {EventCallback[] | EventCallback} callbacks - A single callback or an array of callbacks to execute. + * @return {fs.FSWatcher} A file system watcher. + * @throws {ImperativeError} If the event to be watched does not have an existing file to watch. + * @throws {ImperativeError} Callbacks will fail if the contents of the event cannot be retrieved. + */ + public static setupWatcher(eeInst: EventProcessor, eventName: string, callbacks: EventCallback[] | EventCallback): fs.FSWatcher { + const event = eeInst.subscribedEvents.get(eventName); + const watcher = fs.watch(event.eventFilePath, (trigger: "rename" | "change") => { + // Accommodates for the delay between actual file change event and fsWatcher's perception + //(Node.JS triggers this notification event 3 times) + event.eventTime = EventUtils.getEventContents(event.eventFilePath).eventTime; + if (eeInst.eventTimes.get(eventName) !== event.eventTime) { + eeInst.logger.debug(`EventEmitter: Event "${trigger}" emitted: ${eventName}`); + if (Array.isArray(callbacks)) { + callbacks.forEach(cb => cb()); + } else { + callbacks(); + } + eeInst.eventTimes.set(eventName, event.eventTime); + } + }); + event.subscriptions.push(watcher); + return watcher; + } + + /** + * Writes event data to the corresponding event file in JSON format. + * + * @param {Event} event - The event object. + */ + public static writeEvent(event: Event) { + fs.writeFileSync(event.eventFilePath, JSON.stringify(event.toJson(), null, 2)); + } +} \ No newline at end of file diff --git a/packages/imperative/src/events/src/ImperativeEvent.ts b/packages/imperative/src/events/src/ImperativeEvent.ts deleted file mode 100644 index b06f046560..0000000000 --- a/packages/imperative/src/events/src/ImperativeEvent.ts +++ /dev/null @@ -1,126 +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 { randomUUID } from "crypto"; -import { IImperativeEventJson, 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 location of the event - * @private - * @type {string} - * @memberof ImperativeEvent - */ - private mEventLoc: string; - - /** - * The type of event that occurred - * @private - * @type {string} - * @memberof ImperativeEvent - */ - private mEventType: ImperativeEventType | string; - - /** - * Indicator of user-specific (if true) or shared (if false) events - * @private - * @type {boolean} - * @memberof ImperativeEvent - */ - private isUserEvent: boolean; - - /** - * toString overload to be called automatically on string concatenation - * @returns string representation of the imperative event - */ - public toString = (): string => { - return `Type: ${this.type} \t| Time: ${this.time} \t| App: ${this.appName} \t| ID: ${this.id}`; - }; - - /** - * 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.time, - type: this.type, - source: this.appName, - id: this.id, - loc: this.location, - user: this.isUserEvent, - }; - }; - - constructor(parms: IImperativeEventParms) { - this.mEventTime = new Date().toISOString(); - this.mEventID = randomUUID(); - this.mAppID = parms.appName; - this.mEventType = parms.eventName; - this.isUserEvent = parms.isUser; - parms.logger.debug("ImperativeEvent: " + this); - } - - 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 type(): ImperativeEventType | string { - return this.mEventType; - } - - public get appName(): string { - return this.mAppID; - } - - 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 deleted file mode 100644 index c50e4fbaad..0000000000 --- a/packages/imperative/src/events/src/ImperativeEventEmitter.ts +++ /dev/null @@ -1,289 +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 * as fs from "fs"; -import { homedir } from "os"; -import { join } from "path"; -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/src/Logger"; -import { LoggerManager } from "../../logger/src/LoggerManager"; -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 eventTimes: Map; - public appName: string; - public logger: Logger; - - public static initialize(appName?: string, options?: IImperativeEventEmitterOpts) { - if (this.initialized) { - this.instance.logger.warn("Only one instance of the Imperative Event Emitter is allowed"); - return; - } - this.initialized = true; - - if (ImperativeConfig.instance.loadedConfig == null || LoggerManager.instance.isLoggerInit === false) { - ConfigUtils.initImpUtils("zowe"); - } - - 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(); - 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 - */ - private 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 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 event file. Path: ${filePath}`, causeErrors: err }); - } - } - - /** - * Helper method to initialize the event - * @param eventName The type of event to initialize - * @returns The initialized ImperativeEvent - */ - private initEvent(eventName: ImperativeEventType | string): ImperativeEvent { - this.ensureClassInitialized(); - return new ImperativeEvent({ appName: this.appName, eventName, isUser: this.isUserEvent(eventName), 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) { - event.location = location; - this.ensureEventsDirExists(location); - 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 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(eventName: string, callbacks: ImperativeEventCallback[] = []): fs.FSWatcher { - const dir = this.getEventDir(eventName); - 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); - const eventTime = eventContents.length === 0 ? "" : (JSON.parse(eventContents) as IImperativeEventJson).time; - - if (this.eventTimes.get(eventName) !== eventTime) { - this.logger.debug(`ImperativeEventEmitter: Event "${event}" emitted: ${eventName}`); - // Promise.all(callbacks) - callbacks.forEach(cb => cb()); - this.eventTimes.set(eventName, eventTime); - } - }); - this.subscriptions.set(eventName, [watcher, callbacks]); - return watcher; - } - - /** - * Check to see if the given event is a User event - * @param eventName A string representing the type of event - * @returns True if it is a user event, false otherwise - */ - 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 eventName A string representing the type of event - * @returns True if it is a shared event, false otherwise - */ - 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 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(eventName: string): eventName is ImperativeEventType { - return !this.isUserEvent(eventName) && !this.isSharedEvent(eventName); - } - - /** - * ZOWE HOME directory to search for system wide ImperativeEvents like `configChanged` - */ - private getSharedEventDir(): string { - return join(ImperativeConfig.instance.cliHome, ".events"); - } - - /** - * USER HOME directory to search for user specific ImperativeEvents like `vaultChanged` - */ - private getUserEventDir(): string { - return join(homedir(), ".zowe", ".events"); - } - - /** - * Obtain the directory of the event - * @param eventName The type of event to be emitted - * @returns The directory to where this event will be emitted - */ - public getEventDir(eventName: string): string { - if (this.isUserEvent(eventName)) { - return this.getUserEventDir(); - } 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 { - const eventLoc = join(this.getEventDir(eventName), eventName); - if (!fs.existsSync(eventLoc)) return ""; - return fs.readFileSync(eventLoc).toString(); - } - - /** - * Simple method to write the events to disk - * @param eventName The type of event to write - * @internal We do not want to make this function accessible to any application developers - */ - public emitEvent(eventName: ImperativeEventType) { - const theEvent = this.initEvent(eventName); - - if (this.isCustomEvent(eventName)) { - throw new ImperativeError({ msg: `Unable to determine the type of event. Event: ${eventName}` }); - } - - this.writeEvent(this.getEventDir(eventName), theEvent); - } - - /** - * Simple method to write the events to disk - * @param eventName The type of event to write - * @internal We won't support custom events as part of the MVP - */ - public emitCustomEvent(eventName: string) { //, isUserSpecific: boolean = false) { - const theEvent = this.initEvent(eventName); - - if (!this.isCustomEvent(eventName)) { - throw new ImperativeError({ msg: `Operation not allowed. Event is considered protected. Event: ${eventName}` }); - } - - this.writeEvent(this.getSharedEventDir(), theEvent); - } - - /** - * Method to register your custom actions based on when the given event is emitted - * @param eventName Type of event to register - * @param callback Action to be registered to the given event - */ - public subscribe(eventName: string, callback: ImperativeEventCallback): IImperativeRegisteredAction { - this.ensureClassInitialized(); - - let watcher: fs.FSWatcher; - if (this.subscriptions.get(eventName) != null) { - const [watcherToClose, callbacks] = this.subscriptions.get(eventName); - watcherToClose.removeAllListeners(eventName).close(); - - watcher = this.setupWatcher(eventName, [...callbacks, callback]); - } else { - watcher = this.setupWatcher(eventName, [callback]); - } - return { close: watcher.close }; - } - - /** - * Method to unsubscribe from custom and regular events - * @param eventName Type of registered event - */ - public unsubscribe(eventName: string): void { - this.ensureClassInitialized(); - - if (this.subscriptions.has(eventName)) { - const [watcherToClose, _callbacks] = this.subscriptions.get(eventName); - watcherToClose.removeAllListeners(eventName).close(); - this.subscriptions.delete(eventName); - } - if (this.eventTimes.has(eventName)) { - this.eventTimes.delete(eventName); - } - } -} \ No newline at end of file diff --git a/packages/imperative/src/events/src/doc/IEventDisposable.ts b/packages/imperative/src/events/src/doc/IEventDisposable.ts new file mode 100644 index 0000000000..5dc7e58099 --- /dev/null +++ b/packages/imperative/src/events/src/doc/IEventDisposable.ts @@ -0,0 +1,24 @@ +/* +* 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. +* +*/ + +/** + * Defines a contract for objects that can dispose of resources typically associated with an event. + * (Any object that implements this interface must provide an implementation of the close() method) + * @export + * @interface IEventDisposable + */ +export interface IEventDisposable { + /** + * Disposes of the actions registered to an event, effectively cleaning up or removing event handlers. + * @memberof IEventDisposable + */ + close(): void; +} diff --git a/packages/imperative/src/events/src/doc/IEventInstanceTypes.ts b/packages/imperative/src/events/src/doc/IEventInstanceTypes.ts new file mode 100644 index 0000000000..83615bfef0 --- /dev/null +++ b/packages/imperative/src/events/src/doc/IEventInstanceTypes.ts @@ -0,0 +1,49 @@ +/* +* 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 { EventCallback } from "../EventConstants"; +import { IEventDisposable } from "./IEventDisposable"; + +/** + * Interface for components that can subscribe to and unsubscribe from events. + * @interface IWatcher + */ +export interface IWatcher { + subscribeShared(eventName: string, callbacks: EventCallback[] | EventCallback): IEventDisposable; + subscribeUser(eventName: string, callbacks: EventCallback[] | EventCallback): IEventDisposable; + unsubscribe(eventName: string): void; +} + +/** + * Interface for components that can emit events. + * @interface IEmitter + */ +export interface IEmitter { + emitEvent(eventName: string): void; +} + +/** + * Interface for components that can both emit and watch events. + * Combines the capabilities of both IWatcher and IEmitter interfaces. + * @interface IEmitterAndWatcher + */ +export interface IEmitterAndWatcher extends IWatcher, IEmitter {} + +/** + * Enum representing the types of processors that can be used to handle events. + * Specifies whether the processor is a watcher, an emitter, or capable of both functions. + * @enum {string} + */ +export enum IProcessorTypes { + WATCHER = 'watcher', + EMITTER = 'emitter', + BOTH = 'both', +} \ No newline at end of file diff --git a/packages/imperative/src/events/src/doc/IImperativeEventJson.ts b/packages/imperative/src/events/src/doc/IEventJson.ts similarity index 53% rename from packages/imperative/src/events/src/doc/IImperativeEventJson.ts rename to packages/imperative/src/events/src/doc/IEventJson.ts index b433c49ec7..85bbe6363c 100644 --- a/packages/imperative/src/events/src/doc/IImperativeEventJson.ts +++ b/packages/imperative/src/events/src/doc/IEventJson.ts @@ -9,36 +9,37 @@ * */ +import { EventTypes } from "../EventConstants"; +import * as fs from "fs"; + /** - * Imperative Event JSON representation + * Imperative Event JSON representation for user interpretation * @export - * @interface IImperativeEventJson + * @interface IEventJson */ -export interface IImperativeEventJson { +export interface IEventJson { /** * The time in which the event occurred */ - time: string; + eventTime: string; /** - * The type of event that occurred + * The name of event that occurred */ - type: string; + eventName: string; /** * The application name that triggered the event */ - source: string; + appName: string; /** - * The ID of the event that occurred + * The file path for information on the emitted event */ - id?: string; + eventFilePath: string; /** - * The location in which the event was emitted (User vs Shared) + * The type of event that occurred */ - loc?: string; + eventType?: EventTypes; /** - * The indicator of user-specific (if true) or shared (if false) events + * List of watchers to eventually close */ - user?: boolean; + subscriptions?: fs.FSWatcher[]; } - -export type ImperativeEventCallback = () => void | PromiseLike; diff --git a/packages/imperative/src/events/src/doc/IImperativeEventEmitterOpts.ts b/packages/imperative/src/events/src/doc/IImperativeEventEmitterOpts.ts deleted file mode 100644 index 2f9a20c223..0000000000 --- a/packages/imperative/src/events/src/doc/IImperativeEventEmitterOpts.ts +++ /dev/null @@ -1,26 +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 { 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/IImperativeEventParms.ts b/packages/imperative/src/events/src/doc/IImperativeEventParms.ts deleted file mode 100644 index 3a9568b694..0000000000 --- a/packages/imperative/src/events/src/doc/IImperativeEventParms.ts +++ /dev/null @@ -1,45 +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 { Logger } from "../../../logger"; -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 - */ - eventName: 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} - * @memberof IImperativeEventParms - */ - logger: Logger; -} diff --git a/packages/imperative/src/events/src/doc/IImperativeRegisteredAction.ts b/packages/imperative/src/events/src/doc/IImperativeRegisteredAction.ts deleted file mode 100644 index 3f06b3f821..0000000000 --- a/packages/imperative/src/events/src/doc/IImperativeRegisteredAction.ts +++ /dev/null @@ -1,23 +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. -* -*/ - -/** - * 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 22eb9c922e..4a43ef166c 100644 --- a/packages/imperative/src/events/src/doc/index.ts +++ b/packages/imperative/src/events/src/doc/index.ts @@ -9,7 +9,6 @@ * */ -export * from "./IImperativeEventEmitterOpts"; -export * from "./IImperativeEventParms"; -export * from "./IImperativeRegisteredAction"; -export * from "./IImperativeEventJson"; +export * from "./IEventDisposable"; +export * from "./IEventInstanceTypes"; +export * from "./IEventJson"; 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 fc1c08c823..2d4af8c553 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,7 +23,6 @@ import { expectedProjectConfigObject, expectedSchemaObject } from "../../../../../../__tests__/__integration__/imperative/__tests__/__integration__/cli/config/__resources__/expectedObjects"; jest.mock("fs"); -jest.mock("../../../../../events/src/ImperativeEventEmitter"); const expectedConfigText = JSONC.stringify(expectedProjectConfigObject, null, ConfigConstants.INDENT); const expectedConfigObjectWithoutSchema = lodash.omit(expectedProjectConfigObject, "$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 e4e17c2d98..5a337c38ec 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 @@ -27,7 +27,6 @@ import { OverridesLoader } from "../../../../src/OverridesLoader"; import { ConfigUtils, ImperativeError } from "../../../../.."; 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 b4d881faed..c3194cc9c6 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, ImperativeEventEmitter, Logger } from "../../../../.."; +import { Logger } from "../../../../../logger"; import { Config } from "../../../../../config/src/Config"; import { IConfig, IConfigOpts, IConfigProfile } from "../../../../../config"; import { ImperativeConfig } from "../../../../../utilities"; @@ -26,8 +26,8 @@ import * as lodash from "lodash"; import * as fs from "fs"; import { SessConstants } from "../../../../../rest"; import { setupConfigToLoad } from "../../../../../../__tests__/src/TestUtil"; - -jest.mock("../../../../../events/src/ImperativeEventEmitter"); +import { IHandlerParameters } from "../../../../../cmd"; +import { EventOperator, EventUtils } from "../../../../../events"; let readPromptSpy: any; const getIHandlerParametersObject = (): IHandlerParameters => { @@ -103,7 +103,6 @@ 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"); @@ -125,6 +124,9 @@ describe("Configuration Secure command handler", () => { keytarSetPasswordSpy = jest.spyOn(keytar, "setPassword"); keytarDeletePasswordSpy = jest.spyOn(keytar, "deletePassword"); readPromptSpy.mockClear(); + + jest.spyOn(EventUtils, "validateAppName").mockImplementation(jest.fn()); + jest.spyOn(EventOperator, "getZoweProcessor").mockReturnValue({emitZoweEvent: jest.fn()} as any); }); afterEach( () => { 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 02f922c869..3185c489a6 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, ImperativeEventEmitter } from "../../../../.."; +import { IHandlerParameters } from "../../../../../cmd"; import { Config } from "../../../../../config/src/Config"; import { IConfigOpts } from "../../../../../config"; import { ImperativeConfig } from "../../../../../utilities"; @@ -25,9 +25,7 @@ import * as path from "path"; import * as lodash from "lodash"; import * as fs from "fs"; import { setupConfigToLoad } from "../../../../../../__tests__/src/TestUtil"; - -jest.mock("../../../../../events/src/ImperativeEventEmitter"); - +import { EventOperator, EventUtils } from "../../../../../events"; const getIHandlerParametersObject = (): IHandlerParameters => { const x: any = { @@ -96,7 +94,6 @@ 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"); @@ -117,6 +114,9 @@ describe("Configuration Set command handler", () => { keytarGetPasswordSpy = jest.spyOn(keytar, "getPassword"); keytarSetPasswordSpy = jest.spyOn(keytar, "setPassword"); keytarDeletePasswordSpy = jest.spyOn(keytar, "deletePassword"); + + jest.spyOn(EventUtils, "validateAppName").mockImplementation(jest.fn()); + jest.spyOn(EventOperator, "getZoweProcessor").mockReturnValue({emitZoweEvent: jest.fn()} as any); }); afterEach( () => { diff --git a/packages/imperative/src/imperative/__tests__/plugins/utilities/npm-interface/install.unit.test.ts b/packages/imperative/src/imperative/__tests__/plugins/utilities/npm-interface/install.unit.test.ts index 6d07a1a448..a46f112ab7 100644 --- a/packages/imperative/src/imperative/__tests__/plugins/utilities/npm-interface/install.unit.test.ts +++ b/packages/imperative/src/imperative/__tests__/plugins/utilities/npm-interface/install.unit.test.ts @@ -59,7 +59,7 @@ import { UpdateImpConfig } from "../../../../src/UpdateImpConfig"; import * as fs from "fs"; import * as path from "path"; import { gt as versionGreaterThan } from "semver"; -import { ProfileInfo } from "../../../../../config"; +import { ConfigUtils } from "../../../../../config"; import mockTypeConfig from "../../__resources__/typeConfiguration"; import { updateExtendersJson } from "../../../../src/plugins/utilities/npm-interface/install"; import { IExtendersJsonOpts } from "../../../../../config/src/doc/IExtenderOpts"; @@ -84,9 +84,9 @@ describe("PMF: Install Interface", () => { UpdateImpConfig_addProfiles: UpdateImpConfig.addProfiles as Mock, path: path as unknown as Mock, ConfigSchema_loadSchema: jest.spyOn(ConfigSchema, "loadSchema"), - ProfileInfo: { - readExtendersJsonFromDisk: jest.spyOn(ProfileInfo, "readExtendersJsonFromDisk"), - writeExtendersJson: jest.spyOn(ProfileInfo, "writeExtendersJson") + ConfigUtils: { + readExtendersJson: jest.spyOn(ConfigUtils, "readExtendersJson"), + writeExtendersJson: jest.spyOn(ConfigUtils, "writeExtendersJson") } }; @@ -110,14 +110,14 @@ describe("PMF: Install Interface", () => { mocks.sync.mockReturnValue("fake_find-up_sync_result" as any); jest.spyOn(path, "dirname").mockReturnValue("fake-dirname"); jest.spyOn(path, "join").mockReturnValue("/fake/join/path"); - mocks.ProfileInfo.readExtendersJsonFromDisk.mockReturnValue({ + mocks.ConfigUtils.readExtendersJson.mockReturnValue({ profileTypes: { "zosmf": { from: ["Zowe CLI"] } } }); - mocks.ProfileInfo.writeExtendersJson.mockImplementation(); + mocks.ConfigUtils.writeExtendersJson.mockImplementation(); mocks.ConfigSchema_loadSchema.mockReturnValue([mockTypeConfig]); mocks.ConfigurationLoader_load.mockReturnValue({ profiles: [mockTypeConfig] } as any); }); @@ -405,7 +405,7 @@ describe("PMF: Install Interface", () => { mocks.readFileSync.mockReturnValue(oneOldPlugin as any); if (opts.lastVersion) { - mocks.ProfileInfo.readExtendersJsonFromDisk.mockReturnValueOnce({ + mocks.ConfigUtils.readExtendersJson.mockReturnValueOnce({ profileTypes: { "test-type": { from: [oneOldPlugin.plugin1.package], @@ -426,9 +426,9 @@ describe("PMF: Install Interface", () => { if (opts.version && opts.lastVersion) { if (versionGreaterThan(opts.version, opts.lastVersion)) { - expect(mocks.ProfileInfo.writeExtendersJson).toHaveBeenCalled(); + expect(mocks.ConfigUtils.writeExtendersJson).toHaveBeenCalled(); } else { - expect(mocks.ProfileInfo.writeExtendersJson).not.toHaveBeenCalled(); + expect(mocks.ConfigUtils.writeExtendersJson).not.toHaveBeenCalled(); } } }; diff --git a/packages/imperative/src/imperative/__tests__/plugins/utilities/npm-interface/uninstall.unit.test.ts b/packages/imperative/src/imperative/__tests__/plugins/utilities/npm-interface/uninstall.unit.test.ts index 1a0230e89f..368d237fa5 100644 --- a/packages/imperative/src/imperative/__tests__/plugins/utilities/npm-interface/uninstall.unit.test.ts +++ b/packages/imperative/src/imperative/__tests__/plugins/utilities/npm-interface/uninstall.unit.test.ts @@ -30,7 +30,7 @@ import { PMFConstants } from "../../../../src/plugins/utilities/PMFConstants"; import { readFileSync, writeFileSync } from "jsonfile"; import { findNpmOnPath } from "../../../../src/plugins/utilities/NpmFunctions"; import { uninstall } from "../../../../src/plugins/utilities/npm-interface"; -import { ConfigSchema, ProfileInfo } from "../../../../../config"; +import { ConfigSchema, ConfigUtils } from "../../../../../config"; import mockTypeConfig from "../../__resources__/typeConfiguration"; import { ExecUtils } from "../../../../../utilities"; import { IExtendersJsonOpts } from "../../../../../config/src/doc/IExtenderOpts"; @@ -248,7 +248,7 @@ describe("PMF: Uninstall Interface", () => { const blockMocks = getBlockMocks(); if (opts.schemaUpdated) { blockMocks.fs.existsSync.mockReturnValueOnce(true); - jest.spyOn(ProfileInfo, "readExtendersJsonFromDisk").mockReturnValue({ + jest.spyOn(ConfigUtils, "readExtendersJson").mockReturnValue({ profileTypes: { "test-type": { from: ["a"], @@ -281,13 +281,13 @@ describe("PMF: Uninstall Interface", () => { describe("updateAndGetRemovedTypes", () => { const getBlockMocks = () => { - const profileInfo = { - readExtendersJsonFromDisk: jest.spyOn(ProfileInfo, "readExtendersJsonFromDisk"), - writeExtendersJson: jest.spyOn(ProfileInfo, "writeExtendersJson").mockImplementation(), + const configUtils = { + readExtendersJson: jest.spyOn(ConfigUtils, "readExtendersJson"), + writeExtendersJson: jest.spyOn(ConfigUtils, "writeExtendersJson").mockImplementation(), }; return { - profileInfo, + configUtils }; }; @@ -296,23 +296,23 @@ describe("PMF: Uninstall Interface", () => { schema?: boolean; }, extendersJson: IExtendersJsonOpts) => { const blockMocks = getBlockMocks(); - blockMocks.profileInfo.readExtendersJsonFromDisk.mockReturnValue(extendersJson); + blockMocks.configUtils.readExtendersJson.mockReturnValue(extendersJson); const hasMultipleSources = extendersJson.profileTypes["some-type"].from.length > 1; const wasLatestSource = extendersJson.profileTypes["some-type"].latestFrom === "aPluginPackage"; const typesToRemove = updateAndGetRemovedTypes("aPluginPackage"); if (shouldUpdate.extJson) { - expect(blockMocks.profileInfo.writeExtendersJson).toHaveBeenCalled(); + expect(blockMocks.configUtils.writeExtendersJson).toHaveBeenCalled(); } else { - expect(blockMocks.profileInfo.writeExtendersJson).not.toHaveBeenCalled(); + expect(blockMocks.configUtils.writeExtendersJson).not.toHaveBeenCalled(); return; } - const newExtendersObj = blockMocks.profileInfo.writeExtendersJson.mock.calls[0][0]; + const newExtendersObj = blockMocks.configUtils.writeExtendersJson.mock.calls[0][0]; if (hasMultipleSources) { - expect(blockMocks.profileInfo.writeExtendersJson).not.toHaveBeenCalledWith( + expect(blockMocks.configUtils.writeExtendersJson).not.toHaveBeenCalledWith( expect.objectContaining({ profileTypes: { "some-type": { @@ -369,7 +369,7 @@ describe("PMF: Uninstall Interface", () => { it("returns an empty list when package does not contribute any profile types", () => { const blockMocks = getBlockMocks(); - blockMocks.profileInfo.readExtendersJsonFromDisk.mockReturnValue({ + blockMocks.configUtils.readExtendersJson.mockReturnValue({ profileTypes: { "some-type": { from: ["anotherPkg"] 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 cf13d6e108..12b0696866 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,7 +10,6 @@ */ jest.mock("../../../../logger/src/LoggerUtils"); -jest.mock("../../../../events/src/ImperativeEventEmitter"); import * as fs from "fs"; import * as path from "path"; @@ -22,7 +21,7 @@ import { Config } from "../../../../config"; import { IConfigSecure } from "../../../../config/src/doc/IConfigSecure"; import FakeAuthHandler from "./__data__/FakeAuthHandler"; import { CredentialManagerFactory } from "../../../../security"; -import { ImperativeError, ImperativeEventEmitter } from "../../../.."; +import { EventOperator, EventUtils, ImperativeError } from "../../../.."; const MY_APP = "my_app"; @@ -38,7 +37,6 @@ 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: () => ({ @@ -48,6 +46,11 @@ describe("BaseAuthHandler config", () => { }); }); + beforeEach(() => { + jest.spyOn(EventUtils, "validateAppName").mockImplementation(jest.fn()); + jest.spyOn(EventOperator, "getZoweProcessor").mockReturnValue({emitZoweEvent: jest.fn()} as any); + }); + afterEach(() => { jest.restoreAllMocks(); jest.clearAllMocks(); @@ -466,6 +469,9 @@ describe("BaseAuthHandler config", () => { jest.spyOn(Config, "search").mockReturnValue(configPath); jest.spyOn(fs, "existsSync").mockReturnValueOnce(false).mockReturnValueOnce(true).mockReturnValue(false); fakeConfig = await Config.load(MY_APP, { vault: fakeVault }); + + jest.spyOn(EventUtils, "validateAppName").mockImplementation(jest.fn()); + jest.spyOn(EventOperator, "getZoweProcessor").mockReturnValue({emitZoweEvent: jest.fn()} as any); }); it("should logout successfully from profile specified by user", async () => { diff --git a/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/install.ts b/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/install.ts index a54badb564..7d2ecc0fd3 100644 --- a/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/install.ts +++ b/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/install.ts @@ -26,7 +26,7 @@ import { UpdateImpConfig } from "../../../UpdateImpConfig"; import { CredentialManagerOverride, ICredentialManagerNameMap } from "../../../../../security"; import { IProfileTypeConfiguration } from "../../../../../profiles"; import * as semver from "semver"; -import { ProfileInfo } from "../../../../../config"; +import { ConfigUtils } from "../../../../../config"; import { IExtendersJsonOpts } from "../../../../../config/src/doc/IExtenderOpts"; // Helper function to update extenders.json object during plugin install. @@ -188,7 +188,7 @@ export async function install(packageLocation: string, registry: string, install // Only update global schema if we were able to load it from disk if (loadedSchema != null) { const existingTypes = loadedSchema.map((obj) => obj.type); - const extendersJson = ProfileInfo.readExtendersJsonFromDisk(); + const extendersJson = ConfigUtils.readExtendersJson(); // Determine new profile types to add to schema let shouldUpdate = false; @@ -212,7 +212,7 @@ export async function install(packageLocation: string, registry: string, install if (shouldUpdate) { // Update extenders.json (if necessary) after installing the plugin - ProfileInfo.writeExtendersJson(extendersJson); + ConfigUtils.writeExtendersJson(extendersJson); } const schema = ConfigSchema.buildSchema(loadedSchema); ConfigSchema.updateSchema({ layer: "global", schema }); diff --git a/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/uninstall.ts b/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/uninstall.ts index a328bb160b..90e001720d 100644 --- a/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/uninstall.ts +++ b/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/uninstall.ts @@ -19,7 +19,7 @@ import { ImperativeError } from "../../../../../error"; import { ExecUtils, TextUtils } from "../../../../../utilities"; import { StdioOptions } from "child_process"; import { findNpmOnPath } from "../NpmFunctions"; -import { ConfigSchema, ProfileInfo } from "../../../../../config"; +import { ConfigSchema, ConfigUtils } from "../../../../../config"; import { IProfileTypeConfiguration } from "../../../../../profiles"; const npmCmd = findNpmOnPath(); @@ -30,7 +30,7 @@ const npmCmd = findNpmOnPath(); * @returns A list of types to remove from the schema */ export const updateAndGetRemovedTypes = (npmPackage: string): string[] => { - const extendersJson = ProfileInfo.readExtendersJsonFromDisk(); + const extendersJson = ConfigUtils.readExtendersJson(); const pluginTypes = Object.keys(extendersJson.profileTypes) .filter((type) => extendersJson.profileTypes[type].from.includes(npmPackage)); const typesToRemove: string[] = []; @@ -59,7 +59,7 @@ export const updateAndGetRemovedTypes = (npmPackage: string): string[] => { typesToRemove.push(profileType); } } - ProfileInfo.writeExtendersJson(extendersJson); + ConfigUtils.writeExtendersJson(extendersJson); } return typesToRemove; 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 717be13b89..c0a045a32a 100644 --- a/packages/imperative/src/rest/__tests__/session/ConnectionPropsForSessCfg.unit.test.ts +++ b/packages/imperative/src/rest/__tests__/session/ConnectionPropsForSessCfg.unit.test.ts @@ -10,7 +10,6 @@ */ jest.mock("../../../logger/src/LoggerUtils"); -jest.mock("../../../events/src/ImperativeEventEmitter"); import { ConnectionPropsForSessCfg } from "../../src/session/ConnectionPropsForSessCfg"; import { CliUtils } from "../../../utilities/src/CliUtils"; diff --git a/packages/imperative/src/security/__tests__/CredentialManagerOverride.unit.test.ts b/packages/imperative/src/security/__tests__/CredentialManagerOverride.unit.test.ts index 0a7a6f71d1..13d1824810 100644 --- a/packages/imperative/src/security/__tests__/CredentialManagerOverride.unit.test.ts +++ b/packages/imperative/src/security/__tests__/CredentialManagerOverride.unit.test.ts @@ -17,11 +17,9 @@ 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"; +import { EventOperator, EventUtils } from "../../events"; -jest.mock("../../events/src/ImperativeEventEmitter"); - describe("CredentialManagerOverride", () => { let mockImpConfig: any; let expectedSettings: any; @@ -32,7 +30,8 @@ describe("CredentialManagerOverride", () => { cliHome: __dirname }; jest.spyOn(ImperativeConfig, "instance", "get").mockReturnValue(mockImpConfig); - Object.defineProperty(ImperativeEventEmitter, "instance", { value: { emitEvent: jest.fn() }, configurable: true}); + jest.spyOn(EventUtils, "validateAppName").mockImplementation(jest.fn()); + jest.spyOn(EventOperator, "getZoweProcessor").mockReturnValue({emitZoweEvent: jest.fn()} as any); expectedSettings = { fileName: path.join(mockImpConfig.cliHome, "settings", "imperative.json"), diff --git a/packages/imperative/src/security/src/CredentialManagerOverride.ts b/packages/imperative/src/security/src/CredentialManagerOverride.ts index a5f545a8d0..0a943d1415 100644 --- a/packages/imperative/src/security/src/CredentialManagerOverride.ts +++ b/packages/imperative/src/security/src/CredentialManagerOverride.ts @@ -16,7 +16,8 @@ import { ICredentialManagerNameMap } from "./doc/ICredentialManagerNameMap"; import { ImperativeConfig } from "../../utilities"; import { ImperativeError } from "../../error"; import { ISettingsFile } from "../../settings/src/doc/ISettingsFile"; -import { ImperativeEventEmitter, ImperativeSharedEvents } from "../../events"; +import { ZoweSharedEvents } from "../../events"; +import { EventOperator } from "../../events/src/EventOperator"; /** * This class provides access to the known set of credential manager overrides @@ -133,7 +134,7 @@ export class CredentialManagerOverride { settings.json.overrides.CredentialManager = newCredMgrName; try { writeJsonSync(settings.fileName, settings.json, {spaces: 2}); - ImperativeEventEmitter.instance.emitEvent(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); + EventOperator.getZoweProcessor().emitZoweEvent(ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); } catch (error) { throw new ImperativeError({ msg: "Unable to write settings file = " + settings.fileName + @@ -188,7 +189,7 @@ export class CredentialManagerOverride { settings.json.overrides.CredentialManager = this.DEFAULT_CRED_MGR_NAME; try { writeJsonSync(settings.fileName, settings.json, {spaces: 2}); - ImperativeEventEmitter.instance.emitEvent(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); + EventOperator.getZoweProcessor().emitZoweEvent(ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); } catch (error) { throw new ImperativeError({ msg: "Unable to write settings file = " + settings.fileName + diff --git a/packages/provisioning/package.json b/packages/provisioning/package.json index 86888b30d8..01992a178a 100644 --- a/packages/provisioning/package.json +++ b/packages/provisioning/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/provisioning-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "description": "Zowe SDK to interact with the z/OS provisioning APIs", "author": "Zowe", "license": "EPL-2.0", @@ -49,9 +49,9 @@ }, "devDependencies": { "@types/js-yaml": "^4.0.9", - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/secrets/package.json b/packages/secrets/package.json index 25fdb5d971..cf50460549 100644 --- a/packages/secrets/package.json +++ b/packages/secrets/package.json @@ -3,7 +3,7 @@ "description": "Credential management facilities for Imperative, Zowe CLI, and extenders.", "repository": "https://github.com/zowe/zowe-cli.git", "author": "Zowe", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "homepage": "https://github.com/zowe/zowe-cli/tree/master/packages/secrets#readme", "bugs": { "url": "https://github.com/zowe/zowe-cli/issues" diff --git a/packages/workflows/package.json b/packages/workflows/package.json index 6756a80d57..16cbf9a995 100644 --- a/packages/workflows/package.json +++ b/packages/workflows/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-workflows-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "description": "Zowe SDK to interact with the z/OS workflows APIs", "author": "Zowe", "license": "EPL-2.0", @@ -45,12 +45,12 @@ "prepack": "node ../../scripts/prepareLicenses.js" }, "dependencies": { - "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407051717" + "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407112150" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/zosconsole/package.json b/packages/zosconsole/package.json index abf58e59db..0ca316dda3 100644 --- a/packages/zosconsole/package.json +++ b/packages/zosconsole/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-console-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "description": "Zowe SDK to interact with the z/OS console", "author": "Zowe", "license": "EPL-2.0", @@ -45,9 +45,9 @@ "prepack": "node ../../scripts/prepareLicenses.js" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/zosfiles/package.json b/packages/zosfiles/package.json index 2c4574d1e0..b447a1353e 100644 --- a/packages/zosfiles/package.json +++ b/packages/zosfiles/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-files-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "description": "Zowe SDK to interact with files and data sets on z/OS", "author": "Zowe", "license": "EPL-2.0", @@ -49,10 +49,10 @@ "minimatch": "^9.0.5" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717", - "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150", + "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407112150" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/zosjobs/package.json b/packages/zosjobs/package.json index 455536a3a7..ec97a0f530 100644 --- a/packages/zosjobs/package.json +++ b/packages/zosjobs/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-jobs-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "description": "Zowe SDK to interact with jobs on z/OS", "author": "Zowe", "license": "EPL-2.0", @@ -46,12 +46,12 @@ "prepack": "node ../../scripts/prepareLicenses.js" }, "dependencies": { - "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407051717" + "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407112150" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/zoslogs/package.json b/packages/zoslogs/package.json index f1b7da5a79..8ca33f75c2 100644 --- a/packages/zoslogs/package.json +++ b/packages/zoslogs/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-logs-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "description": "Zowe SDK to interact with the z/OS logs", "author": "Zowe", "license": "EPL-2.0", @@ -45,9 +45,9 @@ "prepack": "node ../../scripts/prepareLicenses.js" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/zosmf/package.json b/packages/zosmf/package.json index eb6dd8c5cc..4b7f87e619 100644 --- a/packages/zosmf/package.json +++ b/packages/zosmf/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zosmf-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "description": "Zowe SDK to interact with the z/OS Management Facility", "author": "Zowe", "license": "EPL-2.0", @@ -44,9 +44,9 @@ "prepack": "node ../../scripts/prepareLicenses.js" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/zostso/package.json b/packages/zostso/package.json index 829fcd438b..eac90956d1 100644 --- a/packages/zostso/package.json +++ b/packages/zostso/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-tso-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "description": "Zowe SDK to interact with TSO on z/OS", "author": "Zowe", "license": "EPL-2.0", @@ -45,12 +45,12 @@ "prepack": "node ../../scripts/prepareLicenses.js" }, "dependencies": { - "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407051717" + "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407112150" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/zosuss/package.json b/packages/zosuss/package.json index 5a6790754c..529f219a42 100644 --- a/packages/zosuss/package.json +++ b/packages/zosuss/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-uss-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "description": "Zowe SDK to interact with USS on z/OS", "author": "Zowe", "license": "EPL-2.0", @@ -49,8 +49,8 @@ }, "devDependencies": { "@types/ssh2": "^1.11.19", - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "peerDependencies": { "@zowe/imperative": "^8.0.0-next"