node-pty 1.1.0-beta3 → 1.1.0-beta30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +6 -5
  2. package/binding.gyp +4 -15
  3. package/deps/winpty/src/agent/Agent.cc +11 -3
  4. package/lib/unixTerminal.js +4 -27
  5. package/lib/unixTerminal.js.map +1 -1
  6. package/lib/unixTerminal.test.js +3 -5
  7. package/lib/unixTerminal.test.js.map +1 -1
  8. package/lib/windowsConoutConnection.js +0 -4
  9. package/lib/windowsConoutConnection.js.map +1 -1
  10. package/lib/windowsPtyAgent.js +41 -21
  11. package/lib/windowsPtyAgent.js.map +1 -1
  12. package/lib/windowsTerminal.js +1 -1
  13. package/lib/windowsTerminal.js.map +1 -1
  14. package/lib/windowsTerminal.test.js +38 -26
  15. package/lib/windowsTerminal.test.js.map +1 -1
  16. package/lib/worker/conoutSocketWorker.js +13 -10
  17. package/lib/worker/conoutSocketWorker.js.map +1 -1
  18. package/package.json +4 -2
  19. package/scripts/increment-version.js +54 -0
  20. package/scripts/post-install.js +51 -10
  21. package/scripts/publish.js +7 -4
  22. package/src/interfaces.ts +1 -0
  23. package/src/native.d.ts +5 -5
  24. package/src/unix/pty.cc +241 -284
  25. package/src/unixTerminal.test.ts +3 -6
  26. package/src/unixTerminal.ts +4 -18
  27. package/src/win/conpty.cc +264 -185
  28. package/src/win/conpty.h +41 -0
  29. package/src/win/conpty_console_list.cc +16 -15
  30. package/src/win/path_util.cc +39 -36
  31. package/src/win/path_util.h +5 -2
  32. package/src/win/winpty.cc +108 -151
  33. package/src/windowsConoutConnection.ts +0 -4
  34. package/src/windowsPtyAgent.ts +38 -20
  35. package/src/windowsTerminal.test.ts +43 -32
  36. package/src/windowsTerminal.ts +1 -1
  37. package/src/worker/conoutSocketWorker.ts +14 -11
  38. package/third_party/conpty/1.22.250204002/win10-arm64/OpenConsole.exe +0 -0
  39. package/third_party/conpty/1.22.250204002/win10-arm64/conpty.dll +0 -0
  40. package/third_party/conpty/1.22.250204002/win10-x64/OpenConsole.exe +0 -0
  41. package/third_party/conpty/1.22.250204002/win10-x64/conpty.dll +0 -0
  42. package/typings/node-pty.d.ts +8 -0
package/src/win/conpty.cc CHANGED
@@ -8,23 +8,21 @@
8
8
  * with pseudo-terminal file descriptors.
9
9
  */
10
10
 
11
- // node versions lower than 10 define this as 0x502 which disables many of the definitions needed to compile
12
- #include <node_version.h>
13
- #if NODE_MODULE_VERSION <= 57
14
- #define _WIN32_WINNT 0x600
15
- #endif
11
+ #define _WIN32_WINNT 0x600
16
12
 
17
- #include <iostream>
18
- #include <nan.h>
13
+ #define NODE_ADDON_API_DISABLE_DEPRECATED
14
+ #include <node_api.h>
15
+ #include <assert.h>
19
16
  #include <Shlwapi.h> // PathCombine, PathIsRelative
20
17
  #include <sstream>
18
+ #include <iostream>
21
19
  #include <string>
20
+ #include <thread>
22
21
  #include <vector>
23
22
  #include <Windows.h>
24
23
  #include <strsafe.h>
25
24
  #include "path_util.h"
26
-
27
- extern "C" void init(v8::Local<v8::Object>);
25
+ #include "conpty.h"
28
26
 
29
27
  // Taken from the RS5 Windows SDK, but redefined here in case we're targeting <= 17134
30
28
  #ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
