1const { Buffer } = require('buffer');
2const fs = require("fs");
3const path = require("path");
4const { once } = require('events');
5
6const prettierContainerPath = process.argv[2];
7if (prettierContainerPath == null || prettierContainerPath.length == 0) {
8 process.stderr.write(`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path\n`);
9 process.exit(1);
10}
11fs.stat(prettierContainerPath, (err, stats) => {
12 if (err) {
13 process.stderr.write(`Path '${prettierContainerPath}' does not exist\n`);
14 process.exit(1);
15 }
16
17 if (!stats.isDirectory()) {
18 process.stderr.write(`Path '${prettierContainerPath}' exists but is not a directory\n`);
19 process.exit(1);
20 }
21});
22const prettierPath = path.join(prettierContainerPath, 'node_modules/prettier');
23
24
25(async () => {
26 let prettier;
27 try {
28 prettier = await loadPrettier(prettierPath);
29 } catch (e) {
30 process.stderr.write(`Failed to load prettier: ${e}\n`);
31 process.exit(1);
32 }
33 process.stderr.write("Prettier loadded successfully\n");
34 process.stdin.resume();
35 handleBuffer(prettier);
36})()
37
38async function handleBuffer(prettier) {
39 for await (let messageText of readStdin()) {
40 let message;
41 try {
42 message = JSON.parse(messageText);
43 } catch (e) {
44 sendResponse(makeError(`Failed to parse message '${messageText}': ${e}`));
45 continue;
46 }
47 handleMessage(message, prettier).catch(e => {
48 sendResponse({ id: message.id, ...makeError(`error during message handling: ${e}`) });
49 });
50 }
51}
52
53const headerSeparator = "\r\n";
54const contentLengthHeaderName = 'Content-Length';
55
56async function* readStdin() {
57 let buffer = Buffer.alloc(0);
58 let streamEnded = false;
59 process.stdin.on('end', () => {
60 streamEnded = true;
61 });
62 process.stdin.on('data', (data) => {
63 buffer = Buffer.concat([buffer, data]);
64 });
65
66 async function handleStreamEnded(errorMessage) {
67 sendResponse(makeError(errorMessage));
68 buffer = Buffer.alloc(0);
69 messageLength = null;
70 await once(process.stdin, 'readable');
71 streamEnded = false;
72 }
73
74 try {
75 let headersLength = null;
76 let messageLength = null;
77 main_loop: while (true) {
78 if (messageLength === null) {
79 while (buffer.indexOf(`${headerSeparator}${headerSeparator}`) === -1) {
80 if (streamEnded) {
81 await handleStreamEnded('Unexpected end of stream: headers not found');
82 continue main_loop;
83 } else if (buffer.length > contentLengthHeaderName.length * 10) {
84 await handleStreamEnded(`Unexpected stream of bytes: no headers end found after ${buffer.length} bytes of input`);
85 continue main_loop;
86 }
87 await once(process.stdin, 'readable');
88 }
89 const headers = buffer.subarray(0, buffer.indexOf(`${headerSeparator}${headerSeparator}`)).toString('ascii');
90 const contentLengthHeader = headers.split(headerSeparator).map(header => header.split(':'))
91 .filter(header => header[2] === undefined)
92 .filter(header => (header[1] || '').length > 0)
93 .find(header => header[0].trim() === contentLengthHeaderName);
94 if (contentLengthHeader === undefined) {
95 await handleStreamEnded(`Missing or incorrect ${contentLengthHeaderName} header: ${headers}`);
96 continue main_loop;
97 }
98 headersLength = headers.length + headerSeparator.length * 2;
99 messageLength = parseInt(contentLengthHeader[1], 10);
100 }
101
102 while (buffer.length < (headersLength + messageLength)) {
103 if (streamEnded) {
104 await handleStreamEnded(
105 `Unexpected end of stream: buffer length ${buffer.length} does not match expected header length ${headersLength} + body length ${messageLength}`);
106 continue main_loop;
107 }
108 await once(process.stdin, 'readable');
109 }
110
111 const messageEnd = headersLength + messageLength;
112 const message = buffer.subarray(headersLength, messageEnd);
113 buffer = buffer.subarray(messageEnd);
114 messageLength = null;
115 yield message.toString('utf8');
116 }
117 } catch (e) {
118 sendResponse(makeError(`Error reading stdin: ${e}`));
119 } finally {
120 process.stdin.off('data', () => { });
121 }
122}
123
124// ?
125// shutdown
126// error
127async function handleMessage(message, prettier) {
128 const { method, id, params } = message;
129 if (method === undefined) {
130 throw new Error(`Message method is undefined: ${JSON.stringify(message)}`);
131 }
132 if (id === undefined) {
133 throw new Error(`Message id is undefined: ${JSON.stringify(message)}`);
134 }
135
136 if (method === 'prettier/format') {
137 if (params === undefined || params.text === undefined) {
138 throw new Error(`Message params.text is undefined: ${JSON.stringify(message)}`);
139 }
140 if (params.options === undefined) {
141 throw new Error(`Message params.options is undefined: ${JSON.stringify(message)}`);
142 }
143 const formattedText = await prettier.format(params.text, params.options);
144 sendResponse({ id, result: { text: formattedText } });
145 } else if (method === 'prettier/clear_cache') {
146 prettier.clearConfigCache();
147 sendResponse({ id, result: null });
148 } else if (method === 'initialize') {
149 sendResponse({
150 id,
151 result: {
152 "capabilities": {}
153 }
154 });
155 } else {
156 throw new Error(`Unknown method: ${method}`);
157 }
158}
159
160function makeError(message) {
161 return {
162 error: {
163 "code": -32600, // invalid request code
164 message,
165 }
166 };
167}
168
169function sendResponse(response) {
170 let responsePayloadString = JSON.stringify({
171 jsonrpc: "2.0",
172 ...response
173 });
174 let headers = `${contentLengthHeaderName}: ${Buffer.byteLength(responsePayloadString)}${headerSeparator}${headerSeparator}`;
175 let dataToSend = headers + responsePayloadString;
176 process.stdout.write(dataToSend);
177}
178
179function loadPrettier(prettierPath) {
180 return new Promise((resolve, reject) => {
181 fs.access(prettierPath, fs.constants.F_OK, (err) => {
182 if (err) {
183 reject(`Path '${prettierPath}' does not exist.Error: ${err}`);
184 } else {
185 try {
186 resolve(require(prettierPath));
187 } catch (err) {
188 reject(`Error requiring prettier module from path '${prettierPath}'.Error: ${err}`);
189 }
190 }
191 });
192 });
193}