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