Use Node.js as Shell - node.js

How might I set up node.js as a shell replacement for bash? For example I should be able to run vi('file') to open a file and cd('location') to change between directories.
Is this even possible?

Sure you can! It will become much less straightforward to use your computer, though.
First off, you will need to know how to set this up. While you could likely set your user shell in Linux to usr/bin/node, this will leave you with only a Node.js REPL with no additional programs set up. What you're going to want to do is write a setup script that can do all of the below setup/convenience steps for you, essentially something that ends with repl.start() to produce a REPL after setting everything up. Of course, since UNIX shell settings can't specify arguments, you will need to write a small C program that executes your shell with those arguments (essentially, exec("/usr/bin/node", "path/to/setup/script.js");) and set that as your UNIX shell.
The main idea here is that any commands that you use beyond the very basics must be require()d into your shell - e.g. to do anything with your filesystem, execute
var fs = require("fs")
and do all of your filesystem calls from the fs object. This is analogous to adding things to your PATH. You can get basic shell commands by using shelljs or similar, and to get at actual executable programs, use Node's built-in child_process.spawnSync for a foreground task or child_process.spawn for a background task.
Since part of your requirement is that you want to call each of your programs like a function, you will need to produce these functions yourself, getting something like:
function ls(path) {
child_process.spawnSync('/bin/ls', [path], { stdio: 'inherit' });
}
for everything you want to run. You can probably do this programmatically by iterating through all the entries in your PATH and using something involving eval() or new Function() to generate execute functions for each, assigning them to the global object so that you don't have to enter any prefixes.
Again, it will become much less straightforward to use your computer, despite having these named functions. Lots of programs that cheat and use bash commands in the background will likely no longer work. But I can certainly see the appeal of being able to leverage JavaScript in your command-line environment.
ADDENDUM: For writing this setup script, the REPLServer object returned by repl.start() has a context object that is the same as the global object accessible to the REPL session it creates. When you write your setup script, you will need to assign everything to the context object:
const context = repl.start('> ').context;
context.ls = function ls(path) { /* . . . */ }
context.cd = function cd(path) { /* . . . */ }

I think it would be an intersting proposition. Create a test account and tell it to use node as it's shell. See 'man useradd' for all options
$ useradd -s /usr/bin/node test
$ su - test

This works on mac and linux.
require('child_process').spawnSync('vi', ['file.txt'], { stdio: 'inherit' })

