-
-
Notifications
You must be signed in to change notification settings - Fork 229
/
Copy pathlocal-process.js
210 lines (181 loc) · 5.95 KB
/
local-process.js
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
'use strict';
const fs = require('fs-extra');
const path = require('path');
const ProcessManager = require('../process-manager');
const PID_FILE = '.ghostpid';
/**
* Local process manager. This is pretty much guaranteed to work on any OS and
* system, so it is used as the fallback if any other process manager doesn't work
*
* @class LocalProcess
*/
class LocalProcess extends ProcessManager {
/**
* Starts the local process
*
* @param {string} cwd CWD of the ghost instance
* @param {string} environment Current running environment
* @return Promise<void>
*
* @method start
* @public
*/
start(cwd, environment) {
const childProcess = require('child_process');
const errors = require('../errors');
// Check that content folder is owned by the current user
if (!this._checkContentFolder(cwd)) {
return Promise.reject(new errors.SystemError(`The content folder is not owned by the current user.
Ensure the content folder has correct permissions and try again.`));
}
return new Promise((resolve, reject) => {
const cp = childProcess.spawn('node', [process.argv[1] , 'run'], {
cwd: cwd,
detached: true,
stdio: ['ignore', 'ignore', 'ignore', 'ipc'],
env: Object.assign({}, process.env, {NODE_ENV: environment})
});
// Stick the pid into the pidfile so we can stop the process later
fs.writeFileSync(path.join(cwd, PID_FILE), cp.pid);
cp.on('error', (error) => {
reject(new errors.CliError({
message: 'An error occurred while starting Ghost.',
err: error
}));
});
cp.on('exit', (code) => {
fs.removeSync(path.join(cwd, PID_FILE));
reject(new errors.GhostError(`Ghost process exited with code: ${code}`));
});
// Wait until Ghost tells us that it's started correctly, then resolve
cp.on('message', (msg) => {
if (msg.error) {
fs.removeSync(path.join(cwd, PID_FILE));
return reject(new errors.GhostError(msg.message));
}
/* istanbul ignore else */
if (msg.started) {
cp.disconnect();
cp.unref();
return resolve();
}
});
});
}
/**
* Stops an instance
*
* @param {string} cwd Current working directory
* @return Promise<void>
*
* @method stop
* @public
*/
stop(cwd) {
const fkill = require('fkill');
const errors = require('../errors');
let pid;
try {
pid = parseInt(fs.readFileSync(path.join(cwd, PID_FILE)));
} catch (e) {
if (e.code === 'ENOENT') {
// pid was not found, exit
return Promise.resolve();
}
return Promise.reject(new errors.CliError({
message: 'An unexpected error occurred when reading the pidfile.',
error: e
}));
}
return fkill(pid, {force: this.system.platform.windows}).catch((error) => {
// TODO: verify windows outputs same error message as mac/linux
if (!error.message.match(/No such process/)) {
return Promise.reject(new errors.CliError({
message: 'An unexpected error occurred while stopping Ghost.',
err: error
}));
}
}).then(() => {
fs.removeSync(path.join(cwd, PID_FILE));
});
}
/**
* Called by the `ghost run` sub-process, this notifies the parent process that
* Ghost has started
*
* @method success
* @public
*/
success() {
/* istanbul ignore else */
if (process.send) {
process.send({started: true});
}
}
/**
* Called by the `ghost run` sub-process, this notifies the parent process
* that an error has occurred.
*
* @param {Error} error
*
* @method error
* @public
*/
error(error) {
/* istanbul ignore else */
if (process.send) {
process.send({error: true, message: error.message});
}
}
/**
* Checks if the Ghost instance is running
*
* @param {string} cwd
* @return bool Whether or not the process is still running
*
* @method isRunning
* @public
*/
isRunning(cwd) {
const isRunning = require('is-running');
const pidfile = path.join(cwd, PID_FILE);
if (!fs.existsSync(pidfile)) {
// Even if the process exists, if the file has been deleted we really can't
// determine if it's still running, so just assume it's not.
return false;
}
const pid = parseInt(fs.readFileSync(pidfile));
const running = isRunning(pid);
if (!running) {
// If not running, cleanup the pid file
fs.removeSync(pidfile);
}
return running;
}
/**
* Check that the content folder is owned by the current user
*
* @param {String} cwd current working directory
* @return {Boolean} true if ownership is correct, otherwise false
*/
_checkContentFolder(cwd) {
if (this.system.platform.windows) {
return true;
}
const stat = fs.lstatSync(path.join(cwd, 'content'));
if (stat.uid === process.getuid()) {
return true;
}
const Mode = require('stat-mode');
const mode = new Mode(stat);
return mode.others.write && mode.others.read;
}
/**
* Because this process manager should work on every system,
* just return true here
*/
static willRun() {
return true;
}
}
module.exports = LocalProcess;