[
  {
    "path": ".config/tsaoptions.json",
    "content": "{\n  \"codebaseName\": \"devdiv_microsoft_vscode_node_pty\",\n  \"instanceUrl\": \"https://devdiv.visualstudio.com/defaultcollection\",\n  \"projectName\": \"DevDiv\",\n  \"areaPath\": \"DevDiv\\\\VS Code (compliance tracking only)\\\\Visual Studio Code NPM Packages\",\n  \"notificationAliases\": [\n    \"stbatt@microsoft.com\",\n    \"lszomoru@microsoft.com\"\n  ]\n}\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\nend_of_line = lf\n\n[*.ts]\nmax_line_length = 100\n"
  },
  {
    "path": ".gitattributes",
    "content": "* text=auto\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "## Environment details\n\n- OS:\n- OS version:\n- node-pty version:\n\n## Issue description\n\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  pull_request:\n\njobs:\n  lint:\n    name: Lint\n    runs-on: ubuntu-slim\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          persist-credentials: false\n\n      - name: Use Node.js 22.x\n        uses: actions/setup-node@v4\n        with:\n          node-version: '22.x'\n\n      - name: Install dependencies (skip build)\n        run: npm ci --ignore-scripts\n\n      - name: Lint\n        run: npm run lint\n\n  build-test:\n    name: Build & Test (${{ matrix.os }})\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-22.04, macos-14, windows-2022, macos-15-intel, ubuntu-22.04-arm, windows-11-arm]\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          persist-credentials: false\n\n      - name: Use Node.js 22.x\n        uses: actions/setup-node@v4\n        with:\n          node-version: '22.x'\n\n      - name: Set architecture\n        id: arch\n        shell: bash\n        run: |\n          if [ \"${{ runner.arch }}\" = \"ARM64\" ]; then\n            echo \"arch=arm64\" >> $GITHUB_OUTPUT\n          else\n            echo \"arch=x64\" >> $GITHUB_OUTPUT\n          fi\n\n      - name: Install sysroot\n        if: runner.os == 'Linux'\n        run: |\n          set -eo pipefail\n          sudo apt-get update -qq\n          sudo apt-get install -y gcc-10 g++-10\n          echo \"CC=gcc-10\" >> $GITHUB_ENV\n          echo \"CXX=g++-10\" >> $GITHUB_ENV\n          SYSROOT_PATH=$(node scripts/linux/install-sysroot.js ${{ steps.arch.outputs.arch }} | grep \"SYSROOT_PATH=\" | cut -d= -f2)\n          echo \"SYSROOT_PATH=$SYSROOT_PATH\" >> $GITHUB_ENV\n          echo \"Sysroot path set to: $SYSROOT_PATH\"\n        env:\n          # Set github token for authenticated sysroot download\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Install dependencies and build\n        run: npm ci\n        env:\n          ARCH: ${{ steps.arch.outputs.arch }}\n          npm_config_arch: ${{ steps.arch.outputs.arch }}\n\n      - name: Verify GLIBC requirements\n        if: runner.os == 'Linux'\n        run: |\n          EXPECTED_GLIBC_VERSION=\"2.28\" \\\n          EXPECTED_GLIBCXX_VERSION=\"3.4.25\" \\\n          SEARCH_PATH=\"build\" \\\n          ./scripts/linux/verify-glibc-requirements.sh\n\n      - name: Test\n        run: npm test\n"
  },
  {
    "path": ".gitignore",
    "content": "build/\n.lock-wscript\nout/\nMakefile.gyp\n*.Makefile\n*.target.gyp.mk\nnode_modules/\nbuilderror.log\nlib/\nnpm-debug.log\nfixtures/space folder/\n.vscode/settings.json\n.vscode/ipch/\nyarn.lock\nprebuilds/\n_codeql_detected_source_root\n"
  },
  {
    "path": ".vscode/launch.json",
    "content": "{\n  // Use IntelliSense to learn about possible attributes.\n  // Hover to view descriptions of existing attributes.\n  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"type\": \"node\",\n      \"request\": \"launch\",\n      \"name\": \"Unit Tests\",\n      \"cwd\": \"${workspaceRoot}\",\n      \"runtimeExecutable\": \"${workspaceRoot}/node_modules/.bin/mocha\",\n      \"windows\": {\n        \"runtimeExecutable\": \"${workspaceRoot}/node_modules/.bin/mocha.cmd\"\n      },\n      \"runtimeArgs\": [\n        \"--colors\",\n        \"--recursive\",\n        \"${workspaceRoot}/lib/**/*.test.js\"\n      ],\n      \"env\": {\n        \"NODE_PATH\": \"${workspaceRoot}/lib\"\n      },\n      \"sourceMaps\": true,\n      \"outFiles\": [\n        \"${workspaceRoot}/lib/**/*.js\"\n      ],\n      \"internalConsoleOptions\": \"openOnSessionStart\"\n    },\n    {\n      \"type\": \"node\",\n      \"request\": \"attach\",\n      \"name\": \"Attach to process ID\",\n      \"processId\": \"${command:PickProcess}\"\n    }\n  ]\n}\n"
  },
  {
    "path": ".vscode/tasks.json",
    "content": "{\n  \"version\": \"2.0.0\",\n  \"presentation\": {\n    \"echo\": false,\n    \"reveal\": \"always\",\n    \"focus\": false,\n    \"panel\": \"dedicated\",\n    \"showReuseMessage\": true\n  },\n  \"tasks\": [\n    {\n      \"label\": \"tsc\",\n      \"type\": \"npm\",\n      \"script\": \"watch\",\n      \"isBackground\": true,\n      \"problemMatcher\": \"$tsc-watch\",\n      \"group\": {\n        \"kind\": \"build\",\n        \"isDefault\": true\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "## Testing in a real terminal\n\nThe recommended way to test node-pty during development is via the electron example:\n\n```sh\ncd examples/electron\nnpm ci\nnpm start\n```\n\nAlternatively, clone the xterm.js repository and link's node-pty module to this directory.\n\n1. Clone xterm.js in a separate folder:\n    ```sh\n    git clone https://github.com/xtermjs/xterm.js\n    npm ci\n    npm run setup\n    ```\n2. Link the node-pty repo:\n    ```\n    rm -rf node_modules/node-pty # in xterm.js folder\n    ln -s <path_to_node-pty> <path to xtermjs>/node_modules/node-pty\n    ```\n3. Hit ctrl/cmd+shift+b in VS Code or run the build/demo scripts manually:\n    ```sh\n    npm run tsc-watch # build ts\n    npm run esbuild-watch # bundle ts/js\n    npm run esbuild-demo # build demo/server\n    npm run start # run server\n    ```\n4. Open http://127.0.0.1:3000 and test\n5. Kill and restart the `npm run start` command to apply any changes made in node-pty\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2012-2015, Christopher Jeffrey (https://github.com/chjj/)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n\n\nThe MIT License (MIT)\n\nCopyright (c) 2016, Daniel Imms (http://www.growingwiththeweb.com)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n\n\nMIT License\n\nCopyright (c) 2018 - present Microsoft Corporation\n\nAll rights reserved.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# node-pty\n\n[![Build Status](https://dev.azure.com/vscode/node-pty/_apis/build/status/Microsoft.node-pty?branchName=main)](https://dev.azure.com/vscode/node-pty/_build/latest?definitionId=11&branchName=main)\n\n`forkpty(3)` bindings for node.js. This allows you to fork processes with pseudoterminal file descriptors. It returns a terminal object which allows reads and writes.\n\nThis is useful for:\n\n- Writing a terminal emulator (eg. via [xterm.js](https://github.com/sourcelair/xterm.js)).\n- Getting certain programs to *think* you're a terminal, such as when you need a program to send you control sequences.\n\n`node-pty` supports Linux, macOS and Windows. Windows support is possible by utilizing the [Windows conpty API](https://blogs.msdn.microsoft.com/commandline/2018/08/02/windows-command-line-introducing-the-windows-pseudo-console-conpty/) on Windows 1809+.\n\n> **Note:** Support for the `winpty` library has been removed. Windows 10 version 1809 (build 18309) or later is now required.\n\n## API\n\nThe full API for node-pty is contained within the [TypeScript declaration file](https://github.com/microsoft/node-pty/blob/main/typings/node-pty.d.ts), use the branch/tag picker in GitHub (`w`) to navigate to the correct version of the API.\n\n## Example Usage\n\n```js\nimport * as os from 'node:os';\nimport * as pty from 'node-pty';\n\nconst shell = os.platform() === 'win32' ? 'powershell.exe' : 'bash';\n\nconst ptyProcess = pty.spawn(shell, [], {\n  name: 'xterm-color',\n  cols: 80,\n  rows: 30,\n  cwd: process.env.HOME,\n  env: process.env\n});\n\nptyProcess.onData((data) => {\n  process.stdout.write(data);\n});\n\nptyProcess.write('ls\\r');\nptyProcess.resize(100, 40);\nptyProcess.write('ls\\r');\n```\n\n## Real-world Uses\n\n`node-pty` powers many different terminal emulators, including:\n\n- [Microsoft Visual Studio Code](https://code.visualstudio.com)\n- [Hyper](https://hyper.is/)\n- [Upterm](https://github.com/railsware/upterm)\n- [Script Runner](https://github.com/ioquatix/script-runner) for Atom.\n- [Theia](https://github.com/theia-ide/theia)\n- [FreeMAN](https://github.com/matthew-matvei/freeman) file manager\n- [terminus](https://atom.io/packages/terminus) - An Atom plugin for providing terminals inside your Atom workspace.\n- [x-terminal](https://atom.io/packages/x-terminal) - Also an Atom plugin that provides terminals inside your Atom workspace.\n- [Termination](https://atom.io/packages/termination) - Also an Atom plugin that provides terminals inside your Atom workspace.\n- [atom-xterm](https://atom.io/packages/atom-xterm) - Also an Atom plugin that provides terminals inside your Atom workspace.\n- [electerm](https://github.com/electerm/electerm) Terminal/SSH/SFTP client(Linux, macOS, Windows).\n- [Extraterm](http://extraterm.org/)\n- [Wetty](https://github.com/krishnasrinivas/wetty) Browser based Terminal over HTTP and HTTPS\n- [nomad](https://github.com/lukebarnard1/nomad-term)\n- [DockerStacks](https://github.com/sfx101/docker-stacks) Local LAMP/LEMP stack using Docker\n- [TeleType](https://github.com/akshaykmr/TeleType): cli tool that allows you to share your terminal online conveniently. Show off mad cli-fu, help a colleague, teach, or troubleshoot.\n- [mesos-term](https://github.com/criteo/mesos-term): A web terminal for Apache Mesos. It allows to execute commands within containers.\n- [Commas](https://github.com/CyanSalt/commas): A hackable terminal and command runner.\n- [ENiGMA½ BBS Software](https://github.com/NuSkooler/enigma-bbs): A modern BBS software with a nostalgic flair!\n- [Tinkerun](https://github.com/tinkerun/tinkerun): A new way of running Tinker.\n- [Tess](https://tessapp.dev): Hackable, simple and rapid terminal for the new era of technology 👍\n- [NxShell](https://nxshell.github.io/): An easy to use new terminal for Windows/Linux/MacOS platform.\n- [OpenSumi](https://github.com/opensumi/core): A framework helps you quickly build Cloud or Desktop IDE products.\n- [Enjoy Git](https://github.com/huangcs427/enjoy-git-release): A modern Git client featuring an intuitive user interface, built with Electron, Vue 3, and TypeScript.\n- [Logos](https://github.com/zixiao-labs/logos): A Modern, Lightweight Code Editor, built with Electron, Vue 3, and TypeScript.\n\nDo you use node-pty in your application as well? Please open a [Pull Request](https://github.com/Tyriar/node-pty/pulls) to include it here. We would love to have it in our list.\n\n## Building\n\n```bash\n# Install dependencies and build C++\nnpm install\n# Compile TypeScript -> JavaScript\nnpm run build\n```\n\n## Dependencies\n\nNode.JS 16 or Electron 19 is required to use `node-pty`. What version of node is supported is currently mostly bound to [whatever version Visual Studio Code is using](https://github.com/microsoft/node-pty/issues/557#issuecomment-1332193541).\n\n### Linux (apt)\n\n```sh\nsudo apt install -y make python build-essential\n```\n\n### macOS\n\nXcode is needed to compile the sources, this can be installed from the App Store.\n\n### Windows\n\n`npm install` requires some tools to be present in the system like Python and C++ compiler. Windows users can easily install them by running the following command in PowerShell as administrator. For more information see https://github.com/felixrieseberg/windows-build-tools:\n\n```sh\nnpm install --global --production windows-build-tools\n```\n\nThe following are also needed:\n\n- [Windows SDK](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk) - only the \"Desktop C++ Apps\" components are needed to be installed\n- Spectre-mitigated libraries - In order to avoid the build error \"MSB8040: Spectre-mitigated libraries are required for this project\", open the Visual Studio Installer, press the Modify button, navigate to the \"Individual components\" tab, search \"Spectre\", and install an option like \"MSVC v143 - VS 2022 C++ x64/x86 Spectre-mitigated libs (Latest)\" (the exact option to install will depend on your version of Visual Studio as well as your operating system architecture)\n\n## Debugging\n\n[The wiki](https://github.com/Microsoft/node-pty/wiki/Debugging) contains instructions for debugging node-pty.\n\n## Security\n\nAll processes launched from node-pty will launch at the same permission level of the parent process. Take care particularly when using node-pty inside a server that's accessible on the internet. We recommend launching the pty inside a container to protect your host machine.\n\n## Thread Safety\n\nNote that node-pty is not thread safe so running it across multiple worker threads in node.js could cause issues.\n\n## Flow Control\n\nAutomatic flow control can be enabled by either providing `handleFlowControl = true` in the constructor options or setting it later on:\n\n```js\nconst PAUSE = '\\x13';   // XOFF\nconst RESUME = '\\x11';  // XON\n\nconst ptyProcess = pty.spawn(shell, [], {handleFlowControl: true});\n\n// flow control in action\nptyProcess.write(PAUSE);  // pty will block and pause the child program\n...\nptyProcess.write(RESUME); // pty will enter flow mode and resume the child program\n\n// temporarily disable/re-enable flow control\nptyProcess.handleFlowControl = false;\n...\nptyProcess.handleFlowControl = true;\n```\n\nBy default `PAUSE` and `RESUME` are XON/XOFF control codes (as shown above). To avoid conflicts in environments that use these control codes for different purposes the messages can be customized as `flowControlPause: string` and `flowControlResume: string` in the constructor options. `PAUSE` and `RESUME` are not passed to the underlying pseudoterminal if flow control is enabled.\n\n## Troubleshooting\n\n### Powershell gives error 8009001d\n\n> Internal Windows PowerShell error.  Loading managed Windows PowerShell failed with error 8009001d.\n\nThis happens when PowerShell is launched with no `SystemRoot` environment variable present.\n\n## pty.js\n\nThis project is forked from [chjj/pty.js](https://github.com/chjj/pty.js) with the primary goals being to provide better support for later Node.js versions and Windows.\n\n## License\n\nCopyright (c) 2012-2015, Christopher Jeffrey (MIT License).<br>\nCopyright (c) 2016, Daniel Imms (MIT License).<br>\nCopyright (c) 2018, Microsoft Corporation (MIT License).\n"
  },
  {
    "path": "SECURITY.md",
    "content": "<!-- BEGIN MICROSOFT SECURITY.MD V0.0.7 BLOCK -->\n\n## Security\n\nMicrosoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).\n\nIf you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.\n\n## Reporting Security Issues\n\n**Please do not report security vulnerabilities through public GitHub issues.**\n\nInstead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).\n\nIf you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com).  If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).\n\nYou should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). \n\nPlease include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:\n\n  * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)\n  * Full paths of source file(s) related to the manifestation of the issue\n  * The location of the affected source code (tag/branch/commit or direct URL)\n  * Any special configuration required to reproduce the issue\n  * Step-by-step instructions to reproduce the issue\n  * Proof-of-concept or exploit code (if possible)\n  * Impact of the issue, including how an attacker might exploit the issue\n\nThis information will help us triage your report more quickly.\n\nIf you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.\n\n## Preferred Languages\n\nWe prefer all communications to be in English.\n\n## Policy\n\nMicrosoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).\n\n<!-- END MICROSOFT SECURITY.MD BLOCK -->\n"
  },
  {
    "path": "binding.gyp",
    "content": "{\n  'target_defaults': {\n    'dependencies': [\n      \"<!(node -p \\\"require('node-addon-api').targets\\\"):node_addon_api_except\",\n    ],\n    'conditions': [\n      ['OS==\"win\"', {\n        'msvs_configuration_attributes': {\n          'SpectreMitigation': 'Spectre'\n        },\n        'msvs_settings': {\n          'VCCLCompilerTool': {\n            'AdditionalOptions': [\n              '/guard:cf',\n              '/sdl',\n              '/W3',\n              '/we4146',\n              '/we4244',\n              '/we4267',\n              '/ZH:SHA_256'\n            ]\n          },\n          'VCLinkerTool': {\n            'AdditionalOptions': [\n              '/DYNAMICBASE',\n              '/guard:cf'\n            ]\n          }\n        },\n      }, {\n        'cflags': ['-O2', '-fstack-protector-strong'],\n      }],\n    ],\n  },\n  'conditions': [\n    ['OS==\"win\"', {\n      'targets': [\n        {\n          'target_name': 'conpty',\n          'sources' : [\n            'src/win/conpty.cc',\n            'src/win/path_util.cc'\n          ],\n          'libraries': [\n            '-lshlwapi'\n          ],\n        },\n        {\n          'target_name': 'conpty_console_list',\n          'sources' : [\n            'src/win/conpty_console_list.cc'\n          ],\n        }\n      ]\n    }, { # OS!=\"win\"\n      'targets': [\n        {\n          'target_name': 'pty',\n          'sources': [\n            'src/unix/pty.cc',\n          ],\n          'libraries': [\n            '-lutil'\n          ],\n          'cflags': ['-Wall'],\n          'ldflags': [],\n          'conditions': [\n            # http://www.gnu.org/software/gnulib/manual/html_node/forkpty.html\n            #   One some systems (at least including Cygwin, Interix,\n            #   OSF/1 4 and 5, and Mac OS X) linking with -lutil is not required.\n            ['OS==\"mac\" or OS==\"solaris\"', {\n              'libraries!': [\n                '-lutil'\n              ]\n            }],\n            ['OS==\"linux\"', {\n              'variables': {\n                'sysroot%': '<!(node -p \"process.env.SYSROOT_PATH || \\'\\'\")',\n                'target_arch%': '<!(node -p \"process.env.npm_config_arch || process.arch\")',\n              },\n              'conditions': [\n                ['sysroot!=\"\"', {\n                  'variables': {\n                    'gcc_include%': '<!(${CXX:-g++} -print-file-name=include)',\n                  },\n                  'conditions': [\n                    ['target_arch==\"x64\"', {\n                      'cflags': [\n                        '--sysroot=<(sysroot)',\n                        '-nostdinc',\n                        '-isystem<(gcc_include)',\n                        '-isystem<(sysroot)/usr/include',\n                        '-isystem<(sysroot)/usr/include/x86_64-linux-gnu'\n                      ],\n                      'cflags_cc': [\n                        '-nostdinc++',\n                        '-isystem<(sysroot)/../include/c++/10.5.0',\n                        '-isystem<(sysroot)/../include/c++/10.5.0/x86_64-linux-gnu',\n                        '-isystem<(sysroot)/../include/c++/10.5.0/backward'\n                      ],\n                      'ldflags': [\n                        '--sysroot=<(sysroot)',\n                        '-L<(sysroot)/lib',\n                        '-L<(sysroot)/usr/lib'\n                      ],\n                    }],\n                    ['target_arch==\"arm64\"', {\n                      'cflags': [\n                        '--sysroot=<(sysroot)',\n                        '-nostdinc',\n                        '-isystem<(gcc_include)',\n                        '-isystem<(sysroot)/usr/include',\n                        '-isystem<(sysroot)/usr/include/aarch64-linux-gnu'\n                      ],\n                      'cflags_cc': [\n                        '-nostdinc++',\n                        '-isystem<(sysroot)/../include/c++/10.5.0',\n                        '-isystem<(sysroot)/../include/c++/10.5.0/aarch64-linux-gnu',\n                        '-isystem<(sysroot)/../include/c++/10.5.0/backward'\n                      ],\n                      'ldflags': [\n                        '--sysroot=<(sysroot)',\n                        '-L<(sysroot)/lib',\n                        '-L<(sysroot)/usr/lib'\n                      ],\n                    }]\n                  ]\n                }]\n              ]\n            }]\n          ]\n        }\n      ]\n    }],\n    ['OS==\"mac\"', {\n      'targets': [\n        {\n          'target_name': 'spawn-helper',\n          'type': 'executable',\n          'sources': [\n            'src/unix/spawn-helper.cc',\n          ],\n          \"xcode_settings\": {\n            \"MACOSX_DEPLOYMENT_TARGET\":\"10.7\"\n          }\n        },\n      ]\n    }]\n  ]\n}\n"
  },
  {
    "path": "eslint.config.js",
    "content": "// @ts-check\nconst tsParser = require('@typescript-eslint/parser');\nconst tsPlugin = require('@typescript-eslint/eslint-plugin');\nconst globals = require('globals');\n\n/** @type {import('eslint').Linter.Config[]} */\nmodule.exports = [\n  {\n    ignores: [\n      '**/typings/*.d.ts',\n      'scripts/**/*',\n      'examples/**/*',\n    ],\n  },\n  {\n    files: ['src/**/*.ts'],\n    languageOptions: {\n      parser: tsParser,\n      parserOptions: {\n        project: 'src/tsconfig.json',\n        sourceType: 'module',\n      },\n      globals: {\n        ...globals.browser,\n        ...globals.es2015,\n        ...globals.node,\n      },\n    },\n    plugins: {\n      '@typescript-eslint': tsPlugin,\n    },\n    rules: {\n      '@typescript-eslint/array-type': [\n        'error',\n        {\n          default: 'array-simple',\n          readonly: 'generic',\n        },\n      ],\n      '@typescript-eslint/consistent-type-definitions': 'error',\n      '@typescript-eslint/explicit-function-return-type': [\n        'error',\n        {\n          'allowExpressions': true,\n        },\n      ],\n      '@typescript-eslint/naming-convention': [\n        'error',\n        { 'selector': 'default', 'format': ['camelCase'] },\n        // variableLike\n        { 'selector': 'variable', 'format': ['camelCase', 'UPPER_CASE'] },\n        { 'selector': 'variable', 'filter': '^I.+Service$', 'format': ['PascalCase'], 'prefix': ['I'] },\n        // memberLike\n        { 'selector': 'memberLike', 'modifiers': ['private'], 'format': ['camelCase'], 'leadingUnderscore': 'require' },\n        { 'selector': 'memberLike', 'modifiers': ['protected'], 'format': ['camelCase'], 'leadingUnderscore': 'require' },\n        { 'selector': 'enumMember', 'format': ['UPPER_CASE'] },\n        // memberLike - Allow enum-like objects to use UPPER_CASE\n        { 'selector': 'property', 'modifiers': ['public'], 'format': ['camelCase', 'UPPER_CASE'] },\n        { 'selector': 'method', 'modifiers': ['public'], 'format': ['camelCase', 'UPPER_CASE'] },\n        // typeLike\n        { 'selector': 'typeLike', 'format': ['PascalCase'] },\n        { 'selector': 'interface', 'format': ['PascalCase'], 'prefix': ['I'] },\n      ],\n      '@typescript-eslint/prefer-namespace-keyword': 'error',\n      'comma-dangle': [\n        'error',\n        {\n          'objects': 'never',\n          'arrays': 'never',\n          'functions': 'never',\n        },\n      ],\n      'curly': [\n        'error',\n        'multi-line',\n      ],\n      'eol-last': 'error',\n      'eqeqeq': [\n        'error',\n        'always',\n      ],\n      'keyword-spacing': 'error',\n      'new-parens': 'error',\n      'no-duplicate-imports': 'error',\n      'no-else-return': [\n        'error',\n        {\n          allowElseIf: false,\n        },\n      ],\n      'no-eval': 'error',\n      'no-irregular-whitespace': 'error',\n      'no-restricted-imports': [\n        'error',\n        {\n          'patterns': [\n            '.*\\\\/out\\\\/.*',\n          ],\n        },\n      ],\n      'no-trailing-spaces': 'error',\n      'no-unsafe-finally': 'error',\n      'no-var': 'error',\n      'one-var': [\n        'error',\n        'never',\n      ],\n      'prefer-const': 'error',\n      'quotes': [\n        'error',\n        'single',\n        { 'allowTemplateLiterals': true },\n      ],\n      'semi': [\n        'error',\n        'always',\n      ],\n      'spaced-comment': [\n        'error',\n        'always',\n        {\n          'markers': ['/'],\n          'exceptions': ['-'],\n        },\n      ],\n    },\n  },\n];\n"
  },
  {
    "path": "examples/electron/README.md",
    "content": "This is a minimal example of getting a terminal running in Electron using [node-pty](https://github.com/microsoft/node-pty) and [xterm.js](https://github.com/xtermjs/xterm.js).\n\n![](./images/preview.png)\n\nIt works by using xterm.js on the renderer process and node-pty on the main process with IPC to communicate back and forth.\n\n## Usage\n\n```bash\n# Install dependencies (Windows)\n./npm-install.bat\n\n# Install dependencies (non-Windows)\n./npm-install.sh\n\n# Launch the app\nnpm start\n```\n"
  },
  {
    "path": "examples/electron/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"UTF-8\">\n    <title>node-pty Electron example</title>\n    <link rel=\"Stylesheet\" href=\"./node_modules/@xterm/xterm/css/xterm.css\">\n  </head>\n  <body>\n    <div id=\"xterm\"></div>\n    <script src=\"./node_modules/@xterm/xterm/lib/xterm.js\"></script>\n    <script type=\"module\" src=\"./renderer.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/electron/main.js",
    "content": "const { app, BrowserWindow, ipcMain } = require('electron');\nconst path = require('path');\nconst os = require('os');\nconst pty = require('../..');\n\nlet mainWindow;\nlet ptyProcess;\n\nfunction createWindow() {\n  mainWindow = new BrowserWindow({\n    width: 800,\n    height: 600,\n    webPreferences: {\n      preload: path.join(__dirname, 'preload.js'),\n      contextIsolation: true,\n      nodeIntegration: false\n    }\n  });\n\n  mainWindow.loadFile('index.html');\n\n  mainWindow.on('closed', () => {\n    if (ptyProcess) {\n      ptyProcess.kill();\n      ptyProcess = null;\n    }\n    mainWindow = null;\n  });\n}\n\n// Handle pty spawn request from renderer\nipcMain.on('pty-spawn', () => {\n  if (ptyProcess) return;\n\n  const shell = process.env[os.platform() === 'win32' ? 'COMSPEC' : 'SHELL'];\n  ptyProcess = pty.spawn(shell, [], {\n    name: 'xterm-256color',\n    cols: 80,\n    rows: 30,\n    cwd: process.env.HOME || process.env.USERPROFILE,\n    env: process.env\n  });\n\n  ptyProcess.onData(data => {\n    if (mainWindow && !mainWindow.isDestroyed()) {\n      mainWindow.webContents.send('pty-data', data);\n    }\n  });\n});\n\n// Handle pty input from renderer\nipcMain.on('pty-write', (_, data) => {\n  if (ptyProcess) {\n    ptyProcess.write(data);\n  }\n});\n\n// Handle resize from renderer\nipcMain.on('pty-resize', (_, { cols, rows }) => {\n  if (ptyProcess) {\n    ptyProcess.resize(cols, rows);\n  }\n});\n\napp.whenReady().then(createWindow);\n\napp.on('window-all-closed', () => {\n  if (process.platform !== 'darwin') {\n    app.quit();\n  }\n});\n\napp.on('activate', () => {\n  if (mainWindow === null) {\n    createWindow();\n  }\n});\n"
  },
  {
    "path": "examples/electron/npm_install.bat",
    "content": "@echo off\nsetlocal\nset npm_config_disturl=\"https://atom.io/download/electron\"\nset npm_config_target=9.1.0\nset npm_config_runtime=\"electron\"\nset npm_config_cache=~\\.npm-electron\nnpm i\nendlocal\n"
  },
  {
    "path": "examples/electron/npm_install.sh",
    "content": "#!/usr/bin/env sh\n\n# Electron's version.\nexport npm_config_target=9.1.0\n# The architecture of Electron, can be ia32 or x64.\nexport npm_config_arch=x64\nexport npm_config_target_arch=x64\n# Download headers for Electron.\nexport npm_config_disturl=https://atom.io/download/electron\n# Tell node-pre-gyp that we are building for Electron.\nexport npm_config_runtime=electron\n# Tell node-pre-gyp to build module from source code.\nexport npm_config_build_from_source=true\n# Install all dependencies, and store cache to ~/.electron-gyp.\nHOME=~/.electron-gyp npm install\n"
  },
  {
    "path": "examples/electron/package.json",
    "content": "{\n  \"name\": \"node-pty-electron-example\",\n  \"version\": \"1.0.0\",\n  \"description\": \"A minimal node-pty Electron example\",\n  \"main\": \"main.js\",\n  \"scripts\": {\n    \"start\": \"electron .\"\n  },\n  \"repository\": \"https://github.com/microsoft/node-pty\",\n  \"author\": \"Tyriar\",\n  \"dependencies\": {\n    \"@xterm/xterm\": \"^6.0.0\",\n    \"electron\": \"^39.8.5\"\n  }\n}\n"
  },
  {
    "path": "examples/electron/preload.js",
    "content": "const { contextBridge, ipcRenderer } = require('electron');\n\ncontextBridge.exposeInMainWorld('pty', {\n  spawn: () => ipcRenderer.send('pty-spawn'),\n  write: (data) => ipcRenderer.send('pty-write', data),\n  onData: (callback) => ipcRenderer.on('pty-data', (_, data) => callback(data)),\n  resize: (cols, rows) => ipcRenderer.send('pty-resize', { cols, rows })\n});\n"
  },
  {
    "path": "examples/electron/renderer.js",
    "content": "// Initialize xterm.js and attach it to the DOM\nconst xterm = new window.Terminal();\nxterm.open(document.getElementById('xterm'));\n\n// Setup communication between xterm.js and node-pty via IPC\nxterm.onData(data => window.pty.write(data));\nwindow.pty.onData(data => xterm.write(data));\n\n// Initiate pty spawn as renderer is ready\nwindow.pty.spawn();\n"
  },
  {
    "path": "examples/fork/index.js",
    "content": "import * as os from 'node:os';\nimport * as pty from '../../lib/index.js';\n\nconst isWindows = os.platform() === 'win32';\nconst shell = isWindows ? 'powershell.exe' : 'bash';\n\nconst ptyProcess = pty.spawn(shell, [], {\n  name: 'xterm-256color',\n  cols: 80,\n  rows: 26,\n  cwd: isWindows ? process.env.USERPROFILE : process.env.HOME,\n  env: Object.assign({ TEST: \"Environment vars work\" }, process.env),\n  useConptyDll: true\n});\n\nptyProcess.onData(data => process.stdout.write(data));\n\nptyProcess.write(isWindows ? 'dir\\r' : 'ls\\r');\n\nsetTimeout(() => {\n  ptyProcess.resize(30, 19);\n  ptyProcess.write(isWindows ? '$Env:TEST\\r' : 'echo $TEST\\r');\n}, 2000);\n\nprocess.on('exit', () => ptyProcess.kill());\n\nsetTimeout(() => process.exit(), 4000);\n"
  },
  {
    "path": "examples/fork/package.json",
    "content": "{\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "examples/killDeepTree/README.md",
    "content": "This is a manual test to verify deeply nested trees are getting killed correctly on Windows.\n\nTo run:\n\n```bash\nnpm i\nnode index.js\n```\n\nIt should launch a notepad window and a webpack dev server, after 10 seconds the webpack dev server should be killed and the notepad instance should not. To verify the server is kill correctly either run the test again or check using ProcessExplorer.\n"
  },
  {
    "path": "examples/killDeepTree/entry.js",
    "content": "const test = 0;\n"
  },
  {
    "path": "examples/killDeepTree/index.js",
    "content": "var os = require('os');\nvar pty = require('../..');\n\nvar shell = os.platform() === 'win32' ? 'powershell.exe' : 'bash';\n\nvar ptyProcess = pty.spawn(shell, [], {\n  name: 'xterm-color',\n  cols: 80,\n  rows: 30,\n  cwd: __dirname,\n  env: process.env\n});\n\nptyProcess.onData((data) => process.stdout.write(data));\n\nptyProcess.write('start notepad\\r');\nptyProcess.write('npm start\\r');\n\n// Kill the tree at the end\nsetTimeout(() => {\n  console.log('Killing pty');\n  ptyProcess.kill();\n}, 10000);\n"
  },
  {
    "path": "examples/killDeepTree/package.json",
    "content": "{\n\t\"name\": \"vscode-process-leak\",\n\t\"version\": \"1.0.0\",\n\t\"description\": \"\",\n\t\"main\": \"index.js\",\n\t\"scripts\": {\n\t\t\"start\": \"webpack-dev-server\"\n\t},\n\t\"private\": true,\n\t\"devDependencies\": {\n\t\t\"webpack\": \"^4.28.4\",\n\t\t\"webpack-dev-server\": \"^3.1.14\"\n\t}\n}\n"
  },
  {
    "path": "examples/killDeepTree/webpack.config.js",
    "content": "const path = require('path');\n\nmodule.exports = {\n\tentry: './entry.js',\n\toutput: {\n\t\tfilename: 'bundle.js',\n\t\tpath: path.resolve(__dirname, 'dist')\n\t},\n\tdevServer: {\n\t\tport: 8000\n\t}\n};\n"
  },
  {
    "path": "fixtures/utf8-character.txt",
    "content": "æ"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"node-pty\",\n  \"description\": \"Fork pseudoterminals in Node.JS\",\n  \"author\": {\n    \"name\": \"Microsoft Corporation\"\n  },\n  \"version\": \"1.1.0\",\n  \"license\": \"MIT\",\n  \"main\": \"./lib/index.js\",\n  \"types\": \"./typings/node-pty.d.ts\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git://github.com/microsoft/node-pty.git\"\n  },\n  \"files\": [\n    \"binding.gyp\",\n    \"lib/**/*.js\",\n    \"!lib/**/*.test.js\",\n    \"scripts/post-install.js\",\n    \"scripts/prebuild.js\",\n    \"src/**/*.{cc,h}\",\n    \"prebuilds/\",\n    \"third_party/\",\n    \"typings/\"\n  ],\n  \"homepage\": \"https://github.com/microsoft/node-pty\",\n  \"bugs\": {\n    \"url\": \"https://github.com/microsoft/node-pty/issues\"\n  },\n  \"keywords\": [\n    \"pty\",\n    \"tty\",\n    \"terminal\",\n    \"pseudoterminal\",\n    \"forkpty\",\n    \"openpty\"\n  ],\n  \"scripts\": {\n    \"build\": \"tsc -b ./src/tsconfig.json\",\n    \"watch\": \"tsc -b -w ./src/tsconfig.json\",\n    \"lint\": \"eslint src/\",\n    \"install\": \"node scripts/prebuild.js || node-gyp rebuild\",\n    \"postinstall\": \"node scripts/post-install.js\",\n    \"compileCommands\": \"node scripts/gen-compile-commands.js\",\n    \"test\": \"cross-env NODE_ENV=test mocha -R spec --exit lib/*.test.js\",\n    \"posttest\": \"npm run lint\",\n    \"prepare\": \"npm run build\",\n    \"prepublishOnly\": \"npm run build\"\n  },\n  \"dependencies\": {\n    \"node-addon-api\": \"^7.1.0\"\n  },\n  \"devDependencies\": {\n    \"@types/mocha\": \"^7.0.2\",\n    \"@types/node\": \"12\",\n    \"@typescript-eslint/eslint-plugin\": \"^8.0.0\",\n    \"@typescript-eslint/parser\": \"^8.0.0\",\n    \"cross-env\": \"^5.1.4\",\n    \"eslint\": \"^9.0.0\",\n    \"mocha\": \"10\",\n    \"node-gyp\": \"^11.4.2\",\n    \"ps-list\": \"^6.0.0\",\n    \"typescript\": \"^5.0.0\"\n  }\n}\n"
  },
  {
    "path": "pipelines/build.yml",
    "content": "parameters:\n  arch: 'x64'\n\nsteps:\n- task: UseNode@1\n  inputs:\n    version: '22.x'\n  displayName: 'Install Node.js 22.x'\n\n- task: UsePythonVersion@0\n  inputs:\n    versionSpec: '3.x'\n    addToPath: true\n  displayName: 'Use latest Python 3.x'\n\n- bash: |\n    if [ \"$(uname)\" = \"Linux\" ]; then\n      sudo apt-get update -qq\n      if [ \"${{ parameters.arch }}\" = \"arm64\" ]; then\n        sudo apt-get install -y gcc-10-aarch64-linux-gnu g++-10-aarch64-linux-gnu\n        echo \"##vso[task.setvariable variable=CC]aarch64-linux-gnu-gcc-10\"\n        echo \"##vso[task.setvariable variable=CXX]aarch64-linux-gnu-g++-10\"\n      else\n        sudo apt-get install -y gcc-10 g++-10\n        echo \"##vso[task.setvariable variable=CC]gcc-10\"\n        echo \"##vso[task.setvariable variable=CXX]g++-10\"\n      fi\n      SYSROOT_PATH=$(node scripts/linux/install-sysroot.js ${{ parameters.arch }} | grep \"SYSROOT_PATH=\" | cut -d= -f2)\n      echo \"##vso[task.setvariable variable=SYSROOT_PATH]$SYSROOT_PATH\"\n      echo \"Sysroot path set to: $SYSROOT_PATH\"\n    elif [ \"$(uname)\" = \"Darwin\" ]; then\n      echo \"##vso[task.setvariable variable=CC]clang\"\n      echo \"##vso[task.setvariable variable=CXX]clang++\"\n    fi\n  displayName: 'Configure compiler'\n\n- script: npm ci\n  displayName: 'Install dependencies'\n  env:\n    ARCH: ${{ parameters.arch }}\n    npm_config_arch: ${{ parameters.arch }}\n    SYSROOT_PATH: $(SYSROOT_PATH)\n    CC: $(CC)\n    CXX: $(CXX)\n"
  },
  {
    "path": "pipelines/prebuilds.yml",
    "content": "trigger:\n  branches:\n    include:\n      - main\npr: none\n\nresources:\n  repositories:\n    - repository: 1esPipelines\n      type: git\n      name: 1ESPipelineTemplates/1ESPipelineTemplates\n      ref: refs/tags/release\n\nextends:\n  template: v1/1ES.Official.PipelineTemplate.yml@1esPipelines\n  parameters:\n    sdl:\n      sourceAnalysisPool: 1es-windows-2022-x64\n      tsa:\n        enabled: true\n    stages:\n      - stage: Build\n        jobs:\n          - job: win32_x64\n            pool:\n              name: 1es-windows-2022-x64\n              os: windows\n            templateContext:\n              outputs:\n                - output: pipelineArtifact\n                  targetPath: $(Build.SourcesDirectory)/build/Release\n                  artifactName: 'win32-x64'\n            steps:\n            - template: pipelines/build.yml@self\n              parameters:\n                arch: x64\n          - job: win32_arm64\n            pool:\n              name: 1es-windows-2022-x64\n              os: windows\n            templateContext:\n              outputs:\n                - output: pipelineArtifact\n                  targetPath: $(Build.SourcesDirectory)/build/Release\n                  artifactName: 'win32-arm64'\n            steps:\n            - template: pipelines/build.yml@self\n              parameters:\n                arch: arm64\n          - job: macOS_x64\n            pool:\n              name: Azure Pipelines\n              vmImage: macOS-latest\n              os: macOS\n            templateContext:\n              outputs:\n                - output: pipelineArtifact\n                  targetPath: $(Build.SourcesDirectory)/build/Release\n                  artifactName: 'darwin-x64'\n            steps:\n            - template: pipelines/build.yml@self\n              parameters:\n                arch: x64\n          - job: macOS_arm64\n            pool:\n              name: Azure Pipelines\n              vmImage: macOS-latest\n              os: macOS\n            templateContext:\n              outputs:\n                - output: pipelineArtifact\n                  targetPath: $(Build.SourcesDirectory)/build/Release\n                  artifactName: 'darwin-arm64'\n            steps:\n            - template: pipelines/build.yml@self\n              parameters:\n                arch: arm64\n          - job: linux_x64\n            pool:\n              name: 1es-ubuntu-22.04-x64\n              os: linux\n            templateContext:\n              outputs:\n                - output: pipelineArtifact\n                  targetPath: $(Build.SourcesDirectory)/build/Release\n                  artifactName: 'linux-x64'\n            steps:\n            - template: pipelines/build.yml@self\n              parameters:\n                arch: x64\n          - job: linux_arm64\n            pool:\n              name: 1es-ubuntu-22.04-x64\n              os: linux\n            templateContext:\n              outputs:\n                - output: pipelineArtifact\n                  targetPath: $(Build.SourcesDirectory)/build/Release\n                  artifactName: 'linux-arm64'\n            steps:\n            - template: pipelines/build.yml@self\n              parameters:\n                arch: arm64\n\n      - stage: Archive\n        dependsOn: Build\n        jobs:\n          - job: archive\n            pool:\n              name: 1es-ubuntu-22.04-x64\n              os: linux\n            templateContext:\n              inputs:\n                - input: pipelineArtifact\n                  artifactName: win32-x64\n                  targetPath: $(Build.ArtifactStagingDirectory)/win32-x64\n                - input: pipelineArtifact\n                  artifactName: win32-arm64\n                  targetPath: $(Build.ArtifactStagingDirectory)/win32-arm64\n                - input: pipelineArtifact\n                  artifactName: darwin-x64\n                  targetPath: $(Build.ArtifactStagingDirectory)/darwin-x64\n                - input: pipelineArtifact\n                  artifactName: darwin-arm64\n                  targetPath: $(Build.ArtifactStagingDirectory)/darwin-arm64\n                - input: pipelineArtifact\n                  artifactName: linux-x64\n                  targetPath: $(Build.ArtifactStagingDirectory)/linux-x64\n                - input: pipelineArtifact\n                  artifactName: linux-arm64\n                  targetPath: $(Build.ArtifactStagingDirectory)/linux-arm64\n              outputs:\n                - output: pipelineArtifact\n                  targetPath: $(Build.ArtifactStagingDirectory)/prebuilds\n                  artifactName: 'prebuilds-$(Build.SourceVersion)'\n            steps:\n              - script: |\n                  mkdir -p $(Build.ArtifactStagingDirectory)/prebuilds\n                  cp -r $(Build.ArtifactStagingDirectory)/win32-x64 $(Build.ArtifactStagingDirectory)/prebuilds/\n                  cp -r $(Build.ArtifactStagingDirectory)/win32-arm64 $(Build.ArtifactStagingDirectory)/prebuilds/\n                  cp -r $(Build.ArtifactStagingDirectory)/darwin-x64 $(Build.ArtifactStagingDirectory)/prebuilds/\n                  cp -r $(Build.ArtifactStagingDirectory)/darwin-arm64 $(Build.ArtifactStagingDirectory)/prebuilds/\n                  cp -r $(Build.ArtifactStagingDirectory)/linux-x64 $(Build.ArtifactStagingDirectory)/prebuilds/\n                  cp -r $(Build.ArtifactStagingDirectory)/linux-arm64 $(Build.ArtifactStagingDirectory)/prebuilds/\n                displayName: 'Create prebuilds archive'\n"
  },
  {
    "path": "publish.yml",
    "content": "name: $(Date:yyyyMMdd)$(Rev:.r)\n\ntrigger:\n  branches:\n    include:\n      - main\n\npr: none\n\nresources:\n  repositories:\n    - repository: templates\n      type: github\n      name: microsoft/vscode-engineering\n      ref: main\n      endpoint: Monaco\n\nparameters:\n  - name: publishPackage\n    displayName: 🚀 Publish node-pty\n    type: boolean\n    default: false\n  - name: releaseQuality\n    displayName: Quality\n    type: string\n    values:\n      - beta\n      - stable\n    default: beta\n\nvariables:\n  - name: releaseQuality\n    value: ${{ parameters.releaseQuality }}\n\nextends:\n  template: azure-pipelines/npm-package/pipeline.yml@templates\n  parameters:\n    npmPackages:\n      - name: node-pty\n\n        buildSteps:\n          - task: DownloadPipelineArtifact@2\n            displayName: 'Download prebuilds'\n            inputs:\n              buildType: 'specific'\n              project: 'Monaco'\n              definition: '647'\n              buildVersionToDownload: 'latestFromBranch'\n              branchName: '$(Build.SourceBranch)'\n              artifactName: 'prebuilds-$(Build.SourceVersion)'\n              targetPath: 'prebuilds'\n          - pwsh: |\n              Get-ChildItem -Path . -Recurse -Directory -Name \"_manifest\" | Remove-Item -Recurse -Force\n            displayName: 'Delete _manifest folders'\n          - bash: chmod +x prebuilds/darwin-*/spawn-helper\n            displayName: 'Ensure spawn-helper is executable'\n          - script: npm ci\n            displayName: 'Install dependencies and build'\n          # The following script leaves the version unchanged for\n          # stable releases, but increments the version for beta releases.\n          - script: node scripts/increment-version.js\n            displayName: 'Increment version'\n\n        testSteps:\n          - task: DownloadPipelineArtifact@2\n            displayName: 'Download prebuilds'\n            inputs:\n              buildType: 'specific'\n              project: 'Monaco'\n              definition: '647'\n              buildVersionToDownload: 'latestFromBranch'\n              branchName: '$(Build.SourceBranch)'\n              artifactName: 'prebuilds-$(Build.SourceVersion)'\n              targetPath: 'prebuilds'\n          - bash: chmod +x prebuilds/darwin-*/spawn-helper\n            displayName: 'Ensure spawn-helper is executable'\n          - script: npm ci\n            displayName: 'Install dependencies and build'\n          - script: npm test\n            displayName: 'Test'\n          - script: npm run lint\n            displayName: 'Lint'\n\n        publishPackage: ${{ parameters.publishPackage }}\n        ${{ if eq(variables['releaseQuality'], 'stable') }}:\n          tag: latest\n          publishRequiresApproval: true\n        ${{ else }}:\n          tag: beta\n          publishRequiresApproval: false\n\n        apiScanExcludes: |\n          package/third_party/conpty/**/win10-arm64/*.*\n          package/prebuilds/win32-arm64/conpty/*.*\n          package/prebuilds/win32-arm64/*.*\n        apiScanSoftwareName: 'vscode-node-pty'\n        apiScanSoftwareVersion: '1'\n"
  },
  {
    "path": "scripts/gen-compile-commands.js",
    "content": "/**\n * Copyright (c) 2025, Microsoft Corporation (MIT License).\n */\n\nconst { execSync } = require('child_process');\n\nconsole.log(`\\x1b[32m> Generating compile_commands.json...\\x1b[0m`);\nexecSync('npx --offline node-gyp configure -- -f compile_commands_json');\n"
  },
  {
    "path": "scripts/increment-version.js",
    "content": "/**\n * Copyright (c) 2019, Microsoft Corporation (MIT License).\n */\n\nconst cp = require('child_process');\nconst fs = require('fs');\nconst path = require('path');\nconst packageJson = require('../package.json');\n\n// Determine if this is a stable or beta release\nconst publishedVersions = getPublishedVersions();\nconst isStableRelease = !publishedVersions.includes(packageJson.version);\n\n// Get the next version\nconst nextVersion = isStableRelease ? packageJson.version : getNextBetaVersion();\nconsole.log(`Setting version to ${nextVersion}`);\n\n// Set the version in package.json\nconst packageJsonFile = path.resolve(__dirname, '..', 'package.json');\npackageJson.version = nextVersion;\nfs.writeFileSync(packageJsonFile, JSON.stringify(packageJson, null, 2));\n\nfunction getNextBetaVersion() {\n  if (!/^[0-9]+\\.[0-9]+\\.[0-9]+$/.exec(packageJson.version)) {\n    console.error('The package.json version must be of the form x.y.z');\n    process.exit(1);\n  }\n  const tag = 'beta';\n  const stableVersion = packageJson.version.split('.');\n  const nextStableVersion = `${stableVersion[0]}.${parseInt(stableVersion[1]) + 1}.0`;\n  const publishedVersions = getPublishedVersions(nextStableVersion, tag);\n  if (publishedVersions.length === 0) {\n    return `${nextStableVersion}-${tag}.1`;\n  }\n  const latestPublishedVersion = publishedVersions.sort((a, b) => {\n    const aVersion = parseInt(a.substr(a.search(/[0-9]+$/)));\n    const bVersion = parseInt(b.substr(b.search(/[0-9]+$/)));\n    return aVersion > bVersion ? -1 : 1;\n  })[0];\n  const latestTagVersion = parseInt(latestPublishedVersion.substr(latestPublishedVersion.search(/[0-9]+$/)), 10);\n  return `${nextStableVersion}-${tag}.${latestTagVersion + 1}`;\n}\n\nfunction getPublishedVersions(version, tag) {\n  const isWin32 = process.platform === 'win32';\n  const versionsProcess = isWin32 ?\n    cp.spawnSync('npm.cmd', ['view', packageJson.name, 'versions', '--json'], { shell: true }) :\n    cp.spawnSync('npm', ['view', packageJson.name, 'versions', '--json']);\n  const versionsJson = JSON.parse(versionsProcess.stdout);\n  if (tag) {\n    return versionsJson.filter(v => !v.search(new RegExp(`${version}-${tag}\\.[0-9]+`)));\n  }\n  return versionsJson;\n}\n"
  },
  {
    "path": "scripts/linux/checksums.txt",
    "content": "3122af49c493c5c767c2b0772a41119cbdc9803125a705683445b4066dc88b82  x86_64-linux-gnu-glibc-2.28-gcc-10.5.0.tar.gz\n3baac81a39b69e0929e4700f4f78f022adefc515010054ec393565657c4fff32  aarch64-linux-gnu-glibc-2.28-gcc-10.5.0.tar.gz\n"
  },
  {
    "path": "scripts/linux/install-sysroot.js",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nconst { execSync } = require('child_process');\nconst { tmpdir } = require('os');\nconst fs = require('fs');\nconst path = require('path');\nconst { createHash } = require('crypto');\n\nconst REPO_ROOT = path.join(__dirname, '..', '..');\n\nconst ghApiHeaders = {\n  Accept: 'application/vnd.github.v3+json',\n  'User-Agent': 'node-pty Build',\n};\n\nif (process.env.GITHUB_TOKEN) {\n  ghApiHeaders.Authorization = 'Basic ' + Buffer.from(process.env.GITHUB_TOKEN).toString('base64');\n  console.error('Using GITHUB_TOKEN for authenticated requests to GitHub API.');\n}\n\nconst ghDownloadHeaders = {\n  ...ghApiHeaders,\n  Accept: 'application/octet-stream',\n};\n\nfunction getSysrootChecksum(expectedName) {\n  const checksumPath = path.join(REPO_ROOT, 'scripts', 'linux', 'checksums.txt');\n  const checksums = fs.readFileSync(checksumPath, 'utf8');\n  for (const line of checksums.split('\\n')) {\n    const [checksum, name] = line.split(/\\s+/);\n    if (name === expectedName) {\n      return checksum;\n    }\n  }\n  return undefined;\n}\n\nasync function fetchUrl(options, retries = 10, retryDelay = 1000) {\n  try {\n    const controller = new AbortController();\n    const timeout = setTimeout(() => controller.abort(), 30 * 1000);\n    const version = '20250407-330404';\n    try {\n      const response = await fetch(`https://api.github.com/repos/Microsoft/vscode-linux-build-agent/releases/tags/v${version}`, {\n        headers: ghApiHeaders,\n        signal: controller.signal\n      });\n      if (response.ok && (response.status >= 200 && response.status < 300)) {\n        console.error(`Fetch completed: Status ${response.status}.`);\n        const contents = Buffer.from(await response.arrayBuffer());\n        const asset = JSON.parse(contents.toString()).assets.find((a) => a.name === options.assetName);\n        if (!asset) {\n          throw new Error(`Could not find asset in release of Microsoft/vscode-linux-build-agent @ ${version}`);\n        }\n        console.error(`Found asset ${options.assetName} @ ${asset.url}.`);\n        const assetResponse = await fetch(asset.url, {\n          headers: ghDownloadHeaders\n        });\n        if (assetResponse.ok && (assetResponse.status >= 200 && assetResponse.status < 300)) {\n          const assetContents = Buffer.from(await assetResponse.arrayBuffer());\n          console.error(`Fetched response body buffer: ${assetContents.byteLength} bytes`);\n          if (options.checksumSha256) {\n            const actualSHA256Checksum = createHash('sha256').update(assetContents).digest('hex');\n            if (actualSHA256Checksum !== options.checksumSha256) {\n              throw new Error(`Checksum mismatch for ${asset.url} (expected ${options.checksumSha256}, actual ${actualSHA256Checksum})`);\n            }\n          }\n          console.error(`Verified SHA256 checksums match for ${asset.url}`);\n\t\t\t\t\tconst tarCommand = `tar -xz -C ${options.dest}`;\n\t\t\t\t\texecSync(tarCommand, { input: assetContents });\n\t\t\t\t\tconsole.error(`Fetch complete!`);\n\t\t\t\t\treturn;\n        }\n        throw new Error(`Request ${asset.url} failed with status code: ${assetResponse.status}`);\n      }\n      throw new Error(`Request https://api.github.com failed with status code: ${response.status}`);\n    } finally {\n      clearTimeout(timeout);\n    }\n  } catch (e) {\n    if (retries > 0) {\n      console.error(`Fetching failed: ${e}`);\n      await new Promise(resolve => setTimeout(resolve, retryDelay));\n      return fetchUrl(options, retries - 1, retryDelay);\n    }\n    throw e;\n  }\n}\n\nasync function getSysroot(arch) {\n  let expectedName;\n  let triple;\n  const prefix = '-glibc-2.28-gcc-10.5.0';\n\n  switch (arch) {\n    case 'x64':\n      expectedName = `x86_64-linux-gnu${prefix}.tar.gz`;\n      triple = 'x86_64-linux-gnu';\n      break;\n    case 'arm64':\n      expectedName = `aarch64-linux-gnu${prefix}.tar.gz`;\n      triple = 'aarch64-linux-gnu';\n      break;\n    default:\n      throw new Error(`Unsupported architecture: ${arch}`);\n  }\n\n  console.log(`Fetching ${expectedName} for ${triple}`);\n  const checksumSha256 = getSysrootChecksum(expectedName);\n  if (!checksumSha256) {\n    throw new Error(`Could not find checksum for ${expectedName}`);\n  }\n\n  const sysroot = path.join(tmpdir(), `vscode-${arch}-sysroot`);\n  const stamp = path.join(sysroot, '.stamp');\n  const result = `${sysroot}/${triple}/${triple}/sysroot`;\n\n  if (fs.existsSync(stamp) && fs.readFileSync(stamp).toString() === expectedName) {\n    console.log(`Sysroot already installed: ${result}`);\n    return result;\n  }\n\n  console.error(`Installing ${arch} root image: ${sysroot}`);\n  fs.rmSync(sysroot, { recursive: true, force: true });\n  fs.mkdirSync(sysroot, { recursive: true });\n\n  await fetchUrl({\n    checksumSha256,\n    assetName: expectedName,\n    dest: sysroot\n  });\n\n  fs.writeFileSync(stamp, expectedName);\n  console.log(`Sysroot installed: ${result}`);\n  return result;\n}\n\nasync function main() {\n  const arch = process.argv[2] || process.env.ARCH || 'x64';\n  console.error(`Installing sysroot for architecture: ${arch}`);\n\n  try {\n    const sysrootPath = await getSysroot(arch);\n    console.log(`SYSROOT_PATH=${sysrootPath}`);\n  } catch (error) {\n    console.error('Error installing sysroot:', error);\n    process.exit(1);\n  }\n}\n\nif (require.main === module) {\n  main();\n}\n\nmodule.exports = { getSysroot };\n"
  },
  {
    "path": "scripts/linux/verify-glibc-requirements.sh",
    "content": "#!/usr/bin/env bash\n\nset -e\n\n# Get all files with .node extension from given folder\nfiles=$(find $SEARCH_PATH -name \"*.node\")\n\necho \"Verifying requirements for files: $files\"\n\nfor file in $files; do\n  glibc_version=\"$EXPECTED_GLIBC_VERSION\"\n  glibcxx_version=\"$EXPECTED_GLIBCXX_VERSION\"\n  while IFS= read -r line; do\n    if [[ $line == *\"GLIBC_\"* ]]; then\n      version=$(echo \"$line\" | awk '{if ($5 ~ /^[0-9a-fA-F]+$/) print $6; else print $5}' | tr -d '()')\n      version=${version#*_}\n      if [[ $(printf \"%s\\n%s\" \"$version\" \"$glibc_version\" | sort -V | tail -n1) == \"$version\" ]]; then\n        glibc_version=$version\n      fi\n    elif [[ $line == *\"GLIBCXX_\"* ]]; then\n      version=$(echo \"$line\" | awk '{if ($5 ~ /^[0-9a-fA-F]+$/) print $6; else print $5}' | tr -d '()')\n      version=${version#*_}\n      if [[ $(printf \"%s\\n%s\" \"$version\" \"$glibcxx_version\" | sort -V | tail -n1) == \"$version\" ]]; then\n        glibcxx_version=$version\n      fi\n    fi\n  done < <(objdump -T \"$file\")\n\n  if [[ \"$glibc_version\" != \"$EXPECTED_GLIBC_VERSION\" ]]; then\n    echo \"Error: File $file has dependency on GLIBC > $EXPECTED_GLIBC_VERSION, found $glibc_version\"\n    exit 1\n  fi\n  if [[ \"$glibcxx_version\" != \"$EXPECTED_GLIBCXX_VERSION\" ]]; then\n    echo \"Error: File $file has dependency on GLIBCXX > $EXPECTED_GLIBCXX_VERSION, found $glibcxx_version\"\n  fi\ndone\n"
  },
  {
    "path": "scripts/post-install.js",
    "content": "//@ts-check\n\nconst fs = require('fs');\nconst os = require('os');\nconst path = require('path');\n\nconst RELEASE_DIR = path.join(__dirname, '../build/Release');\nconst BUILD_FILES = [\n  path.join(RELEASE_DIR, 'conpty.node'),\n  path.join(RELEASE_DIR, 'conpty.pdb'),\n  path.join(RELEASE_DIR, 'conpty_console_list.node'),\n  path.join(RELEASE_DIR, 'conpty_console_list.pdb'),\n  path.join(RELEASE_DIR, 'pty.node'),\n  path.join(RELEASE_DIR, 'pty.pdb'),\n  path.join(RELEASE_DIR, 'spawn-helper')\n];\nconst CONPTY_DIR = path.join(__dirname, '../third_party/conpty');\nconst CONPTY_SUPPORTED_ARCH = ['x64', 'arm64'];\n\nconsole.log('\\x1b[32m> Cleaning release folder...\\x1b[0m');\n\n/** @param {string} folder  */\nfunction cleanFolderRecursive(folder) {\n  var files = [];\n  if (fs.existsSync(folder)) {\n    files = fs.readdirSync(folder);\n    files.forEach(function(file,index) {\n      var curPath = path.join(folder, file);\n      if (fs.lstatSync(curPath).isDirectory()) { // recurse\n        cleanFolderRecursive(curPath);\n        fs.rmdirSync(curPath);\n      } else if (BUILD_FILES.indexOf(curPath) < 0){ // delete file\n        fs.unlinkSync(curPath);\n      }\n    });\n  }\n};\n\ntry {\n  cleanFolderRecursive(RELEASE_DIR);\n} catch(e) {\n  console.log(e);\n  process.exit(1);\n}\n\nconsole.log(`\\x1b[32m> Moving conpty.dll...\\x1b[0m`);\nif (os.platform() !== 'win32') {\n  console.log('  SKIPPED (not Windows)');\n} else {\n  let windowsArch;\n  if (process.env.npm_config_arch) {\n    windowsArch = process.env.npm_config_arch;\n    console.log(`  Using $npm_config_arch: ${windowsArch}`);\n  } else {\n    windowsArch = os.arch();\n    console.log(`  Using os.arch(): ${windowsArch}`);\n  }\n\n  if (!CONPTY_SUPPORTED_ARCH.includes(windowsArch)) {\n    console.log(`  SKIPPED (unsupported architecture ${windowsArch})`);\n  } else {\n    const versionFolder = fs.readdirSync(CONPTY_DIR)[0];\n    console.log(`  Found version ${versionFolder}`);\n    const sourceFolder = path.join(CONPTY_DIR, versionFolder, `win10-${windowsArch}`);\n    const destFolder = path.join(RELEASE_DIR, 'conpty');\n    fs.mkdirSync(destFolder, { recursive: true });\n    for (const file of ['conpty.dll', 'OpenConsole.exe']) {\n      const sourceFile = path.join(sourceFolder, file);\n      const destFile = path.join(destFolder, file);\n      console.log(`  Copying ${sourceFile} -> ${destFile}`);\n      fs.copyFileSync(sourceFile, destFile);\n    }\n  }\n}\n\nprocess.exit(0);\n"
  },
  {
    "path": "scripts/prebuild.js",
    "content": "//@ts-check\n\nconst fs = require('fs');\nconst path = require('path');\n\n/**\n * This script checks for the prebuilt binaries for the current platform and\n * architecture. It exits with 0 if prebuilds are found and 1 if not.\n *\n * If npm_config_build_from_source is set then it removes the prebuilds for the\n * current platform so they are not loaded at runtime.\n *\n * Usage:\n *     node scripts/prebuild.js\n */\n\nconst PREBUILDS_ROOT = path.join(__dirname, '..', 'prebuilds');\nconst PREBUILD_DIR = path.join(__dirname, '..', 'prebuilds', `${process.platform}-${process.arch}`);\n\n// Do not use prebuilds when npm_config_build_from_source is set\nif (process.env.npm_config_build_from_source === 'true') {\n  console.log('\\x1b[33m> Removing prebuilds and rebuilding because npm_config_build_from_source is set\\x1b[0m');\n  fs.rmSync(PREBUILDS_ROOT, { recursive: true, force: true });\n  process.exit(1);\n}\n\n// Check whether the correct prebuilt files exist\nconsole.log('\\x1b[32m> Checking prebuilds...\\x1b[0m');\nif (!fs.existsSync(PREBUILD_DIR)) {\n  console.log(`\\x1b[33m> Rebuilding because directory ${PREBUILD_DIR} does not exist\\x1b[0m`);\n  process.exit(1);\n}\n\nprocess.exit(0);\n"
  },
  {
    "path": "src/conpty_console_list_agent.ts",
    "content": "/**\n * Copyright (c) 2019, Microsoft Corporation (MIT License).\n *\n * This module fetches the console process list for a particular PID. It must be\n * called from a different process (child_process.fork) as there can only be a\n * single console attached to a process.\n */\n\nimport { loadNativeModule } from './utils';\n\nconst getConsoleProcessList = loadNativeModule('conpty_console_list').module.getConsoleProcessList;\nconst shellPid = parseInt(process.argv[2], 10);\nlet consoleProcessList: number[] = [];\nif (shellPid > 0) {\n  try {\n    consoleProcessList = getConsoleProcessList(shellPid);\n  } catch {\n    // AttachConsole can fail if the process already exited or is invalid.\n    consoleProcessList = [];\n  }\n}\nprocess.send!({ consoleProcessList });\nprocess.exit(0);\n"
  },
  {
    "path": "src/eventEmitter2.test.ts",
    "content": "/**\n * Copyright (c) 2019, Microsoft Corporation (MIT License).\n */\n\nimport * as assert from 'assert';\nimport { EventEmitter2 } from './eventEmitter2';\n\ndescribe('EventEmitter2', () => {\n  it('should fire listeners multiple times', () => {\n    const order: string[] = [];\n    const emitter = new EventEmitter2<number>();\n    emitter.event(data => order.push(data + 'a'));\n    emitter.event(data => order.push(data + 'b'));\n    emitter.fire(1);\n    emitter.fire(2);\n    assert.deepEqual(order, [ '1a', '1b', '2a', '2b' ]);\n  });\n\n  it('should not fire listeners once disposed', () => {\n    const order: string[] = [];\n    const emitter = new EventEmitter2<number>();\n    emitter.event(data => order.push(data + 'a'));\n    const disposeB = emitter.event(data => order.push(data + 'b'));\n    emitter.event(data => order.push(data + 'c'));\n    emitter.fire(1);\n    disposeB.dispose();\n    emitter.fire(2);\n    assert.deepEqual(order, [ '1a', '1b', '1c', '2a', '2c' ]);\n  });\n});\n"
  },
  {
    "path": "src/eventEmitter2.ts",
    "content": "/**\n * Copyright (c) 2019, Microsoft Corporation (MIT License).\n */\n\nimport { IDisposable } from './types';\n\ninterface IListener<T> {\n  (e: T): void;\n}\n\nexport interface IEvent<T> {\n  (listener: (e: T) => any): IDisposable;\n}\n\nexport class EventEmitter2<T> {\n  private _listeners: Array<IListener<T>> = [];\n  private _event?: IEvent<T>;\n\n  public get event(): IEvent<T> {\n    if (!this._event) {\n      this._event = (listener: (e: T) => any) => {\n        this._listeners.push(listener);\n        const disposable = {\n          dispose: () => {\n            for (let i = 0; i < this._listeners.length; i++) {\n              if (this._listeners[i] === listener) {\n                this._listeners.splice(i, 1);\n                return;\n              }\n            }\n          }\n        };\n        return disposable;\n      };\n    }\n    return this._event;\n  }\n\n  public fire(data: T): void {\n    const queue: Array<IListener<T>> = [];\n    for (let i = 0; i < this._listeners.length; i++) {\n      queue.push(this._listeners[i]);\n    }\n    for (let i = 0; i < queue.length; i++) {\n      queue[i].call(undefined, data);\n    }\n  }\n}\n"
  },
  {
    "path": "src/index.ts",
    "content": "/**\n * Copyright (c) 2012-2015, Christopher Jeffrey, Peter Sunde (MIT License)\n * Copyright (c) 2016, Daniel Imms (MIT License).\n * Copyright (c) 2018, Microsoft Corporation (MIT License).\n */\n\nimport { ITerminal, IPtyOpenOptions, IPtyForkOptions, IWindowsPtyForkOptions } from './interfaces';\nimport { ArgvOrCommandLine } from './types';\nimport { loadNativeModule } from './utils';\n\nlet terminalCtor: any;\nif (process.platform === 'win32') {\n  terminalCtor = require('./windowsTerminal').WindowsTerminal;\n} else {\n  terminalCtor = require('./unixTerminal').UnixTerminal;\n}\n\n/**\n * Forks a process as a pseudoterminal.\n * @param file The file to launch.\n * @param args The file's arguments as argv (string[]) or in a pre-escaped\n * CommandLine format (string). Note that the CommandLine option is only\n * available on Windows and is expected to be escaped properly.\n * @param options The options of the terminal.\n * @throws When the file passed to spawn with does not exists.\n * @see CommandLineToArgvW https://msdn.microsoft.com/en-us/library/windows/desktop/bb776391(v=vs.85).aspx\n * @see Parsing C++ Comamnd-Line Arguments https://msdn.microsoft.com/en-us/library/17w5ykft.aspx\n * @see GetCommandLine https://msdn.microsoft.com/en-us/library/windows/desktop/ms683156.aspx\n */\nexport function spawn(file?: string, args?: ArgvOrCommandLine, opt?: IPtyForkOptions | IWindowsPtyForkOptions): ITerminal {\n  return new terminalCtor(file, args, opt);\n}\n\n/** @deprecated */\nexport function fork(file?: string, args?: ArgvOrCommandLine, opt?: IPtyForkOptions | IWindowsPtyForkOptions): ITerminal {\n  return new terminalCtor(file, args, opt);\n}\n\n/** @deprecated */\nexport function createTerminal(file?: string, args?: ArgvOrCommandLine, opt?: IPtyForkOptions | IWindowsPtyForkOptions): ITerminal {\n  return new terminalCtor(file, args, opt);\n}\n\nexport function open(options: IPtyOpenOptions): ITerminal {\n  return terminalCtor.open(options);\n}\n\n/**\n * Expose the native API when not Windows, note that this is not public API and\n * could be removed at any time.\n */\nexport const native = (process.platform !== 'win32' ? loadNativeModule('pty').module : null);\n"
  },
  {
    "path": "src/interfaces.ts",
    "content": "/**\n * Copyright (c) 2016, Daniel Imms (MIT License).\n * Copyright (c) 2018, Microsoft Corporation (MIT License).\n */\n\nexport interface IProcessEnv {\n  [key: string]: string | undefined;\n}\n\nexport interface ITerminal {\n  /**\n   * Gets the name of the process.\n   */\n  process: string;\n\n  /**\n   * Gets the process ID.\n   */\n  pid: number;\n\n  /**\n   * Writes data to the socket.\n   * @param data The data to write.\n   */\n  write(data: string | Buffer): void;\n\n  /**\n   * Resize the pty.\n   * @param cols The number of columns.\n   * @param rows The number of rows.\n   * @param pixelSize Optional pixel dimensions of the pty. On Unix, this sets the `ws_xpixel`\n   * and `ws_ypixel` fields of the `winsize` struct. Applications running in the pty can read\n   * these values via the `TIOCGWINSZ` ioctl. This parameter is ignored on Windows.\n   */\n  resize(cols: number, rows: number, pixelSize?: { width: number, height: number }): void;\n\n  /**\n   * Clears the pty's internal representation of its buffer. This is a no-op\n   * unless on Windows/ConPTY.\n   */\n  clear(): void;\n\n  /**\n   * Close, kill and destroy the socket.\n   */\n  destroy(): void;\n\n  /**\n   * Kill the pty.\n   * @param signal The signal to send, by default this is SIGHUP. This is not\n   * supported on Windows.\n   */\n  kill(signal?: string): void;\n\n  /**\n   * Set the pty socket encoding.\n   */\n  setEncoding(encoding: string | null): void;\n\n  /**\n   * Resume the pty socket.\n   */\n  resume(): void;\n\n  /**\n   * Pause the pty socket.\n   */\n  pause(): void;\n\n  /**\n   * Alias for ITerminal.on(eventName, listener).\n   */\n  addListener(eventName: string, listener: (...args: any[]) => any): void;\n\n  /**\n   * Adds the listener function to the end of the listeners array for the event\n   * named eventName.\n   * @param eventName The event name.\n   * @param listener The callback function\n   */\n  on(eventName: string, listener: (...args: any[]) => any): void;\n\n  /**\n   * Returns a copy of the array of listeners for the event named eventName.\n   */\n  listeners(eventName: string): Function[];\n\n  /**\n   * Removes the specified listener from the listener array for the event named\n   * eventName.\n   */\n  removeListener(eventName: string, listener: (...args: any[]) => any): void;\n\n  /**\n   * Removes all listeners, or those of the specified eventName.\n   */\n  removeAllListeners(eventName: string): void;\n\n  /**\n   * Adds a one time listener function for the event named eventName. The next\n   * time eventName is triggered, this listener is removed and then invoked.\n   */\n  once(eventName: string, listener: (...args: any[]) => any): void;\n}\n\ninterface IBasePtyForkOptions {\n  name?: string;\n  cols?: number;\n  rows?: number;\n  cwd?: string;\n  env?: IProcessEnv;\n  encoding?: string | null;\n  handleFlowControl?: boolean;\n  flowControlPause?: string;\n  flowControlResume?: string;\n}\n\nexport interface IPtyForkOptions extends IBasePtyForkOptions {\n  uid?: number;\n  gid?: number;\n}\n\nexport interface IWindowsPtyForkOptions extends IBasePtyForkOptions {\n  /**\n   * Whether to use the ConPTY system on Windows. When this is not set, ConPTY will be used when\n   * the Windows build number is >= 18309 (instead of winpty). Note that ConPTY is available from\n   * build 17134 but is too unstable to enable by default.\n   *\n   * @deprecated This option is ignored and will be removed in a future version.\n   * https://github.com/microsoft/node-pty/issues/871\n   */\n  useConpty?: boolean;\n  useConptyDll?: boolean;\n  conptyInheritCursor?: boolean;\n}\n\nexport interface IPtyOpenOptions {\n  cols?: number;\n  rows?: number;\n  encoding?: string | null;\n}\n"
  },
  {
    "path": "src/native.d.ts",
    "content": "/**\n * Copyright (c) 2018, Microsoft Corporation (MIT License).\n */\n\ninterface IConptyNative {\n  startProcess(file: string, cols: number, rows: number, debug: boolean, pipeName: string, conptyInheritCursor: boolean, useConptyDll: boolean): IConptyProcess;\n  connect(ptyId: number, commandLine: string, cwd: string, env: string[], useConptyDll: boolean, onExitCallback: (exitCode: number) => void): { pid: number };\n  resize(ptyId: number, cols: number, rows: number, useConptyDll: boolean): void;\n  clear(ptyId: number, useConptyDll: boolean): void;\n  kill(ptyId: number, useConptyDll: boolean): void;\n}\n\ninterface IUnixNative {\n  fork(file: string, args: string[], parsedEnv: string[], cwd: string, cols: number, rows: number, uid: number, gid: number, useUtf8: boolean, helperPath: string, onExitCallback: (code: number, signal: number) => void): IUnixProcess;\n  open(cols: number, rows: number): IUnixOpenProcess;\n  process(fd: number, pty?: string): string;\n  resize(fd: number, cols: number, rows: number, pixelWidth: number, pixelHeight: number): void;\n}\n\ninterface IConptyProcess {\n  pty: number;\n  fd: number;\n  conin: string;\n  conout: string;\n}\n\ninterface IUnixProcess {\n  fd: number;\n  pid: number;\n  pty: string;\n}\n\ninterface IUnixOpenProcess {\n  master: number;\n  slave: number;\n  pty: string;\n}\n"
  },
  {
    "path": "src/shared/conout.ts",
    "content": "/**\n * Copyright (c) 2020, Microsoft Corporation (MIT License).\n */\n\nexport interface IWorkerData {\n  conoutPipeName: string;\n}\n\nexport const enum ConoutWorkerMessage {\n  READY = 1\n}\n\nexport function getWorkerPipeName(conoutPipeName: string): string {\n  return `${conoutPipeName}-worker`;\n}\n"
  },
  {
    "path": "src/terminal.test.ts",
    "content": "/**\n * Copyright (c) 2017, Daniel Imms (MIT License).\n * Copyright (c) 2018, Microsoft Corporation (MIT License).\n */\n\nimport * as assert from 'assert';\nimport { Terminal } from './terminal';\nimport { Socket } from 'net';\n\nconst terminalConstructor = (process.platform === 'win32')\n  ? require('./windowsTerminal').WindowsTerminal\n  : require('./unixTerminal').UnixTerminal;\nconst SHELL = (process.platform === 'win32') ? 'cmd.exe' : '/bin/bash';\n\nlet terminalCtor: any; // Will be WindowsTerminal | UnixTerminal depending on conditional report\nif (process.platform === 'win32') {\n  terminalCtor = require('./windowsTerminal');\n} else {\n  terminalCtor = require('./unixTerminal');\n}\n\nclass TestTerminal extends Terminal {\n  public checkType<T>(name: string, value: T, type: string, allowArray: boolean = false): void {\n    this._checkType(name, value, type, allowArray);\n  }\n  protected _write(data: string | Buffer): void {\n    throw new Error('Method not implemented.');\n  }\n  public resize(cols: number, rows: number): void {\n    throw new Error('Method not implemented.');\n  }\n  public clear(): void {\n    throw new Error('Method not implemented.');\n  }\n  public destroy(): void {\n    throw new Error('Method not implemented.');\n  }\n  public kill(signal?: string): void {\n    throw new Error('Method not implemented.');\n  }\n  public get process(): string {\n    throw new Error('Method not implemented.');\n  }\n  public get master(): Socket {\n    throw new Error('Method not implemented.');\n  }\n  public get slave(): Socket {\n    throw new Error('Method not implemented.');\n  }\n}\n\ndescribe('Terminal', () => {\n  describe('constructor', () => {\n    it('should do basic type checks', () => {\n      assert.throws(\n        () => new (<any>terminalCtor)('a', 'b', { 'name': {} }),\n        'name must be a string (not a object)'\n      );\n    });\n  });\n\n  describe('checkType', () => {\n    it('should throw for the wrong type', () => {\n      const t = new TestTerminal();\n      assert.doesNotThrow(() => t.checkType('foo', 'test', 'string'));\n      assert.doesNotThrow(() => t.checkType('foo', 1, 'number'));\n      assert.doesNotThrow(() => t.checkType('foo', {}, 'object'));\n\n      assert.throws(() => t.checkType('foo', 'test', 'number'));\n      assert.throws(() => t.checkType('foo', 1, 'object'));\n      assert.throws(() => t.checkType('foo', {}, 'string'));\n    });\n    it('should throw for wrong types within arrays', () => {\n      const t = new TestTerminal();\n      assert.doesNotThrow(() => t.checkType('foo', ['test'], 'string', true));\n      assert.doesNotThrow(() => t.checkType('foo', [1], 'number', true));\n      assert.doesNotThrow(() => t.checkType('foo', [{}], 'object', true));\n\n      assert.throws(() => t.checkType('foo', ['test'], 'number', true));\n      assert.throws(() => t.checkType('foo', [1], 'object', true));\n      assert.throws(() => t.checkType('foo', [{}], 'string', true));\n    });\n  });\n\n  describe('automatic flow control', () => {\n    it('should respect ctor flow control options', () => {\n      const pty = new terminalConstructor(SHELL, [], {handleFlowControl: true, flowControlPause: 'abc', flowControlResume: '123'});\n      assert.equal(pty.handleFlowControl, true);\n      assert.equal((pty as any)._flowControlPause, 'abc');\n      assert.equal((pty as any)._flowControlResume, '123');\n    });\n    // TODO: I don't think this test ever worked due to pollUntil being used incorrectly\n    // it('should do flow control automatically', async function(): Promise<void> {\n    //   // Flow control doesn't work on Windows\n    //   if (process.platform === 'win32') {\n    //     return;\n    //   }\n\n    //   this.timeout(10000);\n    //   const pty = new terminalConstructor(SHELL, [], {handleFlowControl: true, flowControlPause: 'PAUSE', flowControlResume: 'RESUME'});\n    //   let read: string = '';\n    //   pty.on('data', data => read += data);\n    //   pty.on('pause', () => read += 'paused');\n    //   pty.on('resume', () => read += 'resumed');\n    //   pty.write('1');\n    //   pty.write('PAUSE');\n    //   pty.write('2');\n    //   pty.write('RESUME');\n    //   pty.write('3');\n    //   await pollUntil(() => {\n    //     return stripEscapeSequences(read).endsWith('1pausedresumed23');\n    //   }, 100, 10);\n    // });\n  });\n});\n\nfunction stripEscapeSequences(data: string): string {\n  return data.replace(/\\u001b\\[0K/, '');\n}\n"
  },
  {
    "path": "src/terminal.ts",
    "content": "/**\n * Copyright (c) 2012-2015, Christopher Jeffrey (MIT License)\n * Copyright (c) 2016, Daniel Imms (MIT License).\n * Copyright (c) 2018, Microsoft Corporation (MIT License).\n */\n\nimport { Socket } from 'net';\nimport { EventEmitter } from 'events';\nimport { ITerminal, IPtyForkOptions, IProcessEnv } from './interfaces';\nimport { EventEmitter2, IEvent } from './eventEmitter2';\nimport { IExitEvent } from './types';\n\nexport const DEFAULT_COLS: number = 80;\nexport const DEFAULT_ROWS: number = 24;\n\n/**\n * Default messages to indicate PAUSE/RESUME for automatic flow control.\n * To avoid conflicts with rebound XON/XOFF control codes (such as on-my-zsh),\n * the sequences can be customized in `IPtyForkOptions`.\n */\nconst FLOW_CONTROL_PAUSE =  '\\x13';   // defaults to XOFF\nconst FLOW_CONTROL_RESUME = '\\x11';   // defaults to XON\n\nexport abstract class Terminal implements ITerminal {\n  protected _socket!: Socket; // HACK: This is unsafe\n  protected _pid: number = 0;\n  protected _fd: number = 0;\n  protected _pty: any;\n\n  protected _file!: string; // HACK: This is unsafe\n  protected _name!: string; // HACK: This is unsafe\n  protected _cols: number = 0;\n  protected _rows: number = 0;\n\n  protected _readable: boolean = false;\n  protected _writable: boolean = false;\n\n  protected _internalee: EventEmitter;\n  private _flowControlPause: string;\n  private _flowControlResume: string;\n  public handleFlowControl: boolean;\n\n  private _onData = new EventEmitter2<string>();\n  public get onData(): IEvent<string> { return this._onData.event; }\n  private _onExit = new EventEmitter2<IExitEvent>();\n  public get onExit(): IEvent<IExitEvent> { return this._onExit.event; }\n\n  public get pid(): number { return this._pid; }\n  public get cols(): number { return this._cols; }\n  public get rows(): number { return this._rows; }\n\n  constructor(opt?: IPtyForkOptions) {\n    // for 'close'\n    this._internalee = new EventEmitter();\n\n    // setup flow control handling\n    this.handleFlowControl = !!(opt?.handleFlowControl);\n    this._flowControlPause = opt?.flowControlPause || FLOW_CONTROL_PAUSE;\n    this._flowControlResume = opt?.flowControlResume || FLOW_CONTROL_RESUME;\n\n    if (!opt) {\n      return;\n    }\n\n    // Do basic type checks here in case node-pty is being used within JavaScript. If the wrong\n    // types go through to the C++ side it can lead to hard to diagnose exceptions.\n    this._checkType('name', opt.name ? opt.name : undefined, 'string');\n    this._checkType('cols', opt.cols ? opt.cols : undefined, 'number');\n    this._checkType('rows', opt.rows ? opt.rows : undefined, 'number');\n    this._checkType('cwd', opt.cwd ? opt.cwd : undefined, 'string');\n    this._checkType('env', opt.env ? opt.env : undefined, 'object');\n    this._checkType('uid', opt.uid ? opt.uid : undefined, 'number');\n    this._checkType('gid', opt.gid ? opt.gid : undefined, 'number');\n    this._checkType('encoding', opt.encoding ? opt.encoding : undefined, 'string');\n  }\n\n  protected abstract _write(data: string | Buffer): void;\n\n  public write(data: string | Buffer): void {\n    if (this.handleFlowControl) {\n      // PAUSE/RESUME messages are not forwarded to the pty\n      if (data === this._flowControlPause) {\n        this.pause();\n        return;\n      }\n      if (data === this._flowControlResume) {\n        this.resume();\n        return;\n      }\n    }\n    // everything else goes to the real pty\n    this._write(data);\n  }\n\n  protected _forwardEvents(): void {\n    this.on('data', e => this._onData.fire(e));\n    this.on('exit', (exitCode, signal) => this._onExit.fire({ exitCode, signal }));\n  }\n\n  protected _checkType<T>(name: string, value: T | undefined, type: string, allowArray: boolean = false): void {\n    if (value === undefined) {\n      return;\n    }\n    if (allowArray) {\n      if (Array.isArray(value)) {\n        value.forEach((v, i) => {\n          if (typeof v !== type) {\n            throw new Error(`${name}[${i}] must be a ${type} (not a ${typeof v[i]})`);\n          }\n        });\n        return;\n      }\n    }\n    if (typeof value !== type) {\n      throw new Error(`${name} must be a ${type} (not a ${typeof value})`);\n    }\n  }\n\n  /** See net.Socket.end */\n  public end(data: string): void {\n    this._socket.end(data);\n  }\n\n  /** See stream.Readable.pipe */\n  public pipe(dest: any, options: any): any {\n    return this._socket.pipe(dest, options);\n  }\n\n  /** See net.Socket.pause */\n  public pause(): Socket {\n    return this._socket.pause();\n  }\n\n  /** See net.Socket.resume */\n  public resume(): Socket {\n    return this._socket.resume();\n  }\n\n  /** See net.Socket.setEncoding */\n  public setEncoding(encoding: string | null): void {\n    if ((this._socket as any)._decoder) {\n      delete (this._socket as any)._decoder;\n    }\n    if (encoding) {\n      this._socket.setEncoding(encoding);\n    }\n  }\n\n  public addListener(eventName: string, listener: (...args: any[]) => any): void { this.on(eventName, listener); }\n  public on(eventName: string, listener: (...args: any[]) => any): void {\n    if (eventName === 'close') {\n      this._internalee.on('close', listener);\n      return;\n    }\n    this._socket.on(eventName, listener);\n  }\n\n  public emit(eventName: string, ...args: any[]): any {\n    if (eventName === 'close') {\n      return this._internalee.emit.apply(this._internalee, arguments as any);\n    }\n    return this._socket.emit.apply(this._socket, arguments as any);\n  }\n\n  public listeners(eventName: string): Function[] {\n    return this._socket.listeners(eventName);\n  }\n\n  public removeListener(eventName: string, listener: (...args: any[]) => any): void {\n    this._socket.removeListener(eventName, listener);\n  }\n\n  public removeAllListeners(eventName: string): void {\n    this._socket.removeAllListeners(eventName);\n  }\n\n  public once(eventName: string, listener: (...args: any[]) => any): void {\n    this._socket.once(eventName, listener);\n  }\n\n  public abstract resize(cols: number, rows: number, pixelSize?: { width: number, height: number }): void;\n  public abstract clear(): void;\n  public abstract destroy(): void;\n  public abstract kill(signal?: string): void;\n\n  public abstract get process(): string;\n  public abstract get master(): Socket| undefined;\n  public abstract get slave(): Socket | undefined;\n\n  protected _close(): void {\n    this._socket.readable = false;\n    this.write = () => {};\n    this.end = () => {};\n    this._writable = false;\n    this._readable = false;\n  }\n\n  protected _parseEnv(env: IProcessEnv): string[] {\n    const keys = Object.keys(env || {});\n    const pairs = [];\n\n    for (let i = 0; i < keys.length; i++) {\n      if (keys[i] === undefined) {\n        continue;\n      }\n      pairs.push(keys[i] + '=' + env[keys[i]]);\n    }\n\n    return pairs;\n  }\n}\n"
  },
  {
    "path": "src/testUtils.test.ts",
    "content": "/**\n * Copyright (c) 2019, Microsoft Corporation (MIT License).\n */\n\nexport function pollUntil(cb: () => boolean, timeout: number, interval: number): Promise<void> {\n  return new Promise<void>((resolve, reject) => {\n    const intervalId = setInterval(() => {\n      if (cb()) {\n        clearInterval(intervalId);\n        clearTimeout(timeoutId);\n        resolve();\n      }\n    }, interval);\n    const timeoutId = setTimeout(() => {\n      clearInterval(intervalId);\n      if (cb()) {\n        resolve();\n      } else {\n        reject();\n      }\n    }, timeout);\n  });\n}\n"
  },
  {
    "path": "src/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"es5\",\n    \"rootDir\": \".\",\n    \"outDir\": \"../lib\",\n    \"sourceMap\": true,\n    \"lib\": [\n      \"es2015\"\n    ],\n    \"strict\": true\n  },\n  \"exclude\": [\n    \"node_modules\",\n    \"scripts\",\n    \"index.js\",\n    \"demo.js\",\n    \"lib\",\n    \"test\",\n    \"examples\"\n  ]\n}\n"
  },
  {
    "path": "src/types.ts",
    "content": "/**\n * Copyright (c) 2017, Daniel Imms (MIT License).\n * Copyright (c) 2018, Microsoft Corporation (MIT License).\n */\n\nexport type ArgvOrCommandLine = string[] | string;\n\nexport interface IExitEvent {\n  exitCode: number;\n  signal: number | undefined;\n}\n\nexport interface IDisposable {\n  dispose(): void;\n}\n"
  },
  {
    "path": "src/unix/pty.cc",
    "content": "/**\n * Copyright (c) 2012-2015, Christopher Jeffrey (MIT License)\n * Copyright (c) 2017, Daniel Imms (MIT License)\n *\n * pty.cc:\n *   This file is responsible for starting processes\n *   with pseudo-terminal file descriptors.\n *\n * See:\n *   man pty\n *   man tty_ioctl\n *   man termios\n *   man forkpty\n */\n\n/**\n * Includes\n */\n\n#define NODE_ADDON_API_DISABLE_DEPRECATED\n#include <napi.h>\n#include <assert.h>\n#include <errno.h>\n#include <string.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <thread>\n\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <sys/ioctl.h>\n#include <sys/wait.h>\n#include <fcntl.h>\n#include <signal.h>\n\n/* forkpty */\n/* http://www.gnu.org/software/gnulib/manual/html_node/forkpty.html */\n#if defined(__linux__)\n#include <pty.h>\n#include <dirent.h>\n#include <sys/syscall.h>\n#elif defined(__APPLE__)\n#include <util.h>\n#elif defined(__FreeBSD__)\n#include <libutil.h>\n#include <termios.h>\n#elif defined(__OpenBSD__)\n#include <util.h>\n#include <termios.h>\n#endif\n\n/* Some platforms name VWERASE and VDISCARD differently */\n#if !defined(VWERASE) && defined(VWERSE)\n#define VWERASE\tVWERSE\n#endif\n#if !defined(VDISCARD) && defined(VDISCRD)\n#define VDISCARD\tVDISCRD\n#endif\n\n/* for pty_getproc */\n#if defined(__linux__)\n#include <stdio.h>\n#include <stdint.h>\n#elif defined(__APPLE__)\n#include <libproc.h>\n#include <os/availability.h>\n#include <paths.h>\n#include <spawn.h>\n#include <sys/event.h>\n#include <sys/sysctl.h>\n#include <termios.h>\n#endif\n\n/* NSIG - macro for highest signal + 1, should be defined */\n#ifndef NSIG\n#define NSIG 32\n#endif\n\n/* macOS 10.14 back does not define this constant */\n#ifndef POSIX_SPAWN_SETSID\n  #define POSIX_SPAWN_SETSID 1024\n#endif\n\n/* environ for execvpe */\n/* node/src/node_child_process.cc */\n#if !defined(__APPLE__)\nextern char **environ;\n#endif\n\n#if defined(__APPLE__)\nextern \"C\" {\n// Changes the current thread's directory to a path or directory file\n// descriptor. libpthread only exposes a syscall wrapper starting in\n// macOS 10.12, but the system call dates back to macOS 10.5. On older OSes,\n// the syscall is issued directly.\nint pthread_chdir_np(const char* dir) API_AVAILABLE(macosx(10.12));\nint pthread_fchdir_np(int fd) API_AVAILABLE(macosx(10.12));\n}\n\n#define HANDLE_EINTR(x) ({ \\\n  int eintr_wrapper_counter = 0; \\\n  decltype(x) eintr_wrapper_result; \\\n  do { \\\n    eintr_wrapper_result = (x); \\\n  } while (eintr_wrapper_result == -1 && errno == EINTR && \\\n           eintr_wrapper_counter++ < 100); \\\n  eintr_wrapper_result; \\\n})\n#endif\n\nstruct ExitEvent {\n  int exit_code = 0, signal_code = 0;\n};\n\n#if defined(__linux__)\n\nstatic int\nSetCloseOnExec(int fd) {\n  int flags = fcntl(fd, F_GETFD, 0);\n  if (flags == -1)\n    return flags;\n  if (flags & FD_CLOEXEC)\n    return 0;\n  return fcntl(fd, F_SETFD, flags | FD_CLOEXEC);\n}\n\n/**\n * Close all file descriptors >= 3 to prevent FD leakage to child processes.\n * Uses close_range() syscall on Linux 5.9+, falls back to /proc/self/fd iteration.\n */\nstatic void\npty_close_inherited_fds() {\n  // Try close_range() first (Linux 5.9+, glibc 2.34+)\n  #if defined(SYS_close_range) && defined(CLOSE_RANGE_CLOEXEC)\n  if (syscall(SYS_close_range, 3, ~0U, CLOSE_RANGE_CLOEXEC) == 0) {\n    return;\n  }\n  #endif\n\n  int fd;\n  // Set the CLOEXEC flag on all open descriptors. Unconditionally try the first\n  // 16 file descriptors. After that, bail out after the first error.\n  for (fd = 3; ; fd++)\n    if (SetCloseOnExec(fd) && fd > 15)\n      break;\n}\n#endif\n\nvoid SetupExitCallback(Napi::Env env, Napi::Function cb, pid_t pid) {\n  std::thread *th = new std::thread;\n  // Don't use Napi::AsyncWorker which is limited by UV_THREADPOOL_SIZE.\n  auto tsfn = Napi::ThreadSafeFunction::New(\n      env,\n      cb,                           // JavaScript function called asynchronously\n      \"SetupExitCallback_resource\", // Name\n      0,                            // Unlimited queue\n      1,                            // Only one thread will use this initially\n      [th](Napi::Env) {   // Finalizer used to clean threads up\n        th->join();\n        delete th;\n      });\n  *th = std::thread([tsfn = std::move(tsfn), pid] {\n    auto callback = [](Napi::Env env, Napi::Function cb, ExitEvent *exit_event) {\n      cb.Call({Napi::Number::New(env, exit_event->exit_code),\n               Napi::Number::New(env, exit_event->signal_code)});\n      delete exit_event;\n    };\n\n    int ret;\n    int stat_loc;\n#if defined(__APPLE__)\n    // Based on\n    // https://source.chromium.org/chromium/chromium/src/+/main:base/process/kill_mac.cc;l=35-69?\n    int kq = HANDLE_EINTR(kqueue());\n    struct kevent change = {0};\n    EV_SET(&change, pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL);\n    ret = HANDLE_EINTR(kevent(kq, &change, 1, NULL, 0, NULL));\n    if (ret == -1) {\n      if (errno == ESRCH) {\n        // At this point, one of the following has occurred:\n        // 1. The process has died but has not yet been reaped.\n        // 2. The process has died and has already been reaped.\n        // 3. The process is in the process of dying. It's no longer\n        //    kqueueable, but it may not be waitable yet either. Mark calls\n        //    this case the \"zombie death race\".\n        ret = HANDLE_EINTR(waitpid(pid, &stat_loc, WNOHANG));\n        if (ret == 0) {\n          ret = kill(pid, SIGKILL);\n          if (ret != -1) {\n            HANDLE_EINTR(waitpid(pid, &stat_loc, 0));\n          }\n        }\n      }\n    } else {\n      struct kevent event = {0};\n      ret = HANDLE_EINTR(kevent(kq, NULL, 0, &event, 1, NULL));\n      if (ret == 1) {\n        if ((event.fflags & NOTE_EXIT) &&\n            (event.ident == static_cast<uintptr_t>(pid))) {\n          // The process is dead or dying. This won't block for long, if at\n          // all.\n          HANDLE_EINTR(waitpid(pid, &stat_loc, 0));\n        }\n      }\n    }\n#else\n    while (true) {\n      errno = 0;\n      if ((ret = waitpid(pid, &stat_loc, 0)) != pid) {\n        if (ret == -1 && errno == EINTR) {\n          continue;\n        }\n        if (ret == -1 && errno == ECHILD) {\n          // XXX node v0.8.x seems to have this problem.\n          // waitpid is already handled elsewhere.\n          ;\n        } else {\n          assert(false);\n        }\n      }\n      break;\n    }\n#endif\n    ExitEvent *exit_event = new ExitEvent;\n    if (WIFEXITED(stat_loc)) {\n      exit_event->exit_code = WEXITSTATUS(stat_loc); // errno?\n    }\n    if (WIFSIGNALED(stat_loc)) {\n      exit_event->signal_code = WTERMSIG(stat_loc);\n    }\n    auto status = tsfn.BlockingCall(exit_event, callback); // In main thread\n    switch (status) {\n      case napi_closing:\n        break;\n\n      case napi_queue_full:\n        Napi::Error::Fatal(\"SetupExitCallback\", \"Queue was full\");\n\n      case napi_ok:\n        if (tsfn.Release() != napi_ok) {\n          Napi::Error::Fatal(\"SetupExitCallback\", \"ThreadSafeFunction.Release() failed\");\n        }\n        break;\n\n      default:\n        Napi::Error::Fatal(\"SetupExitCallback\", \"ThreadSafeFunction.BlockingCall() failed\");\n    }\n  });\n}\n\n/**\n * Methods\n */\n\nNapi::Value PtyFork(const Napi::CallbackInfo& info);\nNapi::Value PtyOpen(const Napi::CallbackInfo& info);\nNapi::Value PtyResize(const Napi::CallbackInfo& info);\nNapi::Value PtyGetProc(const Napi::CallbackInfo& info);\n\n/**\n * Functions\n */\n\nstatic int\npty_nonblock(int);\n\n#if defined(__APPLE__)\nstatic char *\npty_getproc(int);\n#else\nstatic char *\npty_getproc(int, char *);\n#endif\n\n#if defined(__APPLE__) || defined(__OpenBSD__)\nstatic void\npty_posix_spawn(char** argv, char** env,\n                const struct termios *termp,\n                const struct winsize *winp,\n                int* master,\n                pid_t* pid,\n                std::string* err);\n#endif\n\nstruct DelBuf {\n  int len;\n  DelBuf(int len) : len(len) {}\n  void operator()(char **p) {\n    if (p == nullptr)\n      return;\n    for (int i = 0; i < len; i++)\n      free(p[i]);\n    delete[] p;\n  }\n};\n\nNapi::Value PtyFork(const Napi::CallbackInfo& info) {\n  Napi::Env napiEnv(info.Env());\n  Napi::HandleScope scope(napiEnv);\n\n  if (info.Length() != 11 ||\n      !info[0].IsString() ||\n      !info[1].IsArray() ||\n      !info[2].IsArray() ||\n      !info[3].IsString() ||\n      !info[4].IsNumber() ||\n      !info[5].IsNumber() ||\n      !info[6].IsNumber() ||\n      !info[7].IsNumber() ||\n      !info[8].IsBoolean() ||\n      !info[9].IsString() ||\n      !info[10].IsFunction()) {\n    throw Napi::Error::New(napiEnv, \"Usage: pty.fork(file, args, env, cwd, cols, rows, uid, gid, utf8, helperPath, onexit)\");\n  }\n\n  // file\n  std::string file = info[0].As<Napi::String>();\n\n  // args\n  Napi::Array argv_ = info[1].As<Napi::Array>();\n\n  // env\n  Napi::Array env_ = info[2].As<Napi::Array>();\n  int envc = env_.Length();\n  std::unique_ptr<char *, DelBuf> env_unique_ptr(new char *[envc + 1], DelBuf(envc + 1));\n  char **env = env_unique_ptr.get();\n  env[envc] = NULL;\n  for (int i = 0; i < envc; i++) {\n    std::string pair = env_.Get(i).As<Napi::String>();\n    env[i] = strdup(pair.c_str());\n  }\n\n  // cwd\n  std::string cwd_ = info[3].As<Napi::String>();\n\n  // size\n  struct winsize winp;\n  winp.ws_col = info[4].As<Napi::Number>().Int32Value();\n  winp.ws_row = info[5].As<Napi::Number>().Int32Value();\n  winp.ws_xpixel = 0;\n  winp.ws_ypixel = 0;\n\n#if !defined(__APPLE__)\n  // uid / gid\n  int uid = info[6].As<Napi::Number>().Int32Value();\n  int gid = info[7].As<Napi::Number>().Int32Value();\n#endif\n\n  // termios\n  struct termios t = termios();\n  struct termios *term = &t;\n  term->c_iflag = ICRNL | IXON | IXANY | IMAXBEL | BRKINT;\n  if (info[8].As<Napi::Boolean>().Value()) {\n#if defined(IUTF8)\n    term->c_iflag |= IUTF8;\n#endif\n  }\n  term->c_oflag = OPOST | ONLCR;\n  term->c_cflag = CREAD | CS8 | HUPCL;\n  term->c_lflag = ICANON | ISIG | IEXTEN | ECHO | ECHOE | ECHOK | ECHOKE | ECHOCTL;\n\n  term->c_cc[VEOF] = 4;\n  term->c_cc[VEOL] = -1;\n  term->c_cc[VEOL2] = -1;\n  term->c_cc[VERASE] = 0x7f;\n  term->c_cc[VWERASE] = 23;\n  term->c_cc[VKILL] = 21;\n  term->c_cc[VREPRINT] = 18;\n  term->c_cc[VINTR] = 3;\n  term->c_cc[VQUIT] = 0x1c;\n  term->c_cc[VSUSP] = 26;\n  term->c_cc[VSTART] = 17;\n  term->c_cc[VSTOP] = 19;\n  term->c_cc[VLNEXT] = 22;\n  term->c_cc[VDISCARD] = 15;\n  term->c_cc[VMIN] = 1;\n  term->c_cc[VTIME] = 0;\n\n  #if (__APPLE__)\n  term->c_cc[VDSUSP] = 25;\n  term->c_cc[VSTATUS] = 20;\n  #endif\n\n  cfsetispeed(term, B38400);\n  cfsetospeed(term, B38400);\n\n  // helperPath\n  std::string helper_path = info[9].As<Napi::String>();\n\n  pid_t pid;\n  int master = -1;\n#if defined(__APPLE__)\n  int argc = argv_.Length();\n  int argl = argc + 4;\n  std::unique_ptr<char *, DelBuf> argv_unique_ptr(new char *[argl], DelBuf(argl));\n  char **argv = argv_unique_ptr.get();\n  argv[0] = strdup(helper_path.c_str());\n  argv[1] = strdup(cwd_.c_str());\n  argv[2] = strdup(file.c_str());\n  argv[argl - 1] = NULL;\n  for (int i = 0; i < argc; i++) {\n    std::string arg = argv_.Get(i).As<Napi::String>();\n    argv[i + 3] = strdup(arg.c_str());\n  }\n\n  std::string err;\n  pty_posix_spawn(argv, env, term, &winp, &master, &pid, &err);\n  if (!err.empty()) {\n    if (master != -1) {\n      close(master);\n    }\n    throw Napi::Error::New(napiEnv, err);\n  }\n  if (pty_nonblock(master) == -1) {\n    throw Napi::Error::New(napiEnv, \"Could not set master fd to nonblocking.\");\n  }\n#else\n  int argc = argv_.Length();\n  int argl = argc + 2;\n  std::unique_ptr<char *, DelBuf> argv_unique_ptr(new char *[argl], DelBuf(argl));\n  char** argv = argv_unique_ptr.get();\n  argv[0] = strdup(file.c_str());\n  argv[argl - 1] = NULL;\n  for (int i = 0; i < argc; i++) {\n    std::string arg = argv_.Get(i).As<Napi::String>();\n    argv[i + 1] = strdup(arg.c_str());\n  }\n\n  sigset_t newmask, oldmask;\n  struct sigaction sig_action;\n  // temporarily block all signals\n  // this is needed due to a race condition in openpty\n  // and to avoid running signal handlers in the child\n  // before exec* happened\n  sigfillset(&newmask);\n  pthread_sigmask(SIG_SETMASK, &newmask, &oldmask);\n\n  pid = forkpty(&master, nullptr, static_cast<termios*>(term), static_cast<winsize*>(&winp));\n\n  if (!pid) {\n    // remove all signal handler from child\n    sig_action.sa_handler = SIG_DFL;\n    sig_action.sa_flags = 0;\n    sigemptyset(&sig_action.sa_mask);\n    for (int i = 0 ; i < NSIG ; i++) {    // NSIG is a macro for all signals + 1\n      sigaction(i, &sig_action, NULL);\n    }\n  }\n\n  // reenable signals\n  pthread_sigmask(SIG_SETMASK, &oldmask, NULL);\n\n  switch (pid) {\n    case -1:\n      throw Napi::Error::New(napiEnv, \"forkpty(3) failed.\");\n    case 0:\n      if (strlen(cwd_.c_str())) {\n        if (chdir(cwd_.c_str()) == -1) {\n          perror(\"chdir(2) failed.\");\n          _exit(1);\n        }\n      }\n\n      if (uid != -1 && gid != -1) {\n        if (setgid(gid) == -1) {\n          perror(\"setgid(2) failed.\");\n          _exit(1);\n        }\n        if (setuid(uid) == -1) {\n          perror(\"setuid(2) failed.\");\n          _exit(1);\n        }\n      }\n\n      // Close inherited FDs to prevent leaking pty master FDs to child\n      pty_close_inherited_fds();\n\n      {\n        char **old = environ;\n        environ = env;\n        execvp(argv[0], argv);\n        environ = old;\n        perror(\"execvp(3) failed.\");\n        _exit(1);\n      }\n    default:\n      if (pty_nonblock(master) == -1) {\n        throw Napi::Error::New(napiEnv, \"Could not set master fd to nonblocking.\");\n      }\n  }\n#endif\n\n  Napi::Object obj = Napi::Object::New(napiEnv);\n  obj.Set(\"fd\", Napi::Number::New(napiEnv, master));\n  obj.Set(\"pid\", Napi::Number::New(napiEnv, pid));\n  obj.Set(\"pty\", Napi::String::New(napiEnv, ptsname(master)));\n\n  // Set up process exit callback.\n  Napi::Function cb = info[10].As<Napi::Function>();\n  SetupExitCallback(napiEnv, cb, pid);\n  return obj;\n}\n\nNapi::Value PtyOpen(const Napi::CallbackInfo& info) {\n  Napi::Env env(info.Env());\n  Napi::HandleScope scope(env);\n\n  if (info.Length() != 2 ||\n      !info[0].IsNumber() ||\n      !info[1].IsNumber()) {\n    throw Napi::Error::New(env, \"Usage: pty.open(cols, rows)\");\n  }\n\n  // size\n  struct winsize winp;\n  winp.ws_col = info[0].As<Napi::Number>().Int32Value();\n  winp.ws_row = info[1].As<Napi::Number>().Int32Value();\n  winp.ws_xpixel = 0;\n  winp.ws_ypixel = 0;\n\n  // pty\n  int master, slave;\n  int ret = openpty(&master, &slave, nullptr, NULL, static_cast<winsize*>(&winp));\n\n  if (ret == -1) {\n    throw Napi::Error::New(env, \"openpty(3) failed.\");\n  }\n\n  if (pty_nonblock(master) == -1) {\n    throw Napi::Error::New(env, \"Could not set master fd to nonblocking.\");\n  }\n\n  if (pty_nonblock(slave) == -1) {\n    throw Napi::Error::New(env, \"Could not set slave fd to nonblocking.\");\n  }\n\n  Napi::Object obj = Napi::Object::New(env);\n  obj.Set(\"master\", Napi::Number::New(env, master));\n  obj.Set(\"slave\", Napi::Number::New(env, slave));\n  obj.Set(\"pty\", Napi::String::New(env, ptsname(master)));\n\n  return obj;\n}\n\nNapi::Value PtyResize(const Napi::CallbackInfo& info) {\n  Napi::Env env(info.Env());\n  Napi::HandleScope scope(env);\n\n  if (info.Length() != 5 ||\n      !info[0].IsNumber() ||\n      !info[1].IsNumber() ||\n      !info[2].IsNumber() ||\n      !info[3].IsNumber() ||\n      !info[4].IsNumber()) {\n    throw Napi::Error::New(env, \"Usage: pty.resize(fd, cols, rows, xPixel, yPixel)\");\n  }\n\n  int fd = info[0].As<Napi::Number>().Int32Value();\n\n  struct winsize winp;\n  winp.ws_col = info[1].As<Napi::Number>().Int32Value();\n  winp.ws_row = info[2].As<Napi::Number>().Int32Value();\n  winp.ws_xpixel = info[3].As<Napi::Number>().Int32Value();\n  winp.ws_ypixel = info[4].As<Napi::Number>().Int32Value();\n\n  if (ioctl(fd, TIOCSWINSZ, &winp) == -1) {\n    switch (errno) {\n      case EBADF:\n        throw Napi::Error::New(env, \"ioctl(2) failed, EBADF\");\n      case EFAULT:\n        throw Napi::Error::New(env, \"ioctl(2) failed, EFAULT\");\n      case EINVAL:\n        throw Napi::Error::New(env, \"ioctl(2) failed, EINVAL\");\n      case ENOTTY:\n        throw Napi::Error::New(env, \"ioctl(2) failed, ENOTTY\");\n    }\n    throw Napi::Error::New(env, \"ioctl(2) failed\");\n  }\n\n  return env.Undefined();\n}\n\n/**\n * Foreground Process Name\n */\nNapi::Value PtyGetProc(const Napi::CallbackInfo& info) {\n  Napi::Env env(info.Env());\n  Napi::HandleScope scope(env);\n\n#if defined(__APPLE__)\n  if (info.Length() != 1 ||\n      !info[0].IsNumber()) {\n    throw Napi::Error::New(env, \"Usage: pty.process(pid)\");\n  }\n\n  int fd = info[0].As<Napi::Number>().Int32Value();\n  char *name = pty_getproc(fd);\n#else\n  if (info.Length() != 2 ||\n      !info[0].IsNumber() ||\n      !info[1].IsString()) {\n    throw Napi::Error::New(env, \"Usage: pty.process(fd, tty)\");\n  }\n\n  int fd = info[0].As<Napi::Number>().Int32Value();\n\n  std::string tty_ = info[1].As<Napi::String>();\n  char *tty = strdup(tty_.c_str());\n  char *name = pty_getproc(fd, tty);\n  free(tty);\n#endif\n\n  if (name == NULL) {\n    return env.Undefined();\n  }\n\n  Napi::String name_ = Napi::String::New(env, name);\n  free(name);\n  return name_;\n}\n\n/**\n * Nonblocking FD\n */\n\nstatic int\npty_nonblock(int fd) {\n  int flags = fcntl(fd, F_GETFL, 0);\n  if (flags == -1) return -1;\n  return fcntl(fd, F_SETFL, flags | O_NONBLOCK);\n}\n\n/**\n * pty_getproc\n * Taken from tmux.\n */\n\n// Taken from: tmux (http://tmux.sourceforge.net/)\n// Copyright (c) 2009 Nicholas Marriott <nicm@users.sourceforge.net>\n// Copyright (c) 2009 Joshua Elsasser <josh@elsasser.org>\n// Copyright (c) 2009 Todd Carson <toc@daybefore.net>\n//\n// Permission to use, copy, modify, and distribute this software for any\n// purpose with or without fee is hereby granted, provided that the above\n// copyright notice and this permission notice appear in all copies.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n// WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER\n// IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING\n// OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n#if defined(__linux__)\n\nstatic char *\npty_getproc(int fd, char *tty) {\n  FILE *f;\n  char *path, *buf;\n  size_t len;\n  int ch;\n  pid_t pgrp;\n  int r;\n\n  if ((pgrp = tcgetpgrp(fd)) == -1) {\n    return NULL;\n  }\n\n  r = asprintf(&path, \"/proc/%lld/cmdline\", (long long)pgrp);\n  if (r == -1 || path == NULL) return NULL;\n\n  if ((f = fopen(path, \"r\")) == NULL) {\n    free(path);\n    return NULL;\n  }\n\n  free(path);\n\n  len = 0;\n  buf = NULL;\n  while ((ch = fgetc(f)) != EOF) {\n    if (ch == '\\0') break;\n    buf = (char *)realloc(buf, len + 2);\n    if (buf == NULL) return NULL;\n    buf[len++] = ch;\n  }\n\n  if (buf != NULL) {\n    buf[len] = '\\0';\n  }\n\n  fclose(f);\n  return buf;\n}\n\n#elif defined(__APPLE__)\n\nstatic char *\npty_getproc(int fd) {\n  int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, 0 };\n  size_t size;\n  struct kinfo_proc kp;\n\n  if ((mib[3] = tcgetpgrp(fd)) == -1) {\n    return NULL;\n  }\n\n  size = sizeof kp;\n  if (sysctl(mib, 4, &kp, &size, NULL, 0) == -1) {\n    return NULL;\n  }\n\n  if (size != (sizeof kp) || *kp.kp_proc.p_comm == '\\0') {\n    return NULL;\n  }\n\n  return strdup(kp.kp_proc.p_comm);\n}\n\n#else\n\nstatic char *\npty_getproc(int fd, char *tty) {\n  return NULL;\n}\n\n#endif\n\n#if defined(__APPLE__)\nstatic std::string format_error(const char* func, int err_code) {\n  char buf[256];\n  snprintf(buf, sizeof(buf), \"%s: %s\", func, strerror(err_code));\n  return buf;\n}\n\nstatic void\npty_posix_spawn(char** argv, char** env,\n                const struct termios *termp,\n                const struct winsize *winp,\n                int* master,\n                pid_t* pid,\n                std::string* err) {\n  int low_fds[3];\n  size_t count = 0;\n  int res = 0;\n  int slave = -1;\n  char slave_pty_name[128];\n  int spawn_err;\n  sigset_t signal_set;\n\n  for (; count < 3; count++) {\n    low_fds[count] = posix_openpt(O_RDWR);\n    if (low_fds[count] >= STDERR_FILENO)\n      break;\n  }\n\n  int flags = POSIX_SPAWN_CLOEXEC_DEFAULT |\n              POSIX_SPAWN_SETSIGDEF |\n              POSIX_SPAWN_SETSIGMASK |\n              POSIX_SPAWN_SETSID;\n\n  posix_spawn_file_actions_t acts;\n  posix_spawn_file_actions_init(&acts);\n\n  posix_spawnattr_t attrs;\n  posix_spawnattr_init(&attrs);\n\n  *master = posix_openpt(O_RDWR);\n  if (*master == -1) {\n    *err = format_error(\"posix_openpt failed\", errno);\n    goto done;\n  }\n\n  res = grantpt(*master);\n  if (res == -1) {\n    *err = format_error(\"grantpt failed\", errno);\n    goto done;\n  }\n\n  res = unlockpt(*master);\n  if (res == -1) {\n    *err = format_error(\"unlockpt failed\", errno);\n    goto done;\n  }\n\n  // Use TIOCPTYGNAME instead of ptsname() to avoid threading problems.\n  res = ioctl(*master, TIOCPTYGNAME, slave_pty_name);\n  if (res == -1) {\n    *err = format_error(\"ioctl(TIOCPTYGNAME) failed\", errno);\n    goto done;\n  }\n\n  slave = open(slave_pty_name, O_RDWR | O_NOCTTY);\n  if (slave == -1) {\n    *err = format_error(\"open slave pty failed\", errno);\n    goto done;\n  }\n\n  if (termp) {\n    res = tcsetattr(slave, TCSANOW, termp);\n    if (res == -1) {\n      *err = format_error(\"tcsetattr failed\", errno);\n      goto done;\n    };\n  }\n\n  if (winp) {\n    res = ioctl(slave, TIOCSWINSZ, winp);\n    if (res == -1) {\n      *err = format_error(\"ioctl(TIOCSWINSZ) failed\", errno);\n      goto done;\n    }\n  }\n\n  posix_spawn_file_actions_adddup2(&acts, slave, STDIN_FILENO);\n  posix_spawn_file_actions_adddup2(&acts, slave, STDOUT_FILENO);\n  posix_spawn_file_actions_adddup2(&acts, slave, STDERR_FILENO);\n  posix_spawn_file_actions_addclose(&acts, slave);\n  posix_spawn_file_actions_addclose(&acts, *master);\n\n  spawn_err = posix_spawnattr_setflags(&attrs, flags);\n  if (spawn_err != 0) {\n    *err = format_error(\"posix_spawnattr_setflags failed\", spawn_err);\n    goto done;\n  }\n\n  /* Reset all signal the child to their default behavior */\n  sigfillset(&signal_set);\n  spawn_err = posix_spawnattr_setsigdefault(&attrs, &signal_set);\n  if (spawn_err != 0) {\n    *err = format_error(\"posix_spawnattr_setsigdefault failed\", spawn_err);\n    goto done;\n  }\n\n  /* Reset the signal mask for all signals */\n  sigemptyset(&signal_set);\n  spawn_err = posix_spawnattr_setsigmask(&attrs, &signal_set);\n  if (spawn_err != 0) {\n    *err = format_error(\"posix_spawnattr_setsigmask failed\", spawn_err);\n    goto done;\n  }\n\n  do\n    spawn_err = posix_spawn(pid, argv[0], &acts, &attrs, argv, env);\n  while (spawn_err == EINTR);\n  if (spawn_err != 0) {\n    *err = format_error(\"posix_spawn failed\", spawn_err);\n  }\ndone:\n  posix_spawn_file_actions_destroy(&acts);\n  posix_spawnattr_destroy(&attrs);\n  if (slave != -1) {\n    close(slave);\n  }\n\n  for (size_t i = 0; i <= count; i++) {\n    close(low_fds[i]);\n  }\n}\n#endif\n\n/**\n * Init\n */\n\nNapi::Object init(Napi::Env env, Napi::Object exports) {\n  exports.Set(\"fork\",    Napi::Function::New(env, PtyFork));\n  exports.Set(\"open\",    Napi::Function::New(env, PtyOpen));\n  exports.Set(\"resize\",  Napi::Function::New(env, PtyResize));\n  exports.Set(\"process\", Napi::Function::New(env, PtyGetProc));\n  return exports;\n}\n\nNODE_API_MODULE(NODE_GYP_MODULE_NAME, init)\n"
  },
  {
    "path": "src/unix/spawn-helper.cc",
    "content": "#include <errno.h>\n#include <fcntl.h>\n#include <string.h>\n#include <unistd.h>\n\nint main (int argc, char** argv) {\n  char *slave_path = ttyname(STDIN_FILENO);\n  // open implicit attaches a process to a terminal device if:\n  // - process has no controlling terminal yet\n  // - O_NOCTTY is not set\n  close(open(slave_path, O_RDWR));\n\n  char *cwd = argv[1];\n  char *file = argv[2];\n  argv = &argv[2];\n\n  if (strlen(cwd) && chdir(cwd) == -1) {\n    _exit(1);\n  }\n\n  execvp(file, argv);\n  return 1;\n}\n"
  },
  {
    "path": "src/unixTerminal.test.ts",
    "content": "/**\n * Copyright (c) 2017, Daniel Imms (MIT License).\n * Copyright (c) 2018, Microsoft Corporation (MIT License).\n */\n\nimport * as assert from 'assert';\nimport * as cp from 'child_process';\nimport * as path from 'path';\nimport * as tty from 'tty';\nimport * as fs from 'fs';\nimport { constants } from 'os';\nimport { pollUntil } from './testUtils.test';\nimport { pid } from 'process';\nimport type { UnixTerminal as UnixTerminalType } from './unixTerminal';\n\nconst FIXTURES_PATH = path.normalize(path.join(__dirname, '..', 'fixtures', 'utf8-character.txt'));\n\nif (process.platform !== 'win32') {\n  // Dynamic require to avoid loading pty.node on Windows\n  // eslint-disable-next-line @typescript-eslint/naming-convention\n  const { UnixTerminal } = require('./unixTerminal') as { UnixTerminal: typeof UnixTerminalType };\n\n  describe('UnixTerminal', () => {\n    describe('Constructor', () => {\n      it('should set a valid pts name', () => {\n        const term = new UnixTerminal('/bin/bash', [], {});\n        let regExp: RegExp | undefined;\n        if (process.platform === 'linux') {\n          // https://linux.die.net/man/4/pts\n          regExp = /^\\/dev\\/pts\\/\\d+$/;\n        }\n        if (process.platform === 'darwin') {\n          // https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man4/pty.4.html\n          regExp = /^\\/dev\\/tty[p-sP-S][a-z0-9]+$/;\n        }\n        if (regExp) {\n          assert.ok(regExp.test(term.ptsName), '\"' + term.ptsName + '\" should match ' + regExp.toString());\n        }\n        assert.ok(tty.isatty(term.fd));\n      });\n    });\n\n    describe('PtyForkEncodingOption', () => {\n      it('should default to utf8', (done) => {\n        const term = new UnixTerminal('/bin/bash', [ '-c', `cat \"${FIXTURES_PATH}\"` ]);\n        term.on('data', (data) => {\n          assert.strictEqual(typeof data, 'string');\n          assert.strictEqual(data, '\\u00E6');\n          done();\n        });\n      });\n      it('should return a Buffer when encoding is null', (done) => {\n        const term = new UnixTerminal('/bin/bash', [ '-c', `cat \"${FIXTURES_PATH}\"` ], {\n          encoding: null\n        });\n        term.on('data', (data) => {\n          assert.strictEqual(typeof data, 'object');\n          assert.ok(data instanceof Buffer);\n          assert.strictEqual(0xC3, data[0]);\n          assert.strictEqual(0xA6, data[1]);\n          done();\n        });\n      });\n      it('should support other encodings', (done) => {\n        const text = 'test æ!';\n        const term = new UnixTerminal(undefined, ['-c', 'echo \"' + text + '\"'], {\n          encoding: 'base64'\n        });\n        let buffer = '';\n        term.onData((data) => {\n          assert.strictEqual(typeof data, 'string');\n          buffer += data;\n        });\n        term.onExit(() => {\n          assert.strictEqual(Buffer.alloc(8, buffer, 'base64').toString().replace('\\r', '').replace('\\n', ''), text);\n          done();\n        });\n      });\n    });\n\n    describe('open', () => {\n      let term: UnixTerminalType;\n\n      afterEach(() => {\n        if (term) {\n          term.slave!.destroy();\n          term.master!.destroy();\n        }\n      });\n\n      it('should open a pty with access to a master and slave socket', (done) => {\n        term = UnixTerminal.open({});\n\n        let slavebuf = '';\n        term.slave!.on('data', (data) => {\n          slavebuf += data;\n        });\n\n        let masterbuf = '';\n        term.master!.on('data', (data) => {\n          masterbuf += data;\n        });\n\n        pollUntil(() => {\n          if (masterbuf === 'slave\\r\\nmaster\\r\\n' && slavebuf === 'master\\n') {\n            done();\n            return true;\n          }\n          return false;\n        }, 200, 10);\n\n        term.slave!.write('slave\\n');\n        term.master!.write('master\\n');\n      });\n    });\n    describe('close', () => {\n      const term = new UnixTerminal('node');\n      it('should exit when terminal is destroyed programmatically', (done) => {\n        term.on('exit', (code, signal) => {\n          assert.strictEqual(code, 0);\n          assert.strictEqual(signal, constants.signals.SIGHUP);\n          done();\n        });\n        term.destroy();\n      });\n    });\n    describe('signals in parent and child', () => {\n      it('SIGINT - custom in parent and child', done => {\n        // this test is cumbersome - we have to run it in a sub process to\n        // see behavior of SIGINT handlers\n        const data = `\n        var pty = require('./lib/index');\n        process.on('SIGINT', () => console.log('SIGINT in parent'));\n        var ptyProcess = pty.spawn('node', ['-e', 'process.on(\"SIGINT\", ()=>console.log(\"SIGINT in child\"));setTimeout(() => null, 300);'], {\n          name: 'xterm-color',\n          cols: 80,\n          rows: 30,\n          cwd: process.env.HOME,\n          env: process.env\n        });\n        ptyProcess.on('data', function (data) {\n          console.log(data);\n        });\n        setTimeout(() => null, 500);\n        console.log('ready', ptyProcess.pid);\n        `;\n        const buffer: string[] = [];\n        const p = cp.spawn('node', ['-e', data]);\n        let sub = '';\n        p.stdout.on('data', (data) => {\n          if (!data.toString().indexOf('ready')) {\n            sub = data.toString().split(' ')[1].slice(0, -1);\n            setTimeout(() => {\n              process.kill(parseInt(sub), 'SIGINT');  // SIGINT to child\n              p.kill('SIGINT');                       // SIGINT to parent\n            }, 200);\n          } else {\n            buffer.push(data.toString().replace(/^\\s+|\\s+$/g, ''));\n          }\n        });\n        p.on('close', () => {\n          // handlers in parent and child should have been triggered\n          assert.strictEqual(buffer.indexOf('SIGINT in child') !== -1, true);\n          assert.strictEqual(buffer.indexOf('SIGINT in parent') !== -1, true);\n          done();\n        });\n      });\n      it('SIGINT - custom in parent, default in child', done => {\n        // this tests the original idea of the signal(...) change in pty.cc:\n        // to make sure the SIGINT handler of a pty child is reset to default\n        // and does not interfere with the handler in the parent\n        const data = `\n        var pty = require('./lib/index');\n        process.on('SIGINT', () => console.log('SIGINT in parent'));\n        var ptyProcess = pty.spawn('node', ['-e', 'setTimeout(() => console.log(\"should not be printed\"), 300);'], {\n          name: 'xterm-color',\n          cols: 80,\n          rows: 30,\n          cwd: process.env.HOME,\n          env: process.env\n        });\n        ptyProcess.on('data', function (data) {\n          console.log(data);\n        });\n        setTimeout(() => null, 500);\n        console.log('ready', ptyProcess.pid);\n        `;\n        const buffer: string[] = [];\n        const p = cp.spawn('node', ['-e', data]);\n        let sub = '';\n        p.stdout.on('data', (data) => {\n          if (!data.toString().indexOf('ready')) {\n            sub = data.toString().split(' ')[1].slice(0, -1);\n            setTimeout(() => {\n              process.kill(parseInt(sub), 'SIGINT');  // SIGINT to child\n              p.kill('SIGINT');                       // SIGINT to parent\n            }, 200);\n          } else {\n            buffer.push(data.toString().replace(/^\\s+|\\s+$/g, ''));\n          }\n        });\n        p.on('close', () => {\n          // handlers in parent and child should have been triggered\n          assert.strictEqual(buffer.indexOf('should not be printed') !== -1, false);\n          assert.strictEqual(buffer.indexOf('SIGINT in parent') !== -1, true);\n          done();\n        });\n      });\n      it('SIGHUP default (child only)', done => {\n        const term = new UnixTerminal('node', [ '-e', `\n        console.log('ready');\n        setTimeout(()=>console.log('timeout'), 200);`\n        ]);\n        let buffer = '';\n        term.on('data', (data) => {\n          if (data === 'ready\\r\\n') {\n            term.kill();\n          } else {\n            buffer += data;\n          }\n        });\n        term.on('exit', () => {\n          // no timeout in buffer\n          assert.strictEqual(buffer, '');\n          done();\n        });\n      });\n      it('SIGUSR1 - custom in parent and child', done => {\n        let pHandlerCalled = 0;\n        const handleSigUsr = function(h: any): any {\n          return function(): void {\n            pHandlerCalled += 1;\n            process.removeListener('SIGUSR1', h);\n          };\n        };\n        process.on('SIGUSR1', handleSigUsr(handleSigUsr));\n\n        const term = new UnixTerminal('node', [ '-e', `\n        process.on('SIGUSR1', () => {\n          console.log('SIGUSR1 in child');\n        });\n        console.log('ready');\n        setTimeout(()=>null, 200);`\n        ]);\n        let buffer = '';\n        term.on('data', (data) => {\n          if (data === 'ready\\r\\n') {\n            process.kill(process.pid, 'SIGUSR1');\n            term.kill('SIGUSR1');\n          } else {\n            buffer += data;\n          }\n        });\n        term.on('exit', () => {\n          // should have called both handlers and only once\n          assert.strictEqual(pHandlerCalled, 1);\n          assert.strictEqual(buffer, 'SIGUSR1 in child\\r\\n');\n          done();\n        });\n      });\n    });\n    describe('spawn', () => {\n      if (process.platform === 'linux') {\n        it('should not leak pty file descriptors to child processes', (done) => {\n          // Spawn 3 ptys - the 3rd should not see FDs from the first two\n          const ptys: UnixTerminalType[] = [];\n          for (let i = 0; i < 3; i++) {\n            ptys.push(new UnixTerminal('/bin/bash', [], {}));\n          }\n\n          let output = '';\n          ptys[2].onData((data) => {\n            output += data;\n          });\n\n          // Check for ptmx FDs in the 3rd terminal's shell\n          ptys[2].write('echo \"PTMX_COUNT:$(file /proc/$$/fd/* 2>/dev/null | grep -c ptmx)\"\\n');\n\n          setTimeout(() => {\n            for (const pty of ptys) {\n              pty.kill();\n            }\n            // Extract the count from output - should be 0\n            const match = output.match(/PTMX_COUNT:(\\d+)/);\n            assert.ok(match, `Could not find PTMX_COUNT in output: ${output}`);\n            assert.strictEqual(match![1], '0', `Expected 0 ptmx FDs but got ${match![1]}`);\n            done();\n          }, 1000);\n        });\n      }\n      if (process.platform === 'darwin') {\n        it('should return the name of the process', (done) => {\n          const term = new UnixTerminal('/bin/echo');\n          assert.strictEqual(term.process, '/bin/echo');\n          term.on('exit', () => done());\n          term.destroy();\n        });\n        it('should return the name of the sub process', (done) => {\n          const data = `\n          var pty = require('./lib/index');\n          var ptyProcess = pty.spawn('zsh', ['-c', 'python3'], {\n            env: process.env\n          });\n          ptyProcess.on('data', function (data) {\n            if (ptyProcess.process === 'Python') {\n              console.log('title', ptyProcess.process);\n              console.log('ready', ptyProcess.pid);\n            }\n          });\n          `;\n          const p = cp.spawn('node', ['-e', data]);\n          let sub = '';\n          let pid = '';\n          p.stdout.on('data', (data) => {\n            const lines = data.toString().split('\\n');\n            for (const line of lines) {\n              if (line.startsWith('title ')) {\n                sub = line.split(' ')[1];\n              } else if (line.startsWith('ready ')) {\n                pid = line.split(' ')[1];\n                process.kill(parseInt(pid), 'SIGINT');\n                p.kill('SIGINT');\n              }\n            }\n          });\n          p.on('exit', () => {\n            assert.notStrictEqual(pid, '');\n            assert.strictEqual(sub, 'Python');\n            done();\n          });\n        });\n        it('should close on exec', (done) => {\n          const data = `\n          var pty = require('./lib/index');\n          var ptyProcess = pty.spawn('node', ['-e', 'setTimeout(() => console.log(\"hello from terminal\"), 300);']);\n          ptyProcess.on('data', function (data) {\n            console.log(data);\n          });\n          setTimeout(() => null, 500);\n          console.log('ready', ptyProcess.pid);\n          `;\n          const buffer: string[] = [];\n          const readFd = fs.openSync(FIXTURES_PATH, 'r');\n          const p = cp.spawn('node', ['-e', data], {\n            stdio: ['ignore', 'pipe', 'pipe', readFd]\n          });\n          let sub = '';\n          p.stdout!.on('data', (data) => {\n            if (!data.toString().indexOf('ready')) {\n              sub = data.toString().split(' ')[1].slice(0, -1);\n              try {\n                fs.statSync(`/proc/${sub}/fd/${readFd}`);\n                done('not reachable');\n              } catch (error) {\n                assert.notStrictEqual((error as NodeJS.ErrnoException).message.indexOf('ENOENT'), -1);\n              }\n              setTimeout(() => {\n                process.kill(parseInt(sub), 'SIGINT');  // SIGINT to child\n                p.kill('SIGINT');                       // SIGINT to parent\n              }, 200);\n            } else {\n              buffer.push(data.toString().replace(/^\\s+|\\s+$/g, ''));\n            }\n          });\n          p.on('close', () => {\n            done();\n          });\n        });\n        it('should not leak /dev/ptmx file descriptors after pty exit', async function(): Promise<void> {\n          this.timeout(30000);\n\n          const getPtmxFDCount = (): number => {\n            try {\n              const output = cp.execSync(`lsof -p ${process.pid} 2>/dev/null`, { encoding: 'utf8' });\n              return output.split('\\n').filter(line => line.includes('ptmx')).length;\n            } catch {\n              return 0;\n            }\n          };\n\n          const initialCount = getPtmxFDCount();\n          for (let i = 0; i < 20; i++) {\n            const term = new UnixTerminal('/bin/bash', ['-c', 'echo hello']);\n            await new Promise<void>(resolve => {\n              term.onExit(() => {\n                term.destroy();\n                resolve();\n              });\n            });\n          }\n\n          await new Promise(r => setTimeout(r, 500));\n\n          const finalCount = getPtmxFDCount();\n          assert.ok(\n            finalCount <= initialCount,\n            `Leaked ${finalCount - initialCount} /dev/ptmx FDs after spawning 20 PTYs (initial: ${initialCount}, final: ${finalCount})`\n          );\n        });\n      }\n      it('should handle exec() errors', (done) => {\n        const term = new UnixTerminal('/bin/bogus.exe', []);\n        term.on('exit', (code, signal) => {\n          assert.strictEqual(code, 1);\n          done();\n        });\n      });\n      it('should handle chdir() errors', (done) => {\n        const term = new UnixTerminal('/bin/echo', [], { cwd: '/nowhere' });\n        term.on('exit', (code, signal) => {\n          assert.strictEqual(code, 1);\n          done();\n        });\n      });\n      it('should not leak child process', (done) => {\n        const count = cp.execSync('ps -ax | grep node | wc -l');\n        const term = new UnixTerminal('node', [ '-e', `\n          console.log('ready');\n          setTimeout(()=>console.log('timeout'), 200);`\n        ]);\n        term.on('data', async (data) => {\n          if (data === 'ready\\r\\n') {\n            process.kill(term.pid, 'SIGINT');\n            await setTimeout(() => null, 1000);\n            const newCount = cp.execSync('ps -ax | grep node | wc -l');\n            assert.strictEqual(count.toString(), newCount.toString());\n            done();\n          }\n        });\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "src/unixTerminal.ts",
    "content": "/**\n * Copyright (c) 2012-2015, Christopher Jeffrey (MIT License)\n * Copyright (c) 2016, Daniel Imms (MIT License).\n * Copyright (c) 2018, Microsoft Corporation (MIT License).\n */\nimport * as fs from 'fs';\nimport * as net from 'net';\nimport * as path from 'path';\nimport * as tty from 'tty';\nimport { Terminal, DEFAULT_COLS, DEFAULT_ROWS } from './terminal';\nimport { IProcessEnv, IPtyForkOptions, IPtyOpenOptions } from './interfaces';\nimport { ArgvOrCommandLine, IDisposable } from './types';\nimport { assign, loadNativeModule } from './utils';\n\nconst native = loadNativeModule('pty');\nconst pty: IUnixNative = native.module;\nlet helperPath = native.dir + '/spawn-helper';\nhelperPath = path.resolve(__dirname, helperPath);\nhelperPath = helperPath.replace('app.asar', 'app.asar.unpacked');\nhelperPath = helperPath.replace('node_modules.asar', 'node_modules.asar.unpacked');\n\nconst DEFAULT_FILE = 'sh';\nconst DEFAULT_NAME = 'xterm';\nconst DESTROY_SOCKET_TIMEOUT_MS = 200;\n\nexport class UnixTerminal extends Terminal {\n  protected _fd: number;\n  protected _pty: string;\n\n  protected _file: string;\n  protected _name: string;\n\n  protected _readable: boolean;\n  protected _writable: boolean;\n\n  private _boundClose: boolean = false;\n  private _emittedClose: boolean = false;\n\n  private _writeStream: CustomWriteStream;\n\n  private _master: net.Socket | undefined;\n  private _slave: net.Socket | undefined;\n\n  public get master(): net.Socket | undefined { return this._master; }\n  public get slave(): net.Socket | undefined { return this._slave; }\n\n  constructor(file?: string, args?: ArgvOrCommandLine, opt?: IPtyForkOptions) {\n    super(opt);\n\n    if (typeof args === 'string') {\n      throw new Error('args as a string is not supported on unix.');\n    }\n\n    // Initialize arguments\n    args = args || [];\n    file = file || DEFAULT_FILE;\n    opt = opt || {};\n    opt.env = opt.env || process.env;\n\n    this._cols = opt.cols || DEFAULT_COLS;\n    this._rows = opt.rows || DEFAULT_ROWS;\n    const uid = opt.uid ?? -1;\n    const gid = opt.gid ?? -1;\n    const env: IProcessEnv = assign({}, opt.env);\n\n    if (opt.env === process.env) {\n      this._sanitizeEnv(env);\n    }\n\n    const cwd = opt.cwd || process.cwd();\n    env.PWD = cwd;\n    const name = opt.name || env.TERM || DEFAULT_NAME;\n    env.TERM = name;\n    const parsedEnv = this._parseEnv(env);\n\n    const encoding = (opt.encoding === undefined ? 'utf8' : opt.encoding);\n\n    const onexit = (code: number, signal: number): void => {\n      // XXX Sometimes a data event is emitted after exit. Wait til socket is\n      // destroyed.\n      if (!this._emittedClose) {\n        if (this._boundClose) {\n          return;\n        }\n        this._boundClose = true;\n        // From macOS High Sierra 10.13.2 sometimes the socket never gets\n        // closed. A timeout is applied here to avoid the terminal never being\n        // destroyed when this occurs.\n        let timeout: NodeJS.Timeout | null = setTimeout(() => {\n          timeout = null;\n          // Destroying the socket now will cause the close event to fire\n          this._socket.destroy();\n        }, DESTROY_SOCKET_TIMEOUT_MS);\n        this.once('close', () => {\n          if (timeout !== null) {\n            clearTimeout(timeout);\n          }\n          this.emit('exit', code, signal);\n        });\n        return;\n      }\n      this.emit('exit', code, signal);\n    };\n\n    // fork\n    const term = pty.fork(file, args, parsedEnv, cwd, this._cols, this._rows, uid, gid, (encoding === 'utf8'), helperPath, onexit);\n\n    this._socket = new tty.ReadStream(term.fd);\n    if (encoding !== null) {\n      this._socket.setEncoding(encoding);\n    }\n    this._writeStream = new CustomWriteStream(term.fd, (encoding || undefined) as BufferEncoding);\n\n    // setup\n    this._socket.on('error', (err: any) => {\n      // NOTE: fs.ReadStream gets EAGAIN twice at first:\n      if (err.code) {\n        if (~err.code.indexOf('EAGAIN')) {\n          return;\n        }\n      }\n\n      // close\n      this._close();\n      // EIO on exit from fs.ReadStream:\n      if (!this._emittedClose) {\n        this._emittedClose = true;\n        this.emit('close');\n      }\n\n      // EIO, happens when someone closes our child process: the only process in\n      // the terminal.\n      // node < 0.6.14: errno 5\n      // node >= 0.6.14: read EIO\n      if (err.code) {\n        if (~err.code.indexOf('errno 5') || ~err.code.indexOf('EIO')) {\n          return;\n        }\n      }\n\n      // throw anything else\n      if (this.listeners('error').length < 2) {\n        throw err;\n      }\n    });\n\n    this._pid = term.pid;\n    this._fd = term.fd;\n    this._pty = term.pty;\n\n    this._file = file;\n    this._name = name;\n\n    this._readable = true;\n    this._writable = true;\n\n    this._socket.on('close', () => {\n      if (this._emittedClose) {\n        return;\n      }\n      this._emittedClose = true;\n      this._close();\n      this.emit('close');\n    });\n\n    this._forwardEvents();\n  }\n\n  protected _write(data: string | Buffer): void {\n    this._writeStream.write(data);\n  }\n\n  /* Accessors */\n  get fd(): number { return this._fd; }\n  get ptsName(): string { return this._pty; }\n\n  /**\n   * openpty\n   */\n\n  public static open(opt: IPtyOpenOptions): UnixTerminal {\n    const self: UnixTerminal = Object.create(UnixTerminal.prototype);\n    opt = opt || {};\n\n    if (arguments.length > 1) {\n      opt = {\n        cols: arguments[1],\n        rows: arguments[2]\n      };\n    }\n\n    const cols = opt.cols || DEFAULT_COLS;\n    const rows = opt.rows || DEFAULT_ROWS;\n    const encoding = (opt.encoding === undefined ? 'utf8' : opt.encoding);\n\n    // open\n    const term: IUnixOpenProcess = pty.open(cols, rows);\n\n    self._master = new tty.ReadStream(term.master);\n    if (encoding !== null) {\n      self._master.setEncoding(encoding);\n    }\n    self._master.resume();\n\n    self._slave = new tty.ReadStream(term.slave);\n    if (encoding !== null) {\n      self._slave.setEncoding(encoding);\n    }\n    self._slave.resume();\n\n    self._socket = self._master;\n    self._pid = -1;\n    self._fd = term.master;\n    self._pty = term.pty;\n\n    self._file = process.argv[0] || 'node';\n    self._name = process.env.TERM || '';\n\n    self._readable = true;\n    self._writable = true;\n\n    self._socket.on('error', err => {\n      self._close();\n      if (self.listeners('error').length < 2) {\n        throw err;\n      }\n    });\n\n    self._socket.on('close', () => {\n      self._close();\n    });\n\n    return self;\n  }\n\n  public destroy(): void {\n    this._close();\n\n    // Need to close the read stream so node stops reading a dead file\n    // descriptor. Then we can safely SIGHUP the shell.\n    this._socket.once('close', () => {\n      this.kill('SIGHUP');\n    });\n\n    this._socket.destroy();\n    this._writeStream.dispose();\n  }\n\n  public kill(signal?: string): void {\n    try {\n      process.kill(this.pid, signal || 'SIGHUP');\n    } catch (e) { /* swallow */ }\n  }\n\n  /**\n   * Gets the name of the process.\n   */\n  public get process(): string {\n    if (process.platform === 'darwin') {\n      const title = pty.process(this._fd);\n      return (title !== 'kernel_task' && title !== 'spawn_helper') ? title : this._file;\n    }\n\n    return pty.process(this._fd, this._pty) || this._file;\n  }\n\n  /**\n   * TTY\n   */\n\n  public resize(cols: number, rows: number, pixelSize?: { width: number, height: number }): void {\n    if (cols <= 0 || rows <= 0 || isNaN(cols) || isNaN(rows) || cols === Infinity || rows === Infinity) {\n      throw new Error('resizing must be done using positive cols and rows');\n    }\n    const pixelWidth = pixelSize?.width ?? 0;\n    const pixelHeight = pixelSize?.height ?? 0;\n    pty.resize(this._fd, cols, rows, pixelWidth, pixelHeight);\n    this._cols = cols;\n    this._rows = rows;\n  }\n\n  public clear(): void {\n\n  }\n\n  private _sanitizeEnv(env: IProcessEnv): void {\n    // Make sure we didn't start our server from inside tmux.\n    delete env['TMUX'];\n    delete env['TMUX_PANE'];\n\n    // Make sure we didn't start our server from inside screen.\n    // http://web.mit.edu/gnu/doc/html/screen_20.html\n    delete env['STY'];\n    delete env['WINDOW'];\n\n    // Delete some variables that might confuse our terminal.\n    delete env['WINDOWID'];\n    delete env['TERMCAP'];\n    delete env['COLUMNS'];\n    delete env['LINES'];\n  }\n}\n\ninterface IWriteTask {\n  /** The buffer being written. */\n  buffer: Buffer;\n  /** The current offset of not yet written data. */\n  offset: number;\n}\n\n/**\n * A custom write stream that writes directly to a file descriptor with proper\n * handling of backpressure and errors. This avoids some event loop exhaustion\n * issues that can occur when using the standard APIs in Node.\n */\nclass CustomWriteStream implements IDisposable {\n\n  private readonly _writeQueue: IWriteTask[] = [];\n  private _writeImmediate: NodeJS.Immediate | undefined;\n\n  constructor(\n    private readonly _fd: number,\n    private readonly _encoding: BufferEncoding\n  ) {\n  }\n\n  dispose(): void {\n    clearImmediate(this._writeImmediate);\n    this._writeImmediate = undefined;\n  }\n\n  write(data: string | Buffer): void {\n    // Writes are put in a queue and processed asynchronously in order to handle\n    // backpressure from the kernel buffer.\n    const buffer = typeof data === 'string'\n      ? Buffer.from(data, this._encoding)\n      : Buffer.from(data);\n\n    if (buffer.byteLength !== 0) {\n      this._writeQueue.push({ buffer, offset: 0 });\n      if (this._writeQueue.length === 1) {\n        this._processWriteQueue();\n      }\n    }\n  }\n\n  private _processWriteQueue(): void {\n    this._writeImmediate = undefined;\n\n    if (this._writeQueue.length === 0) {\n      return;\n    }\n\n    const task = this._writeQueue[0];\n\n    // Write to the underlying file descriptor and handle it directly, rather\n    // than using the `net.Socket`/`tty.WriteStream` wrappers which swallow and\n    // mask errors like EAGAIN and can cause the thread to block indefinitely.\n    fs.write(this._fd, task.buffer, task.offset, (err, written) => {\n      if (err) {\n        if ('code' in err && err.code === 'EAGAIN') {\n          // `setImmediate` is used to yield to the event loop and re-attempt\n          // the write later.\n          this._writeImmediate = setImmediate(() => this._processWriteQueue());\n        } else {\n          // Stop processing immediately on unexpected error and log\n          this._writeQueue.length = 0;\n          console.error('Unhandled pty write error', err);\n        }\n        return;\n      }\n\n      task.offset += written;\n      if (task.offset >= task.buffer.byteLength) {\n        this._writeQueue.shift();\n      }\n\n      // Since there is more room in the kernel buffer, we can continue to write\n      // until we hit EAGAIN or exhaust the queue.\n      //\n      // Note that old versions of bash, like v3.2 which ships in macOS, appears\n      // to have a bug in its readline implementation that causes data\n      // corruption when writes to the pty happens too quickly. Instead of\n      // trying to workaround that we just accept it so that large pastes are as\n      // fast as possible.\n      // Context: https://github.com/microsoft/node-pty/issues/833\n      this._processWriteQueue();\n    });\n  }\n}\n"
  },
  {
    "path": "src/utils.ts",
    "content": "/**\n * Copyright (c) 2017, Daniel Imms (MIT License).\n * Copyright (c) 2018, Microsoft Corporation (MIT License).\n */\n\nexport function assign(target: any, ...sources: any[]): any {\n  sources.forEach(source => Object.keys(source).forEach(key => target[key] = source[key]));\n  return target;\n}\n\n\nexport function loadNativeModule(name: string): {dir: string, module: any} {\n  // Check build, debug, and then prebuilds.\n  const dirs = ['build/Release', 'build/Debug', `prebuilds/${process.platform}-${process.arch}`];\n  // Check relative to the parent dir for unbundled and then the current dir for bundled\n  const relative = ['..', '.'];\n  let lastError: unknown;\n  for (const d of dirs) {\n    for (const r of relative) {\n      const dir = `${r}/${d}`;\n      try {\n        return { dir, module: require(`${dir}/${name}.node`) };\n      } catch (e) {\n        lastError = e;\n      }\n    }\n  }\n  throw new Error(`Failed to load native module: ${name}.node, checked: ${dirs.join(', ')}: ${lastError}`);\n}\n"
  },
  {
    "path": "src/win/conpty.cc",
    "content": "/**\n * Copyright (c) 2013-2015, Christopher Jeffrey, Peter Sunde (MIT License)\n * Copyright (c) 2016, Daniel Imms (MIT License).\n * Copyright (c) 2018, Microsoft Corporation (MIT License).\n *\n * pty.cc:\n *   This file is responsible for starting processes\n *   with pseudo-terminal file descriptors.\n */\n\n#define _WIN32_WINNT 0x600\n\n#define NODE_ADDON_API_DISABLE_DEPRECATED\n#include <node_api.h>\n#include <assert.h>\n#include <Shlwapi.h> // PathCombine, PathIsRelative\n#include <sstream>\n#include <iostream>\n#include <string>\n#include <thread>\n#include <vector>\n#include <Windows.h>\n#include <strsafe.h>\n#include \"path_util.h\"\n#include \"conpty.h\"\n\n// Taken from the RS5 Windows SDK, but redefined here in case we're targeting <= 17134\n#ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE\n#define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE \\\n  ProcThreadAttributeValue(22, FALSE, TRUE, FALSE)\n\ntypedef VOID* HPCON;\ntypedef HRESULT (__stdcall *PFNCREATEPSEUDOCONSOLE)(COORD c, HANDLE hIn, HANDLE hOut, DWORD dwFlags, HPCON* phpcon);\ntypedef HRESULT (__stdcall *PFNRESIZEPSEUDOCONSOLE)(HPCON hpc, COORD newSize);\ntypedef HRESULT (__stdcall *PFNCLEARPSEUDOCONSOLE)(HPCON hpc);\ntypedef void (__stdcall *PFNCLOSEPSEUDOCONSOLE)(HPCON hpc);\ntypedef void (__stdcall *PFNRELEASEPSEUDOCONSOLE)(HPCON hpc);\n\n#endif\n\nstruct pty_baton {\n  int id;\n  HANDLE hIn;\n  HANDLE hOut;\n  HPCON hpc;\n\n  HANDLE hShell;\n\n  pty_baton(int _id, HANDLE _hIn, HANDLE _hOut, HPCON _hpc) : id(_id), hIn(_hIn), hOut(_hOut), hpc(_hpc) {};\n};\n\nstatic std::vector<std::unique_ptr<pty_baton>> ptyHandles;\nstatic volatile LONG ptyCounter;\n\nstatic pty_baton* get_pty_baton(int id) {\n  auto it = std::find_if(ptyHandles.begin(), ptyHandles.end(), [id](const auto& ptyHandle) {\n    return ptyHandle->id == id;\n  });\n  if (it != ptyHandles.end()) {\n    return it->get();\n  }\n  return nullptr;\n}\n\nstatic bool remove_pty_baton(int id) {\n  auto it = std::remove_if(ptyHandles.begin(), ptyHandles.end(), [id](const auto& ptyHandle) {\n    return ptyHandle->id == id;\n  });\n  if (it != ptyHandles.end()) {\n    ptyHandles.erase(it);\n    return true;\n  }\n  return false;\n}\n\nstruct ExitEvent {\n  int exit_code = 0;\n};\n\nvoid SetupExitCallback(Napi::Env env, Napi::Function cb, pty_baton* baton) {\n  std::thread *th = new std::thread;\n  // Don't use Napi::AsyncWorker which is limited by UV_THREADPOOL_SIZE.\n  auto tsfn = Napi::ThreadSafeFunction::New(\n      env,\n      cb,                           // JavaScript function called asynchronously\n      \"SetupExitCallback_resource\", // Name\n      0,                            // Unlimited queue\n      1,                            // Only one thread will use this initially\n      [th](Napi::Env) {   // Finalizer used to clean threads up\n        th->join();\n        delete th;\n      });\n  *th = std::thread([tsfn = std::move(tsfn), baton] {\n    auto callback = [](Napi::Env env, Napi::Function cb, ExitEvent *exit_event) {\n      cb.Call({Napi::Number::New(env, exit_event->exit_code)});\n      delete exit_event;\n    };\n\n    ExitEvent *exit_event = new ExitEvent;\n    // Wait for process to complete.\n    WaitForSingleObject(baton->hShell, INFINITE);\n    // Get process exit code.\n    GetExitCodeProcess(baton->hShell, (LPDWORD)(&exit_event->exit_code));\n    // Clean up handles\n    CloseHandle(baton->hShell);\n    assert(remove_pty_baton(baton->id));\n\n    auto status = tsfn.BlockingCall(exit_event, callback); // In main thread\n    switch (status) {\n      case napi_closing:\n        break;\n\n      case napi_queue_full:\n        Napi::Error::Fatal(\"SetupExitCallback\", \"Queue was full\");\n\n      case napi_ok:\n        if (tsfn.Release() != napi_ok) {\n          Napi::Error::Fatal(\"SetupExitCallback\", \"ThreadSafeFunction.Release() failed\");\n        }\n        break;\n\n      default:\n        Napi::Error::Fatal(\"SetupExitCallback\", \"ThreadSafeFunction.BlockingCall() failed\");\n    }\n  });\n}\n\nNapi::Error errorWithCode(const Napi::CallbackInfo& info, const char* text) {\n  std::stringstream errorText;\n  errorText << text;\n  errorText << \", error code: \" << GetLastError();\n  return Napi::Error::New(info.Env(), errorText.str());\n}\n\n// Returns a new server named pipe.  It has not yet been connected.\nbool createDataServerPipe(bool write,\n                          std::wstring kind,\n                          HANDLE* hServer,\n                          std::wstring &name,\n                          const std::wstring &pipeName)\n{\n  *hServer = INVALID_HANDLE_VALUE;\n\n  name = L\"\\\\\\\\.\\\\pipe\\\\\" + pipeName + L\"-\" + kind;\n\n  const DWORD winOpenMode =  PIPE_ACCESS_INBOUND | PIPE_ACCESS_OUTBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE/*  | FILE_FLAG_OVERLAPPED */;\n\n  SECURITY_ATTRIBUTES sa = {};\n  sa.nLength = sizeof(sa);\n\n  *hServer = CreateNamedPipeW(\n      name.c_str(),\n      /*dwOpenMode=*/winOpenMode,\n      /*dwPipeMode=*/PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,\n      /*nMaxInstances=*/1,\n      /*nOutBufferSize=*/128 * 1024,\n      /*nInBufferSize=*/128 * 1024,\n      /*nDefaultTimeOut=*/30000,\n      &sa);\n\n  return *hServer != INVALID_HANDLE_VALUE;\n}\n\nHANDLE LoadConptyDll(const Napi::CallbackInfo& info,\n                     const bool useConptyDll)\n{\n  if (!useConptyDll) {\n    return LoadLibraryExW(L\"kernel32.dll\", 0, 0);\n  }\n  wchar_t currentDir[MAX_PATH];\n  HMODULE hModule = GetModuleHandleA(\"conpty.node\");\n  if (hModule == NULL) {\n    throw errorWithCode(info, \"Failed to get conpty.node module handle\");\n  }\n  DWORD result = GetModuleFileNameW(hModule, currentDir, MAX_PATH);\n  if (result == 0) {\n    throw errorWithCode(info, \"Failed to get conpty.node module file name\");\n  }\n  PathRemoveFileSpecW(currentDir);\n  wchar_t conptyDllPath[MAX_PATH];\n  PathCombineW(conptyDllPath, currentDir, L\"conpty\\\\conpty.dll\");\n  if (!path_util::file_exists(conptyDllPath)) {\n    std::wstring errorMessage = L\"Cannot find conpty.dll at \" + std::wstring(conptyDllPath);\n    std::string errorMessageStr = path_util::wstring_to_string(errorMessage);\n    throw errorWithCode(info, errorMessageStr.c_str());\n  }\n\n  return LoadLibraryW(conptyDllPath);\n}\n\nHRESULT CreateNamedPipesAndPseudoConsole(const Napi::CallbackInfo& info,\n                                         COORD size,\n                                         DWORD dwFlags,\n                                         HANDLE *phInput,\n                                         HANDLE *phOutput,\n                                         HPCON* phPC,\n                                         std::wstring& inName,\n                                         std::wstring& outName,\n                                         const std::wstring& pipeName,\n                                         const bool useConptyDll)\n{\n  HANDLE hLibrary = LoadConptyDll(info, useConptyDll);\n  DWORD error = GetLastError();\n  bool fLoadedDll = hLibrary != nullptr;\n  if (fLoadedDll)\n  {\n    PFNCREATEPSEUDOCONSOLE const pfnCreate = (PFNCREATEPSEUDOCONSOLE)GetProcAddress(\n        (HMODULE)hLibrary,\n        useConptyDll ? \"ConptyCreatePseudoConsole\" : \"CreatePseudoConsole\");\n    if (pfnCreate)\n    {\n      if (phPC == NULL || phInput == NULL || phOutput == NULL)\n      {\n        return E_INVALIDARG;\n      }\n\n      bool success = createDataServerPipe(true, L\"in\", phInput, inName, pipeName);\n      if (!success)\n      {\n        return HRESULT_FROM_WIN32(GetLastError());\n      }\n      success = createDataServerPipe(false, L\"out\", phOutput, outName, pipeName);\n      if (!success)\n      {\n        return HRESULT_FROM_WIN32(GetLastError());\n      }\n      return pfnCreate(size, *phInput, *phOutput, dwFlags, phPC);\n    }\n    else\n    {\n      // Failed to find CreatePseudoConsole in kernel32. This is likely because\n      //    the user is not running a build of Windows that supports that API.\n      return HRESULT_FROM_WIN32(GetLastError());\n    }\n  } else {\n    throw errorWithCode(info, \"Failed to load conpty.dll\");\n  }\n\n  // Failed to find  kernel32. This is realy unlikely - honestly no idea how\n  //    this is even possible to hit.\n  return HRESULT_FROM_WIN32(GetLastError());\n}\n\nstatic Napi::Value PtyStartProcess(const Napi::CallbackInfo& info) {\n  Napi::Env env(info.Env());\n  Napi::HandleScope scope(env);\n\n  Napi::Object marshal;\n  std::wstring inName, outName;\n  BOOL fSuccess = FALSE;\n  std::unique_ptr<wchar_t[]> mutableCommandline;\n  PROCESS_INFORMATION _piClient{};\n\n  if (info.Length() != 7 ||\n      !info[0].IsString() ||\n      !info[1].IsNumber() ||\n      !info[2].IsNumber() ||\n      !info[3].IsBoolean() ||\n      !info[4].IsString() ||\n      !info[5].IsBoolean() ||\n      !info[6].IsBoolean()) {\n    throw Napi::Error::New(env, \"Usage: pty.startProcess(file, cols, rows, debug, pipeName, inheritCursor, useConptyDll)\");\n  }\n\n  const std::wstring filename(path_util::to_wstring(info[0].As<Napi::String>()));\n  const SHORT cols = static_cast<SHORT>(info[1].As<Napi::Number>().Uint32Value());\n  const SHORT rows = static_cast<SHORT>(info[2].As<Napi::Number>().Uint32Value());\n  const bool debug = info[3].As<Napi::Boolean>().Value();\n  const std::wstring pipeName(path_util::to_wstring(info[4].As<Napi::String>()));\n  const bool inheritCursor = info[5].As<Napi::Boolean>().Value();\n  const bool useConptyDll = info[6].As<Napi::Boolean>().Value();\n\n  // use environment 'Path' variable to determine location of\n  // the relative path that we have recieved (e.g cmd.exe)\n  std::wstring shellpath;\n  if (::PathIsRelativeW(filename.c_str())) {\n    shellpath = path_util::get_shell_path(filename.c_str());\n  } else {\n    shellpath = filename;\n  }\n\n  if (shellpath.empty() || !path_util::file_exists(shellpath)) {\n    std::string why;\n    why += \"File not found: \";\n    why += path_util::wstring_to_string(shellpath);\n    throw Napi::Error::New(env, why);\n  }\n\n  HANDLE hIn, hOut;\n  HPCON hpc;\n  HRESULT hr = CreateNamedPipesAndPseudoConsole(info, {cols, rows}, inheritCursor ? 1/*PSEUDOCONSOLE_INHERIT_CURSOR*/ : 0, &hIn, &hOut, &hpc, inName, outName, pipeName, useConptyDll);\n\n  // Restore default handling of ctrl+c\n  SetConsoleCtrlHandler(NULL, FALSE);\n\n  // Set return values\n  marshal = Napi::Object::New(env);\n\n  if (SUCCEEDED(hr)) {\n    // We were able to instantiate a conpty\n    const int ptyId = InterlockedIncrement(&ptyCounter);\n    marshal.Set(\"pty\", Napi::Number::New(env, ptyId));\n    ptyHandles.emplace_back(\n        std::make_unique<pty_baton>(ptyId, hIn, hOut, hpc));\n  } else {\n    throw Napi::Error::New(env, \"Cannot launch conpty\");\n  }\n\n  std::string inNameStr = path_util::wstring_to_string(inName);\n  if (inNameStr.empty()) {\n    throw Napi::Error::New(env, \"Failed to initialize conpty conin\");\n  }\n  std::string outNameStr = path_util::wstring_to_string(outName);\n  if (outNameStr.empty()) {\n    throw Napi::Error::New(env, \"Failed to initialize conpty conout\");\n  }\n\n  marshal.Set(\"fd\", Napi::Number::New(env, -1));\n  marshal.Set(\"conin\", Napi::String::New(env, inNameStr));\n  marshal.Set(\"conout\", Napi::String::New(env, outNameStr));\n  return marshal;\n}\n\nstatic Napi::Value PtyConnect(const Napi::CallbackInfo& info) {\n  Napi::Env env(info.Env());\n  Napi::HandleScope scope(env);\n\n  // If we're working with conpty's we need to call ConnectNamedPipe here AFTER\n  //    the Socket has attempted to connect to the other end, then actually\n  //    spawn the process here.\n\n  std::stringstream errorText;\n  BOOL fSuccess = FALSE;\n\n  if (info.Length() != 6 ||\n      !info[0].IsNumber() ||\n      !info[1].IsString() ||\n      !info[2].IsString() ||\n      !info[3].IsArray() ||\n      !info[4].IsBoolean() ||\n      !info[5].IsFunction()) {\n    throw Napi::Error::New(env, \"Usage: pty.connect(id, cmdline, cwd, env, useConptyDll, exitCallback)\");\n  }\n\n  const int id = info[0].As<Napi::Number>().Int32Value();\n  const std::wstring cmdline(path_util::to_wstring(info[1].As<Napi::String>()));\n  const std::wstring cwd(path_util::to_wstring(info[2].As<Napi::String>()));\n  const Napi::Array envValues = info[3].As<Napi::Array>();\n  const bool useConptyDll = info[4].As<Napi::Boolean>().Value();\n  Napi::Function exitCallback = info[5].As<Napi::Function>();\n\n  // Fetch pty handle from ID and start process\n  pty_baton* handle = get_pty_baton(id);\n  if (!handle) {\n    throw Napi::Error::New(env, \"Invalid pty handle\");\n  }\n\n  // Prepare command line\n  std::unique_ptr<wchar_t[]> mutableCommandline = std::make_unique<wchar_t[]>(cmdline.length() + 1);\n  HRESULT hr = StringCchCopyW(mutableCommandline.get(), cmdline.length() + 1, cmdline.c_str());\n\n  // Prepare cwd\n  std::unique_ptr<wchar_t[]> mutableCwd = std::make_unique<wchar_t[]>(cwd.length() + 1);\n  hr = StringCchCopyW(mutableCwd.get(), cwd.length() + 1, cwd.c_str());\n\n  // Prepare environment\n  std::wstring envStr;\n  if (!envValues.IsEmpty()) {\n    std::wstring envBlock;\n    for(uint32_t i = 0; i < envValues.Length(); i++) {\n      envBlock += path_util::to_wstring(envValues.Get(i).As<Napi::String>());\n      envBlock += L'\\0';\n    }\n    envBlock += L'\\0';\n    envStr = std::move(envBlock);\n  }\n  std::vector<wchar_t> envV(envStr.cbegin(), envStr.cend());\n  LPWSTR envArg = envV.empty() ? nullptr : envV.data();\n\n  ConnectNamedPipe(handle->hIn, nullptr);\n  ConnectNamedPipe(handle->hOut, nullptr);\n\n  // Attach the pseudoconsole to the client application we're creating\n  STARTUPINFOEXW siEx{0};\n  siEx.StartupInfo.cb = sizeof(STARTUPINFOEXW);\n  siEx.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;\n  siEx.StartupInfo.hStdError = nullptr;\n  siEx.StartupInfo.hStdInput = nullptr;\n  siEx.StartupInfo.hStdOutput = nullptr;\n\n  SIZE_T size = 0;\n  InitializeProcThreadAttributeList(NULL, 1, 0, &size);\n  BYTE *attrList = new BYTE[size];\n  siEx.lpAttributeList = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(attrList);\n\n  fSuccess = InitializeProcThreadAttributeList(siEx.lpAttributeList, 1, 0, &size);\n  if (!fSuccess) {\n    throw errorWithCode(info, \"InitializeProcThreadAttributeList failed\");\n  }\n  fSuccess = UpdateProcThreadAttribute(siEx.lpAttributeList,\n                                       0,\n                                       PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,\n                                       handle->hpc,\n                                       sizeof(HPCON),\n                                       NULL,\n                                       NULL);\n  if (!fSuccess) {\n    throw errorWithCode(info, \"UpdateProcThreadAttribute failed\");\n  }\n\n  PROCESS_INFORMATION piClient{};\n  fSuccess = !!CreateProcessW(\n      nullptr,\n      mutableCommandline.get(),\n      nullptr,                      // lpProcessAttributes\n      nullptr,                      // lpThreadAttributes\n      false,                        // bInheritHandles VERY IMPORTANT that this is false\n      EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT, // dwCreationFlags\n      envArg,                       // lpEnvironment\n      mutableCwd.get(),             // lpCurrentDirectory\n      &siEx.StartupInfo,            // lpStartupInfo\n      &piClient                     // lpProcessInformation\n  );\n  if (!fSuccess) {\n    throw errorWithCode(info, \"Cannot create process\");\n  }\n\n  HANDLE hLibrary = LoadConptyDll(info, useConptyDll);\n  bool fLoadedDll = hLibrary != nullptr;\n  if (useConptyDll && fLoadedDll)\n  {\n    PFNRELEASEPSEUDOCONSOLE const pfnReleasePseudoConsole = (PFNRELEASEPSEUDOCONSOLE)GetProcAddress(\n      (HMODULE)hLibrary, \"ConptyReleasePseudoConsole\");\n    if (pfnReleasePseudoConsole)\n    {\n      pfnReleasePseudoConsole(handle->hpc);\n    }\n  }\n\n  // Update handle\n  handle->hShell = piClient.hProcess;\n\n  // Close the thread handle to avoid resource leak\n  CloseHandle(piClient.hThread);\n  // Close the input read and output write handle of the pseudoconsole\n  CloseHandle(handle->hIn);\n  CloseHandle(handle->hOut);\n\n  SetupExitCallback(env, exitCallback, handle);\n\n  // Return\n  auto marshal = Napi::Object::New(env);\n  marshal.Set(\"pid\", Napi::Number::New(env, piClient.dwProcessId));\n  return marshal;\n}\n\nstatic Napi::Value PtyResize(const Napi::CallbackInfo& info) {\n  Napi::Env env(info.Env());\n  Napi::HandleScope scope(env);\n\n  if (info.Length() != 4 ||\n      !info[0].IsNumber() ||\n      !info[1].IsNumber() ||\n      !info[2].IsNumber() ||\n      !info[3].IsBoolean()) {\n    throw Napi::Error::New(env, \"Usage: pty.resize(id, cols, rows, useConptyDll)\");\n  }\n\n  int id = info[0].As<Napi::Number>().Int32Value();\n  SHORT cols = static_cast<SHORT>(info[1].As<Napi::Number>().Uint32Value());\n  SHORT rows = static_cast<SHORT>(info[2].As<Napi::Number>().Uint32Value());\n  const bool useConptyDll = info[3].As<Napi::Boolean>().Value();\n\n  const pty_baton* handle = get_pty_baton(id);\n\n  if (handle != nullptr) {\n    HANDLE hLibrary = LoadConptyDll(info, useConptyDll);\n    bool fLoadedDll = hLibrary != nullptr;\n    if (fLoadedDll)\n    {\n      PFNRESIZEPSEUDOCONSOLE const pfnResizePseudoConsole = (PFNRESIZEPSEUDOCONSOLE)GetProcAddress(\n        (HMODULE)hLibrary,\n        useConptyDll ? \"ConptyResizePseudoConsole\" : \"ResizePseudoConsole\");\n      if (pfnResizePseudoConsole)\n      {\n        COORD size = {cols, rows};\n        pfnResizePseudoConsole(handle->hpc, size);\n      }\n    }\n  }\n\n  return env.Undefined();\n}\n\nstatic Napi::Value PtyClear(const Napi::CallbackInfo& info) {\n  Napi::Env env(info.Env());\n  Napi::HandleScope scope(env);\n\n  if (info.Length() != 2 ||\n      !info[0].IsNumber() ||\n      !info[1].IsBoolean()) {\n    throw Napi::Error::New(env, \"Usage: pty.clear(id, useConptyDll)\");\n  }\n\n  int id = info[0].As<Napi::Number>().Int32Value();\n  const bool useConptyDll = info[1].As<Napi::Boolean>().Value();\n\n  // This API is only supported for conpty.dll as it was introduced in a later version of Windows.\n  // We could hook it up to point at >= a version of Windows only, but the future is conpty.dll\n  // anyway.\n  if (!useConptyDll) {\n    return env.Undefined();\n  }\n\n  const pty_baton* handle = get_pty_baton(id);\n\n  if (handle != nullptr) {\n    HANDLE hLibrary = LoadConptyDll(info, useConptyDll);\n    bool fLoadedDll = hLibrary != nullptr;\n    if (fLoadedDll)\n    {\n      PFNCLEARPSEUDOCONSOLE const pfnClearPseudoConsole = (PFNCLEARPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, \"ConptyClearPseudoConsole\");\n      if (pfnClearPseudoConsole)\n      {\n        pfnClearPseudoConsole(handle->hpc);\n      }\n    }\n  }\n\n  return env.Undefined();\n}\n\nstatic Napi::Value PtyKill(const Napi::CallbackInfo& info) {\n  Napi::Env env(info.Env());\n  Napi::HandleScope scope(env);\n\n  if (info.Length() != 2 ||\n      !info[0].IsNumber() ||\n      !info[1].IsBoolean()) {\n    throw Napi::Error::New(env, \"Usage: pty.kill(id, useConptyDll)\");\n  }\n\n  int id = info[0].As<Napi::Number>().Int32Value();\n  const bool useConptyDll = info[1].As<Napi::Boolean>().Value();\n\n  const pty_baton* handle = get_pty_baton(id);\n\n  if (handle != nullptr) {\n    HANDLE hLibrary = LoadConptyDll(info, useConptyDll);\n    bool fLoadedDll = hLibrary != nullptr;\n    if (fLoadedDll)\n    {\n      PFNCLOSEPSEUDOCONSOLE const pfnClosePseudoConsole = (PFNCLOSEPSEUDOCONSOLE)GetProcAddress(\n        (HMODULE)hLibrary,\n        useConptyDll ? \"ConptyClosePseudoConsole\" : \"ClosePseudoConsole\");\n      if (pfnClosePseudoConsole)\n      {\n        pfnClosePseudoConsole(handle->hpc);\n      }\n    }\n    if (useConptyDll) {\n      TerminateProcess(handle->hShell, 1);\n    }\n  }\n\n  return env.Undefined();\n}\n\n/**\n* Init\n*/\n\nNapi::Object init(Napi::Env env, Napi::Object exports) {\n  exports.Set(\"startProcess\", Napi::Function::New(env, PtyStartProcess));\n  exports.Set(\"connect\", Napi::Function::New(env, PtyConnect));\n  exports.Set(\"resize\", Napi::Function::New(env, PtyResize));\n  exports.Set(\"clear\", Napi::Function::New(env, PtyClear));\n  exports.Set(\"kill\", Napi::Function::New(env, PtyKill));\n  return exports;\n};\n\nNODE_API_MODULE(NODE_GYP_MODULE_NAME, init);\n"
  },
  {
    "path": "src/win/conpty.h",
    "content": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\n// This header prototypes the Pseudoconsole symbols from conpty.lib with their original names.\n// This is required because we cannot import __imp_CreatePseudoConsole from a static library\n// as it doesn't produce an import lib.\n// We can't use an /ALTERNATENAME trick because it seems that that name is only resolved when the\n// linker cannot otherwise find the symbol.\n\n#pragma once\n\n#include <consoleapi.h>\n\n#ifndef CONPTY_IMPEXP\n#define CONPTY_IMPEXP __declspec(dllimport)\n#endif\n\n#ifndef CONPTY_EXPORT\n#ifdef __cplusplus\n#define CONPTY_EXPORT extern \"C\" CONPTY_IMPEXP\n#else\n#define CONPTY_EXPORT extern CONPTY_IMPEXP\n#endif\n#endif\n\n#define PSEUDOCONSOLE_RESIZE_QUIRK (2u)\n#define PSEUDOCONSOLE_PASSTHROUGH_MODE (8u)\n\nCONPTY_EXPORT HRESULT WINAPI ConptyCreatePseudoConsole(COORD size, HANDLE hInput, HANDLE hOutput, DWORD dwFlags, HPCON* phPC);\nCONPTY_EXPORT HRESULT WINAPI ConptyCreatePseudoConsoleAsUser(HANDLE hToken, COORD size, HANDLE hInput, HANDLE hOutput, DWORD dwFlags, HPCON* phPC);\n\nCONPTY_EXPORT HRESULT WINAPI ConptyResizePseudoConsole(HPCON hPC, COORD size);\nCONPTY_EXPORT HRESULT WINAPI ConptyClearPseudoConsole(HPCON hPC);\nCONPTY_EXPORT HRESULT WINAPI ConptyShowHidePseudoConsole(HPCON hPC, bool show);\nCONPTY_EXPORT HRESULT WINAPI ConptyReparentPseudoConsole(HPCON hPC, HWND newParent);\nCONPTY_EXPORT HRESULT WINAPI ConptyReleasePseudoConsole(HPCON hPC);\n\nCONPTY_EXPORT VOID WINAPI ConptyClosePseudoConsole(HPCON hPC);\nCONPTY_EXPORT VOID WINAPI ConptyClosePseudoConsoleTimeout(HPCON hPC, DWORD dwMilliseconds);\n\nCONPTY_EXPORT HRESULT WINAPI ConptyPackPseudoConsole(HANDLE hServerProcess, HANDLE hRef, HANDLE hSignal, HPCON* phPC);\n"
  },
  {
    "path": "src/win/conpty_console_list.cc",
    "content": "/**\n * Copyright (c) 2019, Microsoft Corporation (MIT License).\n */\n\n#define NODE_ADDON_API_DISABLE_DEPRECATED\n#include <napi.h>\n#include <windows.h>\n\nstatic Napi::Value ApiConsoleProcessList(const Napi::CallbackInfo& info) {\n  Napi::Env env(info.Env());\n  if (info.Length() != 1 ||\n      !info[0].IsNumber()) {\n    throw Napi::Error::New(env, \"Usage: getConsoleProcessList(shellPid)\");\n  }\n\n  const DWORD pid = info[0].As<Napi::Number>().Uint32Value();\n\n  if (!FreeConsole()) {\n    throw Napi::Error::New(env, \"FreeConsole failed\");\n  }\n  if (!AttachConsole(pid)) {\n    throw Napi::Error::New(env, \"AttachConsole failed\");\n  }\n  auto processList = std::vector<DWORD>(64);\n  auto processCount = GetConsoleProcessList(&processList[0], static_cast<DWORD>(processList.size()));\n  if (processList.size() < processCount) {\n      processList.resize(processCount);\n      processCount = GetConsoleProcessList(&processList[0], static_cast<DWORD>(processList.size()));\n  }\n  FreeConsole();\n\n  Napi::Array result = Napi::Array::New(env);\n  for (DWORD i = 0; i < processCount; i++) {\n    result.Set(i, Napi::Number::New(env, processList[i]));\n  }\n  return result;\n}\n\nNapi::Object init(Napi::Env env, Napi::Object exports) {\n  exports.Set(\"getConsoleProcessList\", Napi::Function::New(env, ApiConsoleProcessList));\n  return exports;\n};\n\nNODE_API_MODULE(NODE_GYP_MODULE_NAME, init);\n"
  },
  {
    "path": "src/win/path_util.cc",
    "content": "/**\n * Copyright (c) 2013-2015, Christopher Jeffrey, Peter Sunde (MIT License)\n * Copyright (c) 2016, Daniel Imms (MIT License).\n * Copyright (c) 2018, Microsoft Corporation (MIT License).\n */\n\n#include <stdexcept>\n#include <Shlwapi.h> // PathCombine\n#include <Windows.h>\n#include \"path_util.h\"\n\nnamespace path_util {\n\nstd::wstring to_wstring(const Napi::String& str) {\n  const std::u16string & u16 = str.Utf16Value();\n  return std::wstring(u16.begin(), u16.end());\n}\n\nstd::string wstring_to_string(const std::wstring &wide_string) {\n  if (wide_string.empty()) {\n    return \"\";\n  }\n  const auto size_needed = WideCharToMultiByte(CP_UTF8, 0, &wide_string.at(0), (int)wide_string.size(), nullptr, 0, nullptr, nullptr);\n  if (size_needed <= 0) {\n    return \"\";\n  }\n  std::string result(size_needed, 0);\n  WideCharToMultiByte(CP_UTF8, 0, &wide_string.at(0), (int)wide_string.size(), &result.at(0), size_needed, nullptr, nullptr);\n  return result;\n}\n\nconst char* from_wstring(const wchar_t* wstr) {\n  int bufferSize = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL);\n  if (bufferSize <= 0) {\n    return \"\";\n  }\n  char *output = new char[bufferSize];\n  int status = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, output, bufferSize, NULL, NULL);\n  if (status == 0) {\n    return \"\";\n  }\n  return output;\n}\n\nbool file_exists(std::wstring filename) {\n  DWORD attr = ::GetFileAttributesW(filename.c_str());\n  if (attr == INVALID_FILE_ATTRIBUTES || (attr & FILE_ATTRIBUTE_DIRECTORY)) {\n    return false;\n  }\n  return true;\n}\n\n// cmd.exe -> C:\\Windows\\system32\\cmd.exe\nstd::wstring get_shell_path(std::wstring filename) {\n  std::wstring shellpath;\n\n  if (file_exists(filename)) {\n    return shellpath;\n  }\n\n  wchar_t* buffer_ = new wchar_t[MAX_ENV];\n  int read = ::GetEnvironmentVariableW(L\"Path\", buffer_, MAX_ENV);\n  if (read) {\n    std::wstring delimiter = L\";\";\n    size_t pos = 0;\n    std::vector<std::wstring> paths;\n    std::wstring buffer(buffer_);\n    while ((pos = buffer.find(delimiter)) != std::wstring::npos) {\n      paths.push_back(buffer.substr(0, pos));\n      buffer.erase(0, pos + delimiter.length());\n    }\n\n    const wchar_t *filename_ = filename.c_str();\n\n    for (size_t i = 0; i < paths.size(); ++i) {\n      std::wstring path = paths[i];\n      wchar_t searchPath[MAX_PATH];\n      ::PathCombineW(searchPath, const_cast<wchar_t*>(path.c_str()), filename_);\n\n      if (searchPath == NULL) {\n        continue;\n      }\n\n      if (file_exists(searchPath)) {\n        shellpath = searchPath;\n        break;\n      }\n    }\n  }\n\n  delete[] buffer_;\n  return shellpath;\n}\n\n}  // namespace path_util\n"
  },
  {
    "path": "src/win/path_util.h",
    "content": "/**\n * Copyright (c) 2013-2015, Christopher Jeffrey, Peter Sunde (MIT License)\n * Copyright (c) 2016, Daniel Imms (MIT License).\n * Copyright (c) 2018, Microsoft Corporation (MIT License).\n */\n\n#ifndef NODE_PTY_PATH_UTIL_H_\n#define NODE_PTY_PATH_UTIL_H_\n\n#define NODE_ADDON_API_DISABLE_DEPRECATED\n#include <napi.h>\n#include <string>\n\n#define MAX_ENV 65536\n\nnamespace path_util {\n\nstd::wstring to_wstring(const Napi::String& str);\nstd::string wstring_to_string(const std::wstring &wide_string);\nconst char* from_wstring(const wchar_t* wstr);\nbool file_exists(std::wstring filename);\nstd::wstring get_shell_path(std::wstring filename);\n\n}  // namespace path_util\n\n#endif  // NODE_PTY_PATH_UTIL_H_\n"
  },
  {
    "path": "src/windowsConoutConnection.ts",
    "content": "/**\n * Copyright (c) 2020, Microsoft Corporation (MIT License).\n */\n\nimport { Worker } from 'worker_threads';\nimport { Socket } from 'net';\nimport { IDisposable } from './types';\nimport { IWorkerData, ConoutWorkerMessage, getWorkerPipeName } from './shared/conout';\nimport { join } from 'path';\nimport { IEvent, EventEmitter2 } from './eventEmitter2';\n\n/**\n * The amount of time to wait for additional data after the conpty shell process has exited before\n * shutting down the worker and sockets. The timer will be reset if a new data event comes in after\n * the timer has started.\n */\nconst FLUSH_DATA_INTERVAL = 1000;\n\n/**\n * Connects to and manages the lifecycle of the conout socket. This socket must be drained on\n * another thread in order to avoid deadlocks where Conpty waits for the out socket to drain\n * when `ClosePseudoConsole` is called. This happens when data is being written to the terminal when\n * the pty is closed.\n *\n * See also:\n * - https://github.com/microsoft/node-pty/issues/375\n * - https://github.com/microsoft/vscode/issues/76548\n * - https://github.com/microsoft/terminal/issues/1810\n * - https://docs.microsoft.com/en-us/windows/console/closepseudoconsole\n */\nexport class ConoutConnection implements IDisposable {\n  private _worker: Worker;\n  private _drainTimeout: NodeJS.Timeout | undefined;\n  private _isDisposed: boolean = false;\n\n  private _onReady = new EventEmitter2<void>();\n  public get onReady(): IEvent<void> { return this._onReady.event; }\n\n  constructor(\n    private _conoutPipeName: string,\n    private _useConptyDll: boolean\n  ) {\n    const workerData: IWorkerData = {\n      conoutPipeName: _conoutPipeName\n    };\n    const scriptPath = __dirname.replace('node_modules.asar', 'node_modules.asar.unpacked');\n    this._worker = new Worker(join(scriptPath, 'worker/conoutSocketWorker.js'), { workerData });\n    this._worker.on('message', (message: ConoutWorkerMessage) => {\n      switch (message) {\n        case ConoutWorkerMessage.READY:\n          this._onReady.fire();\n          return;\n        default:\n          console.warn('Unexpected ConoutWorkerMessage', message);\n      }\n    });\n  }\n\n  dispose(): void {\n    if (!this._useConptyDll && this._isDisposed) {\n      return;\n    }\n    this._isDisposed = true;\n    // Drain all data from the socket before closing\n    this._drainDataAndClose();\n  }\n\n  connectSocket(socket: Socket): void {\n    socket.connect(getWorkerPipeName(this._conoutPipeName));\n  }\n\n  private _drainDataAndClose(): void {\n    if (this._drainTimeout) {\n      clearTimeout(this._drainTimeout);\n    }\n    this._drainTimeout = setTimeout(() => this._destroySocket(), FLUSH_DATA_INTERVAL);\n  }\n\n  private async _destroySocket(): Promise<void> {\n    await this._worker.terminate();\n  }\n}\n"
  },
  {
    "path": "src/windowsPtyAgent.test.ts",
    "content": "/**\n * Copyright (c) 2017, Daniel Imms (MIT License).\n * Copyright (c) 2018, Microsoft Corporation (MIT License).\n */\n\nimport * as assert from 'assert';\nimport { argsToCommandLine, WindowsPtyAgent } from './windowsPtyAgent';\n\nfunction check(file: string, args: string | string[], expected: string): void {\n  assert.equal(argsToCommandLine(file, args), expected);\n}\n\nif (process.platform === 'win32') {\n  describe('argsToCommandLine', () => {\n    describe('Plain strings', () => {\n      it('doesn\\'t quote plain string', () => {\n        check('asdf', [], 'asdf');\n      });\n      it('doesn\\'t escape backslashes', () => {\n        check('\\\\asdf\\\\qwer\\\\', [], '\\\\asdf\\\\qwer\\\\');\n      });\n      it('doesn\\'t escape multiple backslashes', () => {\n        check('asdf\\\\\\\\qwer', [], 'asdf\\\\\\\\qwer');\n      });\n      it('adds backslashes before quotes', () => {\n        check('\"asdf\"qwer\"', [], '\\\\\"asdf\\\\\"qwer\\\\\"');\n      });\n      it('escapes backslashes before quotes', () => {\n        check('asdf\\\\\"qwer', [], 'asdf\\\\\\\\\\\\\"qwer');\n      });\n    });\n\n    describe('Quoted strings', () => {\n      it('quotes string with spaces', () => {\n        check('asdf qwer', [], '\"asdf qwer\"');\n      });\n      it('quotes empty string', () => {\n        check('', [], '\"\"');\n      });\n      it('quotes string with tabs', () => {\n        check('asdf\\tqwer', [], '\"asdf\\tqwer\"');\n      });\n      it('escapes only the last backslash', () => {\n        check('\\\\asdf \\\\qwer\\\\', [], '\"\\\\asdf \\\\qwer\\\\\\\\\"');\n      });\n      it('doesn\\'t escape multiple backslashes', () => {\n        check('asdf \\\\\\\\qwer', [], '\"asdf \\\\\\\\qwer\"');\n      });\n      it('escapes backslashes before quotes', () => {\n        check('asdf \\\\\"qwer', [], '\"asdf \\\\\\\\\\\\\"qwer\"');\n      });\n      it('escapes multiple backslashes at the end', () => {\n        check('asdf qwer\\\\\\\\', [], '\"asdf qwer\\\\\\\\\\\\\\\\\"');\n      });\n    });\n\n    describe('Multiple arguments', () => {\n      it('joins arguments with spaces', () => {\n        check('asdf', ['qwer zxcv', '', '\"'], 'asdf \"qwer zxcv\" \"\" \\\\\"');\n      });\n      it('array argument all in quotes', () => {\n        check('asdf', ['\"surounded by quotes\"'], 'asdf \\\\\"surounded by quotes\\\\\"');\n      });\n      it('array argument quotes in the middle', () => {\n        check('asdf', ['quotes \"in the\" middle'], 'asdf \"quotes \\\\\"in the\\\\\" middle\"');\n      });\n      it('array argument quotes near start', () => {\n        check('asdf', ['\"quotes\" near start'], 'asdf \"\\\\\"quotes\\\\\" near start\"');\n      });\n      it('array argument quotes near end', () => {\n        check('asdf', ['quotes \"near end\"'], 'asdf \"quotes \\\\\"near end\\\\\"\"');\n      });\n    });\n\n    describe('Args as CommandLine', () => {\n      it('should handle empty string', () => {\n        check('file', '', 'file');\n      });\n      it('should not change args', () => {\n        check('file', 'foo bar baz', 'file foo bar baz');\n        check('file', 'foo \\\\ba\"r \\baz', 'file foo \\\\ba\"r \\baz');\n      });\n    });\n\n    describe('Real-world cases', () => {\n      it('quotes within quotes', () => {\n        check('cmd.exe', ['/c', 'powershell -noexit -command \\'Set-location \\\"C:\\\\user\\\"\\''], 'cmd.exe /c \"powershell -noexit -command \\'Set-location \\\\\\\"C:\\\\user\\\\\"\\'\"');\n      });\n      it('space within quotes', () => {\n        check('cmd.exe', ['/k', '\"C:\\\\Users\\\\alros\\\\Desktop\\\\test script.bat\"'], 'cmd.exe /k \\\\\"C:\\\\Users\\\\alros\\\\Desktop\\\\test script.bat\\\\\"');\n      });\n    });\n  });\n\n  describe('WindowsPtyAgent', () => {\n    describe('connection timing (issue #763)', () => {\n      it('should defer conptyNative.connect() until worker is ready', function (done) {\n        this.timeout(10000);\n\n        const term = new WindowsPtyAgent(\n          'cmd.exe',\n          '/c echo test',\n          Object.keys(process.env).map(k => `${k}=${process.env[k]}`),\n          process.cwd(),\n          80,\n          30,\n          false,\n          false,\n          false\n        );\n\n        // The innerPid should be 0 initially since connect() is deferred\n        // until the worker signals ready. This verifies the fix for #763.\n        const initialPid = term.innerPid;\n\n        // Wait for the connection to complete via ready_datapipe event\n        term.outSocket.on('ready_datapipe', () => {\n          // After worker is ready and connect() is called, innerPid should be set\n          // Use a small delay to ensure _completePtyConnection has run\n          setTimeout(() => {\n            assert.notStrictEqual(term.innerPid, 0, 'innerPid should be set after worker is ready');\n            assert.strictEqual(initialPid, 0, 'innerPid should have been 0 before worker was ready');\n            term.kill();\n            done();\n          }, 100);\n        });\n      });\n\n      it('should successfully spawn a process after deferred connection', function (done) {\n        this.timeout(10000);\n\n        const term = new WindowsPtyAgent(\n          'cmd.exe',\n          '/c echo hello',\n          Object.keys(process.env).map(k => `${k}=${process.env[k]}`),\n          process.cwd(),\n          80,\n          30,\n          false,\n          false,\n          false\n        );\n\n        let output = '';\n        term.outSocket.on('data', (data: string) => {\n          output += data;\n        });\n\n        // Wait for process to complete and verify output\n        setTimeout(() => {\n          assert.ok(output.includes('hello'), `Expected output to contain \"hello\", got: ${output}`);\n          term.kill();\n          done();\n        }, 2000);\n      });\n\n      it('should allow async work between construction and connection (non-blocking)', function (done) {\n        this.timeout(10000);\n\n        // Track the sequence of events to verify non-blocking behavior\n        const events: string[] = [];\n\n        const term = new WindowsPtyAgent(\n          'cmd.exe',\n          '/c echo test',\n          Object.keys(process.env).map(k => `${k}=${process.env[k]}`),\n          process.cwd(),\n          80,\n          30,\n          false,\n          false,\n          false\n        );\n\n        events.push('constructor_returned');\n        assert.strictEqual(term.innerPid, 0, 'innerPid should be 0 immediately after construction');\n\n        // Schedule async work - this MUST run before ready_datapipe if constructor is non-blocking\n        setImmediate(() => {\n          events.push('setImmediate_ran');\n          // innerPid might still be 0 or might be set by now, depending on timing\n          // The key is that setImmediate ran, proving the event loop wasn't blocked\n        });\n\n        term.outSocket.on('ready_datapipe', () => {\n          events.push('ready_datapipe');\n\n          setTimeout(() => {\n            events.push('final_check');\n\n            // Verify the sequence: constructor returned, then async work could run\n            assert.ok(events.includes('constructor_returned'), 'constructor should have returned');\n            assert.ok(events.includes('setImmediate_ran'), 'setImmediate should have run (event loop not blocked)');\n            assert.ok(events.indexOf('constructor_returned') < events.indexOf('setImmediate_ran'),\n              'constructor should return before setImmediate runs');\n\n            // Most importantly: innerPid should now be set\n            assert.notStrictEqual(term.innerPid, 0, 'innerPid should be set after connection');\n\n            term.kill();\n            done();\n          }, 100);\n        });\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "src/windowsPtyAgent.ts",
    "content": "/**\n * Copyright (c) 2012-2015, Christopher Jeffrey, Peter Sunde (MIT License)\n * Copyright (c) 2016, Daniel Imms (MIT License).\n * Copyright (c) 2018, Microsoft Corporation (MIT License).\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { fork } from 'child_process';\nimport { Socket } from 'net';\nimport { ArgvOrCommandLine } from './types';\nimport { ConoutConnection } from './windowsConoutConnection';\nimport { loadNativeModule } from './utils';\n\nlet conptyNative: IConptyNative;\n\n/**\n * The amount of time to wait for additional data after the conpty shell process has exited before\n * shutting down the socket. The timer will be reset if a new data event comes in after the timer\n * has started.\n */\nconst FLUSH_DATA_INTERVAL = 1000;\n\n/**\n * This agent sits between the WindowsTerminal class and provides an interface for conpty.\n */\nexport class WindowsPtyAgent {\n  private _inSocket: Socket;\n  private _outSocket: Socket;\n  private _innerPid: number = 0;\n  private _closeTimeout: NodeJS.Timer | undefined;\n  private _exitCode: number | undefined;\n  private _conoutSocketWorker: ConoutConnection;\n\n  private _fd: any;\n  private _pty: number;\n  private _ptyNative: IConptyNative;\n\n  public get inSocket(): Socket { return this._inSocket; }\n  public get outSocket(): Socket { return this._outSocket; }\n  public get fd(): any { return this._fd; }\n  public get innerPid(): number { return this._innerPid; }\n  public get pty(): number { return this._pty; }\n\n  private _pendingPtyInfo: { pty: number, commandLine: string, cwd: string, env: string[] } | undefined;\n\n  constructor(\n    file: string,\n    args: ArgvOrCommandLine,\n    env: string[],\n    cwd: string,\n    cols: number,\n    rows: number,\n    debug: boolean,\n    private _useConptyDll: boolean = false,\n    conptyInheritCursor: boolean = false\n  ) {\n    if (!conptyNative) {\n      conptyNative = loadNativeModule('conpty').module;\n    }\n    this._ptyNative = conptyNative;\n\n    // Sanitize input variable.\n    cwd = path.resolve(cwd);\n\n    // Compose command line\n    const commandLine = argsToCommandLine(file, args);\n\n    // Open pty session.\n    const term: IConptyProcess = conptyNative.startProcess(file, cols, rows, debug, this._generatePipeName(), conptyInheritCursor, this._useConptyDll);\n\n    // Not available on windows.\n    this._fd = term.fd;\n\n    // Generated incremental number that has no real purpose besides  using it\n    // as a terminal id.\n    this._pty = term.pty;\n\n    // Create terminal pipe IPC channel and forward to a local unix socket.\n    this._outSocket = new Socket();\n    this._outSocket.setEncoding('utf8');\n    // The conout socket must be ready out on another thread to avoid deadlocks\n    // We must wait for the worker to connect before calling conptyNative.connect()\n    // to avoid blocking the Node.js event loop in ConnectNamedPipe.\n    // See https://github.com/microsoft/node-pty/issues/763\n    this._conoutSocketWorker = new ConoutConnection(term.conout, this._useConptyDll);\n\n    // Store pending connection info - we'll complete the connection when worker is ready\n    this._pendingPtyInfo = { pty: this._pty, commandLine, cwd, env };\n\n    // Timeout to ensure connection completes even if worker fails to signal ready\n    const connectionTimeout = setTimeout(() => {\n      if (this._pendingPtyInfo) {\n        // Worker never signaled ready - complete connection anyway to avoid zombie state\n        this._completePtyConnection();\n      }\n    }, 5000);\n\n    this._conoutSocketWorker.onReady(() => {\n      clearTimeout(connectionTimeout);\n      this._conoutSocketWorker.connectSocket(this._outSocket);\n      // Now that the worker has connected to the output pipe, we can safely call\n      // conptyNative.connect() which calls ConnectNamedPipe - it won't block because\n      // the client (worker) is already connected\n      this._completePtyConnection();\n    });\n    this._outSocket.on('connect', () => {\n      this._outSocket.emit('ready_datapipe');\n    });\n\n    const inSocketFD = fs.openSync(term.conin, 'w');\n    this._inSocket = new Socket({\n      fd: inSocketFD,\n      readable: false,\n      writable: true\n    });\n    this._inSocket.setEncoding('utf8');\n  }\n\n  private _completePtyConnection(): void {\n    if (!this._pendingPtyInfo) {\n      return;\n    }\n    const { pty, commandLine, cwd, env } = this._pendingPtyInfo;\n    this._pendingPtyInfo = undefined;\n\n    const connect = conptyNative.connect(pty, commandLine, cwd, env, this._useConptyDll, c => this._$onProcessExit(c));\n    this._innerPid = connect.pid;\n  }\n\n  public resize(cols: number, rows: number): void {\n    if (this._exitCode !== undefined) {\n      throw new Error('Cannot resize a pty that has already exited');\n    }\n    this._ptyNative.resize(this._pty, cols, rows, this._useConptyDll);\n  }\n\n  public clear(): void {\n    this._ptyNative.clear(this._pty, this._useConptyDll);\n  }\n\n  public kill(): void {\n    // Prevent deferred connection from completing after kill\n    this._pendingPtyInfo = undefined;\n\n    // Tell the agent to kill the pty, this releases handles to the process\n    if (!this._useConptyDll) {\n      this._inSocket.readable = false;\n      this._outSocket.readable = false;\n      this._getConsoleProcessList().then(consoleProcessList => {\n        consoleProcessList.forEach((pid: number) => {\n          try {\n            process.kill(pid);\n          } catch (e) {\n            // Ignore if process cannot be found (kill ESRCH error)\n          }\n        });\n      });\n      this._ptyNative.kill(this._pty, this._useConptyDll);\n      this._conoutSocketWorker.dispose();\n    } else {\n      // Close the input write handle to signal the end of session.\n      this._inSocket.destroy();\n      this._ptyNative.kill(this._pty, this._useConptyDll);\n      this._outSocket.on('data', () => {\n        this._conoutSocketWorker.dispose();\n      });\n    }\n  }\n\n  private _getConsoleProcessList(): Promise<number[]> {\n    if (this._innerPid <= 0) {\n      return Promise.resolve([]);\n    }\n    return new Promise<number[]>(resolve => {\n      const agent = fork(path.join(__dirname, 'conpty_console_list_agent'), [ this._innerPid.toString() ]);\n      agent.on('message', message => {\n        clearTimeout(timeout);\n        resolve(message.consoleProcessList);\n      });\n      const timeout = setTimeout(() => {\n        // Something went wrong, just send back the shell PID\n        agent.kill();\n        resolve([ this._innerPid ]);\n      }, 5000);\n    });\n  }\n\n  public get exitCode(): number | undefined {\n    return this._exitCode;\n  }\n\n  private _generatePipeName(): string {\n    return `conpty-${Math.random() * 10000000}`;\n  }\n\n  /**\n   * Triggered from the native side when a contpy process exits.\n   */\n  private _$onProcessExit(exitCode: number): void {\n    this._exitCode = exitCode;\n    if (!this._useConptyDll) {\n      this._flushDataAndCleanUp();\n      this._outSocket.on('data', () => this._flushDataAndCleanUp());\n    }\n  }\n\n  private _flushDataAndCleanUp(): void {\n    if (this._useConptyDll) {\n      return;\n    }\n    if (this._closeTimeout) {\n      clearTimeout(this._closeTimeout);\n    }\n    this._closeTimeout = setTimeout(() => this._cleanUpProcess(), FLUSH_DATA_INTERVAL);\n  }\n\n  private _cleanUpProcess(): void {\n    if (this._useConptyDll) {\n      return;\n    }\n    this._inSocket.readable = false;\n    this._outSocket.readable = false;\n    this._outSocket.destroy();\n  }\n}\n\n// Convert argc/argv into a Win32 command-line following the escaping convention\n// documented on MSDN (e.g. see CommandLineToArgvW documentation). Copied from\n// winpty project.\nexport function argsToCommandLine(file: string, args: ArgvOrCommandLine): string {\n  if (isCommandLine(args)) {\n    if (args.length === 0) {\n      return file;\n    }\n    return `${argsToCommandLine(file, [])} ${args}`;\n  }\n  const argv = [file];\n  Array.prototype.push.apply(argv, args);\n  let result = '';\n  for (let argIndex = 0; argIndex < argv.length; argIndex++) {\n    if (argIndex > 0) {\n      result += ' ';\n    }\n    const arg = argv[argIndex];\n    // if it is empty or it contains whitespace and is not already quoted\n    const hasLopsidedEnclosingQuote = xOr((arg[0] !== '\"'), (arg[arg.length - 1] !== '\"'));\n    const hasNoEnclosingQuotes = ((arg[0] !== '\"') && (arg[arg.length - 1] !== '\"'));\n    const quote =\n      arg === '' ||\n      (arg.indexOf(' ') !== -1 ||\n      arg.indexOf('\\t') !== -1) &&\n      ((arg.length > 1) &&\n      (hasLopsidedEnclosingQuote || hasNoEnclosingQuotes));\n    if (quote) {\n      result += '\\\"';\n    }\n    let bsCount = 0;\n    for (let i = 0; i < arg.length; i++) {\n      const p = arg[i];\n      if (p === '\\\\') {\n        bsCount++;\n      } else if (p === '\"') {\n        result += repeatText('\\\\', bsCount * 2 + 1);\n        result += '\"';\n        bsCount = 0;\n      } else {\n        result += repeatText('\\\\', bsCount);\n        bsCount = 0;\n        result += p;\n      }\n    }\n    if (quote) {\n      result += repeatText('\\\\', bsCount * 2);\n      result += '\\\"';\n    } else {\n      result += repeatText('\\\\', bsCount);\n    }\n  }\n  return result;\n}\n\nfunction isCommandLine(args: ArgvOrCommandLine): args is string {\n  return typeof args === 'string';\n}\n\nfunction repeatText(text: string, count: number): string {\n  let result = '';\n  for (let i = 0; i < count; i++) {\n    result += text;\n  }\n  return result;\n}\n\nfunction xOr(arg1: boolean, arg2: boolean): boolean {\n  return ((arg1 && !arg2) || (!arg1 && arg2));\n}\n"
  },
  {
    "path": "src/windowsTerminal.test.ts",
    "content": "/**\n * Copyright (c) 2017, Daniel Imms (MIT License).\n * Copyright (c) 2018, Microsoft Corporation (MIT License).\n */\n\nimport * as fs from 'fs';\nimport * as assert from 'assert';\nimport { WindowsTerminal } from './windowsTerminal';\nimport * as path from 'path';\nimport * as psList from 'ps-list';\n\ninterface IProcessState {\n  // Whether the PID must exist or must not exist\n  [pid: number]: boolean;\n}\n\ninterface IWindowsProcessTreeResult {\n  name: string;\n  pid: number;\n}\n\nfunction pollForProcessState(desiredState: IProcessState, intervalMs: number = 100, timeoutMs: number = 2000): Promise<void> {\n  return new Promise<void>(resolve => {\n    let tries = 0;\n    const interval = setInterval(() => {\n      psList({ all: true }).then(ps => {\n        let success = true;\n        const pids = Object.keys(desiredState).map(k => parseInt(k, 10));\n        console.log('expected pids', JSON.stringify(pids));\n        pids.forEach(pid => {\n          if (desiredState[pid]) {\n            if (!ps.some(p => p.pid === pid)) {\n              console.log(`pid ${pid} does not exist`);\n              success = false;\n            }\n          } else {\n            if (ps.some(p => p.pid === pid)) {\n              console.log(`pid ${pid} still exists`);\n              success = false;\n            }\n          }\n        });\n        if (success) {\n          clearInterval(interval);\n          resolve();\n          return;\n        }\n        tries++;\n        if (tries * intervalMs >= timeoutMs) {\n          clearInterval(interval);\n          const processListing = pids.map(k => `${k}: ${desiredState[k]}`).join('\\n');\n          assert.fail(`Bad process state, expected:\\n${processListing}`);\n          resolve();\n        }\n      });\n    }, intervalMs);\n  });\n}\n\nfunction pollForProcessTreeSize(pid: number, size: number, intervalMs: number = 100, timeoutMs: number = 2000): Promise<IWindowsProcessTreeResult[]> {\n  return new Promise<IWindowsProcessTreeResult[]>(resolve => {\n    let tries = 0;\n    const interval = setInterval(() => {\n      psList({ all: true }).then(ps => {\n        const openList: IWindowsProcessTreeResult[] = [];\n        openList.push(ps.filter(p => p.pid === pid).map(p => {\n          return { name: p.name, pid: p.pid };\n        })[0]);\n        const list: IWindowsProcessTreeResult[] = [];\n        while (openList.length) {\n          const current = openList.shift()!;\n          ps.filter(p => p.ppid === current.pid).map(p => {\n            return { name: p.name, pid: p.pid };\n          }).forEach(p => openList.push(p));\n          list.push(current);\n        }\n        console.log('list', JSON.stringify(list));\n        const success = list.length === size;\n        if (success) {\n          clearInterval(interval);\n          resolve(list);\n          return;\n        }\n        tries++;\n        if (tries * intervalMs >= timeoutMs) {\n          clearInterval(interval);\n          assert.fail(`Bad process state, expected: ${size}, actual: ${list.length}`);\n        }\n      });\n    }, intervalMs);\n  });\n}\n\nif (process.platform === 'win32') {\n  [false, true].forEach((useConptyDll) => {\n    describe(`WindowsTerminal (useConptyDll = ${useConptyDll})`, () => {\n      describe('kill', () => {\n        it('should not crash parent process', function (done) {\n          this.timeout(20000);\n          const term = new WindowsTerminal('cmd.exe', [], { useConptyDll });\n          term.on('exit', () => done());\n          term.kill();\n        });\n        it('should kill the process tree', function (done: Mocha.Done): void {\n          this.timeout(20000);\n          const term = new WindowsTerminal('cmd.exe', [], { useConptyDll });\n          const socket = (term as any)._socket;\n          let started = false;\n          const startPolling = (): void => {\n            if (started) {\n              return;\n            }\n            if (term.pid === 0) {\n              setTimeout(startPolling, 50);\n              return;\n            }\n            started = true;\n            // Start sub-processes\n            term.write('powershell.exe\\r');\n            term.write('node.exe\\r');\n            console.log('start poll for tree size');\n            pollForProcessTreeSize(term.pid, 3, 500, 5000).then(list => {\n              assert.strictEqual(list[0].name.toLowerCase(), 'cmd.exe');\n              assert.strictEqual(list[1].name.toLowerCase(), 'powershell.exe');\n              assert.strictEqual(list[2].name.toLowerCase(), 'node.exe');\n              term.kill();\n              const desiredState: IProcessState = {};\n              desiredState[list[0].pid] = false;\n              desiredState[list[1].pid] = false;\n              desiredState[list[2].pid] = false;\n              term.on('exit', () => {\n                pollForProcessState(desiredState, 1000, 5000).then(() => {\n                  done();\n                }).catch(done);\n              });\n            }).catch(done);\n          };\n\n          if (term.pid > 0) {\n            startPolling();\n          } else {\n            socket.once('ready_datapipe', () => setTimeout(startPolling, 50));\n          }\n        });\n      });\n\n      describe('pid', () => {\n        it('should be 0 before ready and set after ready_datapipe (issue #763)', function (done) {\n          this.timeout(10000);\n          const term = new WindowsTerminal('cmd.exe', '/c echo test', { useConptyDll });\n\n          // pid may be 0 immediately after construction due to deferred connection\n          const initialPid = term.pid;\n\n          // Access internal socket to listen for ready_datapipe\n          const socket = (term as any)._socket;\n          socket.on('ready_datapipe', () => {\n            // After ready_datapipe, pid should be set to a valid non-zero value\n            setTimeout(() => {\n              assert.notStrictEqual(term.pid, 0, 'pid should be set after ready_datapipe');\n              assert.strictEqual(typeof term.pid, 'number', 'pid should be a number');\n              // If initial was 0, it should now be different (proves the fix works)\n              if (initialPid === 0) {\n                assert.notStrictEqual(term.pid, initialPid, 'pid should be updated from initial value');\n              }\n              term.on('exit', () => done());\n              term.kill();\n            }, 100);\n          });\n        });\n      });\n\n      describe('resize', () => {\n        it('should throw a non-native exception when resizing an invalid value', function(done) {\n          this.timeout(20000);\n          const term = new WindowsTerminal('cmd.exe', [], { useConptyDll });\n          assert.throws(() => term.resize(-1, -1));\n          assert.throws(() => term.resize(0, 0));\n          assert.doesNotThrow(() => term.resize(1, 1));\n          term.on('exit', () => {\n            done();\n          });\n          term.kill();\n        });\n        it('should throw a non-native exception when resizing a killed terminal', function(done) {\n          this.timeout(20000);\n          const term = new WindowsTerminal('cmd.exe', [], { useConptyDll });\n          (<any>term)._defer(() => {\n            term.once('exit', () => {\n              assert.throws(() => term.resize(1, 1));\n              done();\n            });\n            term.destroy();\n          });\n        });\n      });\n\n      describe('Args as CommandLine', () => {\n        it('should not fail running a file containing a space in the path', function (done) {\n          this.timeout(10000);\n          const spaceFolder = path.resolve(__dirname, '..', 'fixtures', 'space folder');\n          if (!fs.existsSync(spaceFolder)) {\n            fs.mkdirSync(spaceFolder);\n          }\n\n          const cmdCopiedPath = path.resolve(spaceFolder, 'cmd.exe');\n          const data = fs.readFileSync(`${process.env.windir}\\\\System32\\\\cmd.exe`);\n          fs.writeFileSync(cmdCopiedPath, data);\n\n          if (!fs.existsSync(cmdCopiedPath)) {\n            // Skip test if git bash isn't installed\n            return;\n          }\n          const term = new WindowsTerminal(cmdCopiedPath, '/c echo \"hello world\"', { useConptyDll });\n          let result = '';\n          term.on('data', (data) => {\n            result += data;\n          });\n          term.on('exit', () => {\n            assert.ok(result.indexOf('hello world') >= 1);\n            done();\n          });\n        });\n      });\n\n      describe('env', () => {\n        it('should set environment variables of the shell', function (done) {\n          this.timeout(10000);\n          const term = new WindowsTerminal('cmd.exe', '/C echo %FOO%', { useConptyDll, env: { FOO: 'BAR' }});\n          let result = '';\n          term.on('data', (data) => {\n            result += data;\n          });\n          term.on('exit', () => {\n            assert.ok(result.indexOf('BAR') >= 0);\n            done();\n          });\n        });\n      });\n\n      describe('On close', () => {\n        it('should return process zero exit codes', function (done) {\n          this.timeout(10000);\n          const term = new WindowsTerminal('cmd.exe', '/C exit', { useConptyDll });\n          term.on('exit', (code) => {\n            assert.strictEqual(code, 0);\n            done();\n          });\n        });\n\n        it('should return process non-zero exit codes', function (done) {\n          this.timeout(10000);\n          const term = new WindowsTerminal('cmd.exe', '/C exit 2', { useConptyDll });\n          term.on('exit', (code) => {\n            assert.strictEqual(code, 2);\n            done();\n          });\n        });\n      });\n\n      describe('Write', () => {\n        it('should accept input', function (done) {\n          this.timeout(10000);\n          const term = new WindowsTerminal('cmd.exe', '', { useConptyDll });\n          term.write('exit\\r');\n          term.on('exit', () => {\n            done();\n          });\n        });\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "src/windowsTerminal.ts",
    "content": "/**\n * Copyright (c) 2012-2015, Christopher Jeffrey, Peter Sunde (MIT License)\n * Copyright (c) 2016, Daniel Imms (MIT License).\n * Copyright (c) 2018, Microsoft Corporation (MIT License).\n */\n\nimport { Socket } from 'net';\nimport { Terminal, DEFAULT_COLS, DEFAULT_ROWS } from './terminal';\nimport { WindowsPtyAgent } from './windowsPtyAgent';\nimport { IPtyOpenOptions, IWindowsPtyForkOptions } from './interfaces';\nimport { ArgvOrCommandLine } from './types';\nimport { assign } from './utils';\n\nconst DEFAULT_FILE = 'cmd.exe';\nconst DEFAULT_NAME = 'Windows Shell';\n\nexport class WindowsTerminal extends Terminal {\n  private _isReady: boolean;\n  private _deferreds: Array<{ run: () => void }>;\n  private _agent: WindowsPtyAgent;\n\n  constructor(file?: string, args?: ArgvOrCommandLine, opt?: IWindowsPtyForkOptions) {\n    super(opt);\n\n    this._checkType('args', args, 'string', true);\n\n    // Initialize arguments\n    args = args || [];\n    file = file || DEFAULT_FILE;\n    opt = opt || {};\n    opt.env = opt.env || process.env;\n\n    if (opt.encoding) {\n      console.warn('Setting encoding on Windows is not supported');\n    }\n\n    const env = assign({}, opt.env);\n    this._cols = opt.cols || DEFAULT_COLS;\n    this._rows = opt.rows || DEFAULT_ROWS;\n    const cwd = opt.cwd || process.cwd();\n    const name = opt.name || env.TERM || DEFAULT_NAME;\n    const parsedEnv = this._parseEnv(env);\n\n    // If the terminal is ready\n    this._isReady = false;\n\n    // Functions that need to run after `ready` event is emitted.\n    this._deferreds = [];\n\n    // Create new termal.\n    this._agent = new WindowsPtyAgent(file, args, parsedEnv, cwd, this._cols, this._rows, false, opt.useConptyDll, opt.conptyInheritCursor);\n    this._socket = this._agent.outSocket;\n\n    // Not available until `ready` event emitted.\n    this._pid = this._agent.innerPid;\n    this._fd = this._agent.fd;\n    this._pty = this._agent.pty;\n\n    // The forked windows terminal is not available until `ready` event is\n    // emitted.\n    this._socket.on('ready_datapipe', () => {\n      // Update pid now that the agent has connected\n      this._pid = this._agent.innerPid;\n\n      // Run deferreds and set ready state once the first data event is received.\n      this._socket.once('data', () => {\n        // Wait until the first data event is fired then we can run deferreds.\n        if (!this._isReady) {\n          // Terminal is now ready and we can avoid having to defer method\n          // calls.\n          this._isReady = true;\n\n          // Execute all deferred methods\n          this._deferreds.forEach(fn => {\n            // NB! In order to ensure that `this` has all its references\n            // updated any variable that need to be available in `this` before\n            // the deferred is run has to be declared above this forEach\n            // statement.\n            fn.run();\n          });\n\n          // Reset\n          this._deferreds = [];\n        }\n      });\n\n      // Shutdown if `error` event is emitted.\n      this._socket.on('error', err => {\n        // Close terminal session.\n        this._close();\n\n        // EIO, happens when someone closes our child process: the only process\n        // in the terminal.\n        // node < 0.6.14: errno 5\n        // node >= 0.6.14: read EIO\n        if ((<any>err).code) {\n          if (~(<any>err).code.indexOf('errno 5') || ~(<any>err).code.indexOf('EIO')) return;\n        }\n\n        // Throw anything else.\n        if (this.listeners('error').length < 2) {\n          throw err;\n        }\n      });\n\n      // Cleanup after the socket is closed.\n      this._socket.on('close', () => {\n        this.emit('exit', this._agent.exitCode);\n        this._close();\n      });\n\n    });\n\n    this._file = file;\n    this._name = name;\n\n    this._readable = true;\n    this._writable = true;\n\n    this._forwardEvents();\n  }\n\n  protected _write(data: string | Buffer): void {\n    this._defer(this._doWrite, data);\n  }\n\n  private _doWrite(data: string | Buffer): void {\n    this._agent.inSocket.write(data);\n  }\n\n  /**\n   * openpty\n   */\n\n  public static open(options?: IPtyOpenOptions): void {\n    throw new Error('open() not supported on windows, use Fork() instead.');\n  }\n\n  /**\n   * TTY\n   */\n\n  public resize(cols: number, rows: number, pixelSize?: { width: number, height: number }): void {\n    if (cols <= 0 || rows <= 0 || isNaN(cols) || isNaN(rows) || cols === Infinity || rows === Infinity) {\n      throw new Error('resizing must be done using positive cols and rows');\n    }\n    this._deferNoArgs(() => {\n      this._agent.resize(cols, rows);\n      this._cols = cols;\n      this._rows = rows;\n    });\n  }\n\n  public clear(): void {\n    this._deferNoArgs(() => {\n      this._agent.clear();\n    });\n  }\n\n  public destroy(): void {\n    this._deferNoArgs(() => {\n      this.kill();\n    });\n  }\n\n  public kill(signal?: string): void {\n    this._deferNoArgs(() => {\n      if (signal) {\n        throw new Error('Signals not supported on windows.');\n      }\n      this._close();\n      this._agent.kill();\n    });\n  }\n\n  private _deferNoArgs<A>(deferredFn: () => void): void {\n    // If the terminal is ready, execute.\n    if (this._isReady) {\n      deferredFn.call(this);\n      return;\n    }\n\n    // Queue until terminal is ready.\n    this._deferreds.push({\n      run: () => deferredFn.call(this)\n    });\n  }\n\n  private _defer<A>(deferredFn: (arg: A) => void, arg: A): void {\n    // If the terminal is ready, execute.\n    if (this._isReady) {\n      deferredFn.call(this, arg);\n      return;\n    }\n\n    // Queue until terminal is ready.\n    this._deferreds.push({\n      run: () => deferredFn.call(this, arg)\n    });\n  }\n\n  public get process(): string { return this._name; }\n  public get master(): Socket { throw new Error('master is not supported on Windows'); }\n  public get slave(): Socket { throw new Error('slave is not supported on Windows'); }\n}\n"
  },
  {
    "path": "src/worker/conoutSocketWorker.ts",
    "content": "/**\n * Copyright (c) 2020, Microsoft Corporation (MIT License).\n */\n\nimport { parentPort, workerData } from 'worker_threads';\nimport { Socket, createServer } from 'net';\nimport { ConoutWorkerMessage, IWorkerData, getWorkerPipeName } from '../shared/conout';\n\nconst { conoutPipeName } = (workerData as IWorkerData);\n\nconst conoutSocket = new Socket();\nconoutSocket.setEncoding('utf8');\nconoutSocket.connect(conoutPipeName, () => {\n  const server = createServer(workerSocket => {\n    conoutSocket.pipe(workerSocket);\n  });\n  server.listen(getWorkerPipeName(conoutPipeName));\n  if (!parentPort) {\n    throw new Error('worker_threads parentPort is null');\n  }\n  parentPort.postMessage(ConoutWorkerMessage.READY);\n});\n"
  },
  {
    "path": "test/spam-close.js",
    "content": "// This test creates a pty periodically, spamming it with echo calls and killing it shortly after.\n// It's a test case for https://github.com/microsoft/node-pty/issues/375, the script will hang\n// when it show this bug instead of continuing to create more processes.\n\nvar os = require('os');\nvar pty = require('..');\n\nvar isWindows = os.platform() === 'win32';\nvar shell = isWindows ? 'cmd.exe' : 'bash';\n\nlet i = 0;\n\nsetInterval(() => {\n  console.log(`creating pty ${++i}`);\n  var ptyProcess = pty.spawn(shell, [], {\n    name: 'xterm-256color',\n    cols: 80,\n    rows: 26,\n    cwd: isWindows ? process.env.USERPROFILE : process.env.HOME,\n    env: Object.assign({ TEST: \"Environment vars work\" }, process.env)\n  });\n\n  ptyProcess.onData(data => console.log(`  data: ${data.replace(/\\x1b|\\n|\\r/g, '_')}`));\n\n  setInterval(() => {\n    ptyProcess.write('echo foo\\r'.repeat(50));\n  }, 10);\n  setTimeout(() => {\n    console.log(`  killing ${ptyProcess.pid}...`);\n    ptyProcess.kill();\n  }, 100);\n}, 1200);\n"
  },
  {
    "path": "typings/node-pty.d.ts",
    "content": "/**\n * Copyright (c) 2017, Daniel Imms (MIT License).\n * Copyright (c) 2018, Microsoft Corporation (MIT License).\n */\n\ndeclare module 'node-pty' {\n  /**\n   * Forks a process as a pseudoterminal.\n   * @param file The file to launch.\n   * @param args The file's arguments as argv (string[]) or in a pre-escaped CommandLine format\n   * (string). Note that the CommandLine option is only available on Windows and is expected to be\n   * escaped properly.\n   * @param options The options of the terminal.\n   * @see CommandLineToArgvW https://msdn.microsoft.com/en-us/library/windows/desktop/bb776391(v=vs.85).aspx\n   * @see Parsing C++ Command-Line Arguments https://msdn.microsoft.com/en-us/library/17w5ykft.aspx\n   * @see GetCommandLine https://msdn.microsoft.com/en-us/library/windows/desktop/ms683156.aspx\n   */\n  export function spawn(file: string, args: string[] | string, options: IPtyForkOptions | IWindowsPtyForkOptions): IPty;\n\n  export interface IBasePtyForkOptions {\n\n    /**\n     * Name of the terminal to be set in environment ($TERM variable).\n     */\n    name?: string;\n\n    /**\n     * Number of intial cols of the pty.\n     */\n    cols?: number;\n\n    /**\n     * Number of initial rows of the pty.\n     */\n    rows?: number;\n\n    /**\n     * Working directory to be set for the child program.\n     */\n    cwd?: string;\n\n    /**\n     * Environment to be set for the child program.\n     */\n    env?: { [key: string]: string | undefined };\n\n    /**\n     * String encoding of the underlying pty.\n     * If set, incoming data will be decoded to strings and outgoing strings to bytes applying this encoding.\n     * If unset, incoming data will be delivered as raw bytes (Buffer type).\n     * By default 'utf8' is assumed, to unset it explicitly set it to `null`.\n     */\n    encoding?: string | null;\n\n    /**\n     * (EXPERIMENTAL)\n     * Whether to enable flow control handling (false by default). If enabled a message of `flowControlPause`\n     * will pause the socket and thus blocking the child program execution due to buffer back pressure.\n     * A message of `flowControlResume` will resume the socket into flow mode.\n     * For performance reasons only a single message as a whole will match (no message part matching).\n     * If flow control is enabled the `flowControlPause` and `flowControlResume` messages are not forwarded to\n     * the underlying pseudoterminal.\n     */\n    handleFlowControl?: boolean;\n\n    /**\n     * (EXPERIMENTAL)\n     * The string that should pause the pty when `handleFlowControl` is true. Default is XOFF ('\\x13').\n     */\n    flowControlPause?: string;\n\n    /**\n     * (EXPERIMENTAL)\n     * The string that should resume the pty when `handleFlowControl` is true. Default is XON ('\\x11').\n     */\n    flowControlResume?: string;\n  }\n\n  export interface IPtyForkOptions extends IBasePtyForkOptions {\n    /**\n     * Security warning: use this option with great caution,\n     * as opened file descriptors with higher privileges might leak to the child program.\n     */\n    uid?: number;\n    gid?: number;\n  }\n\n  export interface IWindowsPtyForkOptions extends IBasePtyForkOptions {\n  /**\n   * Whether to use the ConPTY system on Windows. When this is not set, ConPTY will be used when\n   * the Windows build number is >= 18309 (instead of winpty). Note that ConPTY is available from\n   * build 17134 but is too unstable to enable by default.\n   *\n   * @deprecated This option is ignored and will be removed in a future version.\n   * https://github.com/microsoft/node-pty/issues/871\n   */\n    useConpty?: boolean;\n\n    /**\n     * (EXPERIMENTAL)\n     *\n     * Whether to use the conpty.dll shipped with the node-pty package instead of the one built into\n     * Windows. Defaults to false.\n     */\n    useConptyDll?: boolean;\n\n    /**\n     * Whether to use PSEUDOCONSOLE_INHERIT_CURSOR in conpty.\n     * @see https://docs.microsoft.com/en-us/windows/console/createpseudoconsole\n     */\n    conptyInheritCursor?: boolean;\n  }\n\n  /**\n   * An interface representing a pseudoterminal.\n   */\n  export interface IPty {\n    /**\n     * The process ID of the outer process.\n     */\n    readonly pid: number;\n\n    /**\n     * The column size in characters.\n     */\n    readonly cols: number;\n\n    /**\n     * The row size in characters.\n     */\n    readonly rows: number;\n\n    /**\n     * The title of the active process.\n     */\n    readonly process: string;\n\n    /**\n     * (EXPERIMENTAL)\n     * Whether to handle flow control. Useful to disable/re-enable flow control during runtime.\n     * Use this for binary data that is likely to contain the `flowControlPause` string by accident.\n     */\n    handleFlowControl: boolean;\n\n    /**\n     * Adds an event listener for when a data event fires. This happens when data is returned from\n     * the pty.\n     * @returns an `IDisposable` to stop listening.\n     */\n    readonly onData: IEvent<string>;\n\n    /**\n     * Adds an event listener for when an exit event fires. This happens when the pty exits.\n     * @returns an `IDisposable` to stop listening.\n     */\n    readonly onExit: IEvent<{ exitCode: number, signal?: number }>;\n\n    /**\n     * Resizes the dimensions of the pty.\n     * @param columns The number of columns to use.\n     * @param rows The number of rows to use.\n     * @param pixelSize Optional pixel dimensions of the pty. On Unix, this sets the `ws_xpixel`\n     * and `ws_ypixel` fields of the `winsize` struct. Applications running in the pty can read\n     * these values via the `TIOCGWINSZ` ioctl. This parameter is ignored on Windows.\n     */\n    resize(columns: number, rows: number, pixelSize?: { width: number, height: number }): void;\n\n    /**\n     * Clears the pty's internal representation of its buffer. This is a no-op\n     * unless on Windows/ConPTY. This is useful if the buffer is cleared on the\n     * frontend in order to synchronize state with the backend to avoid ConPTY\n     * possibly reprinting the screen.\n     */\n    clear(): void;\n\n    /**\n     * Writes data to the pty.\n     * @param data The data to write.\n     */\n    write(data: string | Buffer): void;\n\n    /**\n     * Kills the pty.\n     * @param signal The signal to use, defaults to SIGHUP. This parameter is not supported on\n     * Windows.\n     * @throws Will throw when signal is used on Windows.\n     */\n    kill(signal?: string): void;\n\n    /**\n     * Pauses the pty for customizable flow control.\n     */\n    pause(): void;\n\n    /**\n     * Resumes the pty for customizable flow control.\n     */\n    resume(): void;\n  }\n\n  /**\n   * An object that can be disposed via a dispose function.\n   */\n  export interface IDisposable {\n    dispose(): void;\n  }\n\n  /**\n   * An event that can be listened to.\n   * @returns an `IDisposable` to stop listening.\n   */\n  export interface IEvent<T> {\n    (listener: (e: T) => any): IDisposable;\n  }\n}\n"
  }
]