exec function in Angular - node.js

I am trying to run a bash command through Angular.
After some research i found the code below that should be working
import { ChildProcess, exec } from 'child_process';
#Injectable({
providedIn: 'root'
})
export class CommandService {
runCommand(command: string): Promise<string> {
return new Promise((resolve, reject) => {
exec(command, (error: Error, stdout: string, stderr: string) => {
if (error) {
reject(error);
}
resolve(stdout);
});
});
}
}
then i call it in my .ts file
import { CommandService } from './command.service';
export class YourComponent {
output: string;
constructor(private commandService: CommandService) {}
run() {
this.commandService.runCommand('ls -la')
.then(output => this.output = output)
.catch(error => console.log(error));
}
The problem is that even though there doesnt seem to have any errors, when i try to run it it says:
error TS2307: Cannot find module 'child_process' or its corresponding type declarations.
3 import { ChildProcess } from "child_process";
, even though i can see that the file exists in my project.
Is there something i can do to fix that ? If not can i run a command some other way through Angular ?
Thanks in advance

I afraid you can't do that dave Jason.
child_process is available in a nodeJS runtime and Angular runs in a browser. That's 2 different runtimes which only have in common to use JS as a language.
Also, don't expect to be able to run a bash command from the browser that would be a MASSIVE security issue.

Matthieu Riegler is right, you cannot access OS features from within the browser environment. That being said, depending on what you want to do, you could have the bash command run at build time (e.g. with an npm script) or if you really need it at run time and happen to have a backend as well, you could expose an endpoint that runs the bash command on demand and return the result over http.

For anyone who might come to this question for answers:
it's better to use Nodejs (backend) for bash commands.
an example of what i was trying to do is shown in the code below :
onst express = require("express");
const { exec, execSync } = require("child_process");
const cors = require("cors");
const path = require("path");
const app = express();
app.use(
cors({
origin: "http://localhost:4200",
})
);
app.get("/ls", (req, res) => {
exec("ls -la .", (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return res.status(500).send({ error });
}
res.send({ output: stdout });
});
});
const homeDirectory = execSync("echo ~").toString().trim();
const frontendPath = path.join(homeDirectory, "frontend-software");
process.chdir(frontendPath);
exec("ng serve --open", (err, stdout, stderr) => {
if (err) {
console.error(err);
return;
}
console.log(stdout);
});
The 1st exec is an ls call and the 2nd is to run an angular project.
Hope it helps you all!

Related

Terminate a process from node js

//Run commands to start navigation and robot_pose package
nav = spawn("roslaunch",["turtlebot3_navigation", "turtlebot3_navigation.launch","map_file:=$HOME/catkin_ws/src/robot-rosbridge/server/newmap.yaml"], { shell: true })
nav.stdout.on("data", data => {
console.log(`stdout: ${data}`);
});
nav.on('error', (error) => {
console.log(`error: ${error.message}`);
res.sendStatus(500)
});
Well here I'm trying to terminate the nav process using "nav.kill()" but the process is not terminating .Earlier while I was using spawn without { shell: true } nav.kill() worked fine but now its not working.
On the whole I'm trying to spawn a process when a specific route is requested and terminate it when another route is requested
i.e.,
const express = require('express')
const app = express()
app.get("/start", (req, res) => {
//spawn a process
});
app.get("/stop", (req, res) => {
//kill process
});
I'm confused as to how spawn is working. Can anyone suggest a solution and the working of the spawn function or any better alternative.
Thanks in advance.

In Electron Main Process, I want to execute external CLI app like kubectl

