forked from Divine-Software/ghostly
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathghostly-cli.js
More file actions
executable file
·152 lines (130 loc) · 5.98 KB
/
ghostly-cli.js
File metadata and controls
executable file
·152 lines (130 loc) · 5.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#!/usr/bin/env node
'use strict'
const commander = require('commander');
const daemon = require('daemon');
const fs = require('fs');
const ghostly = require('./ghostly');
const http = require('http');
const os = require('os');
const packageJSON = require('./package.json');
const phantomjs = require('phantomjs-prebuilt');
Promise.resolve().then(() => {
let argv = commander
.usage('[options] [document]')
.description('Render a Ghostly template or start a Ghostly HTTP server.')
.version(packageJSON.version);
function check(cond, message) {
if (!cond) {
argv.outputHelp();
throw `Argument error: ${message}`;
}
}
function int(arg) {
let rc = parseInt(arg);
check(!isNaN(rc), `${arg} is not a number`);
return rc;
}
argv.option(' --content-type <content-type>', 'input document format [application/json]')
.option('-d, --debug', 'enable debug logging')
.option(' --format <content-type>', 'template output format [text/html]')
.option('-H, --http <host:port>', 'run an HTTP server on this host and port')
.option('-o --output <file>', 'template output filename [standard output]')
.option(' --page-cache <num>', 'each worker keeps <num> pages cached [0]', int)
.option(' --phantom-path <file>', 'override PhantomJS/SlimerJS path', phantomjs.path)
.option(' --pidfile <file>', 'fork and write PID to this file')
.option(' --port-base <port>', 'first localhost port to use for workers [use random ports]', int)
.option(' --relaunch-delay <seconds>', 'delay in seconds before relaunching a crashed worker [1]', int)
.option(' --temp-dir <dir>', 'override default directory for temporary files')
.option('-t, --template <url>', 'execute this Ghostly template')
.option('-T, --template-pattern <regexp>', 'restrict template URIs to this regular expression')
.option('-u, --user <user>', 'run as this user')
.option(' --workers <num>', 'number of worker processes [1]', int)
.parse(process.argv);
if (argv.template) {
check(argv.args.length >= 1, 'No input document specified');
check(argv.args.length <= 1, 'Only one input document may be specified');
}
else {
check(!argv.args.length, 'Cannot specify document without --template');
check(!argv.contentType, '--content-type requires --template');
check(!argv.format, '--format requires --template');
check(!argv.output, '--output requires --template');
}
if (argv.http) {
argv.http = ['localhost'].concat(argv.http.split(':')).splice(-2);
argv.http[0] = argv.http[0] || os.hostname();
check(!!Number(argv.http[1]), 'Invalid --http argument');
}
else {
check(!argv.pidfile, '--pidfile can only be used in --http mode');
}
check(argv.http || argv.template, 'Either --http or --template must be specified');
return $main(argv);
}).catch((ex) => {
process.stdout.write(ex + '\n');
return 64;
}).then((rc) => {
setTimeout(() => {
process.exit(rc || 0)
}, 100);
});
function $main(argv) {
if (argv.debug) {
ghostly.logger(console);
}
let config = {};
function arg(name, type) {
if (argv[name] !== undefined) config[name] = type(argv[name]);
}
arg('templatePattern', RegExp);
arg('pageCache', Number);
arg('phantomPath', String);
arg('portBase', Number);
arg('relaunchDelay', Number);
arg('tempDir', String);
arg('workers', Number);
let engine = new ghostly.Engine(config);
return engine.$start()
.then((engine) => {
if (argv.template) {
let template = engine.template(argv.template);
return Promise.all(argv.args.map((file) => { // OK to use sync APIs here
let data = fs.readFileSync(file !== '-' ? file : process.stdin.fd).toString();
return template.$render(data, argv.contentType || 'application/json', argv.format || 'text/html', null)
.then((result) => {
return new Promise((resolve, reject) => {
if (argv.output) {
fs.writeFile(argv.output, result, (error) => {
error && reject(error) || resolve();
});
}
else {
process.stdout.write(result) ? resolve() : process.stdout.once('drain', resolve);
}
});
});
}));
}
})
.then(() => {
if (argv.http) {
let server = http.createServer(engine.httpRequestHandler.bind(engine));
return new Promise((resolve, reject) => {
server.listen(argv.http[1], argv.http[0], () => {
resolve(server.address());
})
}).then((address) => {
console.log(`Listening for requests on http://${address.address}:${address.port}/`);
if (argv.pidfile) {
daemon();
fs.writeFileSync(argv.pidfile, process.pid);
}
if (argv.user) {
process.setgid(Number(childProcess.execSync(`id -g ${argv.user}`)));
process.setuid(argv.user);
}
return new Promise(() => {});
});
}
});
}