diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 779f99c67c..81ac5d97d0 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -17773,6 +17773,9 @@ "engines": { "node": ">=18.12.0" }, + "engines": { + "node": ">=18.12.0" + }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", "@zowe/imperative": "^8.0.0-next" diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 6fb49b665f..d1512a9b3e 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to the Zowe CLI package will be documented in this file. + +## Recent Changes + +- BugFix: Removing stack trace for zosjobs errors. [#2078](https://github.com/zowe/zowe-cli/pull/2078) + ## `8.0.0-next.202403122137` - BugFix: Fixed default base profile missing in config generated by `zowe config auto-init` [#2088](https://github.com/zowe/zowe-cli/pull/2088) diff --git a/packages/cli/__tests__/zosjobs/__integration__/submit/local-file/cli.zos-jobs.submit.local-file.integration.test.ts b/packages/cli/__tests__/zosjobs/__integration__/submit/local-file/cli.zos-jobs.submit.local-file.integration.test.ts index 1aecaf6e68..c70097a608 100644 --- a/packages/cli/__tests__/zosjobs/__integration__/submit/local-file/cli.zos-jobs.submit.local-file.integration.test.ts +++ b/packages/cli/__tests__/zosjobs/__integration__/submit/local-file/cli.zos-jobs.submit.local-file.integration.test.ts @@ -40,7 +40,6 @@ describe("zos-jobs submit local-file command", () => { const response = runCliScript(__dirname + "/__scripts__/submit_invalid_local_file.sh", TEST_ENVIRONMENT); expect(response.status).toBe(1); - expect(response.stderr.toString().toLowerCase()).toContain("error"); expect(response.stderr.toString().toLowerCase()).toContain("no such file"); }); diff --git a/packages/cli/__tests__/zosjobs/__unit__/submit/Submit.shared.handler.unit.test.ts b/packages/cli/__tests__/zosjobs/__unit__/submit/Submit.shared.handler.unit.test.ts index 79ff419564..ac27ef0577 100644 --- a/packages/cli/__tests__/zosjobs/__unit__/submit/Submit.shared.handler.unit.test.ts +++ b/packages/cli/__tests__/zosjobs/__unit__/submit/Submit.shared.handler.unit.test.ts @@ -74,6 +74,33 @@ describe("submit shared handler", () => { expect(error.message).toContain("Unable to determine the JCL source. Please contact support"); }); + it("should return any caught error, ie: ENOENT", async () => { + // Require the handler and create a new instance + const handlerReq = require("../../../../src/zosjobs/submit/Submit.shared.handler"); + const handler = new handlerReq.default(); + + // Vars populated by the mocked function + let error; + + // Local file doesn't exist and should be cause of failure + const theLocalFile: string = "fakefile"; + + const copy = Object.assign({}, LOCALFILE_PARAMETERS); + copy.arguments.localFile = theLocalFile; + try { + // Invoke the handler with a full set of mocked arguments and response functions + await handler.process(copy); + } catch (e) { + error = e; + } + + expect(error).toBeDefined(); + expect(error).toBeInstanceOf(ImperativeError); + expect(error.message).toContain("Node.js File System API error"); + expect(error.additionalDetails).toContain("ENOENT: no such file or directory, open"); + expect(error.additionalDetails).toContain("fakefile"); + }); + it("should not transform an error thrown by the submit JCL API", async () => { // Require the handler and create a new instance const handlerReq = require("../../../../src/zosjobs/submit/Submit.shared.handler"); diff --git a/packages/cli/src/zosjobs/submit/Submit.shared.handler.ts b/packages/cli/src/zosjobs/submit/Submit.shared.handler.ts index a5f714849d..07a74c2ea6 100644 --- a/packages/cli/src/zosjobs/submit/Submit.shared.handler.ts +++ b/packages/cli/src/zosjobs/submit/Submit.shared.handler.ts @@ -11,7 +11,7 @@ import { IHandlerParameters, ImperativeError, ITaskWithStatus, TaskProgress, TaskStage } from "@zowe/imperative"; import * as fs from "fs"; -import { ISubmitParms, SubmitJobs, IJob, ISpoolFile } from "@zowe/zos-jobs-for-zowe-sdk"; +import { ISubmitParms, SubmitJobs, IJob, ISpoolFile, ZosJobsMessages } from "@zowe/zos-jobs-for-zowe-sdk"; import { IDownloadOptions, Get } from "@zowe/zos-files-for-zowe-sdk"; import { ZosmfBaseHandler } from "@zowe/zosmf-for-zowe-sdk"; import { text } from "stream/consumers"; @@ -31,150 +31,167 @@ export default class SharedSubmitHandler extends ZosmfBaseHandler { * @memberof SubmitDataSetHandler */ public async processCmd(params: IHandlerParameters): Promise { - const status: ITaskWithStatus = { - statusMessage: "Submitting job", - percentComplete: TaskProgress.TEN_PERCENT, - stageName: TaskStage.IN_PROGRESS - }; - // Save the needed parameters for convenience - const parms: ISubmitParms = { - jclSource: undefined, - viewAllSpoolContent: this.mArguments.viewAllSpoolContent, - directory: this.mArguments.directory, - extension: this.mArguments.extension, - volume: this.mArguments.volume, - waitForActive: this.mArguments.waitForActive, - waitForOutput: this.mArguments.waitForOutput, - task: status, - jclSymbols: this.mArguments.jclSymbols - }; - const options: IDownloadOptions = {}; - params.response.progress.startBar({task: status}); - - // Determine the positional parameter specified and invoke the correct API - // TODO: More will be added with additional commands - let sourceType: string; - if (this.mArguments.dataset) { - sourceType = "dataset"; - } else if (this.mArguments.file) { - sourceType = "uss-file"; - } else if (this.mArguments.localFile) { - sourceType = "local-file"; - } else if (params.definition.name === "stdin") { - sourceType = "stdin"; - } - let response: IJob; // Response from Submit Job - let apiObj: any; // API Object to set in the command JSON response - let spoolFilesResponse: ISpoolFile[]; // Response from view all spool content option - let source: any; // The actual JCL source (i.e. data-set name, file name, etc.) - let directory: string = this.mArguments.directory;// Path where to download spool content - - // Process depending on the source type - switch (sourceType) { - - // Submit the JCL from a data set - case "dataset": - - // If the data set is not in catalog and volume option is provided - if (parms.volume) { - options.volume = parms.volume; - - // Get JCL from data set or member - const getJcl = await Get.dataSet(this.mSession, this.mArguments.dataset, options); - source = this.mArguments.dataset; - - apiObj = await SubmitJobs.submitJclString(this.mSession, getJcl.toString(), parms); - if (parms.viewAllSpoolContent) { - spoolFilesResponse = apiObj; + try{ + const status: ITaskWithStatus = { + statusMessage: "Submitting job", + percentComplete: TaskProgress.TEN_PERCENT, + stageName: TaskStage.IN_PROGRESS + }; + // Save the needed parameters for convenience + const parms: ISubmitParms = { + jclSource: undefined, + viewAllSpoolContent: this.mArguments.viewAllSpoolContent, + directory: this.mArguments.directory, + extension: this.mArguments.extension, + volume: this.mArguments.volume, + waitForActive: this.mArguments.waitForActive, + waitForOutput: this.mArguments.waitForOutput, + task: status, + jclSymbols: this.mArguments.jclSymbols + }; + const options: IDownloadOptions = {}; + params.response.progress.startBar({task: status}); + + // Determine the positional parameter specified and invoke the correct API + // TODO: More will be added with additional commands + let sourceType: string; + if (this.mArguments.dataset) { + sourceType = "dataset"; + } else if (this.mArguments.file) { + sourceType = "uss-file"; + } else if (this.mArguments.localFile) { + sourceType = "local-file"; + } else if (params.definition.name === "stdin") { + sourceType = "stdin"; + } + let response: IJob; // Response from Submit Job + let apiObj: any; // API Object to set in the command JSON response + let spoolFilesResponse: ISpoolFile[]; // Response from view all spool content option + let source: any; // The actual JCL source (i.e. data-set name, file name, etc.) + let directory: string = this.mArguments.directory;// Path where to download spool content + + // Process depending on the source type + switch (sourceType) { + + // Submit the JCL from a data set + case "dataset": + + // If the data set is not in catalog and volume option is provided + if (parms.volume) { + options.volume = parms.volume; + + // Get JCL from data set or member + const getJcl = await Get.dataSet(this.mSession, this.mArguments.dataset, options); + source = this.mArguments.dataset; + + apiObj = await SubmitJobs.submitJclString(this.mSession, getJcl.toString(), parms); + if (parms.viewAllSpoolContent) { + spoolFilesResponse = apiObj; + } + + break; + } else { + response = await SubmitJobs.submitJobCommon(this.mSession, {jobDataSet: this.mArguments.dataset, + jclSymbols: this.mArguments.jclSymbols}); + apiObj = await SubmitJobs.checkSubmitOptions(this.mSession, parms, response); + source = this.mArguments.dataset; + + if (parms.viewAllSpoolContent) { + spoolFilesResponse = apiObj; + } } break; - } else { - response = await SubmitJobs.submitJobCommon(this.mSession, {jobDataSet: this.mArguments.dataset, + // Submit JCL from a USS file + case "uss-file": + response = await SubmitJobs.submitJobCommon(this.mSession, {jobUSSFile: this.mArguments.file, jclSymbols: this.mArguments.jclSymbols}); apiObj = await SubmitJobs.checkSubmitOptions(this.mSession, parms, response); - source = this.mArguments.dataset; + source = this.mArguments.ussfile; if (parms.viewAllSpoolContent) { spoolFilesResponse = apiObj; } - } - - break; - // Submit JCL from a USS file - case "uss-file": - response = await SubmitJobs.submitJobCommon(this.mSession, {jobUSSFile: this.mArguments.file, - jclSymbols: this.mArguments.jclSymbols}); - apiObj = await SubmitJobs.checkSubmitOptions(this.mSession, parms, response); - source = this.mArguments.ussfile; - - if (parms.viewAllSpoolContent) { - spoolFilesResponse = apiObj; - } - break; - // Submit the JCL from a local file - case "local-file": { - parms.jclSource = this.mArguments.localFile; - const JclString = fs.readFileSync(this.mArguments.localFile).toString(); - apiObj = await SubmitJobs.submitJclString(this.mSession, JclString, parms); - source = this.mArguments.localFile; - if (parms.viewAllSpoolContent) { - spoolFilesResponse = apiObj; + break; + // Submit the JCL from a local file + case "local-file": { + parms.jclSource = this.mArguments.localFile; + let JclString: string; + try { + JclString = fs.readFileSync(this.mArguments.localFile).toString(); + } catch (err) { + throw new ImperativeError({ + msg: ZosJobsMessages.nodeJsFsError.message, + additionalDetails: err.toString(), + causeErrors: err + }); + } + apiObj = await SubmitJobs.submitJclString(this.mSession, JclString, parms); + source = this.mArguments.localFile; + if (parms.viewAllSpoolContent) { + spoolFilesResponse = apiObj; + } + break; } - break; - } - // Submit the JCL piped in on stdin - case "stdin": { - const Jcl = await text(params.stdin); - apiObj = await SubmitJobs.submitJclString(this.mSession, Jcl, parms); - source = "stdin"; - if (parms.viewAllSpoolContent) { - spoolFilesResponse = apiObj; + // Submit the JCL piped in on stdin + case "stdin": { + const Jcl = await text(params.stdin); + apiObj = await SubmitJobs.submitJclString(this.mSession, Jcl, parms); + source = "stdin"; + if (parms.viewAllSpoolContent) { + spoolFilesResponse = apiObj; + } + break; } - break; + default: + throw new ImperativeError({ + msg: `Internal submit error: Unable to determine the JCL source. ` + + `Please contact support.`, + additionalDetails: JSON.stringify(params) + }); } - default: - throw new ImperativeError({ - msg: `Internal submit error: Unable to determine the JCL source. ` + - `Please contact support.`, - additionalDetails: JSON.stringify(params) - }); - } - // Print the response to the command - if (spoolFilesResponse == null) { - params.response.format.output({ - fields: ["jobid", "retcode", "jobname", "status"], - output: apiObj, - format: "object" - }); - // Set the API object to the correct - this.data.setObj(apiObj); - - // Print data from spool content - } else { - for (const spoolFile of spoolFilesResponse) { - if (spoolFile.procName != null && spoolFile.procName.length > 0) { - this.console.log("Spool file: %s (ID #%d, Step: %s, ProcStep: %s)", - spoolFile.ddName, spoolFile.id, spoolFile.stepName, spoolFile.procName); - } else { - this.console.log("Spool file: %s (ID #%d, Step: %s)", - spoolFile.ddName, spoolFile.id, spoolFile.stepName); + // Print the response to the command + if (spoolFilesResponse == null) { + params.response.format.output({ + fields: ["jobid", "retcode", "jobname", "status"], + output: apiObj, + format: "object" + }); + // Set the API object to the correct + this.data.setObj(apiObj); + + // Print data from spool content + } else { + for (const spoolFile of spoolFilesResponse) { + if (spoolFile.procName != null && spoolFile.procName.length > 0) { + this.console.log("Spool file: %s (ID #%d, Step: %s, ProcStep: %s)", + spoolFile.ddName, spoolFile.id, spoolFile.stepName, spoolFile.procName); + } else { + this.console.log("Spool file: %s (ID #%d, Step: %s)", + spoolFile.ddName, spoolFile.id, spoolFile.stepName); + } + this.console.log(spoolFile.data); } - this.console.log(spoolFile.data); - } - // Set the API object to the correct - this.data.setObj(spoolFilesResponse); - } + // Set the API object to the correct + this.data.setObj(spoolFilesResponse); + } - // Print path where spool content was downloaded - if (directory != null && spoolFilesResponse == null) { - directory = directory.includes("./") ? directory : `./${directory}`; - params.response.console.log(`Successfully downloaded output to ${directory}/${apiObj.jobid}`); + // Print path where spool content was downloaded + if (directory != null && spoolFilesResponse == null) { + directory = directory.includes("./") ? directory : `./${directory}`; + params.response.console.log(`Successfully downloaded output to ${directory}/${apiObj.jobid}`); + } + params.response.progress.endBar(); + this.data.setMessage(`Submitted JCL contained in "${sourceType}": "${source}"`); + }catch (err){ + if (err instanceof ImperativeError){ + throw err; + } else { + throw new ImperativeError({msg: err.message, causeErrors: err}); + } } - params.response.progress.endBar(); - this.data.setMessage(`Submitted JCL contained in "${sourceType}": "${source}"`); } -} +} \ No newline at end of file diff --git a/packages/zosjobs/CHANGELOG.md b/packages/zosjobs/CHANGELOG.md index 751853e5a1..7e4bd41370 100644 --- a/packages/zosjobs/CHANGELOG.md +++ b/packages/zosjobs/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to the Zowe z/OS jobs SDK package will be documented in this file. +## Recent Changes + +- BugFix: Removing stack trace for zosjobs errors. Added constant to JobsMessages.ts for error handling. [#2078](https://github.com/zowe/zowe-cli/pull/2078) + ## `8.0.0-next.202403041352` - BugFix: Updated engine to Node 18.12.0. [#2074](https://github.com/zowe/zowe-cli/pull/2074) diff --git a/packages/zosjobs/src/JobsMessages.ts b/packages/zosjobs/src/JobsMessages.ts index dc01bc5638..0eec22f2e3 100644 --- a/packages/zosjobs/src/JobsMessages.ts +++ b/packages/zosjobs/src/JobsMessages.ts @@ -50,5 +50,12 @@ export const ZosJobsMessages: { [key: string]: IMessageDefinition } = { */ missingSearchOption: { message: "You must specify either the `--search-string` or `--search-regex` option" + }, + + /** + * Message indicating that a failure has happened in the NodeJS File System API + */ + nodeJsFsError: { + message: "Node.js File System API error" } };