Problem
I want to automate CLI tools (like kubectl) with electron GUI. It is working well in development environment... But in production build my app can't find programs in PATH.
in development
I'm using shelljs in electron. But exec method is my own implementation. (shelljs exec electron compatibility issue) It spawn child_process then resolve with stdout.
const { spawn } = require('child_process')
exports.childProcessWithResult = async function (args) {
try {
return new Promise((resolve, reject) => {
let result = ''
const res = spawn('sh', args, { shell: true })
res.stdout.on('data', (data) => {
result += `${data}`
})
res.stderr.on('data', (data) => {
result += `error: ${data}`
console.error(data.toString('utf-8'))
})
res.on('error', (error) => {
console.error(error.toString('utf-8'))
})
res.on('close', (code) => {
resolve(result)
})
})
} catch(error) {
console.error(error)
}
}
npm run electron:build
"electron:build": "npm run build && npx electron ."
On Developer Tools (via IPC)
await window.electronAPI.shell.exec('"which kubectl"')
// '/usr/local/bin/kubectl'
await window.electronAPI.shell.which('kubectl')
// '/usr/local/bin/kubectl'
await window.electronAPI.shell.exec('"command -v kubectl"')
// '/usr/local/bin/kubectl'
console.log(await window.electronAPI.shell.exec('"ls"'))
/*
README.md
arkit.json
auth.json
auto-imports.d.ts
coverage
dist
...
*/
in production build
I'm using electron builder. Target is MacOS dmg format. The app is installed and I click the icon to run my app.
await window.electronAPI.shell.exec('"which kubectl"')
// ''
await window.electronAPI.shell.which('kubectl')
// {error: "Cannot read properties of null (reading 'stdout')"}
await window.electronAPI.shell.exec('"command -v kubectl"')
// ''
console.log(await window.electronAPI.shell.exec('"ls"'))
/*
Applications
Library
System
Users
Volumes
bin
*/
My Research about sandboxing
The scripts runs on the main process of the electron.
The main process runs in a Node.js environment, meaning it has the ability to require modules and use all of Node.js APIs. [Electron docs]
Note that as in Chromium, the main (browser) process is privileged and cannot be sandboxed. [Electron docs]
IPC communication
I'm using IPC to communicate with renderer process (my vue spa).
// electron.js (main process)
const { app, BrowserWindow, ipcMain } = require('electron') // electron": "^16.2.5
const shell = require('shelljs') // "shelljs": "^0.8.5",
// <...>
// create BrowserWindow
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
contextIsolation: true,
preload: path.join(__dirname, 'preload.js')
}
})
//<...>
app.whenReady().then(() => {
ipcMain.handle('shell:which', async (event, ...args) => {
return shell.which(...args).catch(e => ({ error: e.message }))
})
ipcMain.handle('shell:exec', async (event, script) => {
return childProcessWithResult(['-c', script])
.then(result => result.trim())
.catch(e => ({ error: e.message }))
})
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
// preload.js (renderer process)
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
shell: {
which: (...args) => ipcRenderer.invoke('shell:which', ...args),
exec: (script) => ipcRenderer.invoke('shell:exec', script)
}
})
I grant File Access and Developer Tools Permission to my app, (by Security & Privacy)
The problem is solved!! I don't know what to say. :) But I will try.
As we know, Electron doesn't sandbox the main process. The reason is just the PATH variable.
In Windows PowerShell, I can't see the problem. Why? Because the PATH environment variables are saved in Windows Registry. It is independent of PowerShell.
But in UNIX(like Mac OS, and Linux), they are set when the shell is initiated with ~/.zshrc or ~/.bashrc.
When I click the packaged app icon, to execute the application... shell is not initiated. No Environment Variables. No PATH. The Shell can not find our cli programs!
But we can read the content of ~/xxshrc. So I just execute the script, and get the PATH variable, then set it to process.env.PATH with other defaults (like brew path).
Next is the code.
const env = await childProcessWithResult('zsh -ic export')
const PATH = env.split('\n')
.find(s => s.startsWith('PATH'))
.replace('PATH=', '')
process.env.PATH = [
'/usr/local/bin',
'/opt/homebrew/bin',
'/opt/homebrew/sbin',
'/usr/local/go/bin',
'/Library/Apple/usr/bin',
'/Applications/Privileges.app/Contents/Resources',
PATH
].join(':')

executing command from child_process.exec and cmd

In my Node.js program, I would like to know if a specific port is used so I execute the command below with child_process.exec (example with port 3001) :
netstat -na | find "3001"
If the port is used I get informations about the port as expected but If it is not used I get the following error:
I don't understand why I get this error because when I run the same command in cmd, If the port is not used it doesn't throw error:
Can anyone please tell me what's wrong ?
Node version: v10.16.0
I think you should try this. I have created API you can directly call.
This will return the result data.
const { exec } = require("child_process");
function os_func() {
this.execCommand = function(cmd, callback) {
exec(cmd, (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return;
}
callback(stdout);
});
}
}
app.get("/", (req, res) => {
console.log("inside get");
var os = new os_func();
os.execCommand('netstat -na | find "3001"', function (returnvalue) {
res.end(returnvalue)
});
});

Load custom configuration runtime

