1 | 'use strict';
|
2 |
|
3 | const path = require('path');
|
4 | const fs = require('fs');
|
5 | const url = require('url');
|
6 | const _ = require('lodash');
|
7 | const express = require('express');
|
8 | const bodyParser = require('body-parser');
|
9 | const Watchpack = require('watchpack');
|
10 | const rekitCore = require('rekit-core');
|
11 | const fetchProjectData = require('./api/fetchProjectData');
|
12 | const getFileContent = require('./api/getFileContent');
|
13 | const saveFile = require('./api/saveFile');
|
14 | const runBuild = require('./api/runBuild');
|
15 | const runTest = require('./api/runTest');
|
16 |
|
17 | const utils = rekitCore.utils;
|
18 |
|
19 |
|
20 | rekitCore.plugin.loadPlugins(rekitCore);
|
21 |
|
22 | let lastProjectData = null;
|
23 | module.exports = function() {
|
24 | let io = null;
|
25 |
|
26 | const bgProcesses = {};
|
27 |
|
28 | const wp = new Watchpack({
|
29 |
|
30 | aggregateTimeout: 1000,
|
31 |
|
32 |
|
33 |
|
34 | poll: false,
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 | ignored: /node_modules/,
|
41 |
|
42 |
|
43 | });
|
44 |
|
45 |
|
46 |
|
47 | wp.watch([], [path.join(rekitCore.utils.getProjectRoot(), 'src')], Date.now() - 10);
|
48 |
|
49 |
|
50 |
|
51 | wp.on('aggregated', (changes) => {
|
52 |
|
53 | rekitCore.vio.reset();
|
54 |
|
55 |
|
56 |
|
57 | if (io) io.emit('fileChanged', changes);
|
58 | });
|
59 |
|
60 | const rootPath = '/rekit';
|
61 |
|
62 | function execCmd(req, res) {
|
63 | try {
|
64 | const args = req.body;
|
65 | rekitCore.handleCommand(args);
|
66 | const logs = rekitCore.vio.flush();
|
67 | rekitCore.vio.reset();
|
68 | res.write(JSON.stringify({
|
69 | args,
|
70 | logs,
|
71 | }));
|
72 | res.end();
|
73 | } catch (e) {
|
74 | res.statusCode = 500;
|
75 | res.write(e.toString());
|
76 | res.end();
|
77 | }
|
78 | }
|
79 |
|
80 | function setupSocketIo(server) {
|
81 | io = require('socket.io')(server);
|
82 |
|
83 | io.on('connection', (client) => {
|
84 | client.on('disconnect', () => {
|
85 | console.log('socket disconnected');
|
86 | });
|
87 | });
|
88 | }
|
89 |
|
90 | function reply403(res) {
|
91 | res.statusCode = 403;
|
92 | res.write('Forbidden: Rekit portal is running on readonly mode.');
|
93 | res.end();
|
94 | }
|
95 |
|
96 | function rekitMiddleware(server, app, args) {
|
97 | args = args || {};
|
98 | setupSocketIo(server);
|
99 | const prjRoot = rekitCore.utils.getProjectRoot();
|
100 | app.use('/coverage', express.static(utils.joinPath(prjRoot, 'coverage'), { fallthrough: false }));
|
101 | app.use(bodyParser.json());
|
102 | app.use(bodyParser.urlencoded({ extended: true }));
|
103 |
|
104 | return (req, res, next) => {
|
105 | const urlObject = url.parse(req.originalUrl);
|
106 | const p = urlObject.pathname.replace(rootPath, '');
|
107 |
|
108 | try {
|
109 | switch (p) {
|
110 | case '/api/project-data': {
|
111 | lastProjectData = fetchProjectData();
|
112 | res.write(JSON.stringify(Object.assign({
|
113 | bgProcesses,
|
114 | }, lastProjectData)));
|
115 | res.end();
|
116 | break;
|
117 | }
|
118 | case '/api/save-file': {
|
119 | const absPath = utils.joinPath(prjRoot, req.body.file);
|
120 | if (!_.startsWith(absPath, prjRoot)) {
|
121 |
|
122 | res.statusCode = 403;
|
123 | res.write('Forbidden: not allowed to access file out of the project.');
|
124 | res.end();
|
125 | break;
|
126 | }
|
127 |
|
128 | if (!fs.existsSync(absPath)) {
|
129 | res.statusCode = 404;
|
130 | res.write(JSON.stringify({ error: 'Not found.' }));
|
131 | res.end();
|
132 | } else {
|
133 | try {
|
134 | saveFile(absPath, req.body.content);
|
135 | res.write({ success: true });
|
136 | } catch (err) {
|
137 | res.write({ error: err });
|
138 | }
|
139 | res.end();
|
140 | }
|
141 | break;
|
142 | }
|
143 | case '/api/file-content': {
|
144 | const absPath = utils.joinPath(prjRoot, req.query.file);
|
145 | if (!_.startsWith(absPath, prjRoot)) {
|
146 |
|
147 | res.statusCode = 403;
|
148 | res.write('Forbidden: not allowed to access file out of the project.');
|
149 | res.end();
|
150 | break;
|
151 | }
|
152 |
|
153 | if (!fs.existsSync(absPath)) {
|
154 | res.statusCode = 404;
|
155 | res.write(JSON.stringify({ error: 'Not found.' }));
|
156 | res.end();
|
157 | } else {
|
158 | res.write(JSON.stringify({ content: getFileContent(absPath) }));
|
159 | res.end();
|
160 | }
|
161 | break;
|
162 | }
|
163 | case '/api/exec-cmd':
|
164 | if (args.readonly) { reply403(res); break; }
|
165 | execCmd(req, res);
|
166 | break;
|
167 | case '/api/run-build':
|
168 | if (args.readonly) { reply403(res); break; }
|
169 | if (bgProcesses.runningBuild) {
|
170 | res.statusCode = 500;
|
171 | res.write(JSON.stringify({ error: 'Build process is running...' }));
|
172 | res.end();
|
173 | } else {
|
174 | bgProcesses.runningBuild = true;
|
175 | runBuild(io).then(() => {
|
176 | bgProcesses.runningBuild = false;
|
177 | }).catch(() => {
|
178 | bgProcesses.runningBuild = false;
|
179 | });
|
180 | res.write('{}');
|
181 | res.end();
|
182 | }
|
183 | break;
|
184 | case '/api/run-test':
|
185 | if (args.readonly) { reply403(res); break; }
|
186 | if (bgProcesses.runningTest) {
|
187 | res.statusCode = 500;
|
188 | res.write(JSON.stringify({ error: 'Test process is running...' }));
|
189 | res.end();
|
190 | } else {
|
191 | bgProcesses.runningTest = true;
|
192 | runTest(io, req.body.testFile || '').then(() => {
|
193 | bgProcesses.runningTest = false;
|
194 | }).catch(() => {
|
195 | bgProcesses.runningTest = false;
|
196 | });
|
197 | res.write('{}');
|
198 | res.end();
|
199 | }
|
200 | break;
|
201 | default: {
|
202 | if (/^\/api\//.test(p)) {
|
203 | res.statusCode = 404;
|
204 | res.write(JSON.stringify({ error: `API not found: ${p}` }));
|
205 | res.end();
|
206 | } else {
|
207 | next();
|
208 | }
|
209 | break;
|
210 | }
|
211 | }
|
212 | } catch (e) {
|
213 | res.statusCode = 500;
|
214 | res.write(e.toString());
|
215 | res.end();
|
216 | }
|
217 | };
|
218 | }
|
219 | return rekitMiddleware;
|
220 | };
|