How to execute forever command remotely without using full path? - node.js

I am trying to execute “forever” command remotely using powershell but I am getting error
'forever' is not recognized as an internal or external command
Here is what I am doing. I have the node js script "MyNodeScript.js" which executes the forever command. The script is on the WebServer "MyWebServer1".
Node.Js and Forever is installed on MyWebServer1 globally.
var exec = require('child_process').exec;
var _ = require('underscore');
var winston = require('winston');
var async = require('async');
var mycommand = 'forever -p ./logs --sourceDir ../wf_manager --workingDir ../wf_manager start -a --uid "ServiceApp" ServiceApp.js'
function start(callback) {
async.waterfall([
function (cb) {
executeCommand(mycommand, false, cb);
}
], function done(err) {
if (err) {
winston.error('failed to start all instances by forever:' + err);
} else {
winston.info('successfully started all instances by forever');
}
callback();
});
}
function executeCommand(command, skip, callback) {
async.waterfall([
function (cb) {
exec(command, cb);
}
], function done(err) {
if (err) {
if (skip) {
// skip the error
callback(null);
} else {
callback(err);
}
} else {
callback(null);
}
});
}
module.exports = {
executeCommand: executeCommand,
start: start
}
start(function(){});
On the same MyWebServer1 under same folder i have a powershell script "MyPowerShellScript.ps1" which call this node script. The powershell script has only one line
node D:\myfolder\maintenance\MyNodeScript.js
I can run this powershell script locally on MyWebServer1 and it works fine. But when i try to execute this powershell script as below from remote machine
invoke-command -computername MyWebServer1 -filepath \\MyWebServer1\MyFolder\maintenance\MyPowerShellScript.ps1
i am getting error
error: failed to start all instances by forever:Error: Command failed:
C:\Windows\system32\cmd.exe /s /c "forever -p ./logs --sourceDir
../wf_manager --workingDir ../wf_manager start -a --uid "ServiceApp"
ServiceApp.js"
+ CategoryInfo : NotSpecified: (error: failed t...ServiceApp.js":String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
+ PSComputerName : MyWebServer1
'forever' is not recognized as an internal or external command, operable program or
batch file.
Note that i can execute the script remotely without any error if i update "MyNodeScript.js" and use full physical path for the forever command
var mycommand = 'C:\\Users\\admin\\AppData\\Roaming\\npm\\forever -p ./logs --sourceDir ../wf_manager --workingDir ../wf_manager start -a --uid "ServiceApp" ServiceApp.js'
However i would like to use just forever command. The path is already added as Environment variable on MyWebServer1

Try calling the ps1-file like this:
Invoke-Command -Scriptblock {powershell.exe -command {d:\blah\MyPowershellscript.ps1 } }
Via -filepath your specify a local scriptfile that should be executed remotely. As your script is present on the target system, you can omit that.

Related

execSync throws and error trying to run node

I am running some processes inside of an EC2 instance.
To run it I initiate it with an SSM command:
cd / && cd home/ec2-user && . .nvm/nvm.sh && cd ufo && npm run start
and inside of it, I have a method in app.ts which is initialized with ts-node app.ts
import { execSync } from 'node:child_process';
import { takeNextScheduledAudit } from './sqs-scheduler';
import { uploadResultsToBucket } from './s3-uploader';
import { AuditRunParams } from "./types";
import { sendAuditResults } from "./sendResults";
(async function conductor(): Promise<void> {
const nextAuditRunParams = await takeNextScheduledAudit();
if (!nextAuditRunParams) {
execSync("sudo shutdown -h now");
}
const { targetUrl, requesterId, endpoint } = nextAuditRunParams as AuditRunParams;
try {
execSync(`npx user-flow --url=${targetUrl} --open=false`);
const resultsUrl = await uploadResultsToBucket(targetUrl);
await sendAuditResults(requesterId, endpoint, resultsUrl);
} catch (error) {
console.log(error);
}
await conductor();
})();
If I log in manually and run npm run start the scripts works as intended but if I run it using the SSM command I get this output:
> start
> ts-node app.ts
Error: Command failed: npx user-flow --url=https://deep-blue.io/ --open=false
at checkExecSyncError (node:child_process:841:11)
at execSync (node:child_process:912:15)
at conductor (/home/ec2-user/ufo/app.ts:15:17)
at processTicksAndRejections (node:internal/process/task_queues:96:5) {
status: 243,
signal: null,
output: [ null, <Buffer >, <Buffer 0a> ],
pid: 2691,
stdout: <Buffer >,
stderr: <Buffer 0a>
}
and this error:
Error: Command failed: sudo shutdown -h now
at checkExecSyncError (node:child_process:841:11)
at execSync (node:child_process:912:15)
at conductor (/home/ec2-user/ufo/app.ts:10:17)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async conductor (/home/ec2-user/ufo/app.ts:21:5) {
status: null,
signal: 'SIGTERM',
output: [ null, Buffer(0) [Uint8Array] [], Buffer(0) [Uint8Array] [] ],
pid: 2705,
stdout: Buffer(0) [Uint8Array] [],
stderr: Buffer(0) [Uint8Array] []
}
failed to run commands: exit status 1
Moreover, if I run execSync("node -v && npx -v") it also throws an error.
Why can I run this script when i am logged in but if i run it via a SSM command it does not recognize node inside of node?
--- Edit - Added Info ---
When running execSync(node -v && npx -v,{shell: '/bin/bash'}) I get an error:
Error: Command failed: node -v && npx -v
When running execSync(ps -p $$ && echo $SHELL, {shell: '/bin/bash'}):
PID TTY TIME CMD
7817 ? 00:00:00 bash
/bin/bash
And when I loggin and do ps -p $$ && echo $SHELL I get:
PID TTY TIME CMD
6873 pts/0 00:00:00 bash
/bin/bash
By default, all of the child_process functions execute in the same environment as the process that launched them. I don't have an account handy to test with, but it's quite likely that SSM skips over a traditional shell and just executes certain runtimes directly.
You can use the exec options like this to set a particular shell in which to launch the process:
const output = execSync('echo "doing stuff"', {
shell: '/bin/bash',
})
console.log('***** output:', output.toString())
This is assuming the OS you're using for the EC2 instance has bash available. Most flavors of linux should, but for what you're doing there, /bin/sh is sufficient if not. To get a list of the available shells, you can run:
cat /etc/shells
## or possibly
sudo cat /etc/shells
EDIT: Since you say it works fine in a shell already, you have presumably already handled this, but user-flow would also have to be available. It's not a module from npmjs, so would need to already be present on the box as either a local dependency or a private repo to which the EC2 instance has access.

Passing parameters from Jenkins CI to npm script

When I run Jenkins build, I would like to pass COMMIT_HASH and BRANCH_NAME to one of my javascript files: publish.js, so that I can remove hard-coded values for tags and consumerVersion.
Here is my code:
Jenkinsfile
stage('Publish Pacts') {
steps {
script {
sh 'npm run publish:pact -Dpact.consumer.version=${COMMIT_HASH} -Dpact.tag=${env.BRANCH_NAME}'
}
}
}
package.json
"scripts": {
"publish:pact": "node ./src/test/pact/publish.js"
}
./src/test/pact/publish.js
let publisher = require('#pact-foundation/pact-node');
let path = require('path');
let opts = {
providerBaseUrl: `http://localhost:${global.port}`,
pactFilesOrDirs: [path.resolve(process.cwd(), 'pacts')],
pactBroker: 'http://localhost:80',
tags: ["prod", "test"], // $BRANCH_NAME
consumerVersion: "2.0.0" // $COMMIT_HASH
};
publisher.publishPacts(opts).then(() => {
console.log("Pacts successfully published");
done()
});
Does anyone know how to do this?
You can pass cli arguments to your node script which end up in your process.argv.
Also npm passes on cli arguments via two dashes --.
To illustrate this consider this example:
Jenkinsfile
stage('Publish Pacts') {
steps {
script {
sh 'npm run publish:pact -- ${COMMIT_HASH} ${env.BRANCH_NAME}'
}
}
}
package.json
"scripts": {
"publish:pact": "node ./src/test/pact/publish.js"
}
publish.js
// process.argv[0] = path to node binary
// process.argv[1] = path to script
console.log('COMMIT_HASH:',process.argv[2]);
console.log('BRANCH_NAME:',process.argv[3]);
I left the cli flags out for simplicity.
Hope this helps

Run another yarn/npm task within a package.json, without specifying yarn or npm

I have a task in my package.json "deploy", which needs to first call "build". I have specified it like this:
"deploy": "yarn run build; ./deploy.sh",
The problem is that this hard codes yarn as the package manager. So if someone doesn't use yarn, it doesn't work. Switching to npm causes a similar issue.
What's a good way to achieve this while remaining agnostic to the choice of npm or yarn?
One simple approach is to use the npm-run-all package, whose documentation states:
Yarn Compatibility
If a script is invoked with Yarn, npm-run-all will correctly use Yarn to execute the plan's child scripts.
So you can do this:
"predeploy": "run-s build",
"deploy": "./deploy.sh",
And the predeploy step will use either npm or yarn depending on how you invoked the deploy task.
I think it is good to have the runs in package.json remain package manager agnostic so that they aren't tied to a specific package manager, but within a project, it is probably prudent to agree on the use of a single package manager so that you're not dealing with conflicting lockfiles.
It's probably not ideal, but you could run a .js file at your project root to make these checks...
You could create a file at your project root called yarnpm.js (or whatever), and call said file in your package.json deploy command..
// package.json (trimmed)
"scripts": {
"deploy": "node yarnpm",
"build": "whatever build command you use"
},
// yarnpm.js
const fs = require('fs');
const FILE_NAME = process.argv[1].replace(/^.*[\\\/]/, '');
// Command you wish to run with `{{}}` in place of `npm` or `yarn'
// This would allow you to easily run multiple `npm`/`yarn` commands without much work
// For example, `{{}} run one && {{}} run two
const COMMAND_TO_RUN = '{{}} run build; ./deploy.sh';
try {
if (fs.existsSync('./package-lock.json')) { // Check for `npm`
execute(COMMAND_TO_RUN.replace('{{}}', 'npm'));
} else if (fs.existsSync('./yarn.lock')) { // Check for `yarn`
execute(COMMAND_TO_RUN.replace('{{}}', 'yarn'));
} else {
console.log('\x1b[33m', `[${FILE_NAME}] Unable to locate either npm or yarn!`, '\033[0m');
}
} catch (err) {
console.log('\x1b[31m', `[${FILE_NAME}] Unable to deploy!`, '\033[0m');
}
function execute(command) { // Helper function, to make running `exec` easier
require('child_process').exec(command,
(error, stdout, stderr) => {
if (error) {
console.log(`error: ${error.message}`);
return;
}
if (stderr) {
console.log(`stderr: ${stderr}`);
return;
}
console.log(stdout);
});
}
Hope this helps in some way! Cheers.
EDIT:
...or if you wanted to parameterize the yarnpm.js script, to make it easily reusable, and to keep all "commands" inside the package.json file, you could do something like this..
// package.json (trimmed, parameterized)
"scripts": {
"deploy": "node yarnpm '{{}} run build; ./deploy.sh'",
"build": "node build.js"
},
// yarnpm.js (parameterized)
const COMMAND_TO_RUN = process.argv[2]; // Technically, the first 'parameter' is the third index
const FILE_NAME = process.argv[1].replace(/^.*[\\\/]/, '');
if (COMMAND_TO_RUN) {
const fs = require('fs');
try {
if (fs.existsSync('./package-lock.json')) { // Check for `npm`
execute(COMMAND_TO_RUN.replace('{{}}', 'npm'));
} else if (fs.existsSync('./yarn.lock')) { // Check for `yarn`
execute(COMMAND_TO_RUN.replace('{{}}', 'yarn'));
} else {
console.log('\x1b[33m', `[${FILE_NAME}] Unable to locate either npm or yarn!`, '\033[0m');
}
} catch (err) {
console.log('\x1b[31m', `[${FILE_NAME}] Unable to deploy!`, '\033[0m');
}
function execute(command) { // Helper function, to make running `exec` easier
require('child_process').exec(command,
(error, stdout, stderr) => {
if (error) {
console.log(`error: ${error.message}`);
return;
}
if (stderr) {
console.log(`stderr: ${stderr}`);
return;
}
console.log(stdout);
});
}
} else {
console.log('\x1b[31m', `[${FILE_NAME}] Requires a single argument!`, '\033[0m')
}
What if check before run?
You can create a new file called build.sh, and it's content below:
# check if current user installed node environment, if not, auto install it.
if command -v node >/dev/null 2>&1; then
echo "version of node: $(node -v)"
echo "version of npm: $(npm -v)"
else
# auto install node environment, suppose platform is centos,
# need change this part to apply other platform.
curl --silent --location https://rpm.nodesource.com/setup_12.x | sudo bash -
yum -y install nodejs
fi
npm run build
Then your script will be:
{
"deploy": "./build.sh && ./deploy.sh"
}
So I think I have a much simpler solution:
"deploy": "yarn run build || npm run build; ./deploy.sh",
Its only real downside is in the case where yarn exists, but the build fails, then npm run build will also take place.

Open Terminal failed with osascript after electron-packager

When I try to execute an osascript command under my Electron project, it works under dev mode(1), but after building it with electron-packager(2) and executing the binary file, an error appears.
It works with this execution command to debug:
# electron .
It fails with this compilation command to package the project:
# electron-packager . MyProject --platform=darwin --arch=x64 --version=1.3.5 --overwrite
This is the source code:
var childProcess = require('child_process');
var script = "/usr/bin/osascript -e 'tell application \"Terminal\"' -e 'set newTab to do script' -e 'end tell'";
childProcess.exec(script, function(error, stdout, stderr) {
console.log('stdout: ' + stdout);
console.log('stderr: ' + stderr);
if (error !== null) {
console.log('exec error: ' + error);
}
});
Console displays this error:
stdout:
stderr: 45:51: syntax error: Expected end of line but found “script”. (-2741)
exec error: Error: Command failed: /usr/bin/osascript -e 'tell application "Terminal"' -e 'set newTab to do script' -e 'end tell'
45:51: syntax error: Expected end of line but found “script”. (-2741)
BUT
The same source code works after packager if the script variable is equal to:
var script = "/usr/bin/osascript -e 'get volume settings'";
My Env
My node version: v4.5.0
My electron version: v1.3.5

nodemon : Passing arguments to the executable when using as a required module

I'm trying to start a script with nodemon, using it as a required module, and I cannot pass arguments correctly.
For example, for
var args = [
process.argv[0], '--harmony',
'/path/to/script.js', '-i', 'logs'
];`
I'm expecting the script to be launched as :
node --harmony /path/to/script.js -i logs
But it doesn't work and all I can manage to get is
node --harmony /path/to/script.js -i logs /path/to/script.js
This is what I tried :
var app = require('nodemon')({
script: args[2],
exec: args.join(' ')
});
I know about execMap, but it's no good as I cannot pass arguments at the end anyway.
How can it be done?
Skimming through the source code, I found the args config options (undocumented...). It turns out to be what I needed.
var app = require('nodemon')({
exec: args.slice(0, 2),
script: args[2],
args: args.slice(3)
});
I recommend use gulp with nodemon
var argv = require('optimist').argv
gulp = require("gulp"),
nodemon = require("gulp-nodemon");
gulp.task("default", [], function(){
nodemon({
script: 'app.js',
ignore: ["public/*"],
env: {'NODE_ENV': 'development'},
args: ["--port="+argv.port],
exec: "node --harmony"
}).on("start");
});

Resources