New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

deity-cli

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

deity-cli - npm Package Compare versions

Comparing version 0.1.0 to 0.2.0

.npmignore

170

bin/deity.js
#!/usr/bin/env node
var cp = require("child_process");
var net = require("net");
require("colors");
// Deity is a command-line application to make it easier to manage and manipulate processes.
// Its star use case is running an interacting with compilers or pre-processors such as SCSS or Coffeescript, without having to run separate shells for each process.
// `deity.js` is the frontend to [deityd.js](deityd.html), the daemon that actually spawns and looks after processes.
// The frontend and daemon communicate though a TCP socket on the following port:
var PORT = 51145;
var HOST = "localhost";
var cp = require("child_process");
var net = require("net");
// Communication with deityd
// ---------------------------
// `deity.js` sends messages to the daemon in the form of stringified JSON.
// This JSON generally contains 3 fields:
//
// - `operation`: Operation to perform (new, kill, list, etc.);
// - `name`: Name of the process, as `deityd` identifies them, to work on;
// - `message`: Any additional information to pass to `deityd`.
//
// Note that the `processName` and `message` fields aren't always applicable for certain operations (like `list`).
//
// The TCP message is sent in the function `send()`.
// The JSON object is made in the function `constructJSON()`.
var INFO_MSG = [
'Deity by Arthanzel',
'== Usage',
'node deity.js <command> [arguments]',
'== Commands',
'new <name> <command>: Creates a new process with a name and command to run',
'list: lists all running processes',
'kill <name>: kills a running process',
'force: force exits the deity daemon'
].join("\n");
var ALLOWED_OPS = ["new", "kill", "list", "out", "in", "exit", "force"];
// Print the help message if no command is given
if (process.argv[2] == null) {
console.log(INFO_MSG);
process.exit();
// If the user specified an operation that's not allowed, print the help and exit.
// Don't even bother pinging `deityd` since it'll do absolutely nothing.
// This will also run if the user didn't type an operation (`argv[2]` is null).
if (ALLOWED_OPS.indexOf(process.argv[2]) == -1) {
printHelp();
}
// Make sure that deityd is running.
// Sending messages
// ----------------
// Make sure that deityd is running before trying to send it commands.
ensureDaemon();
// Ensures that deityd is running by trying to connect to its socket.
// If no connection can be established, deityd is started.
// Ensure that `deityd` is running by connecting to its TCP socket.
// If `deityd` is not running, this function will start €it.
function ensureDaemon() {
var socket = new net.Socket();
// 200 ms is more than enough time for `deityd` to respond to a TCP connection originating from the same host.
// At the same time, we don't want this too short in case the OS is tasked to capacity and `deityd` doesn't repond immediately, but if it's too long then it will hinder Deity's usability.
// In any case, the socket should not be timing out under normal conditions.
// If the socket connects or fails, it will generally do this well before the 200 ms mark.
socket.setTimeout(200);
// Socket connected and deityd is running.
// If the socket connects, `deityd` is running.
// Kill the test socket and start sending messages to `deityd` over TCP.
socket.on("connect", function() {

@@ -40,8 +61,9 @@ socket.end();

//console.log("Deity daemon is running");
start();
send();
});
// Socket failed to connect and deityd is not running.
// If the socket fails, `deityd` is not running.
// Note that this case is NOT the 200 ms timeout.
// A failing socket returns immediately.
// In this case, start `deityd` and send it some messages.€
socket.on("error", function() {

@@ -52,9 +74,10 @@ socket.end();

startDaemon();
console.log("Starting deity daemon...");
console.log("Starting deity daemon...".grey);
// Give the daemon some time to start before sending it commands.
setTimeout(start, 200);
setTimeout(send, 200);
});
// Socket timed out. Can't determine if deityd is running, so notify and exit.
// Socket timed out.
// Can't determine if deityd is running, so notify and exit.
socket.on("timeout", function() {

@@ -64,24 +87,26 @@ socket.end();

console.error("Can't determine if deity is running");
var address = HOST + ":" + PORT;
console.error("Can't determine if deity is running.");
console.error("Try killing all node processes and trying again.");
console.error("You may need to open port localhost:51145.")
console.error("You may need to open port " + address + ".");
});
// Try pinging `deityd`.
// Remember, the above functions are events, so this line will execute first.
socket.connect(PORT);
}
// Consolidates any extra arguments into a single string.
function getArguments() {
return process.argv.slice(3).join(" ");
}
// Sends a message to deityd.
function send(command, message) {
// Sends a message to `deityd`.
function send() {
var socket = new net.Socket();
// Print any data received from the daemon
// Print any data received from the daemon.
socket.on("data", function(buffer) {
// The buffer will contain a newline at the end, so `trim()` it out before printing.
console.log(buffer.toString().trim());
});
// Exit the script if deityd terminates the TCP connection.
// Exit `deity` if `deityd` terminates the TCP connection.
// This happens when an operation has (successfully) completed.
// The socket should always be ended by `deityd`.
socket.on("end", function() {

@@ -91,34 +116,57 @@ process.exit();

// In the case that `deityd` has encountered an error and does not end the socket within 5 seconds,
// print a message and end the socket.
// This will cause this script to exit, as per the above "end" event.
setTimeout(function() {
console.error("ERROR: Deity daemon has stopped responding!")
console.error(" Will try to exit, but your processes may still be running.");
socket.end();
}, 5000);
socket.connect(PORT);
socket.write(command + message);
// Socket will be ended by deityd
socket.write(constructJSON());
}
// Processes arguments and sends the appropriate message to deityd.
function start() {
var command = process.argv[2];
// Constructs a JSON object to send to `deityd` from the process's arguments.
function constructJSON() {
return JSON.stringify ({
operation: process.argv[2],
name: process.argv[3] || "",
message: process.argv.slice(4).join(" ")
});
}
if (command == "exit") {
send("x", "");
}
else if (command == "force") {
send ("!", "");
}
else if (command == "list") {
send("l", "");
}
else if (command == "kill") {
send("k", getArguments());
}
else if (command == "new") {
send("n", getArguments());
}
// Prints the help message to the console and exits.
function printHelp() {
var HELP_MSG = [
"Usage: deity <operation> [arguments]",
"Operations:",
" new <name> <command> Spawns a new process called <name> by running <command>.",
" kill <name> Kills a running process in Deity called <name>",
" out <name> Prints the stdout/stderr for process <name>.",
" in <name> <message> Sends <message> to the stdin of process <name>",
" list Lists running processes with their PID. Also indicates if",
" the process has unread lines in stdout or stderr.",
" exit Nicely quits all Deity processes and exits the Deity daemon.",
" force Force-quits the Deity daemon. Use with caution."
].join("\n");
console.log(HELP_MSG);
process.exit();
}
// Start deityd
// Daemon-starting
// ---------------
function startDaemon() {
var deityd = cp.spawn("deityd", [], { stdio: "ignore", detached: true });
// These options will allow `deityd` to run as a daemon even when this script exits.
// Setting the `detached` flag isn't enough; by default, subprocesses share their parent's standard input and output.
// When the parent process exits, it closes its stdio and so the subprocess will exit as well.
// Node provides a shortcut `stdio: "ignore"` to route the subprocess's stdio to null, so that it's not dependent on the parent's io streams.
var opts = { stdio: 'ignore', detached: true };
// Prevent the current script from waiting until deityd exits.
// We don't need to pass any arguments to `deityd`.
var deityd = cp.spawn("deityd", [], opts);
// Prevent the current script from waiting until deityd exits by taking it out of the event loop.
deityd.unref();
}
#!/usr/bin/env node
var cp = require("child_process");
var fs = require("fs");
var net = require("net");
var moment = require("moment");
require("colors");
// `deityd` is a daemon that runs as long as there are running processes managed by deity.
// Daemonizing allows Deity to retain handles to processes, standard input streams, and events, which would otherwise be impossible if it tracked PIDs.
// Additionally, PIDs aren't very well supported on all systems, even through Node.js.
// You might see some calls to `console.log`, but `deityd` is always started with its stdio routed to null.
// Although it can log output, it doesn't go anywhere unless told otherwise.
// `deityd` receives commands through a TCP socket running on the following port:
var PORT = 51145;
var HOST = "localhost";
var cp = require("child_process");
var dgram = require("dgram");
var net = require("net");
// Messages are received as stringified JSON.
// For an explanation of the schema, see [deity.js](deity.html).
// This hash holds all of the running processes.
// The key corresponds to the name of the process as given to Deity, and values contain process objects created when Node spawns a child process, plus some extra properties that Deity adds.
var processes = {};
startDaemon();
// Serving requests
// ----------------
startServer();
// Returns the number of running processes
function countProcesses() {
return Object.getOwnPropertyNames(processes).length;
}
// Here, `deityd` starts a TCP server that lives as long as the daemon runs.
// This TCP serve accepts connections, parses the JSON object and delegates work to the appropriate functions.
function startServer() {
// The function is fired when a client connects.
var server = net.createServer(function(socket) {
/**
* Close all processes and exit deityd.
*/
function exit(socket, force) {
// Close each process.
for (var p in processes) {
processes[p].kill();
}
// Event fired when a command has been received.
socket.on("data", function(buffer) {
json = JSON.parse(buffer.toString());
if (force)
// Delegate each operation to the appropriate method.
switch (json.operation || "list") {
case "new":
if (!json.name || !json.message) {
socket.end("Usage: deity new <name> <command>")
break;
}
var msg = newProcess(json.name, json.message);
socket.end(msg);
break;
case "kill":
if (!json.name) {
socket.end("Usage: deity kill <name>")
break;
}
msg = killProcess(json.name);
socket.end(msg);
break;
case "list":
msg = listProcesses();
socket.end(msg);
break;
case "out":
if (!json.name) {
socket.end("Usage: deity out <name>")
break;
}
msg = listProcessOut(json.name);
socket.end(msg);
break;
case "in":
if (!json.name || !json.message) {
socket.end("Usage: deity in <name> <message>")
break;
}
msg = writeProcessIn(json.name, json.message);
socket.end(msg);
break;
case "exit":
socket.write("Stopping deity daemon...");
exitDaemon();
break;
case "force":
socket.end("Force exiting!");
process.exit();
break;
default:
// In case of fire, inform user.
socket.end("Don't know how to run " + json.operation);
break;
}
// Exit if no processes are running.
exitIfIdle();
});
});
// Notify and exit if the server failed to start, probably because the port is blocked.
server.on("error", function() {
console.log("Failed to start. Is deityd already running?");
process.exit();
else
exitLoop(socket);
});
// Run the server.
// Will fail if the port is already bound, ensuring a single instance of the daemon.
server.listen(PORT, function() {
console.log("Server listening");
});
}
// Try exiting every 500 ms if every process has died.
// TODO: Implement a process counter using events and exit deityd when all processes are closed.
function exitLoop(socket) {
if (countProcesses() == 0) {
socket.end();
process.exit();
// Performing operations
// ---------------------
// Spawns a new process called `name` by running `command`.
function newProcess(name, command) {
// First, check if a process by the given name already eixsts.
// If so, display a message to the user and return.
if (processes[name] != null) {
return "Process '" + name + "' already exists."
}
else {
setTimeout(function() { exitLoop(socket); }, 500);
}
// Run the process and save it to the `processes` map.
var p = cp.exec(command);
processes[name] = p;
p.cmd = command;
// Save standard error and output into an array in the process object for easy manipulation.
p.outLines = [];
p.stdout.on("data", function(data) {
var time = moment(new Date()).format("HH:mm:ss");
p.outLines.push(time + " :: " + data.trim());
});
p.stderr.on("data", function(data) {
var time = moment(new Date()).format("HH:mm:ss");
p.outLines.push(time + " :: " + data.trim().red);
});
// To allow `deityd` to show if processes have printed to the stdout/err, and highlight old lines of output,
// keep track of how many output lines the user has seen.
p.outLinesRead = 0;
// As soon as a process exits, remove it from the `processes` map.
p.on("exit", function() {
delete processes[name];
// Exit `deityd` if the last process has been killed.
exitIfIdle();
});
// This will be the string that is sent back to `deity.js` through the socket.
return "New process " + name + "(" + p.pid + ")";
}
// Kills the named processes.
function killProcess(p) {
if (processes[p] != null) {
processes[p].kill();
// Kills the named process.
// This function does **not** wait for the process to be killed to return. If the process doesn't die, it will still show up when `deity list` is run.
// Possibly, processes may be waiting for user intervention before quitting, but those kinds of processes are generally not the ones you want to run using `diety`.
function killProcess(name) {
if (processes[name] != null) {
processes[name].kill();
return "Killing " + name;
}

@@ -56,7 +171,6 @@ else {

// Lists all running processes.
function list() {
// Lists all running processes started by `deity`.
function listProcesses() {
var n = countProcesses();
// Check if the processes map is empty
if (n == 0) {

@@ -66,14 +180,20 @@ return "No processes";

else {
var output = "" + n + " running ";
// This will print "n process(es)" with correct grammar.
var output = n.toString() + " process";
if (n > 1) output += "es";
output += "\r\n";
// Pluralize
if (n == 1)
output += "process \r\n";
else
output += "processes \r\n";
// List each process and add it to the output
for (var name in processes) {
p = processes[name];
output += name;
output += "(" + p.pid + ")";
// List each process
for (var p in processes) {
output += p;
output += "(" + processes[p].pid + ")";
// If the process has printed to stdout/error, list the number of new lines.
var difference = p.outLines.length - p.outLinesRead;
if (difference > 0) {
var str = " " + difference + " new messages";
output += str.yellow;
}
output += "\r\n";

@@ -86,72 +206,74 @@ }

// Spawns a new process
function newProcess(message, socket) {
console.log("New process: " + message);
// Show the standard error and output for a process.
// Each log to the stdout/err will be on a separate line and timestamped.
// Lines with linebreaks in them will be indented to maintain consistency.
function listProcessOut(name) {
if (processes[name] == null) {
msg += "Process '" + name + "' doesn't exist.";
return msg;
}
// Separate the process name from the command
var processName = message.split(" ", 1)[0];
var processCommand = message.substring(processName.length + 1);
var p = processes[name];
var output = "";
// Run the process and save it to the list
var process = cp.exec(processCommand);
processes[processName] = process;
processes[processName].cmd = processCommand;
for (var i = 0; i < p.outLines.length; i++) {
var lines = p.outLines[i].split("\n");
// Remove an exited process from the list
process.on("exit", function() {
delete processes[processName];
});
// In case the line in the output contains linebreaks, gracefully split it into multiple lines and indent them all to the same level.
for (var j = 0; j < lines.length; j++) {
if (j > 0) {
output += " ";
// "HH:mm:ss :: "
}
return "New process " + processName + "(" + process.pid + ")";
// If the line was already read, grey it out.
// Note that this will not stop read errors from appearing red.
// Their timestamps, however, will be grey.
if (p.outLinesRead > i)
output += lines[j].grey;
else
output += lines[j];
output += "\n";
}
}
p.outLinesRead = p.outLines.length;
return output;
}
function startDaemon() {
// TCP server on the daemon.
// The function is fired on a connection.
var server = net.createServer(function(socket) {
// Event fired when a command has been received.
socket.on("data", function(buffer) {
data = buffer.toString().trim();
function writeProcessIn(name, message) {
if (processes[name] == null) {
msg += "Process '" + name + "' doesn't exist.";
return msg;
}
var command = data.toString().charAt(0);
var message = data.toString().substring(1);
processes[name].stdin.write(message + "\n");
}
if (command == "k") { // Kill
killProcess(message, socket);
socket.end("Killing " + message);
}
else if (command == "l") { // List
socket.end(list());
}
else if (command == "n") { // New
socket.end(newProcess(message, socket));
}
else if (command == "x") { // Exit
// Kill the connection to the client.
socket.write("Stopping deity daemon...");
exit(socket, false);
}
else if (command == "!") { // Force exit
socket.end("Force exiting!")
process.exit(socket, true);
}
});
// Attempts to kill all processes and exits `deityd`.
function exitDaemon() {
exitIfIdle();
for (var name in processes) {
processes[name].kill();
}
}
// Notify when a connection dies.
socket.on("end", function() {
console.log("Disconnected");
});
});
// Notify and exit if the server failed to start, probably because the port is blocked.
server.on("error", function() {
console.log("Failed to start. Is deityd already running?");
// Automatically exit `deityd` when the last process exits.
// There is no reason to keep `deityd` running if it's not doing anything, and it makes the entire process of starting and stopping processes transparent to the user without having to worry about starting/stopping the daemon.
// This function is called when an operation completes and whenever a process exits.
function exitIfIdle() {
if (countProcesses() == 0) {
process.exit();
});
}
}
// Run the server.
// Will fail if the port is already bound, ensuring a single instance of the daemon.
server.listen(PORT, function() {
console.log("Server listening");
});
}
// Returns the number of running processes.
function countProcesses() {
return Object.getOwnPropertyNames(processes).length;
}
{
"name": "deity-cli",
"version": "0.1.0",
"version": "0.2.0",
"description": "Easy-peasy command-runner for big programming projects.",

@@ -8,2 +8,6 @@ "scripts": {

},
"dependencies": {
"colors": "0.6.2",
"moment": "2.7.0"
},
"bin": {

@@ -10,0 +14,0 @@ "deity": "./bin/deity.js",

@@ -29,11 +29,13 @@ Book of Deity

6. Thou shall kill Processes started by Deity by executing `deity kill GLaDOS` and the Process that shall be killed will be the Process whose name is GLaDOS.
7. Thou shall exit the Daemon and all Processes started by Deity by executing `deity exit`.
8. Thou shall force exit the Daemon by executing `deity force`.
9. Thou shall not force exit needlessly, as it may leave zombie processes and memory leaks.
10. Thou shall hold no creators of Diety above Arthanzel (respect the MIT license and don't mooch credit).
11. **Thou shall not complain if Diety deletes your computer for Deity is a work in progress and you shall be ignored.**
7. Thou shall get the standard input and error of process GlaDOS by executing `deity out GlaDOS`.
8. Thou shall write "cake" to the standard input of process GlaDOS by executing `deity in GlaDOS cake`.
9. Thou shall exit the Daemon and all Processes started by Deity by executing `deity exit`.
10. Thou shall force exit the Daemon by executing `deity force`.
11. Thou shall not force exit needlessly, as it may leave zombie processes and memory leaks.
12. Thou shall hold no creators of Diety above Arthanzel (respect the MIT license and don't mooch credit).
13. **Thou shall not complain if Diety deletes your computer for Deity is a work in progress and you shall be ignored.**
Roadmap
=======
- Run checks whether a process exists already exists before starting a new one.
- "in" and "out commands" to manipulate the stdio and stdout.
Pages todo
==========
- Github badge
- Dark grey on light grey?
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc