-
-
Notifications
You must be signed in to change notification settings - Fork 216
/
Copy pathStudio.ts
149 lines (134 loc) · 4.61 KB
/
Studio.ts
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
import { existsSync, promises as fPromises } from 'fs';
import { SpecificationFileNotFound } from '../errors/specification-file';
import { createServer } from 'http';
import serveHandler from 'serve-handler';
import { WebSocketServer } from 'ws';
import chokidar from 'chokidar';
import open from 'open';
import path from 'path';
import { version as studioVersion } from '@asyncapi/studio/package.json';
import { blueBright,redBright } from 'picocolors';
const { readFile, writeFile } = fPromises;
const sockets: any[] = [];
const messageQueue: string[] = [];
export const DEFAULT_PORT = 3210;
function isValidFilePath(filePath: string): boolean {
return existsSync(filePath);
}
export function start(filePath: string, port: number = DEFAULT_PORT): void {
if (filePath && !isValidFilePath(filePath)) {
throw new SpecificationFileNotFound(filePath);
}
if (filePath) {
chokidar.watch(filePath).on('all', (event, path) => {
switch (event) {
case 'add':
case 'change':
getFileContent(path).then((code: string) => {
messageQueue.push(JSON.stringify({
type: 'file:changed',
code,
}));
sendQueuedMessages();
});
break;
case 'unlink':
messageQueue.push(JSON.stringify({
type: 'file:deleted',
filePath,
}));
sendQueuedMessages();
break;
}
});
}
const server = createServer((request, response) => {
//not all CLI users use npm. Some package managers put dependencies in different weird places
//this is why we need to first figure out where exactly is the index.html located
//and then strip index.html from the path to point to directory with the rest of the studio
const indexLocation = require.resolve('@asyncapi/studio/build/index.html');
const hostFolder = indexLocation.substring(0, indexLocation.lastIndexOf(path.sep));
return serveHandler(request, response, {
public: hostFolder,
});
});
server.on('upgrade', (request, socket, head) => {
if (request.url === '/live-server') {
wsServer.handleUpgrade(request, socket, head, (sock: any) => {
wsServer.emit('connection', sock, request);
});
} else {
socket.destroy();
}
});
const wsServer = new WebSocketServer({ noServer: true });
wsServer.on('connection', (socket: any) => {
sockets.push(socket);
if (filePath) {
getFileContent(filePath).then((code: string) => {
messageQueue.push(JSON.stringify({
type: 'file:loaded',
code,
}));
sendQueuedMessages();
});
} else {
messageQueue.push(JSON.stringify({
type: 'file:loaded',
code: '',
}));
sendQueuedMessages();
}
socket.on('message', (event: string) => {
try {
const json: any = JSON.parse(event);
if (filePath && json.type === 'file:update') {
saveFileContent(filePath, json.code);
} else {
console.warn('Live Server: An unknown event has been received. See details:');
console.log(json);
}
} catch (e) {
console.error(`Live Server: An invalid event has been received. See details:\n${event}`);
}
});
});
wsServer.on('close', (socket: any) => {
sockets.splice(sockets.findIndex(s => s === socket));
});
server.listen(port, () => {
const url = `http://localhost:${port}?liveServer=${port}&studio-version=${studioVersion}`;
console.log(`🎉 Connected to Live Server running at ${blueBright(url)}.`);
console.log(`🌐 Open this URL in your web browser: ${blueBright(url)}`);
console.log(`🛑 If needed, press ${redBright('Ctrl + C')} to stop the process.`);
if (filePath) {
console.log(`👁️ Watching changes on file ${blueBright(filePath)}`);
} else {
console.warn(
'Warning: No file was provided, and we couldn\'t find a default file (like "asyncapi.yaml" or "asyncapi.json") in the current folder. Starting Studio with a blank workspace.'
);
}
open(url);
});
}
function sendQueuedMessages() {
while (messageQueue.length && sockets.length) {
const nextMessage = messageQueue.shift();
for (const socket of sockets) {
socket.send(nextMessage);
}
}
}
function getFileContent(filePath: string): Promise<string> {
return new Promise((resolve) => {
readFile(filePath, { encoding: 'utf8' })
.then((code: string) => {
resolve(code);
})
.catch(console.error);
});
}
function saveFileContent(filePath: string, fileContent: string): void {
writeFile(filePath, fileContent, { encoding: 'utf8' })
.catch(console.error);
}