You could bootstrap a repl session with your own commands, then run the script
#!/bin/bash
node --experimental-repl-await -i -e "$(< scripts/noderc.js)"`
This allows for things like:
> ls()
> run('vi','file.txt')
> await myAsyncFunc()

I think you're looking for something like this https://youtu.be/rcwcigtOwQ0 !
If so.... YES you can!
If you like I can share my code. But I need to fix some bugs first!
tell me if you like.
my .sh function:
const hey = Object.create(null),
sh = Object.create(null);;
hey.shell = Object.create(null);
hey.shell.run = require('child_process').exec;
sh.help = 'Execute an OS command';
sh.action = (...args) => {
// repl_ is the replServer
// the runningExternalProgram property is one way to know if I should
// render the prompt and is not needed. I will create a better
// way to do this (action without if/decision)!
repl_.runningExternalProgram = true;
hey.shell.run(args.join(' '),
(...args) => {
['error', 'log'].forEach((command, idx) => {
if (args[idx]) {
console[command](args[idx]);
}
});
repl_.runningExternalProgram = false;
});
};
PS: to 'cd' into some directory you just need to change the process.cwd (current working directory)
PS2: to avoid need to write .sh for every OS program/command you can use Proxy on the global object.

Related

Managing multiple processes within a NodeJS application

Let's say I'd want to control (start/stop) several other NodeJS scripts from within one "main" NodeJS app. However, not necessarly NodeJS Scripts exclusively, but also simple bash scripts.
I'm looking into the following solution, using execa
// simplified
const managedProcesses = [];
async function Start (pkg) {
const runningProcess = await execa(pkg.run_command, {
cwd : pkg.path
});
return runningProcess;
}
async function Stop (pkg) {
// somehow stop the child process
return
}
const someProcess = await Start({
run_command : 'node app.js',
path : './path/to/my/script/'
});
// Keep Reference of process
managedProcesses.push(someProcess);
I first thought pm2 would be a viable solution, but I guess this would only fit for NodeJS-only scripts.
What Problems could I run into using the approach above ?
Should I consider moving forward with this idea ?
For node.js subprocesses there is the cluster module and I strongly recommend using this. For general subprocesses (e.g. bash scripts as you mentioned) you have to use child_process (-> execa). Communication between processes may then be accomplished via grpc. Your approach is fine, so you can consider moving forward with it.
I decided to go full with pm2 for the time being, as they have an excellent programmatic API - also (which I only just learned about) you can specify different interpreters to run your script. So not only node apps are possible but also bash, python, php and so on - which is exactly what I am looking for.

Simulating Command Line Entries For Node Tests

I am writing some tests for a Node/MongoDB project that runs various modules via command line entries. My question is, for the tests, is there a way I can simulate a command line entry? For instance, if what I write in the command line is:
TASK=run-comparison node server
... is there a way I can effectively simulate that within my tests?
The common practice here as far as I know, is to wrap as much of your app as you can within a function/class where you pass the arguments, so you can easily test it with unit tests:
function myApp(args, env){
// My app code with given args and env variables
}
In your test file:
// Run app with given env variable
myApp("", { TASK: "run-comparison"});
In your particular case, if all your tasks are set through env variables, through editing of process.env, mocks, or .env files you may be able to test that without modifications on your code.
If that is not enough for your case (i.e. you really need to exactly simulate command line execution) I wrote a small library to solve this exact issue some time ago: https://github.com/angrykoala/yerbamate (I'm not sure if there are other alternatives available now).
With the example you provided, A test case could be something like this:
const yerbamate = require('yerbamate');
// Gets the package.json information
const pkg = yerbamate.loadPackage(module);
//Test the given command in the package.json root dir
yerbamate.run("TASK=run-comparison node server", pkg.dir, {}, function(code, out, errs) {
// This callback will be called once the script finished
if (!yerbamate.successCode(code)) console.log("Process exited with error code!");
if (errs.length > 0) console.log("Errors in process:" + errs.length);
console.log("Output: " + out[0]); // Stdoutput
});
In the end, this is a fairly simple wrapper of native child_process which you could also use to solve your problem by directly executing subprocesses.

How to run a process before anything else in a Node.js app?

I want to decrypt several config items based on environment variables before anything else starts running in a Node.js app.
I'm starting my app using the standard node ./app.js. Then I call a simple method from the top of my app.js file:
function setConfig() {
var pass = process.env.pass;
var conf = Encrypt.decrypt(encryptedConfig, pass);
var configObj = JSON.parse(conf);
// do stuff with the configObj
}
This works fine, but since everything is async other processes, which need the config variables, are already running and throwing errors.
What I want is to run my setConfig() before anything else. Is this doable?
Apart from accepted answer, what might be useful in some situations (where you can't/don't want to modify the executed file) is NODE_OPTIONS environmental variable + --require (-r) param of node executable
NODE_OPTIONS='--require "./first.js"' node second.js
That way, first.js executes before second.js.
Docs:
https://nodejs.org/api/cli.html#cli_node_options_options
https://nodejs.org/api/cli.html#cli_r_require_module
If a routine is synchronous, it can be executed before routines that depend on it. Executing it before anything else at the top of main module guarantees that there will be no race conditions:
setConfig();
require('module-that-depends-on-config');
If a routine is asynchronous, it should be treated as such in order to avoid race conditions. It's preferable for all asynchronous routines to return promises, so they could be chained with async function in main module:
(async () => {
await setConfigAsync();
require('module-that-depends-on-config');
...
})().catch(console.error);

Execute node command from UI

I am not very much familiar with nodejs but, I need some guidance in my task. Any help would be appreciated.
I have nodejs file which runs from command line.
filename arguments and that do some operation whatever arguments I have passed.
Now, I have html page and different options to select different operation. Based on selection, I can pass my parameters to any file. that can be any local node js file which calls my another nodejs file internally. Is that possible ? I am not sure about what would be my approach !
I always have to run different command from terminal to execute different task. so, my goal is to reduce that overhead. I can select options from UI and do operations through nodejs file.
I was bored so I decided to try to answer this even though I'm not totally sure it's what you're asking. If you mean you just need to run a node script from a node web app and you normally run that script from the terminal, just require your script and run it programmatically.
Let's pretend this script you run looks like this:
// myscript.js
var task = process.argv[2];
if (!task) {
console.log('Please provide a task.');
return;
}
switch (task.toLowerCase()) {
case 'task1':
console.log('Performed Task 1');
break;
case 'task2':
console.log('Performed Task 2');
break;
default:
console.log('Unrecognized task.');
break;
}
With that you'd normally do something like:
$ node myscript task1
Instead you could modify the script to look like this:
// Define our task logic as functions attached to exports.
// This allows our script to be required by other node apps.
exports.task1 = function () {
console.log('Performed Task 1');
};
exports.task2 = function () {
console.log('Performed Task 2');
};
// If process.argv has more than 2 items then we know
// this is running from the terminal and the third item
// is the task we want to run :)
if (process.argv.length > 2) {
var task = process.argv[2];
if (!task) {
console.error('Please provide a task.');
return;
}
// Check the 3rd command line argument. If it matches a
// task name, invoke the related task function.
if (exports.hasOwnProperty(task)) {
exports[task]();
} else {
console.error('Unrecognized task.');
}
}
Now you can run it from the terminal the same way:
$ node myscript task1
Or you can require it from an application, including a web application:
// app.js
var taskScript = require('./myscript.js');
taskScript.task1();
taskScript.task2();
Click the animated gif for a larger smoother version. Just remember that if a user invokes your task script from your web app via a button or something, the script will be running on the web server and not the user's local machine. That should be obvious but I thought I'd remind you anyway :)
EDIT
I already did the video so I'm not going to redo it, but I just discovered module.parent. The parent property is only populated if your script was loaded from another script via require. This is a better way to test if your script is being run directly from the terminal or not. The way I did it might have problems if you pass an argument in when you start your app.js file, such as --debug. It would try to run a task called "--debug" and then print out "Unrecognized task." to the console when you start your app.
I suggest changing this:
if (process.argv.length > 2) {
To this:
if (!module.parent) {
Reference: Can I know, in node.js, if my script is being run directly or being loaded by another script?

node.js - eval'ing to a live process

Did anyone set up something like this for himself using the existing
node.js REPL? I didn't think of a quick way to do it.
The way I do it today is using emacs and this:
https://github.com/ivan4th/swank-js
This module is composed of:
A SLIME-js addon to emacs which, in combination with js2-mode, lets
you simply issue a C-M-x somewhere in the body of a function def - and
off goes the function's string to the ..
Swank-js server (yes, you could eval from your local-machine
directly to a remote process) written in Node.js - It receives the
string of the function you eval'ed and actually evals it
A whole part that lets you connect to another port on that server
with your BROWSER and then lets you manipulate the DOM on that browser
(which is pretty amazing but not relevant)
My solution uses SLIME-js on the emacs side AND I require('swank-
js') on my app.js file
Now.. I have several issues and questions regarding my solution or
other possible ones:
Q1: Is this overdoing it? Does someone have a secret way to eval stuff
from nano into his live process?
Q2: I had to change the way swank-js is EVALing.. it used some
kind of black magic like this:
var Script = process.binding('evals').Script;
var evalcx = Script.runInContext;
....
this.context = Script.createContext();
for (var i in global) this.context[i] = global[i];
this.context.module = module;
this.context.require = require;
...
r = evalcx("CODECODE", this.context, "repl");
which, as far I understand, just copies the global variables to the
new context, and upon eval, doesn't change the original function
definitions - SOOO.. I am just using plain "eval" and IT
WORKS.
Do you have any comments regarding this?
Q3: In order to re-eval a function, it needs to be a GLOBAL function -
Is it bad practice to have all function definitions as global (clojure-like) ? Do you think there is another way to do this?
Actually, swank.js is getting much better, and it is now much easier to set up swank js with your project using NPM. I'm in the process of writing the documentation right now, but the functionality is there!
Check this out http://nodejs.org/api/vm.html
var util = require('util'),
vm = require('vm'),
sandbox = {
animal: 'cat',
count: 2
};
vm.runInNewContext('count += 1; name = "kitty"', sandbox, 'myfile.vm');
console.log(util.inspect(sandbox));
// { animal: 'cat', count: 3, name: 'kitty' }
Should help you a lot, all of the sandbox things for node uses it :) but you can use it directly :)
You might take a look at jsapp.us, which runs JS in a sandbox, and then exposes that to the world as a quick little test server. Here's the jsapp.us github repo.
Also, stop into #node.js and ask questions for a quicker response :)

Resources