Pull merge.
[yaffs-website] / node_modules / phridge / lib / spawn.js
1 "use strict";
2
3 var childProcess = require("child_process");
4 var phantomjs = require("phantomjs-prebuilt");
5 var config = require("./config.js");
6 var fs = require("fs");
7 var temp = require("temp");
8 var path = require("path");
9 var Phantom = require("./Phantom.js");
10 var forkStdout = require("./forkStdout.js");
11 var lift = require("./lift.js");
12
13 var startScript = path.resolve(__dirname, "./phantom/start.js");
14 var writeFile = lift(fs.writeFile);
15 var close = lift(fs.close);
16 var open = lift(temp.open);
17 var initialMessage = "message to node: hi";
18
19 /**
20  * Spawns a new PhantomJS process with the given phantom config. Returns a Promises/A+ compliant promise
21  * which resolves when the process is ready to execute commands.
22  *
23  * @see http://phantomjs.org/api/command-line.html
24  * @param {Object} phantomJsConfig
25  * @returns {Promise}
26  */
27 function spawn(phantomJsConfig) {
28     var args;
29     var configPath;
30     var stdout;
31     var stderr;
32     var child;
33
34     phantomJsConfig = phantomJsConfig || {};
35
36     // Saving a reference of the current stdout and stderr because this is (probably) the expected behaviour.
37     // If we wouldn't save a reference, the config of a later state would be applied because we have to
38     // do asynchronous tasks before piping the streams.
39     stdout = config.stdout;
40     stderr = config.stderr;
41
42     /**
43      * Step 1: Write the config
44      */
45     return open(null)
46         .then(function writeConfig(info) {
47             configPath = info.path;
48
49             // Pass config items in CLI style (--some-config) separately to avoid Phantom's JSON config bugs
50             // @see https://github.com/peerigon/phridge/issues/31
51             args = Object.keys(phantomJsConfig)
52                 .filter(function filterCliStyle(configKey) {
53                     return configKey.charAt(0) === "-";
54                 })
55                 .map(function returnConfigValue(configKey) {
56                     var configValue = phantomJsConfig[configKey];
57
58                     delete phantomJsConfig[configKey];
59
60                     return configKey + "=" + configValue;
61                 });
62
63             return writeFile(info.path, JSON.stringify(phantomJsConfig))
64                 .then(function () {
65                     return close(info.fd);
66                 });
67         })
68     /**
69      * Step 2: Start PhantomJS with the config path and pipe stderr and stdout.
70      */
71         .then(function startPhantom() {
72             return new Promise(function (resolve, reject) {
73                 function onStdout(chunk) {
74                     var message = chunk.toString("utf8");
75
76                     child.stdout.removeListener("data", onStdout);
77                     child.stderr.removeListener("data", onStderr);
78
79                     if (message.slice(0, initialMessage.length) === initialMessage) {
80                         resolve();
81                     } else {
82                         reject(new Error(message));
83                     }
84                 }
85
86                 // istanbul ignore next because there is no way to trigger stderr artificially in a test
87                 function onStderr(chunk) {
88                     var message = chunk.toString("utf8");
89
90                     child.stdout.removeListener("data", onStdout);
91                     child.stderr.removeListener("data", onStderr);
92
93                     reject(new Error(message));
94                 }
95
96                 args.push(
97                   "--config=" + configPath,
98                   startScript,
99                   configPath
100                 );
101
102                 child = childProcess.spawn(phantomjs.path, args);
103
104                 prepareChildProcess(child);
105
106                 child.stdout.on("data", onStdout);
107                 child.stderr.on("data", onStderr);
108
109                 // Our destination streams should not be ended if the childProcesses exists
110                 // thus { end: false }
111                 // @see https://github.com/peerigon/phridge/issues/27
112                 if (stdout) {
113                     child.cleanStdout.pipe(stdout, { end: false });
114                 }
115                 if (stderr) {
116                     child.stderr.pipe(stderr, { end: false });
117                 }
118             });
119         })
120     /**
121      * Step 3: Create the actual Phantom-instance and return it.
122      */
123         .then(function () {
124             return new Phantom(child);
125         });
126 }
127
128 /**
129  * Prepares childProcess' stdout for communication with phridge. The childProcess gets two new properties:
130  * - phridge: A stream which provides all messages to the phridge module
131  * - cleanStdout: A stream which provides all the other data piped to stdout.
132  *
133  * @param {child_process.ChildProcess} childProcess
134  * @private
135  */
136 function prepareChildProcess(childProcess) {
137     var stdoutFork = forkStdout(childProcess.stdout);
138
139     childProcess.cleanStdout = stdoutFork.cleanStdout;
140     childProcess.phridge = stdoutFork.phridge;
141     childProcess.on("exit", disposeChildProcess);
142 }
143
144 /**
145  * Clean up our childProcess extensions
146  *
147  * @private
148  * @this ChildProcess
149  */
150 function disposeChildProcess() {
151     var childProcess = this;
152
153     childProcess.phridge.removeAllListeners();
154     childProcess.phridge = null;
155     childProcess.cleanStdout.removeAllListeners();
156     childProcess.cleanStdout = null;
157 }
158
159 module.exports = spawn;