I have a nuxt application in which I will need to append data from a generated configuration file when the application is first started. The reason I cannot do this in the actual build is because the configuration file does not exists at this point; it is generated just before calling npm start by a bootstrap script.
Why don't I generated the configuration file before starting the application you may ask and this is because the application is run in a docker container and the built image cannot include environment specific configuration files since it should be used on different environments such as testing, staging and production.
Currently I am trying to use a hook to solve this, but I am not really sure on how to actually set the configuration data in the application so it can be used everywhere:
# part of nuxt.config.js
hooks: {
listen(server, listener) {
# load the custom configuration file.
fs.readFile('./config.json', (err, data) => {
let configData = JSON.parse(data));
});
}
},
The above hook is fired when the application first starts to listen for connecting clients. Not sure this is the best or even a possible way to go.
I also made an attempt of using a plugin to solve this:
import axios from ‘axios’;
export default function (ctx, inject) {
// server-side logic
if (ctx.isServer) {
// here I would like to simply use fs.readFile to load the configuration, but this is not working?
} else {
// client-side logic
axios.get(‘/config.json’)
.then((res) => {
inject(‘storeViews’, res.data);
});
}
};
In the above code I have problems both with using the fs module and axios.
I was also thinking about using a middleware to do this, but not sure on how to proceed.
If someone else has this kind of problem here is the solution I came up with in the end:
// plugins/config.js
class Settings
{
constructor (app, req) {
if (process.server) {
// Server side we load the file simply by using fs
const fs = require('fs');
this.json = fs.readFileSync('config.json');
} else {
// Client side we make a request to the server
fetch('/config')
.then((response) => {
if (response.ok) {
return response.json();
}
})
.then((json) => {
this.json = json;
});
}
}
}
export default function ({ req, app }, inject) {
inject('config', new Settings(app, req));
};
For this to work we need to use a server middleware:
// api/config.js
const fs = require('fs');
const express = require('express');
const app = express();
// Here we pick up requests to /config and reads and return the
// contents of the configuration file
app.get('/', (req, res) => {
fs.readFile('config.json', (err, contents) => {
if (err) {
throw err;
}
res.set('Content-Type', 'application/json');
res.end(contents);
});
});
module.exports = {
path: '/config',
handler: app
};

Need to run a NodeJs application from another NodeJs application

I have a NodeJs application running in the following directory
First Application's Path '/users/user1/projects/sampleProject' which is running at 3000 port.
Second Application's Path '/users/user1/demoProjects/demo1' which is going to run at 5000 port on triggering the router function from first application.
The second NodeJs application is not yet started(It will run at port 5000). It need to run independently on hitting a router function in the first NodeJs Application which is running on port 3000 ie(http://localhost:3000/server/startServer). I'm new to NodeJs child processes, Kindly correct me if i'm wrong. And suggest me a right way to do it. Thanks
Start another node application using node.js?
I have tried it like below
// First NodeJs application
import { exec } from "child_process";
router.get('/startServer', async (req, res, next) => {
console.log("Initiated request")
let startServerInstance = 'cd "/users/user1/demoProjects/demo1" && npm run dev'; // path for the second NodeJs application
console.log("Server instance path => " + startServerInstance)
try {
// exec from child process, Spawns a shell then executes the command within that shell
let child = exec(startServerInstance, function (err, stdout, stderr) {
if (err) throw err;
else {
console.log("result ")
res.json({
status: 'success'
});
}
});
} catch (error) {
res.json({
status: 'error',
message: error
});
}
});
The above code executes the command and triggered the second application to run in background but it doesn't return anything. Either error or success result.
You need to use stout and stderror to check other server logs. Also your code is not correct. If you use if without {} it will not go to else statement. That is why you don't see 'result' text in console.
import {
exec
} from "child_process";
router.get('/startServer', async (req, res, next) => {
console.log("Initiated request")
let startServerInstance = 'cd "/users/user1/demoProjects/demo1" && npm run dev'; // path for the second NodeJs application
console.log("Server instance path => " + startServerInstance)
try {
// exec from child process, Spawns a shell then executes the command within that shell
let child = exec(startServerInstance, function(err) {
if (err) throw err;
console.log("Server started");
});
child.stdout.on('data', (data) => {
// this is new server output
console.log(data.toString());
});
child.stderr.on('data', (data) => {
// this is new server error output
console.log(data.toString());
});
res.json({
status: 'success'
});
} catch (error) {
res.json({
status: 'error',
message: error
});
}
});
Child process callback is only called once the process terminates. If the process keeps running, callback is not triggered.
Explained here - https://nodejs.org/docs/latest-v10.x/api/child_process.html#child_process_child_process_exec_command_options_callback

Resources