@@ -36,6 +34,7 @@ typedef HRESULT (__stdcall *PFNCREATEPSEUDOCONSOLE)(COORD c, HANDLE hIn, HANDLE
36
34
  typedef HRESULT (__stdcall *PFNRESIZEPSEUDOCONSOLE)(HPCON hpc, COORD newSize);
37
35
  typedef HRESULT (__stdcall *PFNCLEARPSEUDOCONSOLE)(HPCON hpc);
38
36
  typedef void (__stdcall *PFNCLOSEPSEUDOCONSOLE)(HPCON hpc);
37
+ typedef void (__stdcall *PFNRELEASEPSEUDOCONSOLE)(HPCON hpc);
39
38
 
40
39
  #endif
41
40
 
@@ -46,40 +45,91 @@ struct pty_baton {
46
45
  HPCON hpc;
47
46
 
48
47
  HANDLE hShell;
49
- HANDLE hWait;
50
- Nan::Callback cb;
51
- uv_async_t async;
52
- uv_thread_t tid;
53
48
 
54
49
  pty_baton(int _id, HANDLE _hIn, HANDLE _hOut, HPCON _hpc) : id(_id), hIn(_hIn), hOut(_hOut), hpc(_hpc) {};
55
50
  };
56
51
 
57
- static std::vector<pty_baton*> ptyHandles;
52
+ static std::vector<std::unique_ptr<pty_baton>> ptyHandles;
58
53
  static volatile LONG ptyCounter;
59
54
 
60
55
  static pty_baton* get_pty_baton(int id) {
61
- for (size_t i = 0; i < ptyHandles.size(); ++i) {
62
- pty_baton* ptyHandle = ptyHandles[i];
63
- if (ptyHandle->id == id) {
64
- return ptyHandle;
65
- }
56
+ auto it = std::find_if(ptyHandles.begin(), ptyHandles.end(), [id](const auto& ptyHandle) {
57
+ return ptyHandle->id == id;
58
+ });
59
+ if (it != ptyHandles.end()) {
60
+ return it->get();
66
61
  }
67
62
  return nullptr;
68
63
  }
69
64
 
70
- template <typename T>
71
- std::vector<T> vectorFromString(const std::basic_string<T> &str) {
72
- return std::vector<T>(str.begin(), str.end());
65
+ static bool remove_pty_baton(int id) {
66
+ auto it = std::remove_if(ptyHandles.begin(), ptyHandles.end(), [id](const auto& ptyHandle) {
67
+ return ptyHandle->id == id;
68
+ });
69
+ if (it != ptyHandles.end()) {
70
+ ptyHandles.erase(it);
71
+ return true;
72
+ }
73
+ return false;
74
+ }
75
+
76
+ struct ExitEvent {
77
+ int exit_code = 0;
78
+ };
79
+
80
+ void SetupExitCallback(Napi::Env env, Napi::Function cb, pty_baton* baton) {
81
+ std::thread *th = new std::thread;
82
+ // Don't use Napi::AsyncWorker which is limited by UV_THREADPOOL_SIZE.
83
+ auto tsfn = Napi::ThreadSafeFunction::New(
84
+ env,
85
+ cb, // JavaScript function called asynchronously
86
+ "SetupExitCallback_resource", // Name
87
+ 0, // Unlimited queue
88
+ 1, // Only one thread will use this initially
89
+ [th](Napi::Env) { // Finalizer used to clean threads up
90
+ th->join();
91
+ delete th;
92
+ });
93
+ *th = std::thread([tsfn = std::move(tsfn), baton] {
94
+ auto callback = [](Napi::Env env, Napi::Function cb, ExitEvent *exit_event) {
95
+ cb.Call({Napi::Number::New(env, exit_event->exit_code)});
96
+ delete exit_event;
97
+ };
98
+
99
+ ExitEvent *exit_event = new ExitEvent;
100
+ // Wait for process to complete.
101
+ WaitForSingleObject(baton->hShell, INFINITE);
102
+ // Get process exit code.
103
+ GetExitCodeProcess(baton->hShell, (LPDWORD)(&exit_event->exit_code));
104
+ // Clean up handles
105
+ CloseHandle(baton->hShell);
106
+ assert(remove_pty_baton(baton->id));
107
+
108
+ auto status = tsfn.BlockingCall(exit_event, callback); // In main thread
109
+ switch (status) {
110
+ case napi_closing:
111
+ break;
112
+
113
+ case napi_queue_full:
114
+ Napi::Error::Fatal("SetupExitCallback", "Queue was full");
115
+
116
+ case napi_ok:
117
+ if (tsfn.Release() != napi_ok) {
118
+ Napi::Error::Fatal("SetupExitCallback", "ThreadSafeFunction.Release() failed");
119
+ }
120
+ break;
121
+
122
+ default:
123
+ Napi::Error::Fatal("SetupExitCallback", "ThreadSafeFunction.BlockingCall() failed");
124
+ }
125
+ });
73
126
  }
74
127
 
75
- void throwNanError(const Nan::FunctionCallbackInfo<v8::Value>* info, const char* text, const bool getLastError) {
128
+ Napi::Error errorWithCode(const Napi::CallbackInfo& info, const char* text) {
76
129
  std::stringstream errorText;
77
130
  errorText << text;
78
- if (getLastError) {
79
- errorText << ", error code: " << GetLastError();
80
- }
81
- Nan::ThrowError(errorText.str().c_str());
82
- (*info).GetReturnValue().SetUndefined();
131
+ errorText << ", error code: " << GetLastError();
132
+ return Napi::Error::New(info.Env(), errorText.str());
83
133
  }
84
134
 
85
135
  // Returns a new server named pipe. It has not yet been connected.
@@ -111,20 +161,52 @@ bool createDataServerPipe(bool write,
111
161
  return *hServer != INVALID_HANDLE_VALUE;
112
162
  }
113
163
 
114
- HRESULT CreateNamedPipesAndPseudoConsole(COORD size,
164
+ HANDLE LoadConptyDll(const Napi::CallbackInfo& info,
165
+ const bool useConptyDll)
166
+ {
167
+ if (!useConptyDll) {
168
+ return LoadLibraryExW(L"kernel32.dll", 0, 0);
169
+ }
170
+ wchar_t currentDir[MAX_PATH];
171
+ HMODULE hModule = GetModuleHandleA("conpty.node");
172
+ if (hModule == NULL) {
173
+ throw errorWithCode(info, "Failed to get conpty.node module handle");
174
+ }
175
+ DWORD result = GetModuleFileNameW(hModule, currentDir, MAX_PATH);
176
+ if (result == 0) {
177
+ throw errorWithCode(info, "Failed to get conpty.node module file name");
178
+ }
179
+ PathRemoveFileSpecW(currentDir);
180
+ wchar_t conptyDllPath[MAX_PATH];
181
+ PathCombineW(conptyDllPath, currentDir, L"conpty\\conpty.dll");
182
+ if (!path_util::file_exists(conptyDllPath)) {
183
+ std::wstring errorMessage = L"Cannot find conpty.dll at " + std::wstring(conptyDllPath);
184
+ std::string errorMessageStr = path_util::wstring_to_string(errorMessage);
185
+ throw errorWithCode(info, errorMessageStr.c_str());
186
+ }
187
+
188
+ return LoadLibraryW(conptyDllPath);
189
+ }
190
+
191
+ HRESULT CreateNamedPipesAndPseudoConsole(const Napi::CallbackInfo& info,
192
+ COORD size,
115
193
  DWORD dwFlags,
116
194
  HANDLE *phInput,
117
195
  HANDLE *phOutput,
118
196
  HPCON* phPC,
119
197
  std::wstring& inName,
120
198
  std::wstring& outName,
121
- const std::wstring& pipeName)
199
+ const std::wstring& pipeName,
200
+ const bool useConptyDll)
122
201
  {
123
- HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
202
+ HANDLE hLibrary = LoadConptyDll(info, useConptyDll);
203
+ DWORD error = GetLastError();
124
204
  bool fLoadedDll = hLibrary != nullptr;
125
205
  if (fLoadedDll)
126
206
  {
127
- PFNCREATEPSEUDOCONSOLE const pfnCreate = (PFNCREATEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "CreatePseudoConsole");
207
+ PFNCREATEPSEUDOCONSOLE const pfnCreate = (PFNCREATEPSEUDOCONSOLE)GetProcAddress(
208
+ (HMODULE)hLibrary,
209
+ useConptyDll ? "ConptyCreatePseudoConsole" : "CreatePseudoConsole");
128
210
  if (pfnCreate)
129
211
  {
130
212
  if (phPC == NULL || phInput == NULL || phOutput == NULL)
@@ -151,6 +233,8 @@ HRESULT CreateNamedPipesAndPseudoConsole(COORD size,
151
233
  // We should fall back to winpty in this case.
152
234
  return HRESULT_FROM_WIN32(GetLastError());
153
235
  }
236
+ } else {
237
+ throw errorWithCode(info, "Failed to load conpty.dll");
154
238
  }
155
239
 
156
240
  // Failed to find kernel32. This is realy unlikely - honestly no idea how
@@ -158,32 +242,34 @@ HRESULT CreateNamedPipesAndPseudoConsole(COORD size,
158
242
  return HRESULT_FROM_WIN32(GetLastError());
159
243
  }
160
244
 
161
- static NAN_METHOD(PtyStartProcess) {
162
- Nan::HandleScope scope;
245
+ static Napi::Value PtyStartProcess(const Napi::CallbackInfo& info) {
246
+ Napi::Env env(info.Env());
247
+ Napi::HandleScope scope(env);
163
248
 
164
- v8::Local<v8::Object> marshal;
249
+ Napi::Object marshal;
165
250
  std::wstring inName, outName;
166
251
  BOOL fSuccess = FALSE;
167
252
  std::unique_ptr<wchar_t[]> mutableCommandline;
168
253
  PROCESS_INFORMATION _piClient{};
169
254
 
170
- if (info.Length() != 6 ||
171
- !info[0]->IsString() ||
172
- !info[1]->IsNumber() ||
173
- !info[2]->IsNumber() ||
174
- !info[3]->IsBoolean() ||
175
- !info[4]->IsString() ||
176
- !info[5]->IsBoolean()) {
177
- Nan::ThrowError("Usage: pty.startProcess(file, cols, rows, debug, pipeName, inheritCursor)");
178
- return;
255
+ if (info.Length() != 7 ||
256
+ !info[0].IsString() ||
257
+ !info[1].IsNumber() ||
258
+ !info[2].IsNumber() ||
259
+ !info[3].IsBoolean() ||
260
+ !info[4].IsString() ||
261
+ !info[5].IsBoolean() ||
262
+ !info[6].IsBoolean()) {
263
+ throw Napi::Error::New(env, "Usage: pty.startProcess(file, cols, rows, debug, pipeName, inheritCursor, useConptyDll)");
179
264
  }
180
265
 
181
- const std::wstring filename(path_util::to_wstring(Nan::Utf8String(info[0])));
182
- const SHORT cols = static_cast<SHORT>(info[1]->Uint32Value(Nan::GetCurrentContext()).FromJust());
183
- const SHORT rows = static_cast<SHORT>(info[2]->Uint32Value(Nan::GetCurrentContext()).FromJust());
184
- const bool debug = Nan::To<bool>(info[3]).FromJust();
185
- const std::wstring pipeName(path_util::to_wstring(Nan::Utf8String(info[4])));
186
- const bool inheritCursor = Nan::To<bool>(info[5]).FromJust();
266
+ const std::wstring filename(path_util::to_wstring(info[0].As<Napi::String>()));
267
+ const SHORT cols = static_cast<SHORT>(info[1].As<Napi::Number>().Uint32Value());
268
+ const SHORT rows = static_cast<SHORT>(info[2].As<Napi::Number>().Uint32Value());
269
+ const bool debug = info[3].As<Napi::Boolean>().Value();
270
+ const std::wstring pipeName(path_util::to_wstring(info[4].As<Napi::String>()));
271
+ const bool inheritCursor = info[5].As<Napi::Boolean>().Value();
272
+ const bool useConptyDll = info[6].As<Napi::Boolean>().Value();
187
273
 
188
274
  // use environment 'Path' variable to determine location of
189
275
  // the relative path that we have recieved (e.g cmd.exe)
@@ -195,81 +281,50 @@ static NAN_METHOD(PtyStartProcess) {
195
281
  }
196
282
 
197
283
  if (shellpath.empty() || !path_util::file_exists(shellpath)) {
198
- std::wstringstream why;
199
- why << "File not found: " << shellpath;
200
- Nan::ThrowError(path_util::from_wstring(why.str().c_str()));
201
- return;
284
+ std::string why;
285
+ why += "File not found: ";
286
+ why += path_util::wstring_to_string(shellpath);
287
+ throw Napi::Error::New(env, why);
202
288
  }
203
289
 
204
290
  HANDLE hIn, hOut;
205
291
  HPCON hpc;
206
- HRESULT hr = CreateNamedPipesAndPseudoConsole({cols, rows}, inheritCursor ? 1/*PSEUDOCONSOLE_INHERIT_CURSOR*/ : 0, &hIn, &hOut, &hpc, inName, outName, pipeName);
292
+ HRESULT hr = CreateNamedPipesAndPseudoConsole(info, {cols, rows}, inheritCursor ? 1/*PSEUDOCONSOLE_INHERIT_CURSOR*/ : 0, &hIn, &hOut, &hpc, inName, outName, pipeName, useConptyDll);
207
293
 
208
294
  // Restore default handling of ctrl+c
209
295
  SetConsoleCtrlHandler(NULL, FALSE);
210
296
 
211
297
  // Set return values
212
- marshal = Nan::New<v8::Object>();
298
+ marshal = Napi::Object::New(env);
213
299
 
214
300
  if (SUCCEEDED(hr)) {
215
301
  // We were able to instantiate a conpty
216
302
  const int ptyId = InterlockedIncrement(&ptyCounter);
217
- Nan::Set(marshal, Nan::New<v8::String>("pty").ToLocalChecked(), Nan::New<v8::Number>(ptyId));
218
- ptyHandles.insert(ptyHandles.end(), new pty_baton(ptyId, hIn, hOut, hpc));
303
+ marshal.Set("pty", Napi::Number::New(env, ptyId));
304
+ ptyHandles.emplace_back(
305
+ std::make_unique<pty_baton>(ptyId, hIn, hOut, hpc));
219
306
  } else {
220
- Nan::ThrowError("Cannot launch conpty");
221
- return;
307
+ throw Napi::Error::New(env, "Cannot launch conpty");
222
308
  }
223
309
 
224
- std::string inNameStr(path_util::from_wstring(inName.c_str()));
310
+ std::string inNameStr = path_util::wstring_to_string(inName);
225
311
  if (inNameStr.empty()) {
226
- Nan::ThrowError("Failed to initialize conpty conin");
227
- return;
312
+ throw Napi::Error::New(env, "Failed to initialize conpty conin");
228
313
  }
229
- std::string outNameStr(path_util::from_wstring(outName.c_str()));
314
+ std::string outNameStr = path_util::wstring_to_string(outName);
230
315
  if (outNameStr.empty()) {
231
- Nan::ThrowError("Failed to initialize conpty conout");
232
- return;
316
+ throw Napi::Error::New(env, "Failed to initialize conpty conout");
233
317
  }
234
318
 
235
- Nan::Set(marshal, Nan::New<v8::String>("fd").ToLocalChecked(), Nan::New<v8::Number>(-1));
236
- Nan::Set(marshal, Nan::New<v8::String>("conin").ToLocalChecked(), Nan::New<v8::String>(inNameStr).ToLocalChecked());
237
- Nan::Set(marshal, Nan::New<v8::String>("conout").ToLocalChecked(), Nan::New<v8::String>(outNameStr).ToLocalChecked());
238
- info.GetReturnValue().Set(marshal);
239
- }
240
-
241
- VOID CALLBACK OnProcessExitWinEvent(
242
- _In_ PVOID context,
243
- _In_ BOOLEAN TimerOrWaitFired) {
244
- pty_baton *baton = static_cast<pty_baton*>(context);
245
-
246
- // Fire OnProcessExit
247
- uv_async_send(&baton->async);
319
+ marshal.Set("fd", Napi::Number::New(env, -1));
320
+ marshal.Set("conin", Napi::String::New(env, inNameStr));
321
+ marshal.Set("conout", Napi::String::New(env, outNameStr));
322
+ return marshal;
248
323
  }
249
324
 
250
- static void OnProcessExit(uv_async_t *async) {
251
- Nan::HandleScope scope;
252
- pty_baton *baton = static_cast<pty_baton*>(async->data);
253
-
254
- UnregisterWait(baton->hWait);
255
-
256
- // Get exit code
257
- DWORD exitCode = 0;
258
- GetExitCodeProcess(baton->hShell, &exitCode);
259
-
260
- // Call function
261
- v8::Local<v8::Value> args[1] = {
262
- Nan::New<v8::Number>(exitCode)
263
- };
264
-
265
- Nan::AsyncResource asyncResource("node-pty.callback");
266
- baton->cb.Call(1, args, &asyncResource);
267
- // Clean up
268
- baton->cb.Reset();
269
- }
270
-
271
- static NAN_METHOD(PtyConnect) {
272
- Nan::HandleScope scope;
325
+ static Napi::Value PtyConnect(const Napi::CallbackInfo& info) {
326
+ Napi::Env env(info.Env());
327
+ Napi::HandleScope scope(env);
273
328
 
274
329
  // If we're working with conpty's we need to call ConnectNamedPipe here AFTER
275
330
  // the Socket has attempted to connect to the other end, then actually
@@ -278,27 +333,27 @@ static NAN_METHOD(PtyConnect) {
278
333
  std::stringstream errorText;
279
334
  BOOL fSuccess = FALSE;
280
335
 
281
- if (info.Length() != 5 ||
282
- !info[0]->IsNumber() ||
283
- !info[1]->IsString() ||
284
- !info[2]->IsString() ||
285
- !info[3]->IsArray() ||
286
- !info[4]->IsFunction()) {
287
- Nan::ThrowError("Usage: pty.connect(id, cmdline, cwd, env, exitCallback)");
288
- return;
336
+ if (info.Length() != 6 ||
337
+ !info[0].IsNumber() ||
338
+ !info[1].IsString() ||
339
+ !info[2].IsString() ||
340
+ !info[3].IsArray() ||
341
+ !info[4].IsBoolean() ||
342
+ !info[5].IsFunction()) {
343
+ throw Napi::Error::New(env, "Usage: pty.connect(id, cmdline, cwd, env, useConptyDll, exitCallback)");
289
344
  }
290
345
 
291
- const int id = info[0]->Int32Value(Nan::GetCurrentContext()).FromJust();
292
- const std::wstring cmdline(path_util::to_wstring(Nan::Utf8String(info[1])));
293
- const std::wstring cwd(path_util::to_wstring(Nan::Utf8String(info[2])));
294
- const v8::Local<v8::Array> envValues = info[3].As<v8::Array>();
295
- const v8::Local<v8::Function> exitCallback = v8::Local<v8::Function>::Cast(info[4]);
346
+ const int id = info[0].As<Napi::Number>().Int32Value();
347
+ const std::wstring cmdline(path_util::to_wstring(info[1].As<Napi::String>()));
348
+ const std::wstring cwd(path_util::to_wstring(info[2].As<Napi::String>()));
349
+ const Napi::Array envValues = info[3].As<Napi::Array>();
350
+ const bool useConptyDll = info[4].As<Napi::Boolean>().Value();
351
+ Napi::Function exitCallback = info[5].As<Napi::Function>();
296
352
 
297
353
  // Fetch pty handle from ID and start process
298
354
  pty_baton* handle = get_pty_baton(id);
299
355
  if (!handle) {
300
- Nan::ThrowError("Invalid pty handle");
301
- return;
356
+ throw Napi::Error::New(env, "Invalid pty handle");
302
357
  }
303
358
 
304
359
  // Prepare command line
@@ -310,17 +365,17 @@ static NAN_METHOD(PtyConnect) {
310
365
  hr = StringCchCopyW(mutableCwd.get(), cwd.length() + 1, cwd.c_str());
311
366
 
312
367
  // Prepare environment
313
- std::wstring env;
368
+ std::wstring envStr;
314
369
  if (!envValues.IsEmpty()) {
315
- std::wstringstream envBlock;
316
- for(uint32_t i = 0; i < envValues->Length(); i++) {
317
- std::wstring envValue(path_util::to_wstring(Nan::Utf8String(Nan::Get(envValues, i).ToLocalChecked())));
318
- envBlock << envValue << L'\0';
370
+ std::wstring envBlock;
371
+ for(uint32_t i = 0; i < envValues.Length(); i++) {
372
+ envBlock += path_util::to_wstring(envValues.Get(i).As<Napi::String>());
373
+ envBlock += L'\0';
319
374
  }
320
- envBlock << L'\0';
321
- env = envBlock.str();
375
+ envBlock += L'\0';
376
+ envStr = std::move(envBlock);
322
377
  }
323
- auto envV = vectorFromString(env);
378
+ std::vector<wchar_t> envV(envStr.cbegin(), envStr.cend());
324
379
  LPWSTR envArg = envV.empty() ? nullptr : envV.data();
325
380
 
326
381
  ConnectNamedPipe(handle->hIn, nullptr);
@@ -341,7 +396,7 @@ static NAN_METHOD(PtyConnect) {
341
396
 
342
397
  fSuccess = InitializeProcThreadAttributeList(siEx.lpAttributeList, 1, 0, &size);
343
398
  if (!fSuccess) {
344
- return throwNanError(&info, "InitializeProcThreadAttributeList failed", true);
399
+ throw errorWithCode(info, "InitializeProcThreadAttributeList failed");
345
400
  }
346
401
  fSuccess = UpdateProcThreadAttribute(siEx.lpAttributeList,
347
402
  0,
@@ -351,7 +406,7 @@ static NAN_METHOD(PtyConnect) {
351
406
  NULL,
352
407
  NULL);
353
408
  if (!fSuccess) {
354
- return throwNanError(&info, "UpdateProcThreadAttribute failed", true);
409
+ throw errorWithCode(info, "UpdateProcThreadAttribute failed");
355
410
  }
356
411
 
357
412
  PROCESS_INFORMATION piClient{};
@@ -368,49 +423,65 @@ static NAN_METHOD(PtyConnect) {
368
423
  &piClient // lpProcessInformation
369
424
  );
370
425
  if (!fSuccess) {
371
- return throwNanError(&info, "Cannot create process", true);
426
+ throw errorWithCode(info, "Cannot create process");
427
+ }
428
+
429
+ HANDLE hLibrary = LoadConptyDll(info, useConptyDll);
430
+ bool fLoadedDll = hLibrary != nullptr;
431
+ if (fLoadedDll)
432
+ {
433
+ PFNRELEASEPSEUDOCONSOLE const pfnReleasePseudoConsole = (PFNRELEASEPSEUDOCONSOLE)GetProcAddress(
434
+ (HMODULE)hLibrary, "ConptyReleasePseudoConsole");
435
+ if (pfnReleasePseudoConsole)
436
+ {
437
+ pfnReleasePseudoConsole(handle->hpc);
438
+ }
372
439
  }
373
440
 
374
441
  // Update handle
375
442
  handle->hShell = piClient.hProcess;
376
- handle->cb.Reset(exitCallback);
377
- handle->async.data = handle;
378
443
 
379
- // Setup OnProcessExit callback
380
- uv_async_init(uv_default_loop(), &handle->async, OnProcessExit);
444
+ // Close the thread handle to avoid resource leak
445
+ CloseHandle(piClient.hThread);
446
+ // Close the input read and output write handle of the pseudoconsole
447
+ CloseHandle(handle->hIn);
448
+ CloseHandle(handle->hOut);
381
449
 
382
- // Setup Windows wait for process exit event
383
- RegisterWaitForSingleObject(&handle->hWait, piClient.hProcess, OnProcessExitWinEvent, (PVOID)handle, INFINITE, WT_EXECUTEONLYONCE);
450
+ SetupExitCallback(env, exitCallback, handle);
384
451
 
385
452
  // Return
386
- v8::Local<v8::Object> marshal = Nan::New<v8::Object>();
387
- Nan::Set(marshal, Nan::New<v8::String>("pid").ToLocalChecked(), Nan::New<v8::Number>(piClient.dwProcessId));
388
- info.GetReturnValue().Set(marshal);
453
+ auto marshal = Napi::Object::New(env);
454
+ marshal.Set("pid", Napi::Number::New(env, piClient.dwProcessId));
455
+ return marshal;
389
456
  }
390
457
 
391
- static NAN_METHOD(PtyResize) {
392
- Nan::HandleScope scope;
458
+ static Napi::Value PtyResize(const Napi::CallbackInfo& info) {
459
+ Napi::Env env(info.Env());
460
+ Napi::HandleScope scope(env);
393
461
 
394
- if (info.Length() != 3 ||
395
- !info[0]->IsNumber() ||
396
- !info[1]->IsNumber() ||
397
- !info[2]->IsNumber()) {
398
- Nan::ThrowError("Usage: pty.resize(id, cols, rows)");
399
- return;
462
+ if (info.Length() != 4 ||
463
+ !info[0].IsNumber() ||
464
+ !info[1].IsNumber() ||
465
+ !info[2].IsNumber() ||
466
+ !info[3].IsBoolean()) {
467
+ throw Napi::Error::New(env, "Usage: pty.resize(id, cols, rows, useConptyDll)");
400
468
  }
401
469
 
402
- int id = info[0]->Int32Value(Nan::GetCurrentContext()).FromJust();
403
- SHORT cols = static_cast<SHORT>(info[1]->Uint32Value(Nan::GetCurrentContext()).FromJust());
404
- SHORT rows = static_cast<SHORT>(info[2]->Uint32Value(Nan::GetCurrentContext()).FromJust());
470
+ int id = info[0].As<Napi::Number>().Int32Value();
471
+ SHORT cols = static_cast<SHORT>(info[1].As<Napi::Number>().Uint32Value());
472
+ SHORT rows = static_cast<SHORT>(info[2].As<Napi::Number>().Uint32Value());
473
+ const bool useConptyDll = info[3].As<Napi::Boolean>().Value();
405
474
 
406
475
  const pty_baton* handle = get_pty_baton(id);
407
476
 
408
477
  if (handle != nullptr) {
409
- HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
478
+ HANDLE hLibrary = LoadConptyDll(info, useConptyDll);
410
479
  bool fLoadedDll = hLibrary != nullptr;
411
480
  if (fLoadedDll)
412
481
  {
413
- PFNRESIZEPSEUDOCONSOLE const pfnResizePseudoConsole = (PFNRESIZEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ResizePseudoConsole");
482
+ PFNRESIZEPSEUDOCONSOLE const pfnResizePseudoConsole = (PFNRESIZEPSEUDOCONSOLE)GetProcAddress(
483
+ (HMODULE)hLibrary,
484
+ useConptyDll ? "ConptyResizePseudoConsole" : "ResizePseudoConsole");
414
485
  if (pfnResizePseudoConsole)
415
486
  {
416
487
  COORD size = {cols, rows};
@@ -419,28 +490,37 @@ static NAN_METHOD(PtyResize) {
419
490
  }
420
491
  }
421
492
 
422
- return info.GetReturnValue().SetUndefined();
493
+ return env.Undefined();
423
494
  }
424
495
 
425
- static NAN_METHOD(PtyClear) {
426
- Nan::HandleScope scope;
496
+ static Napi::Value PtyClear(const Napi::CallbackInfo& info) {
497
+ Napi::Env env(info.Env());
498
+ Napi::HandleScope scope(env);
427
499
 
428
- if (info.Length() != 1 ||
429
- !info[0]->IsNumber()) {
430
- Nan::ThrowError("Usage: pty.clear(id)");
431
- return;
500
+ if (info.Length() != 2 ||
501
+ !info[0].IsNumber() ||
502
+ !info[1].IsBoolean()) {
503
+ throw Napi::Error::New(env, "Usage: pty.clear(id, useConptyDll)");
432
504
  }
433
505
 
434
- int id = info[0]->Int32Value(Nan::GetCurrentContext()).FromJust();
506
+ int id = info[0].As<Napi::Number>().Int32Value();
507
+ const bool useConptyDll = info[1].As<Napi::Boolean>().Value();
508
+
509
+ // This API is only supported for conpty.dll as it was introduced in a later version of Windows.
510
+ // We could hook it up to point at >= a version of Windows only, but the future is conpty.dll
511
+ // anyway.
512
+ if (!useConptyDll) {
513
+ return env.Undefined();
514
+ }
435
515
 
436
516
  const pty_baton* handle = get_pty_baton(id);
437
517
 
438
518
  if (handle != nullptr) {
439
- HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
519
+ HANDLE hLibrary = LoadConptyDll(info, useConptyDll);
440
520
  bool fLoadedDll = hLibrary != nullptr;
441
521
  if (fLoadedDll)
442
522
  {
443
- PFNCLEARPSEUDOCONSOLE const pfnClearPseudoConsole = (PFNCLEARPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ClearPseudoConsole");
523
+ PFNCLEARPSEUDOCONSOLE const pfnClearPseudoConsole = (PFNCLEARPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ConptyClearPseudoConsole");
444
524
  if (pfnClearPseudoConsole)
445
525
  {
446
526
  pfnClearPseudoConsole(handle->hpc);
@@ -448,55 +528,54 @@ static NAN_METHOD(PtyClear) {
448
528
  }
449
529
  }
450
530
 
451
- return info.GetReturnValue().SetUndefined();
531
+ return env.Undefined();
452
532
  }
453
533
 
454
- static NAN_METHOD(PtyKill) {
455
- Nan::HandleScope scope;
534
+ static Napi::Value PtyKill(const Napi::CallbackInfo& info) {
535
+ Napi::Env env(info.Env());
536
+ Napi::HandleScope scope(env);
456
537
 
457
- if (info.Length() != 1 ||
458
- !info[0]->IsNumber()) {
459
- Nan::ThrowError("Usage: pty.kill(id)");
460
- return;
538
+ if (info.Length() != 2 ||
539
+ !info[0].IsNumber() ||
540
+ !info[1].IsBoolean()) {
541
+ throw Napi::Error::New(env, "Usage: pty.kill(id, useConptyDll)");
461
542
  }
462
543
 
463
- int id = info[0]->Int32Value(Nan::GetCurrentContext()).FromJust();
544
+ int id = info[0].As<Napi::Number>().Int32Value();
545
+ const bool useConptyDll = info[1].As<Napi::Boolean>().Value();
464
546
 
465
547
  const pty_baton* handle = get_pty_baton(id);
466
548
 
467
549
  if (handle != nullptr) {
468
- HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
550
+ HANDLE hLibrary = LoadConptyDll(info, useConptyDll);
469
551
  bool fLoadedDll = hLibrary != nullptr;
470
552
  if (fLoadedDll)
471
553
  {
472
- PFNCLOSEPSEUDOCONSOLE const pfnClosePseudoConsole = (PFNCLOSEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ClosePseudoConsole");
554
+ PFNCLOSEPSEUDOCONSOLE const pfnClosePseudoConsole = (PFNCLOSEPSEUDOCONSOLE)GetProcAddress(
555
+ (HMODULE)hLibrary,
556
+ useConptyDll ? "ConptyClosePseudoConsole" : "ClosePseudoConsole");
473
557
  if (pfnClosePseudoConsole)
474
558
  {
475
559
  pfnClosePseudoConsole(handle->hpc);
476
560
  }
477
561
  }
478
-
479
- DisconnectNamedPipe(handle->hIn);
480
- DisconnectNamedPipe(handle->hOut);
481
- CloseHandle(handle->hIn);
482
- CloseHandle(handle->hOut);
483
- CloseHandle(handle->hShell);
562
+ TerminateProcess(handle->hShell, 1);
484
563
  }
485
564
 
486
- return info.GetReturnValue().SetUndefined();
565
+ return env.Undefined();
487
566
  }
488
567
 
489
568
  /**
490
569
  * Init
491
570
  */
492
571
 
493
- extern "C" void init(v8::Local<v8::Object> target) {
494
- Nan::HandleScope scope;
495
- Nan::SetMethod(target, "startProcess", PtyStartProcess);
496
- Nan::SetMethod(target, "connect", PtyConnect);
497
- Nan::SetMethod(target, "resize", PtyResize);
498
- Nan::SetMethod(target, "clear", PtyClear);
499
- Nan::SetMethod(target, "kill", PtyKill);
572
+ Napi::Object init(Napi::Env env, Napi::Object exports) {
573
+ exports.Set("startProcess", Napi::Function::New(env, PtyStartProcess));
574
+ exports.Set("connect", Napi::Function::New(env, PtyConnect));
575
+ exports.Set("resize", Napi::Function::New(env, PtyResize));
576
+ exports.Set("clear", Napi::Function::New(env, PtyClear));
577
+ exports.Set("kill", Napi::Function::New(env, PtyKill));
578
+ return exports;
500
579
  };
501
580
 
502
- NODE_MODULE(pty, init);
581
+ NODE_API_MODULE(NODE_GYP_MODULE_NAME, init);