Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

start/stop/status command done #1

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "xadillax-style"
}
64 changes: 63 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,64 @@
# akyuu-bin
# Akyuu-bin

CLI tool for Akyuu.js.

# Install

```
# npm install akyuu-bin
# or
# yarn add akyuu-bin
```

# Feature

* [ ] Init project
* [x] One step to start/stop Application (akyuu-bin start/stop)
* [x] Check out application status (akyuu-bin status)
* And More...

# Command

## Init

> Work in Progress

## Start/Stop

1. Add `cluster` config

```
{
cluster: {
// ... extends akyuu-cluster config

// mode: 'cluster' | 'fork', default: 'cluster'
mode: 'cluster',
// pid file, default: project-root/run/process.pid
pid: 'pid file',
// master log
log: {
info: 'info log path',
error: 'error log path'
}
}
}
```

2. Add start/stop scripts

```
// package.json
{
...
scripts: {
"start": "akyuu-bin start",
"stop": "akyuu-bin stop"
}
...
}
```

3. Type your script to start/stop

# Thanks
3 changes: 3 additions & 0 deletions bin/akyuu-bin
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env node

require("../lib");
49 changes: 49 additions & 0 deletions lib/cluster.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"use strict";

const fs = require("fs");
const { Console } = require("console");
const akyuu = require("akyuu");

const config = akyuu.config.cluster;
let { stdout, stderr } = process;

if(config.log.info) {
stdout = fs.createWriteStream(config.log.info, { flags: "a" });
}

if(config.log.error) {
stderr = fs.createWriteStream(config.log.error, { flags: "a" });
}

// eslint-disable-next-line
const console = new Console(stdout, stderr);

console.info(`${new Date().toString()} [${process.pid}] Master Started`);

akyuu.startCluster({
onFork(worker) {
console.info(`${new Date().toString()} [${worker.process.pid}] Fork Success: ${worker.id}`);
},
onDisconnect(worker) {
console.info(`${new Date().toString()} [${worker.process.pid}] Disconnect: ${worker.id}`);
},
onExit(worker, code, signal) {
console.info(
`${new Date().toString()} [${worker.process.pid}] Exit: ${worker.id} with code ${code} and ${signal}`
);
},
onUnexpectedExit(worker, code, signal) {
console.error(
`${new Date().toString()} [${worker.process.pid}]` +
` Unexpected Exit: ${worker.id} with code ${code} and ${signal}`
);
},
onReachReforkLimit() {
console.warn(`${new Date().toString()} [${process.pid}] Reach Refork Limit`);
}
});

process.on("uncaughtException", err => {
console.error(`${new Date().toString()} [${process.pid}] Master Uncaught Exception`);
console.error(err);
});
60 changes: 60 additions & 0 deletions lib/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"use strict";

const fs = require("fs-extra");
const path = require("path");
const defaultsDeep = require("lodash.defaultsdeep");

const defaultConfig = {
mode: "cluster",
log: {
info: "",
error: ""
},
pid: path.join(".", "run", "master.pid"),
entry: ""
};

// Process Manager Config
// use config/default/cluster.js config
class PMConfig {
/**
* @param {*} root Project root
*/
constructor(root) {
// Because Akyuu.js don't have independent package to handle config
// So I have to assume all project sturcture is
// root
// -- config
// -- -- default
// -- -- -- cluster.js
// -- controllers
// -- models
// Hard coded to get cluster config
const configPath = path.join(root, "config", "default", "cluster.js");
this.root = root;

if(fs.pathExistsSync(configPath)) {
const config = require(configPath);
defaultsDeep(this, config, defaultConfig);
} else {
// TODO
console.warn("No cluster config found");
process.exit(1);
}

this.entry = this._joinPath(this.entry || "");
this.log.info = this._joinPath(this.log.info);
this.log.error = this._joinPath(this.log.error);
this.pid = this._joinPath(this.pid);
}

// convert to absolute path
_joinPath(p) {
if(path.isAbsolute(p)) {
return p;
}
return path.join(this.root, p);
}
}

module.exports = PMConfig;
35 changes: 35 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"use strict";

const yargs = require("yargs");
const start = require("./start");
const stop = require("./stop");
const restart = require("./restart");
const status = require("./stsuts");

