UNPKG

hay-caf

Version:

Haystacks Cloud Automation Framework: A demonstration testing automation framework application for the purposes of demonstrating the usage and integration of D-CAF, Hest, Hexiom, HayRunner, HayMonkey, and HayD-CAG in a single testing automation framework

260 lines (230 loc) 11.9 kB
/** * @file win32SpawnedProcess.js * @module win32SpawnedProcess * @description Creates the shell to execute the given command on Windows. * @requires module:application.message.constants * @requires module:application.system.constants * @requires {@link https://www.npmjs.com/package/@haystacks/constants|@haystacks/constants} * @requires {@link https://nodejs.dev/learn/the-nodejs-fs-module|fs} * @requires {@link https://www.npmjs.com/package/tmp|tmp} * @requires {@link https://nodejs.dev/learn/the-nodejs-process-module|process} * @requires {@link https://nodejs.dev/learn/the-nodejs-child_process-module|child_process} * @requires {@link https://www.npmjs.com/package/path|path} * @author Karl-Edward FP Jean-Mehu * @date 2023/01/02 * @copyright Copyright © 2023-… by Karl-Edward FP Jean-Mehu. All rights reserved. */ // Internal imports import * as app_msg from '../../constants/application.message.constants.js'; import * as app_sys from '../../constants/application.system.constants.js'; // External imports import hayConst from '@haystacks/constants'; import fs from 'fs'; import tmp from 'tmp'; import process from 'process'; import childProcess from 'child_process'; import path from 'path'; const { bas, gen, msg, num, sys, wrd } = hayConst; const baseFileName = path.basename(import.meta.url, path.extname(import.meta.url)); // framework.childProcess.shells.win32SpawnedProcess. const namespacePrefix = wrd.cframework + bas.cDot + wrd.cchild + wrd.cProcess + bas.cDot + wrd.cshells + bas.cDot + baseFileName + bas.cDot; // Checks whether the provided Shell executable exists and is executable // TODO: check each executable before running commands function isExecutableExists(pathToShellExecutable) { // let functionName = isExecutableExists.name; // console.log(msg.cBEGIN_Space + namespacePrefix + functionName + msg.cSpaceFunction); // pathToShellExecutable is: // console.log(app_msg.cPathToShellExecutableIs + pathToShellExecutable); let returnData = false; try { // Check if the file exists fs.accessSync(pathToShellExecutable, fs.constants.X_OK); returnData = true; // File exists and is executable } catch (err) { if (err.code === gen.cENOENT || err.code === gen.cEACCES) { returnData = false; // File does not exist or is not executable } else { throw err; // Other error, rethrow it } } // console.log(msg.creturnDataIs + returnData); // console.log(msg.cEND_Space + namespacePrefix + functionName + msg.cSpaceFunction); return returnData; } /** * @function shell * @description Creates temporary shell and executes given command. * @param {string} shellCommandToRun The command to be executed. * @param {Object} options Options to change behavior how shell is executed. * @param {string} [options.shell = "powershell|cmd|bash"] Optional. Options to change behavior how shell is executed * @param {function(arg1)=} callback Optional callback to send data back to spawnedProcess * @return {void} * @author Karl-Edward FP Jean-Mehu * @date 2023/01/02 */ export async function shell(shellCommandToRun, options, callback) { // let functionName = shell.name; // console.log(msg.cBEGIN_Space + namespacePrefix + functionName + msg.cSpaceFunction); // shellCommandToRun is: // console.log(app_msg.cshellCommandToRunIs + shellCommandToRun); // options are: // console.log(msg.coptionsAre + options); // Shell object let shellScript = null; // File options for temporary shell script. const tempFileOptions = { mode: 0o755, postfix: gen.cDotsh, tmpdir: bas.cDot + bas.cForwardSlash, keep: true, }; try { if (typeof shellCommandToRun == 'undefined') { // Shell command not defined throw new Error(app_msg.cshellCommandNotDefined); } // Sets the shell used to invoke the desired command // based on the user selected shell. let spawnOptions; let scriptContent = shellCommandToRun; // TODO: Add automatic retry if desired shell did not work. switch(options.shell.toLowerCase()) { case sys.cpowershell: spawnOptions = [ wrd.cstart, [sys.cpowershell, app_sys.cdashNoExit, app_sys.cdashNoProfile, app_sys.cdashExecutionPolicy, wrd.cBypass, app_sys.cInvokeDashExpression, app_sys.cdashCommand], {shell: true} ]; // Script extension tempFileOptions.postfix = gen.cDotps1; // Closes the window 3 seconds // after the command completes // ; Start-Sleep -Seconds 3; Exit shellCommandToRun = shellCommandToRun + bas.cSemiColon + bas.cSpace + app_sys.cStartDashSleep + bas.cSpace + app_sys.cDashSeconds + bas.cSpace + num.c3 + bas.cSemiColon + bas.cSpace + wrd.cExit; // Powershell command to execute commands scriptContent = app_sys.cSetDashLocation + bas.cSpace + bas.cDoubleQuote + options.CAFfeinatedPath + bas.cDoubleQuote + bas.cCarRetNewLin + shellCommandToRun; break; case gen.ccmd: case sys.cdos: spawnOptions = [gen.ccmd, [bas.cForwardSlash + bas.cc]]; // Closes the window 3 seconds // after the command completes // & timeout /t 3 nobreak & exit shellCommandToRun = shellCommandToRun + bas.cSpace + bas.cAndPersand + bas.cSpace + wrd.ctime + wrd.cout + bas.cSpace + bas.cForwardSlash + bas.ct + bas.cSpace + num.c3 + bas.cSpace + wrd.cno + wrd.cbreak + bas.cSpace + bas.cAndPersand + bas.cSpace + wrd.cexit; // Powershell command to execute commands // Need to cd into the CAFfeinated folder first, // because for some reason testcafe needs to be running from the local instance of testcafe, not the global instance of testcafe. // Without this, the test will start, run for a few seconds and then FAIL! // cd ${options.CAFfeinatedPath} && start /wait ${shellCommandToRun} scriptContent = bas.ccd + bas.cSpace + options.CAFfeinatedPath + app_sys.cDosBatAndStartWait + shellCommandToRun; // Script extension tempFileOptions.postfix = gen.cDotbat; break; case sys.cbash: // spawnOptions = [wrd.cstart, ['"C:/Program Files/Git/git-bash.exe"']]; // WAS WORKING PERFECTLY // spawnOptions = [wrd.cstart, ['"git-bash"']]; // ALSO WAS WORKING PERFECTLY spawnOptions = [wrd.cstart, [bas.cDoubleQuote + sys.cgitDashBash + bas.cDoubleQuote]]; // Closes the window 3 seconds // after the command completes // ; sleep 3; exit shellCommandToRun = shellCommandToRun + bas.cSemiColon + bas.cSpace + wrd.csleep + bas.cSpace + num.c3 + bas.cSemiColon + bas.cSpace + wrd.cexit; // Bash command to execute commands scriptContent = bas.ccd + bas.cSpace + options.CAFfeinatedPath + bas.cCarRetNewLin + shellCommandToRun + bas.cCarRetNewLin + bas.cDollar + wrd.cSHELL; break; default: // Selected shell not found. throw new Error(app_msg.cselectedShellNotFound); } // Write shell script to temporary shell file shellScript = tmp.fileSync(tempFileOptions); // shellScript file name is: // console.log(app_msg.cshellScriptFileNameIs + shellScript.name); await fs.writeFile(shellScript.fd, scriptContent, gen.cUTF8, async (error) => { await fs.close(shellScript.fd); // Close the file explicitly to avoid confusion. // Script content is: // console.log(msg.cscriptContentIs + scriptContent); switch(options.shell.toLowerCase()) { case sys.cpowershell: // spawnOptions[1].push(`\\"@(${shellScript.name})\\"`); // WORKING ALL THE WAY, but without ECHO in child process // No need for double back slashes when it's like this, don't need to escape the escape to escape. spawnOptions[1].push(bas.cBackSlash + bas.cDoubleQuote + bas.cAt + bas.cOpenParenthesis + shellScript.name + bas.cCloseParenthesis + bas.cBackSlash + bas.cDoubleQuote); break; case sys.ccmd: case sys.cdos: spawnOptions[1].push(shellScript.name); // DOS break; case sys.cbash: spawnOptions[1].push(shellScript.name); // BASH break; default: } // Check and proceed if the temporary file has successfully been written if (error) { // Error creating the tmp file // console.log(app_msg.cerrorCreatingTheTmpFile + error); } else { // Tmp file successfully written: // console.log(app_msg.cTmpFileSuccessfullyWritten + shellScript.name); // await callback(shellScript.name); // Call the callback function to pass back the name of the script file. if (process[wrd.csend]) { process.send({[wrd.cName]: shellScript.name}); } // Ensure the use of a single shell instance let child; if (child && !child.killed) { // ${scriptContent}/n child.stdin.write(scriptContent + bas.cBackSlash + bas.cn, gen.cUTF8, (err) => { if (err) { console.log(err); } }); } else { // Opening [bash|powershell and etc] // Opening: console.log(msg.cOpeningColon + options.shell); // spawn command RAW is: // console.log(app_msg.cspawnCommandRawIs + spawnOptions.join(bas.cSpace)); // let spawnOptionsString = spawnOptions[0] + ' ' + [...spawnOptions.slice(1)].join(' '); // spawn command is: // console.log(app_msg.cspawnCommandIs + spawnOptionsString); child = childProcess.spawn(spawnOptions[0], ...spawnOptions.slice(1), {shell: true}); // Handles actions taken when errors occurs on child process child.on(wrd.cerror, async (error) => { // let eventName = bas.cDot + wrd.cerror; // console.log(msg.cBEGIN_Space + namespacePrefix + functionName + eventName + msg.cSpaceEvent); console.log(msg.cerrorIs + error); // Error from child: if (process[wrd.csend]) process.send(bas.cCarRetNewLin + msg.cErrorFromChildColon + error); // console.log(msg.cEND_Space + namespacePrefix + functionName + eventName +msg.cSpaceEvent); }); child.on(wrd.cspawn, async () => { // let eventName = bas.cDot + wrd.cspawn; // console.log(msg.cBEGIN_Space + namespacePrefix + functionName + eventName + msg.cSpaceEvent); console.log(app_msg.cProcessWasSpawned); // console.log(msg.cEND_Space + namespacePrefix + functionName + eventName +msg.cSpaceEvent); }); child.on(wrd.cdisconnect, async () => { // let eventName = bas.cDot + wrd.cdisconnect; // console.log(msg.cBEGIN_Space + namespacePrefix + functionName + eventName + msg.cSpaceEvent); if (process[wrd.csend]) process.send(bas.cCarRetNewLin + msg.cChildDisconnected); // console.log(msg.cEND_Space + namespacePrefix + functionName + eventName +msg.cSpaceEvent); }); child.on(wrd.cexit, async (code, signal) => { // let eventName = bas.cDot + wrd.cexit; // console.log(msg.cBEGIN_Space + namespacePrefix + functionName + eventName + msg.cSpaceEvent); // console.log(msg.ccodeIs + code); // console.log(msg.csignalIs + signal); // Exiting child process if (process[wrd.csend]) process.send(bas.cCarRetNewLin + msg.cExitingChildProcess); // console.log(msg.cEND_Space + namespacePrefix + functionName + eventName +msg.cSpaceEvent); }); } } }) } catch (error) { // Error on shell: console.log(msg.cErrorOnShell + error.message) } // console.log(msg.cEND_Space + namespacePrefix + functionName + msg.cSpaceFunction); }