|
1 |
| -import { spawn } from 'child_process'; |
2 |
| -import colors from 'colors'; |
3 |
| -import { NextFunction, Request, Response } from 'express'; |
4 |
| -import { hostname } from 'os'; |
5 |
| -import * as path from 'path'; |
6 |
| - |
7 |
| -import deployDeleteController from './controller/delete'; |
8 |
| -import uploadController from './controller/upload'; |
9 |
| - |
10 |
| -import { |
11 |
| - DeleteBody, |
12 |
| - DeployBody, |
13 |
| - Deployment, |
14 |
| - FetchBranchListBody, |
15 |
| - FetchFilesFromRepoBody, |
16 |
| - ProtocolMessageType, |
17 |
| - WorkerMessage, |
18 |
| - WorkerMessageUnknown, |
19 |
| - allApplications, |
20 |
| - childProcesses, |
21 |
| - deploymentMap |
22 |
| -} from './constants'; |
23 |
| - |
24 |
| -import AppError from './utils/appError'; |
| 1 | +import { callFunction } from './controller/call'; |
| 2 | +import { deployDelete } from './controller/delete'; |
| 3 | +import { deploy } from './controller/deploy'; |
| 4 | +import { globalError } from './controller/error'; |
| 5 | +import { logs } from './controller/logs'; |
25 | 6 | import {
|
26 |
| - catchAsync, |
27 |
| - deleteRepoFolderIfExist, |
28 |
| - dirName, |
29 |
| - ensureFolderExists, |
30 |
| - execPromise, |
31 |
| - exists, |
32 |
| - installDependencies, |
33 |
| - isIAllApps, |
34 |
| - logProcessOutput |
35 |
| -} from './utils/utils'; |
36 |
| - |
37 |
| -import { PackageError } from '@metacall/protocol/package'; |
38 |
| -import { appsDirectory } from './utils/config'; |
39 |
| - |
40 |
| -const appsDir = appsDirectory(); |
41 |
| - |
42 |
| -colors.enable(); |
43 |
| - |
44 |
| -export const callFnByName = ( |
45 |
| - req: Request, |
46 |
| - res: Response, |
47 |
| - next: NextFunction |
48 |
| -): Response | void => { |
49 |
| - if (!(req.params && req.params.name)) |
50 |
| - next( |
51 |
| - new AppError( |
52 |
| - 'A function name is required in the path; i.e: /call/sum.', |
53 |
| - 404 |
54 |
| - ) |
55 |
| - ); |
56 |
| - |
57 |
| - const { appName: app, name } = req.params; |
58 |
| - const args = Object.values(req.body); |
59 |
| - |
60 |
| - if (!(app in childProcesses)) { |
61 |
| - return res |
62 |
| - .status(404) |
63 |
| - .send( |
64 |
| - `Oops! It looks like the application (${app}) hasn't been deployed yet. Please deploy it before you can call its functions.` |
65 |
| - ); |
66 |
| - } |
67 |
| - |
68 |
| - let responseSent = false; // Flag to track if response has been sent |
69 |
| - let errorCame = false; |
70 |
| - |
71 |
| - childProcesses[app].send({ |
72 |
| - type: ProtocolMessageType.Invoke, |
73 |
| - data: { |
74 |
| - name, |
75 |
| - args |
76 |
| - } |
77 |
| - }); |
78 |
| - |
79 |
| - childProcesses[app].on('message', (message: WorkerMessageUnknown) => { |
80 |
| - if (!responseSent) { |
81 |
| - // Check if response has already been sent |
82 |
| - if (message.type === ProtocolMessageType.InvokeResult) { |
83 |
| - responseSent = true; // Set flag to true to indicate response has been sent |
84 |
| - return res.send(JSON.stringify(message.data)); |
85 |
| - } else { |
86 |
| - errorCame = true; |
87 |
| - } |
88 |
| - } |
89 |
| - }); |
90 |
| - |
91 |
| - // Default response in case the 'message' event is not triggered |
92 |
| - if (!responseSent && errorCame) { |
93 |
| - responseSent = true; // Set flag to true to indicate response has been sent |
94 |
| - errorCame = false; |
95 |
| - return res.send('Function calling error'); |
96 |
| - } |
| 7 | + fetchBranchList, |
| 8 | + fetchFileList, |
| 9 | + fetchFilesFromRepo |
| 10 | +} from './controller/repository'; |
| 11 | +import { serveStatic } from './controller/static'; |
| 12 | +import { uploadPackage } from './controller/upload'; |
| 13 | +import { validate } from './controller/validate'; |
| 14 | + |
| 15 | +export default { |
| 16 | + callFunction, |
| 17 | + deployDelete, |
| 18 | + deploy, |
| 19 | + globalError, |
| 20 | + logs, |
| 21 | + fetchFilesFromRepo, |
| 22 | + fetchBranchList, |
| 23 | + fetchFileList, |
| 24 | + serveStatic, |
| 25 | + uploadPackage, |
| 26 | + validate |
97 | 27 | };
|
98 |
| - |
99 |
| -export const serveStatic = catchAsync( |
100 |
| - async (req: Request, res: Response, next: NextFunction): Promise<void> => { |
101 |
| - if (!req.params) next(new AppError('Invalid API endpoint', 404)); |
102 |
| - |
103 |
| - const { app, file } = req.params; |
104 |
| - |
105 |
| - const appLocation = path.join(appsDir, `${app}/${file}`); |
106 |
| - |
107 |
| - if (!(app in childProcesses)) { |
108 |
| - next( |
109 |
| - new AppError( |
110 |
| - `Oops! It looks like the application (${app}) hasn't been deployed yet. Please deploy it before you can call its functions.`, |
111 |
| - 404 |
112 |
| - ) |
113 |
| - ); |
114 |
| - } |
115 |
| - |
116 |
| - if (!(await exists(appLocation))) |
117 |
| - next( |
118 |
| - new AppError( |
119 |
| - "The file you're looking for might not be available or the application may not be deployed.", |
120 |
| - 404 |
121 |
| - ) |
122 |
| - ); |
123 |
| - |
124 |
| - return res.status(200).sendFile(appLocation); |
125 |
| - } |
126 |
| -); |
127 |
| - |
128 |
| -export const fetchFiles = ( |
129 |
| - req: Request, |
130 |
| - res: Response, |
131 |
| - next: NextFunction |
132 |
| -): void => uploadController(req, res, next); |
133 |
| - |
134 |
| -export const fetchFilesFromRepo = catchAsync( |
135 |
| - async ( |
136 |
| - req: Omit<Request, 'body'> & { body: FetchFilesFromRepoBody }, |
137 |
| - res: Response, |
138 |
| - next: NextFunction |
139 |
| - ) => { |
140 |
| - const { branch, url } = req.body; |
141 |
| - |
142 |
| - await ensureFolderExists(appsDir); |
143 |
| - |
144 |
| - try { |
145 |
| - await deleteRepoFolderIfExist(appsDir, url); |
146 |
| - } catch (err) { |
147 |
| - next( |
148 |
| - new AppError( |
149 |
| - 'error occurred in deleting repository directory', |
150 |
| - 500 |
151 |
| - ) |
152 |
| - ); |
153 |
| - } |
154 |
| - |
155 |
| - await execPromise( |
156 |
| - `cd ${appsDir}; git clone --single-branch --depth=1 --branch ${branch} ${url} ` |
157 |
| - ); |
158 |
| - |
159 |
| - const id = dirName(req.body.url); |
160 |
| - |
161 |
| - // TODO: This method is wrong |
162 |
| - // deployment.id = id; |
163 |
| - // deployment.path = `${appsDir}/${id}`; |
164 |
| - |
165 |
| - return res.status(201).send({ id }); |
166 |
| - } |
167 |
| -); |
168 |
| - |
169 |
| -export const fetchBranchList = catchAsync( |
170 |
| - async ( |
171 |
| - req: Omit<Request, 'body'> & { body: FetchBranchListBody }, |
172 |
| - res: Response |
173 |
| - ) => { |
174 |
| - const { stdout } = await execPromise( |
175 |
| - `git ls-remote --heads ${req.body.url}` |
176 |
| - ); |
177 |
| - |
178 |
| - const branches: string[] = []; |
179 |
| - |
180 |
| - JSON.stringify(stdout.toString()) |
181 |
| - .split('\\n') |
182 |
| - .forEach(el => { |
183 |
| - if (el.trim().length > 1) { |
184 |
| - branches.push(el.split('refs/heads/')[1]); |
185 |
| - } |
186 |
| - }); |
187 |
| - |
188 |
| - return res.send({ branches }); |
189 |
| - } |
190 |
| -); |
191 |
| - |
192 |
| -export const fetchFileList = catchAsync( |
193 |
| - async ( |
194 |
| - req: Omit<Request, 'body'> & { body: FetchFilesFromRepoBody }, |
195 |
| - res: Response, |
196 |
| - next: NextFunction |
197 |
| - ) => { |
198 |
| - await ensureFolderExists(appsDir); |
199 |
| - |
200 |
| - try { |
201 |
| - await deleteRepoFolderIfExist(appsDir, req.body.url); |
202 |
| - } catch (err) { |
203 |
| - next( |
204 |
| - new AppError( |
205 |
| - 'error occurred in deleting repository directory', |
206 |
| - 500 |
207 |
| - ) |
208 |
| - ); |
209 |
| - } |
210 |
| - await execPromise( |
211 |
| - `cd ${appsDir} ; git clone ${req.body.url} --depth=1 --no-checkout` |
212 |
| - ); |
213 |
| - |
214 |
| - const dirPath = `${appsDir}/${dirName(req.body.url)}`; |
215 |
| - |
216 |
| - const { stdout } = await execPromise( |
217 |
| - `cd ${dirPath} ; git ls-tree -r ${req.body.branch} --name-only; cd .. ; rm -r ${dirPath}` |
218 |
| - ); |
219 |
| - |
220 |
| - return res.send({ |
221 |
| - files: JSON.stringify(stdout.toString()).split('\\n') |
222 |
| - }); |
223 |
| - } |
224 |
| -); |
225 |
| - |
226 |
| -export const deploy = catchAsync( |
227 |
| - async ( |
228 |
| - req: Omit<Request, 'body'> & { body: DeployBody }, |
229 |
| - res: Response, |
230 |
| - next: NextFunction |
231 |
| - ) => { |
232 |
| - try { |
233 |
| - // TODO: Implement repository |
234 |
| - // req.body.resourceType == 'Repository' && |
235 |
| - // (await calculatePackages(next)); |
236 |
| - |
237 |
| - const deployment = deploymentMap[req.body.suffix]; |
238 |
| - |
239 |
| - if (deployment === undefined) { |
240 |
| - return next( |
241 |
| - new AppError( |
242 |
| - `Invalid deployment id: ${req.body.suffix}`, |
243 |
| - 400 |
244 |
| - ) |
245 |
| - ); |
246 |
| - } |
247 |
| - |
248 |
| - await installDependencies(deployment); |
249 |
| - |
250 |
| - const desiredPath = path.join(__dirname, 'worker', 'index.js'); |
251 |
| - |
252 |
| - const proc = spawn('metacall', [desiredPath], { |
253 |
| - stdio: ['pipe', 'pipe', 'pipe', 'ipc'] |
254 |
| - }); |
255 |
| - |
256 |
| - proc.send({ |
257 |
| - type: ProtocolMessageType.Load, |
258 |
| - data: deployment |
259 |
| - }); |
260 |
| - |
261 |
| - logProcessOutput(proc.stdout, proc.pid, deployment.id); |
262 |
| - logProcessOutput(proc.stderr, proc.pid, deployment.id); |
263 |
| - |
264 |
| - proc.on('message', (payload: WorkerMessageUnknown) => { |
265 |
| - if (payload.type === ProtocolMessageType.MetaData) { |
266 |
| - const message = payload as WorkerMessage<Deployment>; |
267 |
| - if (isIAllApps(message.data)) { |
268 |
| - const appName = Object.keys(message.data)[0]; |
269 |
| - childProcesses[appName] = proc; |
270 |
| - allApplications[appName] = message.data[appName]; |
271 |
| - } |
272 |
| - } |
273 |
| - }); |
274 |
| - |
275 |
| - return res.status(200).json({ |
276 |
| - suffix: hostname(), |
277 |
| - prefix: deployment.id, |
278 |
| - version: 'v1' |
279 |
| - }); |
280 |
| - } catch (err) { |
281 |
| - // Check if the error is PackageError.Empty |
282 |
| - if (err === PackageError.Empty) { |
283 |
| - return next(err); |
284 |
| - } |
285 |
| - return next(err); |
286 |
| - } |
287 |
| - } |
288 |
| -); |
289 |
| - |
290 |
| -export const showLogs = (req: Request, res: Response): Response => { |
291 |
| - return res.send('Demo Logs...'); |
292 |
| -}; |
293 |
| - |
294 |
| -export const deployDelete = ( |
295 |
| - req: Omit<Request, 'body'> & { body: DeleteBody }, |
296 |
| - res: Response, |
297 |
| - next: NextFunction |
298 |
| -): void => deployDeleteController(req, res, next); |
299 |
| - |
300 |
| -export const validateAndDeployEnabled = ( |
301 |
| - req: Request, |
302 |
| - res: Response |
303 |
| -): Response => |
304 |
| - res.status(200).json({ |
305 |
| - status: 'success', |
306 |
| - data: true |
307 |
| - }); |
0 commit comments