// eslint-disable-next-line
yargs
.usage("$0 [options] <command>")
.help("h")
.alias("h", "help")
.alias("v", "version")
.command("start", "Start Akyuu.js Application in Production Mode", () => {
// EMPTY
}, argv => {
start(argv).then(() => {
process.exit(0);
}).catch(() => {
process.exit(1);
});
})
.command("stop", "Stop Akyuu.js Application", () => {
// EMPTY
}, stop)
.command("restart", "Restart Application", () => {
// EMPTY
}, restart)
.command("dev", "Start Akyuu.js Aplication in Development Mode")
.command("status", "Show Akyuu.js Application Status", () => {
// EMPTY
}, status)
.demandCommand()
.argv;
8 changes: 8 additions & 0 deletions lib/restart.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const start = require("./start");
const stop = require("./stop");

module.exports = function(argv) {
stop(argv).then(() => {
start(argv);
});
};
61 changes: 61 additions & 0 deletions lib/start.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"use strict";

const { spawn } = require("child_process");
const fs = require("fs-extra");
const PMConfig = require("./config");
const path = require("path");
const isRunning = require("is-running");
const ora = require("ora");

module.exports = function() {
const spinner = ora("Starting process").start();
const root = process.cwd();
const config = new PMConfig(root);
const pidFile = config.pid;

if(fs.pathExistsSync(pidFile)) {
const pid = parseInt(fs.readFileSync(pidFile, { encode: "utf-8" }));
if(pid && isRunning(pid)) {
spinner.info(`[${pid}] process already is running`);
process.exit(0);
}
}

const env = Object.create(process.env);
env.NODE_CONFIG_DIR = path.join(root, "config");

const child = spawn("node", [ path.join(__dirname, "./cluster.js") ], {
detached: true,
env
});

let startFailed;
const promise = new Promise((resolve, reject) => {
startFailed = reject;
setTimeout(() => {
resolve();
}, 5000);
});

child.on("exit", code => {
startFailed(new Error(`process exit with ${code}`));
});

child.on("error", e => {
startFailed(e);
});

// child.stdout.pipe(process.stdout)
// child.stderr.pipe(process.stderr)

fs.writeFileSync(pidFile, child.pid);
spinner.text = `[${child.pid}] process created, wating`;

return promise.then(() => {
spinner.succeed(`[${child.pid}] process start success`);
child.unref();
}).catch(e => {
spinner.fail(`[${child.pid}] process start failed`);
console.error(e);
});
};
47 changes: 47 additions & 0 deletions lib/stop.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"use strict";

const fs = require("fs-extra");
const PMConfig = require("./config");
const isRunning = require("is-running");
const ora = require("ora");

module.exports = function() {
const spinner = ora("Stop process").start();
const root = process.cwd();
const config = new PMConfig(root);

const pidFile = config.pid;

if(fs.pathExistsSync(pidFile)) {
const pid = parseInt(fs.readFileSync(pidFile, { encode: "utf8" }));
if(isRunning(pid)) {
process.kill(pid);
return new Promise((resolve, reject) => {
let timeer = 0;
function check() {
if(isRunning(pid)) {
if(timeer > 25) {
reject(new Error());
spinner.fail(`[${pid}] cannot stop process, please try again`);
} else {
// check process status every 200 ms
setTimeout(check, 200);
}
} else {
resolve();
spinner.succeed(`[${pid}] stop process success`);
fs.removeSync(pidFile);
}
timeer++;
}
check();
});
} else {
spinner.info(`[${pid}] process stopped`);
}
} else {
spinner.warn(`no pid found: ${pidFile}`);
}

return Promise.resolve();
};
24 changes: 24 additions & 0 deletions lib/stsuts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"use strict";

const fs = require("fs-extra");
const ora = require("ora");
const PMConfig = require("./config");
const isRunning = require("is-running");

module.exports = function() {
const spinner = ora("Checking").start();
const root = process.cwd();
const config = new PMConfig(root);
const pidFile = config.pid;

if(fs.pathExistsSync(pidFile)) {
const pid = parseInt(fs.readFileSync(pidFile, { encode: "utf8" }));
if(isRunning) {
spinner.succeed(`[${pid}] process is running`);
} else {
spinner.info(`[${pid}] process is stopped`);
}
} else {
spinner.fail(`No pid file found: ${pidFile}`);
}
};
Loading