UNPKG

5.09 kBPlain TextView Raw
1import {
2 clone,
3 merge
4} from 'lodash';
5import * as fs from 'fs-extra';
6import * as path from 'path';
7import { spawn, ChildProcess } from 'child_process';
8import { ui, Command, Project, unwrap } from 'denali-cli';
9import * as createDebug from 'debug';
10
11const debug = createDebug('denali:commands:server');
12
13/**
14 * Runs the denali server for local or production use.
15 *
16 * @package commands
17 */
18export default class ServerCommand extends Command {
19
20 /* tslint:disable:completed-docs typedef */
21 static commandName = 'server';
22 static description = 'Runs the denali server for local or production use.';
23 static longDescription = unwrap`
24 Launches the Denali server running your application.
25
26 In a development environment, the server does several things:
27
28 * watches your local filesystem for changes and automatically restarts for you.
29 * lint your code on build
30 * run a security audit of your package.json on build (via nsp)
31
32 In production, the above features are disabled by default, and instead:
33
34 * the server will fork worker processes to maximize CPU core usage`;
35
36 static runsInApp = true;
37
38 static flags = {
39 environment: {
40 description: 'The target environment to build for.',
41 default: process.env.NODE_ENV || 'development',
42 type: <any>'string'
43 },
44 debug: {
45 description: 'Run in debug mode (add the --debug flag to node, launch node-inspector)',
46 default: false,
47 type: <any>'boolean'
48 },
49 watch: {
50 description: 'Restart the server when the source files change (default: true in development)',
51 type: <any>'boolean'
52 },
53 port: {
54 description: 'The port the HTTP server should bind to (default: process.env.PORT or 3000)',
55 default: process.env.PORT || 3000,
56 type: <any>'number'
57 },
58 skipBuild: {
59 description: "Don't build the app before launching the server. Useful in production if you prebuild the app before deploying. Implies --skip-lint and --skip-audit.",
60 default: false,
61 type: <any>'boolean'
62 },
63 skipLint: {
64 description: 'Skip linting the app source files',
65 default: false,
66 type: <any>'boolean'
67 },
68 skipAudit: {
69 description: 'Skip auditing your package.json for vulnerabilites',
70 default: false,
71 type: <any>'boolean'
72 },
73 output: {
74 description: 'The directory to write the compiled app to. Defaults to a tmp directory',
75 default: 'dist',
76 type: <any>'string'
77 },
78 production: {
79 description: 'Shorthand for "--skip-build --environment production"',
80 default: false,
81 type: <any>'boolean'
82 },
83 printSlowTrees: {
84 description: 'Print out an analysis of the build process, showing the slowest nodes.',
85 default: false,
86 type: <any>'boolean'
87 }
88 };
89
90 server: ChildProcess;
91
92 async run(argv: any) {
93 debug('running server command');
94 if (argv.production) {
95 argv.skipBuild = true;
96 argv.environment = 'production';
97 }
98 argv.watch = argv.watch || argv.environment === 'development';
99
100 if (argv.skipBuild) {
101 this.startServer(argv);
102 return;
103 }
104
105 let project = new Project({
106 environment: argv.environment,
107 printSlowTrees: argv.printSlowTrees,
108 audit: !argv.skipAudit,
109 lint: !argv.skipLint,
110 buildDummy: true
111 });
112
113 process.on('exit', this.cleanExit.bind(this));
114 process.on('SIGINT', this.cleanExit.bind(this));
115 process.on('SIGTERM', this.cleanExit.bind(this));
116
117 if (argv.watch) {
118 debug('starting watcher');
119 project.watch({
120 outputDir: argv.output,
121 onBuild: () => {
122 if (this.server) {
123 debug('killing existing server');
124 this.server.removeAllListeners('exit');
125 this.server.kill();
126 }
127 this.startServer(argv);
128 }
129 });
130 } else {
131 debug('building project');
132 await project.build(argv.output);
133 this.startServer(argv);
134 }
135 }
136
137 protected cleanExit() {
138 if (this.server) {
139 this.server.kill();
140 }
141 }
142
143 protected startServer(argv: any) {
144 let dir = argv.output;
145 let args = [ 'app/index.js' ];
146 if (argv.debug) {
147 args.unshift('--inspect', '--debug-brk');
148 }
149 if (!fs.existsSync(path.join(dir, 'app', 'index.js'))) {
150 ui.error('Unable to start your application: missing app/index.js file');
151 return;
152 }
153 debug(`starting server process: ${ process.execPath } ${ args.join(' ') }`);
154 this.server = spawn(process.execPath, args, {
155 cwd: dir,
156 stdio: [ 'pipe', process.stdout, process.stderr ],
157 env: merge(clone(process.env), {
158 PORT: argv.port,
159 NODE_ENV: argv.environment
160 })
161 });
162 this.server.on('error', (error) => {
163 ui.error('Unable to start your application:');
164 ui.error(error.stack);
165 });
166 if (argv.watch) {
167 this.server.on('exit', (code) => {
168 let result = code === 0 ? 'exited' : 'crashed';
169 ui.error(`Server ${ result }. waiting for changes to restart ...`);
170 });
171 }
172 }
173
174}