├── .dockerignore ├── .gitignore ├── .npmignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── Dockerfile ├── LICENSE ├── README.md ├── SECURITY.md ├── ThirdPartyNotices.txt ├── api-extractor.json ├── build ├── azure-pipelines.oss.yml └── azure-pipelines.yml ├── dummy.js ├── package-lock.json ├── package.json ├── src ├── api.ts ├── auth.ts ├── main.ts ├── manifest.ts ├── nls.ts ├── npm.ts ├── package.ts ├── publicgalleryapi.ts ├── publish.ts ├── search.ts ├── secretLint.ts ├── show.ts ├── store.ts ├── test │ ├── fixtures │ │ ├── devDependencies │ │ │ ├── node_modules │ │ │ │ ├── fake │ │ │ │ │ ├── .dotfile │ │ │ │ │ ├── dependency.js │ │ │ │ │ └── package.json │ │ │ │ ├── real │ │ │ │ │ ├── dependency.js │ │ │ │ │ ├── node_modules │ │ │ │ │ │ └── real_sub │ │ │ │ │ │ │ ├── dependency.js │ │ │ │ │ │ │ └── package.json │ │ │ │ │ └── package.json │ │ │ │ ├── real2 │ │ │ │ │ ├── dependency.js │ │ │ │ │ └── package.json │ │ │ │ └── real_sub │ │ │ │ │ ├── dependency.js │ │ │ │ │ └── package.json │ │ │ └── package.json │ │ ├── env │ │ │ ├── .env │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── main.ts │ │ │ └── package.json │ │ ├── icon │ │ │ ├── icon.png │ │ │ ├── package-lock.json │ │ │ └── package.json │ │ ├── manifestFiles │ │ │ ├── .vscode │ │ │ │ └── tasks.json │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── foo │ │ │ │ └── bar │ │ │ │ │ └── hello.txt │ │ │ ├── foo2 │ │ │ │ └── bar2 │ │ │ │ │ ├── ignore.me │ │ │ │ │ └── include.me │ │ │ ├── foo3 │ │ │ │ └── bar3 │ │ │ │ │ └── hello.txt │ │ │ ├── logger.log │ │ │ └── package.json │ │ ├── nls │ │ │ ├── package.json │ │ │ └── package.nls.json │ │ ├── packagedDependencies │ │ │ ├── node_modules │ │ │ │ ├── .yarn-integrity │ │ │ │ ├── isexe │ │ │ │ │ ├── .npmignore │ │ │ │ │ ├── README.md │ │ │ │ │ ├── access.js │ │ │ │ │ ├── index.js │ │ │ │ │ ├── mode.js │ │ │ │ │ ├── package.json │ │ │ │ │ ├── test │ │ │ │ │ │ └── basic.js │ │ │ │ │ └── windows.js │ │ │ │ └── which │ │ │ │ │ ├── CHANGELOG.md │ │ │ │ │ ├── LICENSE │ │ │ │ │ ├── README.md │ │ │ │ │ ├── bin │ │ │ │ │ └── which │ │ │ │ │ ├── node_modules │ │ │ │ │ └── isexe │ │ │ │ │ │ ├── .npmignore │ │ │ │ │ │ ├── LICENSE │ │ │ │ │ │ ├── README.md │ │ │ │ │ │ ├── index.js │ │ │ │ │ │ ├── mode.js │ │ │ │ │ │ ├── package.json │ │ │ │ │ │ ├── test │ │ │ │ │ │ └── basic.js │ │ │ │ │ │ └── windows.js │ │ │ │ │ ├── package.json │ │ │ │ │ └── which.js │ │ │ ├── package.json │ │ │ └── yarn.lock │ │ ├── readme │ │ │ ├── readme.branch.main.expected.md │ │ │ ├── readme.branch.override.content.expected.md │ │ │ ├── readme.branch.override.images.expected.md │ │ │ ├── readme.default.md │ │ │ ├── readme.expected.md │ │ │ ├── readme.github.expected.md │ │ │ ├── readme.github.md │ │ │ ├── readme.gitlab.branch.main.expected.md │ │ │ ├── readme.gitlab.branch.override.content.expected.md │ │ │ ├── readme.gitlab.branch.override.images.expected.md │ │ │ ├── readme.gitlab.default.md │ │ │ ├── readme.gitlab.expected.md │ │ │ ├── readme.gitlab.md │ │ │ ├── readme.images.expected.md │ │ │ └── readme.md │ │ ├── secrets │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── noSecret1.ts │ │ │ ├── noSecret1Ignore │ │ │ ├── noSecret2.ts │ │ │ ├── noSecret2Ignore │ │ │ ├── noSecret3.ts │ │ │ ├── noSecret3Ignore │ │ │ ├── package.json │ │ │ ├── secret1.ts │ │ │ ├── secret1Ignore │ │ │ ├── secret2.ts │ │ │ └── secret2Ignore │ │ ├── target │ │ │ ├── darwin-arm64 │ │ │ │ └── file.txt │ │ │ ├── deep │ │ │ │ ├── darwin-arm64 │ │ │ │ │ └── file.txt │ │ │ │ ├── file.txt │ │ │ │ ├── linux-x64 │ │ │ │ │ └── file.txt │ │ │ │ ├── random │ │ │ │ │ └── file.txt │ │ │ │ └── web │ │ │ │ │ └── file.txt │ │ │ ├── file.txt │ │ │ ├── linux-x64 │ │ │ │ └── file.txt │ │ │ ├── package.json │ │ │ ├── random │ │ │ │ └── file.txt │ │ │ └── web │ │ │ │ └── file.txt │ │ ├── uuid │ │ │ └── package.json │ │ ├── version │ │ │ └── package.json │ │ ├── vscodeignore │ │ │ ├── .vscode │ │ │ │ └── tasks.json │ │ │ ├── .vscodeignore │ │ │ ├── foo │ │ │ │ └── bar │ │ │ │ │ └── hello.txt │ │ │ ├── logger.log │ │ │ └── package.json │ │ └── vsixmanifest │ │ │ ├── ohno.vsixmanifest │ │ │ └── package.json │ ├── package.test.ts │ ├── store.test.ts │ └── validation.test.ts ├── typings │ ├── parse-semver.d.ts │ └── secret-lint-types.ts ├── util.ts ├── validation.ts ├── viewutils.ts ├── xml.ts └── zip.ts ├── tsconfig.json └── vsce /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | out/ 3 | npm-debug.log 4 | !src/test/**/node_modules 5 | Dockerfile 6 | .gitignore 7 | .npmignore 8 | LICENSE 9 | README.md 10 | SECURITY.md 11 | azure-pipelines.yml 12 | ThirdPartyNotices.txt -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | out/ 3 | dist/ 4 | npm-debug.log 5 | !src/test/**/node_modules 6 | yarn.lock -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .github/ 2 | .vscode/ 3 | build/ 4 | src/ 5 | out/test/ 6 | out/**/*.d.ts 7 | .dockerignore 8 | .gitignore 9 | Dockerfile 10 | SECURITY.md 11 | tsconfig.json 12 | api-extractor.json 13 | **/*.js.map -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | // List of extensions which should be recommended for users of this workspace. 5 | "recommendations": ["hbenl.vscode-mocha-test-adapter"], 6 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 7 | "unwantedRecommendations": [] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | // "cwd": "", 12 | "program": "${workspaceFolder}/vsce", 13 | "args": [ 14 | "--version" 15 | // "ls", "package", "publish" 16 | ], 17 | "sourceMaps": true, 18 | "outputCapture": "std" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "editor.insertSpaces": false, 4 | "search.exclude": { 5 | "**/node_modules": true, 6 | "out": true 7 | }, 8 | "typescript.tsdk": "./node_modules/typescript/lib", 9 | "git.branchProtection": ["main"], 10 | "files.associations": { "*.json": "jsonc" } 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch:build", 9 | "problemMatcher": ["$tsc-watch"], 10 | "group": { 11 | "kind": "build", 12 | "isDefault": true 13 | }, 14 | "isBackground": true 15 | }, 16 | { 17 | "type": "npm", 18 | "script": "test", 19 | "problemMatcher": [], 20 | "label": "npm: test", 21 | "detail": "mocha", 22 | "group": { 23 | "kind": "test", 24 | "isDefault": true 25 | } 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine 2 | RUN apk add --update-cache \ 3 | libsecret \ 4 | && rm -rf /var/cache/apk/* 5 | WORKDIR /opt/vsce 6 | COPY package.json package-lock.json ./ 7 | RUN npm install 8 | COPY . . 9 | RUN npm run compile 10 | RUN rm package-lock.json tsconfig.json 11 | VOLUME /workspace 12 | WORKDIR /workspace 13 | ENTRYPOINT ["/opt/vsce/vsce"] 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) Microsoft Corporation 3 | 4 | All rights reserved. 5 | 6 | MIT License 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 9 | files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 10 | modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 11 | is furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 16 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 17 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 18 | OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @vscode/vsce 2 | 3 | > _The Visual Studio Code Extension Manager_ 4 | 5 | [![Build Status](https://dev.azure.com/monacotools/Monaco/_apis/build/status/npm/microsoft.vscode-vsce?repoName=microsoft%2Fvscode-vsce&branchName=main)](https://dev.azure.com/monacotools/Monaco/_build/latest?definitionId=446&repoName=microsoft%2Fvscode-vsce&branchName=main) 6 | [![Version](https://img.shields.io/npm/v/@vscode/vsce.svg)](https://npmjs.org/package/@vscode/vsce) 7 | 8 | This tool assists in packaging and publishing Visual Studio Code extensions. 9 | 10 | Read the [**Documentation**](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VS Code website. 11 | 12 | ## Requirements 13 | 14 | [Node.js](https://nodejs.org/en/) at least `20.x.x`. 15 | 16 | ### Linux 17 | 18 | In order to save credentials safely, this project uses [`keytar`](https://www.npmjs.com/package/keytar) which uses `libsecret`, which you may need to install before publishing extensions. Setting the `VSCE_STORE=file` environment variable will revert back to the file credential store. Using the `VSCE_PAT` environment variable will also avoid using `keytar`. 19 | 20 | Depending on your distribution, you will need to run the following command: 21 | 22 | - Debian/Ubuntu: `sudo apt-get install libsecret-1-dev` 23 | - Alpine: `apk add libsecret` 24 | - Red Hat-based: `sudo yum install libsecret-devel` 25 | - Arch Linux: `sudo pacman -S libsecret` 26 | 27 | ## Usage 28 | 29 | ```console 30 | $ npx @vscode/vsce --version 31 | ``` 32 | 33 | `@vscode/vsce` is meant to be mainly used as a command-line tool. It can also be used as a library since it exposes a small [API](https://github.com/microsoft/vscode-vsce/blob/main/src/api.ts). When using `@vscode/vsce` as a library, be sure to sanitize any user input used in API calls to prevent security issues. 34 | 35 | Supported package managers: 36 | 37 | - `npm >=6` 38 | - `yarn >=1 <2` 39 | 40 | ## Configuration 41 | 42 | You can configure the behavior of `vsce` by using CLI flags (run `vsce --help` to list them all). Example: 43 | 44 | ```console 45 | $ npx @vscode/vsce publish --baseImagesUrl https://my.custom/base/images/url 46 | ``` 47 | 48 | Or you can also set them in the `package.json`, so that you avoid having to retype the common options again. Example: 49 | 50 | ```jsonc 51 | // package.json 52 | { 53 | "vsce": { 54 | "baseImagesUrl": "https://my.custom/base/images/url", 55 | "dependencies": true, 56 | "yarn": false 57 | } 58 | } 59 | ``` 60 | 61 | ## Development 62 | 63 | First clone this repository, then: 64 | 65 | ```console 66 | $ npm install 67 | $ npm run watch:build # or `watch:test` to also build tests 68 | ``` 69 | 70 | Once the watcher is up and running, you can run out of sources with: 71 | 72 | ```console 73 | $ node vsce 74 | ``` 75 | 76 | Tests can be executed with: 77 | 78 | ```console 79 | $ npm test 80 | ``` 81 | 82 | > **Note:** [Yarn](https://www.npmjs.com/package/yarn) is required to run the tests. 83 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft 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/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If 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://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You 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://www.microsoft.com/msrc). 18 | 19 | Please 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: 20 | 21 | - Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | - Full paths of source file(s) related to the manifestation of the issue 23 | - The location of the affected source code (tag/branch/commit or direct URL) 24 | - Any special configuration required to reproduce the issue 25 | - Step-by-step instructions to reproduce the issue 26 | - Proof-of-concept or exploit code (if possible) 27 | - Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If 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://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /ThirdPartyNotices.txt: -------------------------------------------------------------------------------- 1 | microsoft-vscode-vsce 2 | 3 | THIRD-PARTY SOFTWARE NOTICES AND INFORMATION 4 | Do Not Translate or Localize 5 | 6 | This project incorporates components from the projects listed below. The original copyright notices and the licenses 7 | under which Microsoft received such components are set forth below. Microsoft reserves all rights not expressly granted 8 | herein, whether by implication, estoppel or otherwise. 9 | 10 | 1. commander version 2.8.1 (https://github.com/tj/commander.js) 11 | 2. denodeify version 1.2.1 (https://github.com/matthew-andrews/denodeify) 12 | 3. glob version 5.0.14 (https://github.com/isaacs/node-glob) 13 | 4. lodash version 3.10.1 (https://github.com/lodash/lodash) 14 | 5. mime version 1.3.4 (https://github.com/broofa/node-mime) 15 | 6. minimatch version 2.0.10 (https://github.com/isaacs/minimatch) 16 | 7. osenv version 0.1.3 (https://github.com/npm/osenv) 17 | 8. read version 1.0.7 (https://github.com/isaacs/read) 18 | 9. semver version 5.1.0 (https://github.com/npm/node-semver) 19 | 10. tmp version 0.0.27 (https://github.com/raszi/node-tmp) 20 | 11. url-join version 0.0.1 (https://github.com/jfromaniello/url-join) 21 | 12. vso-node-api version 0.5.0 (https://github.com/Microsoft/vso-node-api) 22 | 13. yauzl version 2.3.1 (https://github.com/thejoshwolfe/yauzl) 23 | 14. yazl version 2.2.2 (https://github.com/thejoshwolfe/yazl) 24 | 25 | %% commander NOTICES AND INFORMATION BEGIN HERE 26 | (The MIT License) 27 | 28 | Copyright (c) 2011 TJ Holowaychuk 29 | 30 | Permission is hereby granted, free of charge, to any person obtaining 31 | a copy of this software and associated documentation files (the 32 | 'Software'), to deal in the Software without restriction, including 33 | without limitation the rights to use, copy, modify, merge, publish, 34 | distribute, sublicense, and/or sell copies of the Software, and to 35 | permit persons to whom the Software is furnished to do so, subject to 36 | the following conditions: 37 | 38 | The above copyright notice and this permission notice shall be 39 | included in all copies or substantial portions of the Software. 40 | 41 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 42 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 43 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 44 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 45 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 46 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 47 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 48 | END OF commander NOTICES AND INFORMATION 49 | 50 | %% denodeify NOTICES AND INFORMATION BEGIN HERE 51 | Credits and collaboration 52 | 53 | The lead developer of denodeify is Matt Andrews at FT Labs with much help and support from Kornel Lesiński. All open source code released by FT Labs is licenced under the MIT licence. We welcome comments, feedback and suggestions. Please feel free to raise an issue or pull request. 54 | END OF denodeify NOTICES AND INFORMATION 55 | 56 | %% glob NOTICES AND INFORMATION BEGIN HERE 57 | The ISC License 58 | 59 | Copyright (c) Isaac Z. Schlueter and Contributors 60 | 61 | Permission to use, copy, modify, and/or distribute this software for any 62 | purpose with or without fee is hereby granted, provided that the above 63 | copyright notice and this permission notice appear in all copies. 64 | 65 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 66 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 67 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 68 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 69 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 70 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 71 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 72 | END OF glob NOTICES AND INFORMATION 73 | 74 | %% lodash NOTICES AND INFORMATION BEGIN HERE 75 | Copyright 2012-2015 The Dojo Foundation 76 | Based on Underscore.js, copyright 2009-2015 Jeremy Ashkenas, 77 | DocumentCloud and Investigative Reporters & Editors 78 | 79 | Permission is hereby granted, free of charge, to any person obtaining 80 | a copy of this software and associated documentation files (the 81 | "Software"), to deal in the Software without restriction, including 82 | without limitation the rights to use, copy, modify, merge, publish, 83 | distribute, sublicense, and/or sell copies of the Software, and to 84 | permit persons to whom the Software is furnished to do so, subject to 85 | the following conditions: 86 | 87 | The above copyright notice and this permission notice shall be 88 | included in all copies or substantial portions of the Software. 89 | 90 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 91 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 92 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 93 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 94 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 95 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 96 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 97 | END OF lodash NOTICES AND INFORMATION 98 | 99 | %% mime NOTICES AND INFORMATION BEGIN HERE 100 | Copyright (c) 2010 Benjamin Thomas, Robert Kieffer 101 | 102 | Permission is hereby granted, free of charge, to any person obtaining a copy 103 | of this software and associated documentation files (the "Software"), to deal 104 | in the Software without restriction, including without limitation the rights 105 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 106 | copies of the Software, and to permit persons to whom the Software is 107 | furnished to do so, subject to the following conditions: 108 | 109 | The above copyright notice and this permission notice shall be included in 110 | all copies or substantial portions of the Software. 111 | 112 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 113 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 114 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 115 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 116 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 117 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 118 | THE SOFTWARE. 119 | END OF mime NOTICES AND INFORMATION 120 | 121 | %% minimatch NOTICES AND INFORMATION BEGIN HERE 122 | The ISC License 123 | 124 | Copyright (c) Isaac Z. Schlueter and Contributors 125 | 126 | Permission to use, copy, modify, and/or distribute this software for any 127 | purpose with or without fee is hereby granted, provided that the above 128 | copyright notice and this permission notice appear in all copies. 129 | 130 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 131 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 132 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 133 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 134 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 135 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 136 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 137 | END OF minimatch NOTICES AND INFORMATION 138 | 139 | %% osenv NOTICES AND INFORMATION BEGIN HERE 140 | The ISC License 141 | 142 | Copyright (c) Isaac Z. Schlueter and Contributors 143 | 144 | Permission to use, copy, modify, and/or distribute this software for any 145 | purpose with or without fee is hereby granted, provided that the above 146 | copyright notice and this permission notice appear in all copies. 147 | 148 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 149 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 150 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 151 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 152 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 153 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 154 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 155 | END OF osenv NOTICES AND INFORMATION 156 | 157 | %% read NOTICES AND INFORMATION BEGIN HERE 158 | The ISC License 159 | 160 | Copyright (c) Isaac Z. Schlueter and Contributors 161 | 162 | Permission to use, copy, modify, and/or distribute this software for any 163 | purpose with or without fee is hereby granted, provided that the above 164 | copyright notice and this permission notice appear in all copies. 165 | 166 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 167 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 168 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 169 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 170 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 171 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 172 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 173 | END OF read NOTICES AND INFORMATION 174 | 175 | %% semver NOTICES AND INFORMATION BEGIN HERE 176 | The ISC License 177 | 178 | Copyright (c) Isaac Z. Schlueter and Contributors 179 | 180 | Permission to use, copy, modify, and/or distribute this software for any 181 | purpose with or without fee is hereby granted, provided that the above 182 | copyright notice and this permission notice appear in all copies. 183 | 184 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 185 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 186 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 187 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 188 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 189 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 190 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 191 | END OF semver NOTICES AND INFORMATION 192 | 193 | %% tmp NOTICES AND INFORMATION BEGIN HERE 194 | The MIT License (MIT) 195 | 196 | Copyright (c) 2014 KARASZI István 197 | 198 | Permission is hereby granted, free of charge, to any person obtaining a copy 199 | of this software and associated documentation files (the "Software"), to deal 200 | in the Software without restriction, including without limitation the rights 201 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 202 | copies of the Software, and to permit persons to whom the Software is 203 | furnished to do so, subject to the following conditions: 204 | 205 | The above copyright notice and this permission notice shall be included in all 206 | copies or substantial portions of the Software. 207 | 208 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 209 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 210 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 211 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 212 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 213 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 214 | SOFTWARE. 215 | END OF tmp NOTICES AND INFORMATION 216 | 217 | %% url-join NOTICES AND INFORMATION BEGIN HERE 218 | License 219 | MIT 220 | END OF url-join NOTICES AND INFORMATION 221 | 222 | %% vso-node-api NOTICES AND INFORMATION BEGIN HERE 223 | The MIT License (MIT) 224 | 225 | Copyright (c) 2015 Microsoft 226 | 227 | Permission is hereby granted, free of charge, to any person obtaining a copy 228 | of this software and associated documentation files (the "Software"), to deal 229 | in the Software without restriction, including without limitation the rights 230 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 231 | copies of the Software, and to permit persons to whom the Software is 232 | furnished to do so, subject to the following conditions: 233 | 234 | The above copyright notice and this permission notice shall be included in all 235 | copies or substantial portions of the Software. 236 | 237 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 238 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 239 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 240 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 241 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 242 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 243 | SOFTWARE. 244 | END OF vso-node-api NOTICES AND INFORMATION 245 | 246 | %% yauzl NOTICES AND INFORMATION BEGIN HERE 247 | The MIT License (MIT) 248 | 249 | Copyright (c) 2014 Josh Wolfe 250 | 251 | Permission is hereby granted, free of charge, to any person obtaining a copy 252 | of this software and associated documentation files (the "Software"), to deal 253 | in the Software without restriction, including without limitation the rights 254 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 255 | copies of the Software, and to permit persons to whom the Software is 256 | furnished to do so, subject to the following conditions: 257 | 258 | The above copyright notice and this permission notice shall be included in all 259 | copies or substantial portions of the Software. 260 | 261 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 262 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 263 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 264 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 265 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 266 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 267 | SOFTWARE. 268 | END OF yauzl NOTICES AND INFORMATION 269 | 270 | %% yazl NOTICES AND INFORMATION BEGIN HERE 271 | The MIT License (MIT) 272 | 273 | Copyright (c) 2014 Josh Wolfe 274 | 275 | Permission is hereby granted, free of charge, to any person obtaining a copy 276 | of this software and associated documentation files (the "Software"), to deal 277 | in the Software without restriction, including without limitation the rights 278 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 279 | copies of the Software, and to permit persons to whom the Software is 280 | furnished to do so, subject to the following conditions: 281 | 282 | The above copyright notice and this permission notice shall be included in all 283 | copies or substantial portions of the Software. 284 | 285 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 286 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 287 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 288 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 289 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 290 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 291 | SOFTWARE. 292 | END OF yazl NOTICES AND INFORMATION 293 | -------------------------------------------------------------------------------- /build/azure-pipelines.oss.yml: -------------------------------------------------------------------------------- 1 | trigger: none 2 | 3 | pr: [main] 4 | 5 | variables: 6 | - name: Codeql.SkipTaskAutoInjection 7 | value: true 8 | - name: skipComponentGovernanceDetection 9 | value: true 10 | 11 | jobs: 12 | - job: BuildAndTest 13 | strategy: 14 | matrix: 15 | Linux: 16 | imageName: 'ubuntu-latest' 17 | MacOS: 18 | imageName: 'macos-latest' 19 | Windows: 20 | imageName: 'windows-latest' 21 | 22 | pool: 23 | vmImage: $(imageName) 24 | 25 | steps: 26 | - task: NodeTool@0 27 | inputs: 28 | versionSpec: '20.x' 29 | displayName: 'Install Node.js' 30 | 31 | - script: npm ci 32 | displayName: 'Install dependencies' 33 | 34 | - script: npm test 35 | displayName: 'Run tests' -------------------------------------------------------------------------------- /build/azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | batch: true 3 | branches: 4 | include: 5 | - main 6 | 7 | pr: [main] 8 | 9 | resources: 10 | repositories: 11 | - repository: templates 12 | type: github 13 | name: microsoft/vscode-engineering 14 | ref: main 15 | endpoint: Monaco 16 | 17 | parameters: 18 | - name: nextVersion 19 | displayName: '🚀 Release Version (eg: none, major, minor, patch, prerelease, or X.X.X)' 20 | type: string 21 | default: 'none' 22 | 23 | name: "$(Date:yyyyMMdd).$(Rev:r)${{ replace(format(' (🚀 {0})', parameters.nextVersion), ' (🚀 none)', '') }}" 24 | 25 | extends: 26 | template: azure-pipelines/npm-package/pipeline.yml@templates 27 | parameters: 28 | npmPackages: 29 | - name: vsce 30 | buildSteps: 31 | - script: npm ci 32 | - script: npm run build 33 | testPlatforms: 34 | - name: Linux 35 | nodeVersions: [20.x] 36 | - name: MacOS 37 | nodeVersions: [20.x] 38 | - name: Windows 39 | nodeVersions: [20.x] 40 | testSteps: 41 | - script: npm ci 42 | - script: npm test 43 | ${{ if or(eq(parameters.nextVersion, 'prerelease'), and(in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI'), eq(variables['Build.SourceBranch'], 'refs/heads/main'))) }}: 44 | publishPackage: true 45 | publishRequiresApproval: false 46 | nextVersion: prerelease 47 | tag: next 48 | ${{ elseif eq(parameters.nextVersion, 'none') }}: 49 | publishPackage: false 50 | ${{ else }}: 51 | publishPackage: true 52 | nextVersion: ${{ parameters.nextVersion }} 53 | ghCreateRelease: true 54 | ghReleaseAddChangeLog: true -------------------------------------------------------------------------------- /dummy.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-vsce/fafad8a63e9cf31179f918eb7a4eeb376834c904/dummy.js -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vscode/vsce", 3 | "version": "0.0.0", 4 | "description": "VS Code Extensions Manager", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/Microsoft/vsce" 8 | }, 9 | "type": "commonjs", 10 | "homepage": "https://code.visualstudio.com", 11 | "bugs": "https://github.com/Microsoft/vsce/issues", 12 | "keywords": [ 13 | "vscode", 14 | "vsce", 15 | "extension" 16 | ], 17 | "publishConfig": { 18 | "access": "public" 19 | }, 20 | "contributors": [ 21 | "Microsoft Corporation" 22 | ], 23 | "author": "Microsoft Corporation", 24 | "license": "MIT", 25 | "main": "out/api.js", 26 | "typings": "dist/vsce.d.ts", 27 | "bin": { 28 | "vsce": "vsce" 29 | }, 30 | "scripts": { 31 | "compile": "tsc", 32 | "api": "api-extractor run --local --verbose", 33 | "build": "npm run compile && npm run api", 34 | "watch:build": "npm run compile -- --watch", 35 | "test": "mocha", 36 | "watch:test": "npm run test -- --watch" 37 | }, 38 | "engines": { 39 | "node": ">= 20" 40 | }, 41 | "dependencies": { 42 | "@azure/identity": "^4.1.0", 43 | "@secretlint/node": "^10.1.1", 44 | "@secretlint/secretlint-formatter-sarif": "^10.1.1", 45 | "@secretlint/secretlint-rule-no-dotenv": "^10.1.1", 46 | "@secretlint/secretlint-rule-preset-recommend": "^10.1.1", 47 | "@vscode/vsce-sign": "^2.0.0", 48 | "azure-devops-node-api": "^12.5.0", 49 | "chalk": "^4.1.2", 50 | "cheerio": "^1.0.0-rc.9", 51 | "cockatiel": "^3.1.2", 52 | "commander": "^12.1.0", 53 | "form-data": "^4.0.0", 54 | "glob": "^11.0.0", 55 | "hosted-git-info": "^4.0.2", 56 | "jsonc-parser": "^3.2.0", 57 | "leven": "^3.1.0", 58 | "markdown-it": "^14.1.0", 59 | "mime": "^1.3.4", 60 | "minimatch": "^3.0.3", 61 | "parse-semver": "^1.1.1", 62 | "read": "^1.0.7", 63 | "secretlint": "^10.1.1", 64 | "semver": "^7.5.2", 65 | "tmp": "^0.2.3", 66 | "typed-rest-client": "^1.8.4", 67 | "url-join": "^4.0.1", 68 | "xml2js": "^0.5.0", 69 | "yauzl": "^2.3.1", 70 | "yazl": "^2.2.2" 71 | }, 72 | "devDependencies": { 73 | "@microsoft/api-extractor": "^7.33.7", 74 | "@types/cheerio": "^0.22.29", 75 | "@types/glob": "^8.1.0", 76 | "@types/hosted-git-info": "^3.0.2", 77 | "@types/markdown-it": "^0.0.2", 78 | "@types/mime": "^1", 79 | "@types/minimatch": "^3.0.3", 80 | "@types/mocha": "^7.0.2", 81 | "@types/node": "^16.11.7", 82 | "@types/read": "^0.0.28", 83 | "@types/semver": "^6.0.0", 84 | "@types/tmp": "^0.2.2", 85 | "@types/url-join": "^4.0.1", 86 | "@types/xml2js": "^0.4.4", 87 | "@types/yauzl": "^2.9.2", 88 | "@types/yazl": "^2.4.2", 89 | "mocha": "^11.1.0", 90 | "source-map-support": "^0.4.2", 91 | "ts-node": "^10.9.1", 92 | "typescript": "^4.3.2" 93 | }, 94 | "optionalDependencies": { 95 | "keytar": "^7.7.0" 96 | }, 97 | "mocha": { 98 | "require": [ 99 | "ts-node/register" 100 | ], 101 | "watch-files": "src/**", 102 | "spec": "src/test/**/*.ts" 103 | } 104 | } -------------------------------------------------------------------------------- /src/api.ts: -------------------------------------------------------------------------------- 1 | import { publish as _publish, IPublishOptions, unpublish as _unpublish, IUnpublishOptions } from './publish'; 2 | import { packageCommand, listFiles as _listFiles, IPackageOptions } from './package'; 3 | 4 | /** 5 | * @deprecated prefer IPackageOptions instead 6 | * @public 7 | */ 8 | export type IBaseVSIXOptions = Pick< 9 | IPackageOptions, 10 | 'baseContentUrl' | 'baseImagesUrl' | 'githubBranch' | 'gitlabBranch' | 'useYarn' | 'target' | 'preRelease' 11 | >; 12 | 13 | /** 14 | * @deprecated prefer IPackageOptions instead 15 | * @public 16 | */ 17 | export type ICreateVSIXOptions = Pick & IBaseVSIXOptions; 18 | 19 | /** 20 | * The supported list of package managers. 21 | * @public 22 | */ 23 | export enum PackageManager { 24 | Npm, 25 | Yarn, 26 | None, 27 | } 28 | 29 | /** 30 | * Options for the `listFiles` function. 31 | * @public 32 | */ 33 | export interface IListFilesOptions { 34 | /** 35 | * The working directory of the extension. Defaults to `process.cwd()`. 36 | */ 37 | cwd?: string; 38 | 39 | /** 40 | * The package manager to use. Defaults to `PackageManager.Npm`. 41 | */ 42 | packageManager?: PackageManager; 43 | 44 | /** 45 | * A subset of the top level dependencies which should be included. The 46 | * default is `undefined` which include all dependencies, an empty array means 47 | * no dependencies will be included. 48 | */ 49 | packagedDependencies?: string[]; 50 | 51 | /** 52 | * The location of an alternative .vscodeignore file to be used. 53 | * The `.vscodeignore` file located at the root of the project will be taken 54 | * instead, if none is specified. 55 | */ 56 | ignoreFile?: string; 57 | } 58 | 59 | export type { IPackageOptions } from './package'; 60 | 61 | /** 62 | * Creates a VSIX from the extension in the current working directory. 63 | * @public 64 | */ 65 | export function createVSIX(options: IPackageOptions = {}): Promise { 66 | return packageCommand(options); 67 | } 68 | 69 | export type { IPublishOptions } from './publish'; 70 | 71 | /** 72 | * Publishes the extension in the current working directory. 73 | * @public 74 | */ 75 | export function publish(options: IPublishOptions = {}): Promise { 76 | return _publish(options); 77 | } 78 | 79 | /** 80 | * Lists the files included in the extension's package. 81 | * @public 82 | */ 83 | export function listFiles(options: IListFilesOptions = {}): Promise { 84 | return _listFiles({ 85 | ...options, 86 | useYarn: options.packageManager === PackageManager.Yarn, 87 | dependencies: options.packageManager !== PackageManager.None, 88 | }); 89 | } 90 | 91 | /** 92 | * Options for the `publishVSIX` function. 93 | * @public 94 | */ 95 | export type IPublishVSIXOptions = IPublishOptions & Pick; 96 | 97 | /** 98 | * Publishes a pre-build VSIX. 99 | * @public 100 | */ 101 | export function publishVSIX(packagePath: string | string[], options: IPublishVSIXOptions = {}): Promise { 102 | return _publish({ 103 | packagePath: typeof packagePath === 'string' ? [packagePath] : packagePath, 104 | ...options, 105 | targets: typeof options.target === 'string' ? [options.target] : undefined, 106 | ...{ target: undefined }, 107 | }); 108 | } 109 | 110 | /** 111 | * Options for the `unpublishVSIX` function. 112 | * @public 113 | */ 114 | export type IUnpublishVSIXOptions = IPublishOptions & Pick; 115 | 116 | /** 117 | * Deletes a specific extension from the marketplace. 118 | * @public 119 | */ 120 | export function unpublishVSIX(options: IUnpublishVSIXOptions = {}): Promise { 121 | return _unpublish({ force: true, ...options }); 122 | } -------------------------------------------------------------------------------- /src/auth.ts: -------------------------------------------------------------------------------- 1 | import { AzureCliCredential, AzureDeveloperCliCredential, AzurePowerShellCredential, ChainedTokenCredential, EnvironmentCredential, ManagedIdentityCredential } from "@azure/identity"; 2 | 3 | function createChainedTokenCredential(): ChainedTokenCredential { 4 | return new ChainedTokenCredential( 5 | new EnvironmentCredential(), 6 | new AzureCliCredential(), 7 | new ManagedIdentityCredential({ clientId: process.env.AZURE_CLIENT_ID }), 8 | new AzurePowerShellCredential({ tenantId: process.env.AZURE_TENANT_ID }), 9 | new AzureDeveloperCliCredential({ tenantId: process.env.AZURE_TENANT_ID }) 10 | ); 11 | } 12 | 13 | export async function getAzureCredentialAccessToken(): Promise { 14 | try { 15 | const credential = createChainedTokenCredential() 16 | const token = await credential.getToken('499b84ac-1321-427f-aa17-267ca6975798/.default', { 17 | tenantId: process.env.AZURE_TENANT_ID 18 | }); 19 | 20 | return token.token; 21 | } catch (error) { 22 | throw new Error('Can not acquire a Microsoft Entra ID access token. Additional information:\n\n' + error) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/manifest.ts: -------------------------------------------------------------------------------- 1 | export interface Person { 2 | name: string; 3 | url?: string; 4 | email?: string; 5 | } 6 | 7 | export interface Translation { 8 | id: string; 9 | path: string; 10 | } 11 | 12 | export interface Localization { 13 | languageId: string; 14 | languageName?: string; 15 | localizedLanguageName?: string; 16 | translations: Translation[]; 17 | } 18 | 19 | export interface Language { 20 | readonly id: string; 21 | readonly aliases?: string[]; 22 | readonly extensions?: string[]; 23 | } 24 | 25 | export interface Grammar { 26 | readonly language: string; 27 | readonly scopeName: string; 28 | readonly path: string; 29 | } 30 | 31 | export interface Command { 32 | readonly command: string; 33 | readonly title: string; 34 | } 35 | 36 | export interface Authentication { 37 | readonly id: string; 38 | readonly label: string; 39 | } 40 | 41 | export interface CustomEditor { 42 | readonly viewType: string; 43 | readonly priority: string; 44 | readonly selector: readonly { 45 | readonly filenamePattern?: string; 46 | }[]; 47 | } 48 | 49 | export interface View { 50 | readonly id: string; 51 | readonly name: string; 52 | } 53 | 54 | export interface Contributions { 55 | readonly localizations?: Localization[]; 56 | readonly languages?: Language[]; 57 | readonly grammars?: Grammar[]; 58 | readonly commands?: Command[]; 59 | readonly authentication?: Authentication[]; 60 | readonly customEditors?: CustomEditor[]; 61 | readonly views?: { [location: string]: View[] }; 62 | readonly [contributionType: string]: any; 63 | } 64 | 65 | export type ExtensionKind = 'ui' | 'workspace' | 'web'; 66 | 67 | export interface ManifestPackage { 68 | // mandatory (npm) 69 | name: string; 70 | version: string; 71 | engines: { vscode: string;[name: string]: string }; 72 | 73 | // vscode 74 | publisher?: string; 75 | icon?: string; 76 | contributes?: Contributions; 77 | activationEvents?: string[]; 78 | extensionDependencies?: string[]; 79 | extensionPack?: string[]; 80 | galleryBanner?: { color?: string; theme?: string }; 81 | preview?: boolean; 82 | badges?: { url: string; href: string; description: string }[]; 83 | markdown?: 'github' | 'standard'; 84 | _bundling?: { [name: string]: string }[]; 85 | _testing?: string; 86 | enableProposedApi?: boolean; 87 | enabledApiProposals?: readonly string[]; 88 | qna?: 'marketplace' | string | false; 89 | extensionKind?: ExtensionKind | ExtensionKind[]; 90 | sponsor?: { url: string }; 91 | 92 | // optional (npm) 93 | author?: string | Person; 94 | displayName?: string; 95 | description?: string; 96 | keywords?: string[]; 97 | categories?: string[]; 98 | homepage?: string; 99 | bugs?: string | { url?: string; email?: string }; 100 | license?: string; 101 | contributors?: string | Person[]; 102 | main?: string; 103 | browser?: string; 104 | repository?: string | { type?: string; url?: string }; 105 | scripts?: { [name: string]: string }; 106 | dependencies?: { [name: string]: string }; 107 | devDependencies?: { [name: string]: string }; 108 | private?: boolean; 109 | pricing?: string; 110 | files?: string[]; 111 | 112 | // vsce 113 | vsce?: any; 114 | 115 | // not supported (npm) 116 | // bin 117 | // man 118 | // directories 119 | // config 120 | // peerDependencies 121 | // bundledDependencies 122 | // optionalDependencies 123 | // os?: string[]; 124 | // cpu?: string[]; 125 | // preferGlobal 126 | // publishConfig 127 | } 128 | 129 | export interface ManifestPublish extends ManifestPackage { 130 | publisher: string; 131 | } 132 | 133 | type RecursivePartial = { 134 | [P in keyof T]?: T[P] extends object ? RecursivePartial : T[P]; 135 | }; 136 | 137 | export type UnverifiedManifest = RecursivePartial; 138 | -------------------------------------------------------------------------------- /src/nls.ts: -------------------------------------------------------------------------------- 1 | import { ManifestPackage } from './manifest'; 2 | 3 | export interface ITranslations { 4 | [key: string]: string; 5 | } 6 | 7 | const regex = /^%([\w\d.]+)%$/i; 8 | 9 | function createPatcher(translations: ITranslations): (value: T) => T { 10 | return (value: T): T => { 11 | if (typeof value !== 'string') { 12 | return value; 13 | } 14 | 15 | const match = regex.exec(value); 16 | 17 | if (!match) { 18 | return value; 19 | } 20 | 21 | const translation = translations[match[1]] as unknown; 22 | if (translation === undefined) { 23 | throw new Error(`No translation found for ${value}`); 24 | } 25 | 26 | return translation as T; 27 | }; 28 | } 29 | 30 | export function patchNLS(manifest: ManifestPackage, translations: ITranslations): ManifestPackage { 31 | const patcher = createPatcher(translations); 32 | return JSON.parse(JSON.stringify(manifest, (_, value: any) => patcher(value))); 33 | } 34 | -------------------------------------------------------------------------------- /src/npm.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as fs from 'fs'; 3 | import * as cp from 'child_process'; 4 | import parseSemver from 'parse-semver'; 5 | import { CancellationToken, log, nonnull } from './util'; 6 | 7 | const exists = (file: string) => 8 | fs.promises.stat(file).then( 9 | _ => true, 10 | _ => false 11 | ); 12 | 13 | interface IOptions { 14 | cwd?: string; 15 | stdio?: any; 16 | customFds?: any; 17 | env?: any; 18 | timeout?: number; 19 | maxBuffer?: number; 20 | killSignal?: string; 21 | } 22 | 23 | function parseStdout({ stdout }: { stdout: string }): string { 24 | return stdout.split(/[\r\n]/).filter(line => !!line)[0]; 25 | } 26 | 27 | function exec( 28 | command: string, 29 | options: IOptions = {}, 30 | cancellationToken?: CancellationToken 31 | ): Promise<{ stdout: string; stderr: string }> { 32 | return new Promise((c, e) => { 33 | let disposeCancellationListener: Function | null = null; 34 | 35 | const child = cp.exec(command, { ...options, encoding: 'utf8' } as any, (err, stdout: string, stderr: string) => { 36 | if (disposeCancellationListener) { 37 | disposeCancellationListener(); 38 | disposeCancellationListener = null; 39 | } 40 | 41 | if (err) { 42 | return e(err); 43 | } 44 | c({ stdout, stderr }); 45 | }); 46 | 47 | if (cancellationToken) { 48 | disposeCancellationListener = cancellationToken.subscribe((err: any) => { 49 | child.kill(); 50 | e(err); 51 | }); 52 | } 53 | }); 54 | } 55 | 56 | async function checkNPM(cancellationToken?: CancellationToken): Promise { 57 | const { stdout } = await exec('npm -v', {}, cancellationToken); 58 | const version = stdout.trim(); 59 | 60 | if (/^3\.7\.[0123]$/.test(version)) { 61 | throw new Error(`npm@${version} doesn't work with vsce. Please update npm: npm install -g npm`); 62 | } 63 | } 64 | 65 | function getNpmDependencies(cwd: string): Promise { 66 | return checkNPM() 67 | .then(() => 68 | exec('npm list --production --parseable --depth=99999 --loglevel=error', { cwd, maxBuffer: 5000 * 1024 }) 69 | ) 70 | .then(({ stdout }) => stdout.split(/[\r\n]/).filter(dir => path.isAbsolute(dir))); 71 | } 72 | 73 | interface YarnTreeNode { 74 | name: string; 75 | children: YarnTreeNode[]; 76 | } 77 | 78 | export interface YarnDependency { 79 | name: string; 80 | path: string; 81 | children: YarnDependency[]; 82 | } 83 | 84 | function asYarnDependency(prefix: string, tree: YarnTreeNode, prune: boolean): YarnDependency | null { 85 | if (prune && /@[\^~]/.test(tree.name)) { 86 | return null; 87 | } 88 | 89 | let name: string; 90 | 91 | try { 92 | const parseResult = parseSemver(tree.name); 93 | name = parseResult.name; 94 | } catch (err) { 95 | name = tree.name.replace(/^([^@+])@.*$/, '$1'); 96 | } 97 | 98 | const dependencyPath = path.join(prefix, name); 99 | const children: YarnDependency[] = []; 100 | 101 | for (const child of tree.children || []) { 102 | const dep = asYarnDependency(path.join(prefix, name, 'node_modules'), child, prune); 103 | 104 | if (dep) { 105 | children.push(dep); 106 | } 107 | } 108 | 109 | return { name, path: dependencyPath, children }; 110 | } 111 | 112 | function selectYarnDependencies(deps: YarnDependency[], packagedDependencies: string[]): YarnDependency[] { 113 | const index = new (class { 114 | private data: { [name: string]: YarnDependency } = Object.create(null); 115 | constructor() { 116 | for (const dep of deps) { 117 | if (this.data[dep.name]) { 118 | throw Error(`Dependency seen more than once: ${dep.name}`); 119 | } 120 | this.data[dep.name] = dep; 121 | } 122 | } 123 | find(name: string): YarnDependency { 124 | let result = this.data[name]; 125 | if (!result) { 126 | throw new Error(`Could not find dependency: ${name}`); 127 | } 128 | return result; 129 | } 130 | })(); 131 | 132 | const reached = new (class { 133 | values: YarnDependency[] = []; 134 | add(dep: YarnDependency): boolean { 135 | if (this.values.indexOf(dep) < 0) { 136 | this.values.push(dep); 137 | return true; 138 | } 139 | return false; 140 | } 141 | })(); 142 | 143 | const visit = (name: string) => { 144 | let dep = index.find(name); 145 | if (!reached.add(dep)) { 146 | // already seen -> done 147 | return; 148 | } 149 | for (const child of dep.children) { 150 | visit(child.name); 151 | } 152 | }; 153 | packagedDependencies.forEach(visit); 154 | return reached.values; 155 | } 156 | 157 | async function getYarnProductionDependencies(cwd: string, packagedDependencies?: string[]): Promise { 158 | const raw = await new Promise((c, e) => 159 | cp.exec( 160 | 'yarn list --prod --json', 161 | { cwd, encoding: 'utf8', env: { DISABLE_V8_COMPILE_CACHE: "1", ...process.env }, maxBuffer: 5000 * 1024 }, 162 | (err, stdout) => (err ? e(err) : c(stdout)) 163 | ) 164 | ); 165 | const match = /^{"type":"tree".*$/m.exec(raw); 166 | 167 | if (!match || match.length !== 1) { 168 | throw new Error('Could not parse result of `yarn list --json`'); 169 | } 170 | 171 | const usingPackagedDependencies = Array.isArray(packagedDependencies); 172 | const trees = JSON.parse(match[0]).data.trees as YarnTreeNode[]; 173 | 174 | let result = trees 175 | .map(tree => asYarnDependency(path.join(cwd, 'node_modules'), tree, !usingPackagedDependencies)) 176 | .filter(nonnull); 177 | 178 | if (usingPackagedDependencies) { 179 | result = selectYarnDependencies(result, packagedDependencies!); 180 | } 181 | 182 | return result; 183 | } 184 | 185 | async function getYarnDependencies(cwd: string, packagedDependencies?: string[]): Promise { 186 | const result = new Set([cwd]); 187 | 188 | const deps = await getYarnProductionDependencies(cwd, packagedDependencies); 189 | const flatten = (dep: YarnDependency) => { 190 | result.add(dep.path); 191 | dep.children.forEach(flatten); 192 | }; 193 | deps.forEach(flatten); 194 | 195 | return [...result]; 196 | } 197 | 198 | export async function detectYarn(cwd: string): Promise { 199 | for (const name of ['yarn.lock', '.yarnrc', '.yarnrc.yaml', '.pnp.cjs', '.yarn']) { 200 | if (await exists(path.join(cwd, name))) { 201 | if (!process.env['VSCE_TESTS']) { 202 | log.info( 203 | `Detected presence of ${name}. Using 'yarn' instead of 'npm' (to override this pass '--no-yarn' on the command line).` 204 | ); 205 | } 206 | return true; 207 | } 208 | } 209 | return false; 210 | } 211 | 212 | export async function getDependencies( 213 | cwd: string, 214 | dependencies: 'npm' | 'yarn' | 'none' | undefined, 215 | packagedDependencies?: string[] 216 | ): Promise { 217 | if (dependencies === 'none') { 218 | return [cwd]; 219 | } else if (dependencies === 'yarn' || (dependencies === undefined && (await detectYarn(cwd)))) { 220 | return await getYarnDependencies(cwd, packagedDependencies); 221 | } else { 222 | return await getNpmDependencies(cwd); 223 | } 224 | } 225 | 226 | export function getLatestVersion(name: string, cancellationToken?: CancellationToken): Promise { 227 | return checkNPM(cancellationToken) 228 | .then(() => exec(`npm show ${name} version`, {}, cancellationToken)) 229 | .then(parseStdout); 230 | } 231 | -------------------------------------------------------------------------------- /src/publicgalleryapi.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient, HttpClientResponse } from 'typed-rest-client/HttpClient'; 2 | import { 3 | PublishedExtension, 4 | ExtensionQueryFlags, 5 | FilterCriteria, 6 | ExtensionQueryFilterType, 7 | TypeInfo, 8 | } from 'azure-devops-node-api/interfaces/GalleryInterfaces'; 9 | import { IHeaders } from 'azure-devops-node-api/interfaces/common/VsoBaseInterfaces'; 10 | import { ContractSerializer } from 'azure-devops-node-api/Serialization'; 11 | 12 | export interface ExtensionQuery { 13 | readonly pageNumber?: number; 14 | readonly pageSize?: number; 15 | readonly flags?: ExtensionQueryFlags[]; 16 | readonly criteria?: FilterCriteria[]; 17 | readonly assetTypes?: string[]; 18 | } 19 | 20 | interface VSCodePublishedExtension extends PublishedExtension { 21 | publisher: { displayName: string; publisherName: string }; 22 | } 23 | 24 | export class PublicGalleryAPI { 25 | private readonly client = new HttpClient('vsce'); 26 | 27 | constructor(private baseUrl: string, private apiVersion = '3.0-preview.1') {} 28 | 29 | private post(url: string, data: string, additionalHeaders?: IHeaders): Promise { 30 | return this.client.post(`${this.baseUrl}/_apis/public${url}`, data, additionalHeaders); 31 | } 32 | 33 | async extensionQuery({ 34 | pageNumber = 1, 35 | pageSize = 1, 36 | flags = [], 37 | criteria = [], 38 | assetTypes = [], 39 | }: ExtensionQuery): Promise { 40 | const data = JSON.stringify({ 41 | filters: [{ pageNumber, pageSize, criteria }], 42 | assetTypes, 43 | flags: flags.reduce((memo, flag) => memo | flag, 0), 44 | }); 45 | 46 | const res = await this.post('/gallery/extensionquery', data, { 47 | Accept: `application/json;api-version=${this.apiVersion}`, 48 | 'Content-Type': 'application/json', 49 | }); 50 | const raw = JSON.parse(await res.readBody()); 51 | 52 | if (raw.errorCode !== undefined) { 53 | throw new Error(raw.message); 54 | } 55 | 56 | return ContractSerializer.deserialize(raw.results[0].extensions, TypeInfo.PublishedExtension, false, false); 57 | } 58 | 59 | async getExtension(extensionId: string, flags: ExtensionQueryFlags[] = []): Promise { 60 | const query = { criteria: [{ filterType: ExtensionQueryFilterType.Name, value: extensionId }], flags }; 61 | const extensions = await this.extensionQuery(query); 62 | return extensions.filter( 63 | ({ publisher: { publisherName: publisher }, extensionName: name }) => 64 | extensionId.toLowerCase() === `${publisher}.${name}`.toLowerCase() 65 | )[0]; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/publish.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import { promisify } from 'util'; 3 | import * as semver from 'semver'; 4 | import { ExtensionQueryFlags, PublishedExtension } from 'azure-devops-node-api/interfaces/GalleryInterfaces'; 5 | import { pack, readManifest, versionBump, prepublish, signPackage, createSignatureArchive } from './package'; 6 | import * as tmp from 'tmp'; 7 | import { IVerifyPatOptions, getPublisher } from './store'; 8 | import { getGalleryAPI, read, getPublishedUrl, log, getHubUrl, patchOptionsWithManifest } from './util'; 9 | import { ManifestPackage, ManifestPublish } from './manifest'; 10 | import { readVSIXPackage } from './zip'; 11 | import { validatePublisher } from './validation'; 12 | import { GalleryApi } from 'azure-devops-node-api/GalleryApi'; 13 | import FormData from 'form-data'; 14 | import { basename } from 'path'; 15 | import { IterableBackoff, handleWhen, retry } from 'cockatiel'; 16 | import { getAzureCredentialAccessToken } from './auth'; 17 | 18 | const tmpName = promisify(tmp.tmpName); 19 | 20 | /** 21 | * Options for the `publish` function. 22 | * @public 23 | */ 24 | export interface IPublishOptions { 25 | readonly packagePath?: string[]; 26 | readonly version?: string; 27 | readonly targets?: string[]; 28 | readonly ignoreOtherTargetFolders?: boolean; 29 | readonly commitMessage?: string; 30 | readonly gitTagVersion?: boolean; 31 | readonly updatePackageJson?: boolean; 32 | 33 | /** 34 | * The location of the extension in the file system. 35 | * 36 | * Defaults to `process.cwd()`. 37 | */ 38 | readonly cwd?: string; 39 | readonly readmePath?: string; 40 | readonly changelogPath?: string; 41 | readonly githubBranch?: string; 42 | readonly gitlabBranch?: string; 43 | 44 | /** 45 | * The base URL for links detected in Markdown files. 46 | */ 47 | readonly baseContentUrl?: string; 48 | 49 | /** 50 | * The base URL for images detected in Markdown files. 51 | */ 52 | readonly baseImagesUrl?: string; 53 | 54 | /** 55 | * Should use Yarn instead of NPM. 56 | */ 57 | readonly useYarn?: boolean; 58 | readonly dependencyEntryPoints?: string[]; 59 | readonly ignoreFile?: string; 60 | 61 | /** 62 | * Recurse into symlinked directories instead of treating them as files 63 | */ 64 | readonly followSymlinks?: boolean; 65 | 66 | /** 67 | * The Personal Access Token to use. 68 | * 69 | * Defaults to the stored one. 70 | */ 71 | readonly pat?: string; 72 | readonly azureCredential?: boolean; 73 | readonly allowProposedApi?: boolean; 74 | readonly noVerify?: boolean; 75 | readonly allowProposedApis?: string[]; 76 | readonly allowAllProposedApis?: boolean; 77 | readonly allowPackageSecrets?: string[]; 78 | readonly allowPackageAllSecrets?: boolean; 79 | readonly allowPackageEnvFile?: boolean; 80 | readonly dependencies?: boolean; 81 | readonly preRelease?: boolean; 82 | readonly allowStarActivation?: boolean; 83 | readonly allowMissingRepository?: boolean; 84 | readonly allowUnusedFilesPattern?: boolean; 85 | readonly skipDuplicate?: boolean; 86 | readonly skipLicense?: boolean; 87 | 88 | readonly sigzipPath?: string[]; 89 | readonly manifestPath?: string[]; 90 | readonly signaturePath?: string[]; 91 | readonly signTool?: string; 92 | } 93 | 94 | export async function publish(options: IPublishOptions = {}): Promise { 95 | if (options.packagePath) { 96 | if (options.version) { 97 | throw new Error(`Both options not supported simultaneously: 'packagePath' and 'version'.`); 98 | } else if (options.targets) { 99 | throw new Error( 100 | `Both options not supported simultaneously: 'packagePath' and 'target'. Use 'vsce package --target ' to first create a platform specific package, then use 'vsce publish --packagePath ' to publish it.` 101 | ); 102 | } 103 | 104 | if (options.manifestPath || options.signaturePath) { 105 | if (options.packagePath.length !== options.manifestPath?.length || options.packagePath.length !== options.signaturePath?.length) { 106 | throw new Error(`Either all packages must be signed or none of them.`); 107 | } 108 | } 109 | 110 | for (let index = 0; index < options.packagePath.length; index++) { 111 | const packagePath = options.packagePath[index]; 112 | const vsix = await readVSIXPackage(packagePath); 113 | let target: string | undefined; 114 | 115 | try { 116 | target = vsix.xmlManifest.PackageManifest.Metadata[0].Identity[0].$.TargetPlatform ?? undefined; 117 | } catch (err) { 118 | throw new Error(`Invalid extension VSIX manifest. ${err}`); 119 | } 120 | 121 | if (options.preRelease) { 122 | let isPreReleasePackage = false; 123 | try { 124 | isPreReleasePackage = !!vsix.xmlManifest.PackageManifest.Metadata[0].Properties[0].Property.some( 125 | p => p.$.Id === 'Microsoft.VisualStudio.Code.PreRelease' 126 | ); 127 | } catch (err) { 128 | throw new Error(`Invalid extension VSIX manifest. ${err}`); 129 | } 130 | if (!isPreReleasePackage) { 131 | throw new Error( 132 | `Cannot use '--pre-release' flag with a package that was not packaged as pre-release. Please package it using the '--pre-release' flag and publish again.` 133 | ); 134 | } 135 | } 136 | 137 | const manifestValidated = validateManifestForPublishing(vsix.manifest, options); 138 | 139 | let sigzipPath: string | undefined; 140 | if (options.manifestPath?.[index] && options.signaturePath?.[index]) { 141 | sigzipPath = await createSignatureArchive(options.manifestPath[index], options.signaturePath[index]); 142 | } 143 | 144 | if (!sigzipPath) { 145 | sigzipPath = options.sigzipPath?.[index]; 146 | } 147 | 148 | if (!sigzipPath && options.signTool) { 149 | sigzipPath = await signPackage(packagePath, options.signTool); 150 | } 151 | 152 | await _publish(packagePath, sigzipPath, manifestValidated, { ...options, target }); 153 | } 154 | } else { 155 | const cwd = options.cwd || process.cwd(); 156 | const manifest = await readManifest(cwd); 157 | patchOptionsWithManifest(options, manifest); 158 | 159 | // Validate marketplace requirements before prepublish to avoid unnecessary work 160 | validateManifestForPublishing(manifest, options); 161 | 162 | await prepublish(cwd, manifest, options.useYarn); 163 | await versionBump(options); 164 | 165 | if (options.targets) { 166 | for (const target of options.targets) { 167 | const packagePath = await tmpName(); 168 | const packageResult = await pack({ ...options, target, packagePath }); 169 | const manifestValidated = validateManifestForPublishing(packageResult.manifest, options); 170 | const sigzipPath = options.signTool ? await signPackage(packagePath, options.signTool) : undefined; 171 | await _publish(packagePath, sigzipPath, manifestValidated, { ...options, target }); 172 | } 173 | } else { 174 | const packagePath = await tmpName(); 175 | const packageResult = await pack({ ...options, packagePath }); 176 | const manifestValidated = validateManifestForPublishing(packageResult.manifest, options); 177 | const sigzipPath = options.signTool ? await signPackage(packagePath, options.signTool) : undefined; 178 | await _publish(packagePath, sigzipPath, manifestValidated, options); 179 | } 180 | } 181 | } 182 | 183 | export interface IInternalPublishOptions { 184 | readonly target?: string; 185 | readonly pat?: string; 186 | readonly allowProposedApi?: boolean; 187 | readonly noVerify?: boolean; 188 | readonly allowProposedApis?: string[]; 189 | readonly allowAllProposedApis?: boolean; 190 | readonly skipDuplicate?: boolean; 191 | } 192 | 193 | async function _publish(packagePath: string, sigzipPath: string | undefined, manifest: ManifestPublish, options: IInternalPublishOptions) { 194 | const pat = await getPAT(manifest.publisher, options); 195 | const api = await getGalleryAPI(pat); 196 | const packageStream = fs.createReadStream(packagePath); 197 | const name = `${manifest.publisher}.${manifest.name}`; 198 | const description = options.target 199 | ? `${name} (${options.target}) v${manifest.version}` 200 | : `${name} v${manifest.version}`; 201 | 202 | log.info(`Publishing '${description}'...`); 203 | 204 | let extension: PublishedExtension | null = null; 205 | 206 | try { 207 | try { 208 | extension = await api.getExtension( 209 | null, 210 | manifest.publisher, 211 | manifest.name, 212 | undefined, 213 | ExtensionQueryFlags.IncludeVersions 214 | ); 215 | } catch (err: any) { 216 | if (err.statusCode !== 404) { 217 | throw err; 218 | } 219 | } 220 | 221 | if (extension && extension.versions) { 222 | const versionExists = extension.versions.some(v => 223 | (v.version === manifest.version) && 224 | (v.targetPlatform === options.target)); 225 | 226 | if (versionExists) { 227 | if (options.skipDuplicate) { 228 | log.done(`Version ${manifest.version} is already published. Skipping publish.`); 229 | return; 230 | } else { 231 | throw new Error(`${description} already exists.`); 232 | } 233 | 234 | } 235 | 236 | if (sigzipPath) { 237 | await _publishSignedPackage(api, basename(packagePath), packageStream, basename(sigzipPath), fs.createReadStream(sigzipPath), manifest); 238 | } else { 239 | try { 240 | await api.updateExtension(undefined, packageStream, manifest.publisher, manifest.name); 241 | } catch (err: any) { 242 | if (err.statusCode === 409) { 243 | if (options.skipDuplicate) { 244 | log.done(`Version ${manifest.version} is already published. Skipping publish.`); 245 | return; 246 | } else { 247 | throw new Error(`${description} already exists.`); 248 | } 249 | } else { 250 | throw err; 251 | } 252 | } 253 | } 254 | } else { 255 | if (sigzipPath) { 256 | await _publishSignedPackage(api, basename(packagePath), packageStream, basename(sigzipPath), fs.createReadStream(sigzipPath), manifest); 257 | } else { 258 | await api.createExtension(undefined, packageStream); 259 | } 260 | } 261 | } catch (err: any) { 262 | const message = (err && err.message) || ''; 263 | 264 | if (/Personal Access Token used has expired/.test(message)) { 265 | err.message = `${err.message}\n\nYou're using an expired Personal Access Token, please get a new PAT.\nMore info: https://aka.ms/vscodepat`; 266 | } else if (/Invalid Resource/.test(message)) { 267 | err.message = `${err.message}\n\nYou're likely using an expired Personal Access Token, please get a new PAT.\nMore info: https://aka.ms/vscodepat`; 268 | } 269 | 270 | throw err; 271 | } 272 | 273 | log.info(`Extension URL (might take a few minutes): ${getPublishedUrl(name)}`); 274 | log.info(`Hub URL: ${getHubUrl(manifest.publisher, manifest.name)}`); 275 | log.done(`Published ${description}.`); 276 | } 277 | 278 | async function _publishSignedPackage(api: GalleryApi, packageName: string, packageStream: fs.ReadStream, sigzipName: string, sigzipStream: fs.ReadStream, manifest: ManifestPublish) { 279 | const extensionType = 'Visual Studio Code'; 280 | const form = new FormData(); 281 | const lineBreak = '\r\n'; 282 | form.setBoundary('0f411892-ef48-488f-89d3-4f0546e84723'); 283 | form.append('vsix', packageStream, { 284 | header: `--${form.getBoundary()}${lineBreak}Content-Disposition: attachment; name=vsix; filename=\"${packageName}\"${lineBreak}Content-Type: application/octet-stream${lineBreak}${lineBreak}` 285 | }); 286 | form.append('sigzip', sigzipStream, { 287 | header: `--${form.getBoundary()}${lineBreak}Content-Disposition: attachment; name=sigzip; filename=\"${sigzipName}\"${lineBreak}Content-Type: application/octet-stream${lineBreak}${lineBreak}` 288 | }); 289 | 290 | const publishWithRetry = retry(handleWhen(err => err.message.includes('timeout')), { 291 | maxAttempts: 3, 292 | backoff: new IterableBackoff([5_000, 10_000, 20_000]) 293 | }); 294 | 295 | return await publishWithRetry.execute(async () => { 296 | return await api.publishExtensionWithPublisherSignature(undefined, form, manifest.publisher, manifest.name, extensionType); 297 | }); 298 | } 299 | 300 | /** 301 | * Options for the `unpublish` function. 302 | * @public 303 | */ 304 | export interface IUnpublishOptions extends IPublishOptions { 305 | id?: string; 306 | force?: boolean; 307 | } 308 | 309 | export async function unpublish(options: IUnpublishOptions = {}): Promise { 310 | let publisher: string, name: string; 311 | 312 | if (options.id) { 313 | [publisher, name] = options.id.split('.'); 314 | } else { 315 | const manifest = await readManifest(options.cwd); 316 | publisher = validatePublisher(manifest.publisher); 317 | name = manifest.name; 318 | } 319 | 320 | const fullName = `${publisher}.${name}`; 321 | 322 | if (!options.force) { 323 | const answer = await read(`This will delete ALL published versions! Please type '${fullName}' to confirm: `); 324 | 325 | if (answer !== fullName) { 326 | throw new Error('Aborted'); 327 | } 328 | } 329 | 330 | const pat = await getPAT(publisher, options); 331 | const api = await getGalleryAPI(pat); 332 | 333 | await api.deleteExtension(publisher, name); 334 | log.done(`Deleted extension: ${fullName}!`); 335 | } 336 | 337 | function validateManifestForPublishing(manifest: ManifestPackage, options: IInternalPublishOptions): ManifestPublish { 338 | if (manifest.enableProposedApi && !options.allowAllProposedApis && !options.noVerify) { 339 | throw new Error( 340 | "Extensions using proposed API (enableProposedApi: true) can't be published to the Marketplace. Use --allow-all-proposed-apis to bypass. https://code.visualstudio.com/api/advanced-topics/using-proposed-api" 341 | ); 342 | } 343 | 344 | if (manifest.enabledApiProposals && !options.allowAllProposedApis && !options.noVerify && manifest.enabledApiProposals?.some(p => !options.allowProposedApis?.includes(p))) { 345 | throw new Error( 346 | `Extensions using unallowed proposed API (enabledApiProposals: [${manifest.enabledApiProposals}], allowed: [${options.allowProposedApis ?? []}]) can't be published to the Marketplace. Use --allow-proposed-apis or --allow-all-proposed-apis to bypass. https://code.visualstudio.com/api/advanced-topics/using-proposed-api` 347 | ); 348 | } 349 | 350 | if (semver.prerelease(manifest.version)) { 351 | throw new Error(`The VS Marketplace doesn't support prerelease versions: '${manifest.version}'. Checkout our pre-release versioning recommendation here: https://code.visualstudio.com/api/working-with-extensions/publishing-extension#prerelease-extensions`); 352 | } 353 | 354 | return { ...manifest, publisher: validatePublisher(manifest.publisher) }; 355 | } 356 | 357 | export async function getPAT(publisher: string, options: IPublishOptions | IUnpublishOptions | IVerifyPatOptions): Promise { 358 | if (options.pat) { 359 | return options.pat; 360 | } 361 | 362 | if (options.azureCredential) { 363 | return await getAzureCredentialAccessToken(); 364 | } 365 | 366 | return (await getPublisher(publisher)).pat; 367 | } 368 | -------------------------------------------------------------------------------- /src/search.ts: -------------------------------------------------------------------------------- 1 | import { getPublicGalleryAPI } from './util'; 2 | import { 3 | ExtensionQueryFilterType, 4 | ExtensionQueryFlags, 5 | PublishedExtension, 6 | ExtensionStatistic, 7 | } from 'azure-devops-node-api/interfaces/GalleryInterfaces'; 8 | import { ratingStars, tableView, wordTrim } from './viewutils'; 9 | import { ExtensionStatisticsMap } from './show'; 10 | const installationTarget = 'Microsoft.VisualStudio.Code'; 11 | const excludeFlags = '37888'; //Value to exclude un-published, locked or hidden extensions 12 | 13 | const baseResultsTableHeaders = ['', '', '']; 14 | 15 | interface VSCodePublishedExtension extends PublishedExtension { 16 | publisher: { displayName: string; publisherName: string }; 17 | } 18 | export async function search( 19 | searchText: string, 20 | json: boolean = false, 21 | pageSize: number = 10, 22 | stats: boolean = false 23 | ): Promise { 24 | const api = getPublicGalleryAPI(); 25 | const results = (await api.extensionQuery({ 26 | pageSize, 27 | criteria: [ 28 | { filterType: ExtensionQueryFilterType.SearchText, value: searchText }, 29 | { filterType: ExtensionQueryFilterType.InstallationTarget, value: installationTarget }, 30 | { filterType: ExtensionQueryFilterType.ExcludeWithFlags, value: excludeFlags }, 31 | ], 32 | flags: [ 33 | ExtensionQueryFlags.ExcludeNonValidated, 34 | ExtensionQueryFlags.IncludeLatestVersionOnly, 35 | stats ? ExtensionQueryFlags.IncludeStatistics : 0, 36 | ], 37 | })) as VSCodePublishedExtension[]; 38 | 39 | if (stats || !json) { 40 | console.log( 41 | [ 42 | `Search results:`, 43 | '', 44 | ...buildResultTableView(results, stats), 45 | '', 46 | 'For more information on an extension use "vsce show "', 47 | ] 48 | .map(line => wordTrim(line.replace(/\s+$/g, ''))) 49 | .join('\n') 50 | ); 51 | return; 52 | } 53 | 54 | if (!results.length) { 55 | console.log('No matching results'); 56 | return; 57 | } 58 | 59 | if (json) { 60 | console.log(JSON.stringify(results, undefined, '\t')); 61 | return; 62 | } 63 | } 64 | 65 | function buildResultTableView(results: VSCodePublishedExtension[], stats: boolean): string[] { 66 | const values = results.map(({ publisher, extensionName, displayName, shortDescription, statistics }) => [ 67 | publisher.publisherName + '.' + extensionName, 68 | publisher.displayName, 69 | wordTrim(displayName || '', 25), 70 | stats ? buildExtensionStatisticsText(statistics!) : wordTrim(shortDescription || '', 150).replace(/\n|\r|\t/g, ' '), 71 | ]); 72 | 73 | var resultsTableHeaders = stats 74 | ? [...baseResultsTableHeaders, '', ''] 75 | : [...baseResultsTableHeaders, '']; 76 | 77 | const resultsTable = tableView([resultsTableHeaders, ...values]); 78 | 79 | return resultsTable; 80 | } 81 | 82 | function buildExtensionStatisticsText(statistics: ExtensionStatistic[]): string { 83 | const { install: installs = 0, averagerating = 0, ratingcount = 0 } = statistics?.reduce( 84 | (map, { statisticName, value }) => ({ ...map, [statisticName!]: value }), 85 | {} 86 | ); 87 | 88 | return ( 89 | `${Number(installs).toLocaleString('en-US').padStart(12, ' ')} \t\t` + 90 | ` ${ratingStars(averagerating).padEnd(3, ' ')} (${ratingcount})` 91 | ); 92 | } 93 | -------------------------------------------------------------------------------- /src/secretLint.ts: -------------------------------------------------------------------------------- 1 | import chalk from "chalk"; 2 | import { Convert, Location, Region, Result, Level } from "./typings/secret-lint-types"; 3 | import { log } from "./util"; 4 | 5 | interface SecretLintEngineResult { 6 | ok: boolean; 7 | output: string; 8 | } 9 | 10 | interface SecretLintResult { 11 | ok: boolean; 12 | results: Result[]; 13 | } 14 | 15 | const secretsScanningRules = [ 16 | { 17 | id: "@secretlint/secretlint-rule-preset-recommend", 18 | rules: [ 19 | { 20 | id: "@secretlint/secretlint-rule-basicauth", 21 | allowMessageIds: ["BasicAuth"] 22 | }, 23 | { 24 | id: "@secretlint/secretlint-rule-privatekey", 25 | options: { 26 | allows: [ 27 | // Allow all keys which do not start and end with the BEGIN/END PRIVATE KEY and has at least 50 characters in between 28 | // https://github.com/microsoft/vscode-vsce/issues/1147 29 | "/^(?![\\s\\S]*-----BEGIN .*PRIVATE KEY-----[A-Za-z0-9+/=\\r\\n]{50,}-----END .*PRIVATE KEY-----)[\\s\\S]*$/" 30 | ] 31 | } 32 | }, { 33 | id: "@secretlint/secretlint-rule-npm", 34 | options: { 35 | allows: [ 36 | // An npm token has the prefix npm_ followed by 36 Base62 characters (30 random + 6-character checksum), totaling 40 characters. 37 | // https://github.com/microsoft/vscode-vsce/issues/1153 38 | "/^(?!(?:npm_[0-9A-Za-z]{36})$).+$/" 39 | ] 40 | } 41 | } 42 | ] 43 | } 44 | ]; 45 | 46 | const dotEnvRules = [ 47 | { 48 | id: "@secretlint/secretlint-rule-no-dotenv" 49 | } 50 | ]; 51 | 52 | // Helper function to dynamically import the createEngine function 53 | async function getEngine(scanSecrets: boolean, scanDotEnv: boolean) { 54 | // Use a raw dynamic import that will not be transformed 55 | // This is necessary because @secretlint/node is an ESM module 56 | const secretlintModule = await eval('import("@secretlint/node")'); 57 | 58 | const rules = []; 59 | if (scanSecrets) { 60 | rules.push(...secretsScanningRules); 61 | } 62 | if (scanDotEnv) { 63 | rules.push(...dotEnvRules); 64 | } 65 | 66 | const lintOptions = { 67 | configFileJSON: { rules: rules }, 68 | formatter: "@secretlint/secretlint-formatter-sarif", // checkstyle, compact, jslint-xml, junit, pretty-error, stylish, tap, unix, json, mask-result, table 69 | color: true, 70 | maskSecrets: false 71 | }; 72 | 73 | const engine = await secretlintModule.createEngine(lintOptions); 74 | return engine; 75 | } 76 | 77 | export async function lintFiles( 78 | filePaths: string[], 79 | scanSecrets: boolean, 80 | scanDotEnv: boolean 81 | ): Promise { 82 | const engine = await getEngine(scanSecrets, scanDotEnv); 83 | 84 | let engineResult; 85 | try { 86 | engineResult = await engine.executeOnFiles({ 87 | filePathList: filePaths 88 | }); 89 | } catch (error) { 90 | log.error('Error occurred while scanning secrets (files):', error); 91 | process.exit(1); 92 | } 93 | 94 | return parseResult(engineResult); 95 | } 96 | 97 | export async function lintText( 98 | content: string, 99 | fileName: string, 100 | scanSecrets: boolean, 101 | scanDotEnv: boolean 102 | ): Promise { 103 | const engine = await getEngine(scanSecrets, scanDotEnv); 104 | 105 | let engineResult; 106 | try { 107 | engineResult = await engine.executeOnContent({ 108 | content, 109 | filePath: fileName 110 | }); 111 | } catch (error) { 112 | log.error('Error occurred while scanning secrets (content):', error); 113 | process.exit(1); 114 | } 115 | return parseResult(engineResult); 116 | } 117 | 118 | function parseResult(result: SecretLintEngineResult): SecretLintResult { 119 | const output = Convert.toSecretLintOutput(result.output); 120 | const results = output.runs.at(0)?.results ?? []; 121 | return { ok: result.ok, results }; 122 | } 123 | 124 | export function getRuleNameFromRuleId(ruleId: string): string { 125 | const parts = ruleId.split('-rule-'); 126 | return parts[parts.length - 1]; 127 | } 128 | 129 | export function prettyPrintLintResult(result: Result): string { 130 | if (!result.message.text) { 131 | return JSON.stringify(result); 132 | } 133 | 134 | const text = result.message.text; 135 | const titleColor = result.level === undefined || result.level === Level.Error ? chalk.bold.red : chalk.bold.yellow; 136 | const title = text.length > 54 ? text.slice(0, 50) + '...' : text; 137 | const ruleName = result.ruleId ? getRuleNameFromRuleId(result.ruleId) : 'unknown'; 138 | 139 | let output = `\t${titleColor(title)} [${ruleName}]\n`; 140 | 141 | if (result.locations) { 142 | result.locations.forEach(location => { 143 | output += `\t${prettyPrintLocation(location)}\n`; 144 | }); 145 | } 146 | return output; 147 | } 148 | 149 | function prettyPrintLocation(location: Location): string { 150 | if (!location.physicalLocation) { return JSON.stringify(location); } 151 | 152 | const uri = location.physicalLocation.artifactLocation?.uri; 153 | if (!uri) { return JSON.stringify(location); } 154 | 155 | let output = uri; 156 | 157 | const region = location.physicalLocation.region; 158 | const regionStringified = region ? prettyPrintRegion(region) : undefined; 159 | if (regionStringified) { 160 | output += `#${regionStringified}`; 161 | } 162 | 163 | return output; 164 | } 165 | 166 | function prettyPrintRegion(region: Region): string | undefined { 167 | const startPosition = prettyPrintPosition(region.startLine, region.startColumn); 168 | const endPosition = prettyPrintPosition(region.endLine, region.endColumn); 169 | 170 | if (!startPosition) { 171 | return undefined; 172 | } 173 | 174 | let output = startPosition; 175 | if (endPosition && startPosition !== endPosition) { 176 | output += `-${endPosition}`; 177 | } 178 | 179 | return output; 180 | } 181 | 182 | function prettyPrintPosition(line: number | undefined, column: number | undefined): string | undefined { 183 | if (line === undefined) { 184 | return undefined; 185 | } 186 | let output: string = line.toString(); 187 | if (column !== undefined) { 188 | output += `:${column}`; 189 | } 190 | 191 | return output; 192 | } -------------------------------------------------------------------------------- /src/show.ts: -------------------------------------------------------------------------------- 1 | import { getPublicGalleryAPI, log } from './util'; 2 | import { ExtensionQueryFlags, ExtensionVersion, PublishedExtension } from 'azure-devops-node-api/interfaces/GalleryInterfaces'; 3 | import { ViewTable, formatDate, formatDateTime, ratingStars, tableView, indentRow, wordWrap, icons } from './viewutils'; 4 | 5 | const limitVersions = 6; 6 | const isExtensionTag = /^__ext_(.*)$/; 7 | 8 | export interface ExtensionStatisticsMap { 9 | install: number; 10 | averagerating: number; 11 | ratingcount: number; 12 | } 13 | 14 | interface VSCodePublishedExtension extends PublishedExtension { 15 | publisher: { displayName: string; publisherName: string }; 16 | } 17 | 18 | export function show(extensionId: string, json: boolean = false): Promise { 19 | const flags = [ 20 | ExtensionQueryFlags.IncludeCategoryAndTags, 21 | ExtensionQueryFlags.IncludeMetadata, 22 | ExtensionQueryFlags.IncludeStatistics, 23 | ExtensionQueryFlags.IncludeVersions, 24 | ExtensionQueryFlags.IncludeVersionProperties, 25 | ]; 26 | return getPublicGalleryAPI() 27 | .getExtension(extensionId, flags) 28 | .then(extension => { 29 | if (json) { 30 | console.log(JSON.stringify(extension, undefined, '\t')); 31 | } else { 32 | if (extension === undefined) { 33 | log.error(`Extension "${extensionId}" not found.`); 34 | } else { 35 | showOverview(extension as VSCodePublishedExtension); 36 | } 37 | } 38 | }); 39 | } 40 | 41 | function round(num: number): number { 42 | return Math.round(num * 100) / 100; 43 | } 44 | 45 | function unit(value: number, statisticName: string): string { 46 | switch (statisticName) { 47 | case 'install': 48 | return `${value} installs`; 49 | case 'updateCount': 50 | return `${value} updates`; 51 | case 'averagerating': 52 | case 'weightedRating': 53 | return `${value} stars`; 54 | case 'ratingcount': 55 | return `${value} ratings`; 56 | case 'downloadCount': 57 | return `${value} downloads`; 58 | default: 59 | return `${value}`; 60 | } 61 | } 62 | 63 | function getVersionTable(versions: ExtensionVersion[]): ViewTable { 64 | if (!versions.length) { 65 | return []; 66 | } 67 | 68 | const set = new Set(); 69 | const result = versions 70 | .filter(({ version }) => !set.has(version!) && set.add(version!)) 71 | .slice(0, limitVersions) 72 | .map(({ version, lastUpdated, properties }) => [version, formatDate(lastUpdated!), properties?.some(p => p.key === 'Microsoft.VisualStudio.Code.PreRelease')]); 73 | 74 | // Only show pre-release column if there are any pre-releases 75 | if (result.every(v => !v[2])) { 76 | for (const version of result) { 77 | version.pop(); 78 | } 79 | result.unshift(['Version', 'Last Updated']); 80 | } else { 81 | for (const version of result) { 82 | version[2] = version[2] ? `✔️` : ''; 83 | } 84 | result.unshift(['Version', 'Last Updated', 'Pre-release']); 85 | } 86 | 87 | return result as ViewTable; 88 | } 89 | 90 | function showOverview({ 91 | displayName = 'unknown', 92 | extensionName = 'unknown', 93 | shortDescription = '', 94 | versions = [], 95 | publisher: { displayName: publisherDisplayName, publisherName }, 96 | categories = [], 97 | tags = [], 98 | statistics = [], 99 | publishedDate, 100 | lastUpdated, 101 | }: VSCodePublishedExtension) { 102 | const [{ version = 'unknown' } = {}] = versions; 103 | const versionTable = getVersionTable(versions); 104 | 105 | const latestVersionTargets = versions 106 | .filter(v => v.version === version) 107 | .filter(v => v.targetPlatform) 108 | .map(v => v.targetPlatform); 109 | 110 | const { install: installs = 0, averagerating = 0, ratingcount = 0 } = statistics.reduce( 111 | (map, { statisticName, value }) => ({ ...map, [statisticName!]: value }), 112 | {} 113 | ); 114 | 115 | const rows = [ 116 | `${displayName}`, 117 | `${publisherDisplayName} | ${icons.download} ` + 118 | `${Number(installs).toLocaleString()} installs |` + 119 | ` ${ratingStars(averagerating)} (${ratingcount})`, 120 | '', 121 | `${shortDescription}`, 122 | '', 123 | ...(versionTable.length ? tableView(versionTable).map(indentRow) : ['no versions found']), 124 | '', 125 | 'Categories:', 126 | ` ${categories.join(', ')}`, 127 | '', 128 | 'Tags:', 129 | ` ${tags.filter(tag => !isExtensionTag.test(tag)).join(', ')}` 130 | ]; 131 | 132 | if (latestVersionTargets.length) { 133 | rows.push( 134 | '', 135 | 'Targets:', 136 | ` ${latestVersionTargets.join(', ')}`, 137 | ); 138 | } 139 | 140 | rows.push( 141 | '', 142 | 'More info:', 143 | ...tableView([ 144 | ['Unique identifier:', `${publisherName}.${extensionName}`], 145 | ['Version:', version], 146 | ['Last updated:', formatDateTime(lastUpdated!)], 147 | ['Publisher:', publisherDisplayName], 148 | ['Published at:', formatDate(publishedDate!)], 149 | ]).map(indentRow), 150 | '', 151 | 'Statistics:', 152 | ...tableView( 153 | statistics 154 | .filter(({ statisticName }) => !/^trending/.test(statisticName!)) 155 | .map(({ statisticName, value }) => [statisticName, unit(round(value!), statisticName!)]) 156 | ).map(indentRow), 157 | ); 158 | 159 | // Render 160 | console.log(rows.map(line => wordWrap(line)).join('\n')); 161 | } 162 | -------------------------------------------------------------------------------- /src/store.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | import { homedir } from 'os'; 4 | import { read, getGalleryAPI, getSecurityRolesAPI, log, getMarketplaceUrl } from './util'; 5 | import { validatePublisher } from './validation'; 6 | import { readManifest } from './package'; 7 | import { getPAT } from './publish'; 8 | 9 | export interface IPublisher { 10 | readonly name: string; 11 | readonly pat: string; 12 | } 13 | 14 | export interface IStore extends Iterable { 15 | readonly size: number; 16 | get(name: string): IPublisher | undefined; 17 | add(publisher: IPublisher): Promise; 18 | delete(name: string): Promise; 19 | } 20 | 21 | export class FileStore implements IStore { 22 | private static readonly DefaultPath = path.join(homedir(), '.vsce'); 23 | 24 | static async open(path: string = FileStore.DefaultPath): Promise { 25 | try { 26 | const rawStore = await fs.promises.readFile(path, 'utf8'); 27 | return new FileStore(path, JSON.parse(rawStore).publishers); 28 | } catch (err: any) { 29 | if (err.code === 'ENOENT') { 30 | return new FileStore(path, []); 31 | } else if (/SyntaxError/.test(err)) { 32 | throw new Error(`Error parsing file store: ${path}`); 33 | } 34 | 35 | throw err; 36 | } 37 | } 38 | 39 | get size(): number { 40 | return this.publishers.length; 41 | } 42 | 43 | private constructor(readonly path: string, private publishers: IPublisher[]) { } 44 | 45 | private async save(): Promise { 46 | await fs.promises.writeFile(this.path, JSON.stringify({ publishers: this.publishers }), { mode: '0600' }); 47 | } 48 | 49 | async deleteStore(): Promise { 50 | try { 51 | await fs.promises.unlink(this.path); 52 | } catch { 53 | // noop 54 | } 55 | } 56 | 57 | get(name: string): IPublisher | undefined { 58 | return this.publishers.filter(p => p.name === name)[0]; 59 | } 60 | 61 | async add(publisher: IPublisher): Promise { 62 | this.publishers = [...this.publishers.filter(p => p.name !== publisher.name), publisher]; 63 | await this.save(); 64 | } 65 | 66 | async delete(name: string): Promise { 67 | this.publishers = this.publishers.filter(p => p.name !== name); 68 | await this.save(); 69 | } 70 | 71 | [Symbol.iterator]() { 72 | return this.publishers[Symbol.iterator](); 73 | } 74 | } 75 | 76 | export class KeytarStore implements IStore { 77 | static async open(serviceName = 'vscode-vsce'): Promise { 78 | const keytar = await import('keytar'); 79 | const creds = await keytar.findCredentials(serviceName); 80 | 81 | return new KeytarStore( 82 | keytar, 83 | serviceName, 84 | creds.map(({ account, password }) => ({ name: account, pat: password })) 85 | ); 86 | } 87 | 88 | get size(): number { 89 | return this.publishers.length; 90 | } 91 | 92 | private constructor( 93 | private readonly keytar: typeof import('keytar'), 94 | private readonly serviceName: string, 95 | private publishers: IPublisher[] 96 | ) { } 97 | 98 | get(name: string): IPublisher { 99 | return this.publishers.filter(p => p.name === name)[0]; 100 | } 101 | 102 | async add(publisher: IPublisher): Promise { 103 | this.publishers = [...this.publishers.filter(p => p.name !== publisher.name), publisher]; 104 | await this.keytar.setPassword(this.serviceName, publisher.name, publisher.pat); 105 | } 106 | 107 | async delete(name: string): Promise { 108 | this.publishers = this.publishers.filter(p => p.name !== name); 109 | await this.keytar.deletePassword(this.serviceName, name); 110 | } 111 | 112 | [Symbol.iterator](): Iterator { 113 | return this.publishers[Symbol.iterator](); 114 | } 115 | } 116 | 117 | export interface IVerifyPatOptions { 118 | readonly publisherName?: string; 119 | readonly pat?: string; 120 | readonly azureCredential?: boolean; 121 | } 122 | 123 | export async function verifyPat(options: IVerifyPatOptions): Promise { 124 | const publisherName = options.publisherName ?? validatePublisher((await readManifest()).publisher); 125 | const pat = await getPAT(publisherName, options); 126 | 127 | try { 128 | // If the caller of the `getRoleAssignments` API has any of the roles 129 | // (Creator, Owner, Contributor, Reader) on the publisher, we get a 200, 130 | // otherwise we get a 403. 131 | const api = await getSecurityRolesAPI(pat); 132 | await api.getRoleAssignments('gallery.publisher', publisherName); 133 | } catch (error: unknown) { 134 | if (error instanceof Error) { 135 | throw new Error('The Personal Access Token verification has failed. Additional information:\n\n' + error.message); 136 | } 137 | throw new Error('The Personal Access Token verification has failed.' + error); 138 | } 139 | 140 | console.log(`The Personal Access Token verification succeeded for the publisher '${publisherName}'.`); 141 | } 142 | 143 | async function requestPAT(publisherName: string): Promise { 144 | console.log(`${getMarketplaceUrl()}/manage/publishers/`); 145 | 146 | const pat = await read(`Personal Access Token for publisher '${publisherName}':`, { silent: true, replace: '*' }); 147 | await verifyPat({ publisherName, pat }); 148 | return pat; 149 | } 150 | 151 | async function openDefaultStore(): Promise { 152 | if (/^file$/i.test(process.env['VSCE_STORE'] ?? '')) { 153 | return await FileStore.open(); 154 | } 155 | 156 | let keytarStore: IStore; 157 | 158 | try { 159 | keytarStore = await KeytarStore.open(); 160 | } catch (err) { 161 | const store = await FileStore.open(); 162 | log.warn(`Failed to open credential store. Falling back to storing secrets clear-text in: ${store.path}`); 163 | return store; 164 | } 165 | 166 | const fileStore = await FileStore.open(); 167 | 168 | // migrate from file store 169 | if (fileStore.size) { 170 | for (const publisher of fileStore) { 171 | await keytarStore.add(publisher); 172 | } 173 | 174 | await fileStore.deleteStore(); 175 | log.info(`Migrated ${fileStore.size} publishers to system credential manager. Deleted local store '${fileStore.path}'.`); 176 | } 177 | 178 | return keytarStore; 179 | } 180 | 181 | export async function getPublisher(publisherName: string): Promise { 182 | validatePublisher(publisherName); 183 | 184 | const store = await openDefaultStore(); 185 | let publisher = store.get(publisherName); 186 | 187 | if (publisher) { 188 | return publisher; 189 | } 190 | 191 | const pat = await requestPAT(publisherName); 192 | publisher = { name: publisherName, pat }; 193 | await store.add(publisher); 194 | 195 | return publisher; 196 | } 197 | 198 | export async function loginPublisher(publisherName: string): Promise { 199 | validatePublisher(publisherName); 200 | 201 | const store = await openDefaultStore(); 202 | let publisher = store.get(publisherName); 203 | 204 | if (publisher) { 205 | console.log(`Publisher '${publisherName}' is already known`); 206 | const answer = await read('Do you want to overwrite its PAT? [y/N] '); 207 | 208 | if (!/^y$/i.test(answer)) { 209 | throw new Error('Aborted'); 210 | } 211 | } 212 | 213 | const pat = await requestPAT(publisherName); 214 | publisher = { name: publisherName, pat }; 215 | await store.add(publisher); 216 | 217 | return publisher; 218 | } 219 | 220 | export async function logoutPublisher(publisherName: string): Promise { 221 | validatePublisher(publisherName); 222 | 223 | const store = await openDefaultStore(); 224 | const publisher = store.get(publisherName); 225 | 226 | if (!publisher) { 227 | throw new Error(`Unknown publisher '${publisherName}'`); 228 | } 229 | 230 | await store.delete(publisherName); 231 | } 232 | 233 | export async function deletePublisher(publisherName: string): Promise { 234 | const publisher = await getPublisher(publisherName); 235 | const answer = await read(`This will FOREVER delete '${publisherName}'! Are you sure? [y/N] `); 236 | 237 | if (!/^y$/i.test(answer)) { 238 | throw new Error('Aborted'); 239 | } 240 | 241 | const api = await getGalleryAPI(publisher.pat); 242 | await api.deletePublisher(publisherName); 243 | 244 | const store = await openDefaultStore(); 245 | await store.delete(publisherName); 246 | log.done(`Deleted publisher '${publisherName}'.`); 247 | } 248 | 249 | export async function listPublishers(): Promise { 250 | const store = await openDefaultStore(); 251 | 252 | for (const publisher of store) { 253 | console.log(publisher.name); 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /src/test/fixtures/devDependencies/node_modules/fake/.dotfile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-vsce/fafad8a63e9cf31179f918eb7a4eeb376834c904/src/test/fixtures/devDependencies/node_modules/fake/.dotfile -------------------------------------------------------------------------------- /src/test/fixtures/devDependencies/node_modules/fake/dependency.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-vsce/fafad8a63e9cf31179f918eb7a4eeb376834c904/src/test/fixtures/devDependencies/node_modules/fake/dependency.js -------------------------------------------------------------------------------- /src/test/fixtures/devDependencies/node_modules/fake/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fake", 3 | "version": "1.0.0" 4 | } -------------------------------------------------------------------------------- /src/test/fixtures/devDependencies/node_modules/real/dependency.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-vsce/fafad8a63e9cf31179f918eb7a4eeb376834c904/src/test/fixtures/devDependencies/node_modules/real/dependency.js -------------------------------------------------------------------------------- /src/test/fixtures/devDependencies/node_modules/real/node_modules/real_sub/dependency.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-vsce/fafad8a63e9cf31179f918eb7a4eeb376834c904/src/test/fixtures/devDependencies/node_modules/real/node_modules/real_sub/dependency.js -------------------------------------------------------------------------------- /src/test/fixtures/devDependencies/node_modules/real/node_modules/real_sub/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "real_sub", 3 | "version": "1.0.0" 4 | } -------------------------------------------------------------------------------- /src/test/fixtures/devDependencies/node_modules/real/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "real", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "real_sub": "1.0.0", 6 | "real2": "*" 7 | } 8 | } -------------------------------------------------------------------------------- /src/test/fixtures/devDependencies/node_modules/real2/dependency.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-vsce/fafad8a63e9cf31179f918eb7a4eeb376834c904/src/test/fixtures/devDependencies/node_modules/real2/dependency.js -------------------------------------------------------------------------------- /src/test/fixtures/devDependencies/node_modules/real2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "real2", 3 | "description": "This package is needed by real to include real_sub@1.0.0 and real_sub@2.0.0", 4 | "version": "1.0.0", 5 | "dependencies": { 6 | "real_sub": "2.0.0" 7 | } 8 | } -------------------------------------------------------------------------------- /src/test/fixtures/devDependencies/node_modules/real_sub/dependency.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-vsce/fafad8a63e9cf31179f918eb7a4eeb376834c904/src/test/fixtures/devDependencies/node_modules/real_sub/dependency.js -------------------------------------------------------------------------------- /src/test/fixtures/devDependencies/node_modules/real_sub/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "real_sub", 3 | "version": "2.0.0" 4 | } -------------------------------------------------------------------------------- /src/test/fixtures/devDependencies/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root-test-package", 3 | "publisher": "joaomoreno", 4 | "version": "1.0.0", 5 | "engines": { "vscode": "*" }, 6 | "dependencies": { 7 | "real": "*" 8 | }, 9 | "devDependencies": { 10 | "fake": "*" 11 | } 12 | } -------------------------------------------------------------------------------- /src/test/fixtures/env/.env: -------------------------------------------------------------------------------- 1 | A=1 2 | B=2 3 | C=3 -------------------------------------------------------------------------------- /src/test/fixtures/env/LICENSE: -------------------------------------------------------------------------------- 1 | LICENSE... -------------------------------------------------------------------------------- /src/test/fixtures/env/README.md: -------------------------------------------------------------------------------- 1 | Test -------------------------------------------------------------------------------- /src/test/fixtures/env/main.ts: -------------------------------------------------------------------------------- 1 | 2 | // Here a Fibonacci sequence function 3 | export function fib(n: number): number { 4 | if (n <= 1) return n; 5 | return fib(n - 1) + fib(n - 2); 6 | } -------------------------------------------------------------------------------- /src/test/fixtures/env/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uuid", 3 | "publisher": "joaomoreno", 4 | "version": "1.0.0", 5 | "engines": { 6 | "vscode": "*" 7 | }, 8 | "files": [ 9 | "main.ts", 10 | "package.json", 11 | "LICENSE", 12 | "README.md", 13 | ".env" 14 | ] 15 | } -------------------------------------------------------------------------------- /src/test/fixtures/icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-vsce/fafad8a63e9cf31179f918eb7a4eeb376834c904/src/test/fixtures/icon/icon.png -------------------------------------------------------------------------------- /src/test/fixtures/icon/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "version", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "version", 9 | "version": "1.0.0", 10 | "engines": { 11 | "vscode": "*" 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/fixtures/icon/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "version", 3 | "publisher": "joaomoreno", 4 | "version": "1.0.0", 5 | "icon": "./icon.png", 6 | "engines": { 7 | "vscode": "*" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/test/fixtures/manifestFiles/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": [], 3 | } -------------------------------------------------------------------------------- /src/test/fixtures/manifestFiles/LICENSE: -------------------------------------------------------------------------------- 1 | LICENSE... -------------------------------------------------------------------------------- /src/test/fixtures/manifestFiles/README.md: -------------------------------------------------------------------------------- 1 | Test -------------------------------------------------------------------------------- /src/test/fixtures/manifestFiles/foo/bar/hello.txt: -------------------------------------------------------------------------------- 1 | hi there -------------------------------------------------------------------------------- /src/test/fixtures/manifestFiles/foo2/bar2/ignore.me: -------------------------------------------------------------------------------- 1 | This should be ignored as if it was added to the .vscodeignore file -------------------------------------------------------------------------------- /src/test/fixtures/manifestFiles/foo2/bar2/include.me: -------------------------------------------------------------------------------- 1 | This file is included because it is in package.json files property -------------------------------------------------------------------------------- /src/test/fixtures/manifestFiles/foo3/bar3/hello.txt: -------------------------------------------------------------------------------- 1 | Some text in here! 2 | Some text in here! 3 | Some text in here! 4 | Some text in here! 5 | Some text in here! 6 | Some text in here! 7 | Some text in here! 8 | Some text in here! 9 | Some text in here! 10 | Some text in here! 11 | Some text in here! 12 | Some text in here! 13 | -------------------------------------------------------------------------------- /src/test/fixtures/manifestFiles/logger.log: -------------------------------------------------------------------------------- 1 | log -------------------------------------------------------------------------------- /src/test/fixtures/manifestFiles/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uuid", 3 | "publisher": "joaomoreno", 4 | "version": "1.0.0", 5 | "engines": { 6 | "vscode": "*" 7 | }, 8 | "files": [ 9 | "foo", 10 | "foo2/bar2/include.me", 11 | "*/bar3/**", 12 | "package.json", 13 | "LICENSE", 14 | "README.md" 15 | ] 16 | } -------------------------------------------------------------------------------- /src/test/fixtures/nls/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-debug", 3 | "displayName": "Node Debug", 4 | "version": "1.6.2", 5 | "publisher": "ms-vscode", 6 | "description": "%extension.description%", 7 | "categories": [ 8 | "Debuggers" 9 | ], 10 | "author": { 11 | "name": "Microsoft Corporation" 12 | }, 13 | "license": "MIT", 14 | "private": true, 15 | "scripts": { 16 | "prepublish": "node ./node_modules/gulp/bin/gulp.js build", 17 | "build": "node ./node_modules/gulp/bin/gulp.js build", 18 | "test": "node ./node_modules/mocha/bin/mocha --timeout 10000 -u tdd ./out/tests/", 19 | "nodemon": "./node_modules/.bin/nodemon --debug --nolazy ./out/node/nodeDebug.js --server=4711", 20 | "postinstall": "node ./node_modules/vscode/bin/install" 21 | }, 22 | "engines": { 23 | "vscode": "^1.6.0", 24 | "node": "^5.10.0" 25 | }, 26 | "dependencies": { 27 | "vscode-debugprotocol": "^1.13.0-pre.5", 28 | "vscode-debugadapter": "^1.13.0-pre.4", 29 | "source-map": "^0.5.3", 30 | "vscode-nls": "^2.0.0", 31 | "request-light": "^0.1.0", 32 | "glob": "^7.0.6" 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "https://github.com/Microsoft/vscode-node-debug.git" 37 | }, 38 | "bugs": { 39 | "url": "https://github.com/Microsoft/vscode-node-debug/issues" 40 | }, 41 | "devDependencies": { 42 | "@types/es6-collections": "^0.5.29", 43 | "@types/es6-promise": "0.0.32", 44 | "@types/mocha": "^2.2.32", 45 | "@types/node": "^6.0.39", 46 | "@types/source-map": "^0.1.27", 47 | "gulp": "^3.9.1", 48 | "gulp-util": "^3.0.7", 49 | "gulp-typescript": "^2.14.0", 50 | "gulp-tsb": "^2.0.0", 51 | "gulp-sourcemaps": "^1.6.0", 52 | "gulp-filter": "^3.0.1", 53 | "gulp-azure-storage": "*", 54 | "gulp-tslint": "^6.1.1", 55 | "gulp-uglify": "^2.0.0", 56 | "tslint-microsoft-contrib": "^2.0.10", 57 | "git-rev-sync": "*", 58 | "del": "*", 59 | "run-sequence": "*", 60 | "gulp-vinyl-zip": "*", 61 | "mocha": "^2.4.5", 62 | "tslint": "^3.15.1", 63 | "typescript": "^2.0.2", 64 | "vscode": "^0.11.18", 65 | "vscode-nls-dev": "^2.0.0-beta.1", 66 | "vscode-debugadapter-testsupport": "^1.13.0-pre.3", 67 | "nodemon": "^1.10.2", 68 | "vsce": "^1.12.0" 69 | }, 70 | "main": "./out/node/extension", 71 | "activationEvents": [ 72 | "onCommand:extension.pickNodeProcess", 73 | "onCommand:extension.provideInitialConfigurations" 74 | ], 75 | "contributes": { 76 | "breakpoints": [ 77 | { 78 | "language": "javascript" 79 | }, 80 | { 81 | "language": "javascriptreact" 82 | } 83 | ], 84 | "debuggers": [ 85 | { 86 | "type": "node", 87 | "label": "%node.label%", 88 | "program": "./out/node/nodeDebug.js", 89 | "runtime": "node", 90 | "variables": { 91 | "PickProcess": "extension.pickNodeProcess" 92 | }, 93 | "initialConfigurations": "extension.provideInitialConfigurations", 94 | "configurationAttributes": { 95 | "launch": { 96 | "required": [ 97 | "cwd" 98 | ], 99 | "properties": { 100 | "program": { 101 | "type": "string", 102 | "description": "%node.launch.program.description%" 103 | }, 104 | "stopOnEntry": { 105 | "type": "boolean", 106 | "description": "%node.stopOnEntry.description%", 107 | "default": true 108 | }, 109 | "externalConsole": { 110 | "type": "boolean", 111 | "description": "%node.launch.externalConsole.description%", 112 | "default": true, 113 | "errorMessage": "%node.launch.externalConsole.error%", 114 | "enum": [] 115 | }, 116 | "console": { 117 | "enum": [ 118 | "internalConsole", 119 | "integratedTerminal", 120 | "externalTerminal" 121 | ], 122 | "description": "%node.launch.console.description%", 123 | "default": "internalConsole" 124 | }, 125 | "args": { 126 | "type": "array", 127 | "description": "%node.launch.args.description%", 128 | "items": { 129 | "type": "string" 130 | }, 131 | "default": [] 132 | }, 133 | "cwd": { 134 | "type": "string", 135 | "description": "%node.launch.cwd.description%", 136 | "default": "${workspaceRoot}" 137 | }, 138 | "runtimeExecutable": { 139 | "type": [ 140 | "string", 141 | "null" 142 | ], 143 | "description": "%node.launch.runtimeExecutable.description%", 144 | "default": null 145 | }, 146 | "runtimeArgs": { 147 | "type": "array", 148 | "description": "%node.launch.runtimeArgs.description%", 149 | "items": { 150 | "type": "string" 151 | }, 152 | "default": [] 153 | }, 154 | "env": { 155 | "type": "object", 156 | "additionalProperties": { 157 | "type": "string" 158 | }, 159 | "description": "%node.launch.env.description%", 160 | "default": {} 161 | }, 162 | "sourceMaps": { 163 | "type": "boolean", 164 | "description": "%node.sourceMaps.description%", 165 | "default": true 166 | }, 167 | "outDir": { 168 | "type": [ 169 | "string", 170 | "null" 171 | ], 172 | "description": "%node.outDir.description%", 173 | "default": null 174 | }, 175 | "outFiles": { 176 | "type": "array", 177 | "description": "%node.outFiles.description%", 178 | "items": { 179 | "type": "string" 180 | }, 181 | "default": [] 182 | }, 183 | "port": { 184 | "type": "number", 185 | "description": "%node.port.description%", 186 | "default": 5858 187 | }, 188 | "address": { 189 | "type": "string", 190 | "description": "%node.address.description%", 191 | "default": "localhost" 192 | }, 193 | "timeout": { 194 | "type": "number", 195 | "description": "%node.timeout.description%", 196 | "default": 10000 197 | }, 198 | "smartStep": { 199 | "type": "boolean", 200 | "description": "%node.smartStep.description%", 201 | "default": true 202 | } 203 | } 204 | }, 205 | "attach": { 206 | "properties": { 207 | "processId": { 208 | "type": "string", 209 | "description": "%node.attach.processId.description%", 210 | "default": "${command.PickProcess}" 211 | }, 212 | "port": { 213 | "type": "number", 214 | "description": "%node.port.description%", 215 | "default": 5858 216 | }, 217 | "address": { 218 | "type": "string", 219 | "description": "%node.address.description%", 220 | "default": "localhost" 221 | }, 222 | "timeout": { 223 | "type": "number", 224 | "description": "%node.timeout.description%", 225 | "default": 10000 226 | }, 227 | "restart": { 228 | "type": "boolean", 229 | "description": "%node.attach.restart.description%", 230 | "default": true 231 | }, 232 | "sourceMaps": { 233 | "type": "boolean", 234 | "description": "%node.sourceMaps.description%", 235 | "default": true 236 | }, 237 | "outDir": { 238 | "type": [ 239 | "string", 240 | "null" 241 | ], 242 | "description": "%node.outDir.description%", 243 | "default": null 244 | }, 245 | "outFiles": { 246 | "type": "array", 247 | "description": "%node.outFiles.description%", 248 | "items": { 249 | "type": "string" 250 | }, 251 | "default": [] 252 | }, 253 | "stopOnEntry": { 254 | "type": "boolean", 255 | "description": "%node.stopOnEntry.description%", 256 | "default": true 257 | }, 258 | "localRoot": { 259 | "type": [ 260 | "string", 261 | "null" 262 | ], 263 | "description": "%node.attach.localRoot.description%", 264 | "default": null 265 | }, 266 | "remoteRoot": { 267 | "type": [ 268 | "string", 269 | "null" 270 | ], 271 | "description": "%node.attach.remoteRoot.description%", 272 | "default": null 273 | }, 274 | "smartStep": { 275 | "type": "boolean", 276 | "description": "%node.smartStep.description%", 277 | "default": true 278 | } 279 | } 280 | } 281 | } 282 | }, 283 | { 284 | "type": "extensionHost", 285 | "label": "%extensionHost.label%", 286 | "program": "./out/node/nodeDebug.js", 287 | "runtime": "node", 288 | "initialConfigurations": [ 289 | { 290 | "name": "%extensionHost.launch.config.name%", 291 | "type": "extensionHost", 292 | "request": "launch", 293 | "runtimeExecutable": "${execPath}", 294 | "args": [ 295 | "--extensionDevelopmentPath=${workspaceRoot}" 296 | ], 297 | "stopOnEntry": false, 298 | "sourceMaps": true, 299 | "outDirs": [ 300 | "${workspaceRoot}/out/**/*.js" 301 | ], 302 | "preLaunchTask": "npm" 303 | } 304 | ], 305 | "configurationAttributes": { 306 | "launch": { 307 | "required": [ 308 | "runtimeExecutable", 309 | "args" 310 | ], 311 | "properties": { 312 | "runtimeExecutable": { 313 | "type": [ 314 | "string", 315 | "null" 316 | ], 317 | "description": "%extensionHost.launch.runtimeExecutable.description%", 318 | "default": "${execPath}" 319 | }, 320 | "args": { 321 | "type": "array", 322 | "description": "%extensionHost.launch.args.description%", 323 | "items": { 324 | "type": "string" 325 | }, 326 | "default": [ 327 | "--extensionDevelopmentPath=${workspaceRoot}" 328 | ] 329 | }, 330 | "stopOnEntry": { 331 | "type": "boolean", 332 | "description": "%extensionHost.launch.stopOnEntry.description%", 333 | "default": true 334 | }, 335 | "sourceMaps": { 336 | "type": "boolean", 337 | "description": "%extensionHost.launch.sourceMaps.description%", 338 | "default": true 339 | }, 340 | "outDir": { 341 | "type": [ 342 | "string", 343 | "null" 344 | ], 345 | "description": "%extensionHost.launch.outDir.description%", 346 | "default": "out" 347 | }, 348 | "outFiles": { 349 | "type": "array", 350 | "description": "%extensionHost.launch.outFiles.description%", 351 | "items": { 352 | "type": "string" 353 | }, 354 | "default": [] 355 | }, 356 | "smartStep": { 357 | "type": "boolean", 358 | "description": "%extensionHost.smartStep.description%", 359 | "default": true 360 | } 361 | } 362 | } 363 | } 364 | } 365 | ] 366 | } 367 | } -------------------------------------------------------------------------------- /src/test/fixtures/nls/package.nls.json: -------------------------------------------------------------------------------- 1 | { 2 | "extension.description": "Visual Studio Code debugger extension for Node.js", 3 | 4 | "node.label": "Node.js", 5 | 6 | // Example comment 7 | "node.sourceMaps.description": "Use JavaScript source maps (if they exist).", 8 | "node.outDir.description": "If source maps are enabled, the generated code is expected in this directory. Deprecated: use 'outFiles' attribute instead.", 9 | "node.outFiles.description": "If source maps are enabled, these glob patterns specify the generated JavaScript files. If a pattern starts with '!' the files are excluded. If not specified, the generated code is expected in the same directory as its source.", 10 | "node.stopOnEntry.description": "Automatically stop program after launch.", 11 | "node.port.description": "Debug port to attach to. Default is 5858.", 12 | "node.address.description": "TCP/IP address of debug port (for Node.js >= 5.0 only). Default is 'localhost'.", 13 | "node.timeout.description": "Retry for this number of milliseconds to connect to Node.js. Default is 10000 ms.", 14 | "node.smartStep.description": "Automatically step through generated code that cannot be mapped back to the original source.", 15 | 16 | "node.launch.program.description": "Absolute path to the program.", 17 | "node.launch.externalConsole.description": "Launch debug target in external console.", 18 | "node.launch.externalConsole.error": "Deprecated: use 'console' attribute instead.", 19 | "node.launch.console.description": "Where to launch the debug target: internal console, integrated terminal, or external terminal.", 20 | "node.launch.args.description": "Command line arguments passed to the program.", 21 | "node.launch.cwd.description": "Absolute path to the working directory of the program being debugged.", 22 | "node.launch.runtimeExecutable.description": "Runtime to use. Either an absolute path or the name of a runtime available on the PATH. If omitted 'node' is assumed.", 23 | "node.launch.runtimeArgs.description": "Optional arguments passed to the runtime executable.", 24 | "node.launch.env.description": "Environment variables passed to the program.", 25 | 26 | "node.launch.config.name": "Launch", 27 | 28 | "node.attach.processId.description": "Id of process to attach to.", 29 | "node.attach.restart.description": "Restart session after Node.js has terminated.", 30 | "node.attach.localRoot.description": "The local source root that corresponds to the 'remoteRoot'.", 31 | "node.attach.remoteRoot.description": "The source root of the remote host.", 32 | 33 | "node.attach.config.name": "Attach", 34 | 35 | "node.processattach.config.name": "Attach to Process", 36 | 37 | "extensionHost.label": "VS Code Extension Development", 38 | 39 | "extensionHost.launch.runtimeExecutable.description": "Absolute path to VS Code.", 40 | "extensionHost.launch.args.description": "Command line arguments passed to the program.", 41 | "extensionHost.launch.stopOnEntry.description": "Automatically stop the extension host after launch.", 42 | "extensionHost.launch.sourceMaps.description": "Use JavaScript source maps.", 43 | "extensionHost.launch.outDir.description": "If source maps are enabled, the generated code is expected in this directory. Deprecated: use 'outFiles' attribute instead.", 44 | "extensionHost.launch.outFiles.description": "If source maps are enabled, these glob patterns specify the generated JavaScript files. If a pattern starts with '!' the files are excluded. If not specified, the generated code is expected in the same directory as its source.", 45 | "extensionHost.smartStep.description": "Automatically step through generated code that cannot be mapped back to the original source.", 46 | 47 | "extensionHost.launch.config.name": "Launch Extension" 48 | } 49 | -------------------------------------------------------------------------------- /src/test/fixtures/packagedDependencies/node_modules/.yarn-integrity: -------------------------------------------------------------------------------- 1 | { 2 | "systemParams": "darwin-x64-57", 3 | "modulesFolders": [ 4 | "node_modules" 5 | ], 6 | "flags": [], 7 | "linkedModules": [ 8 | "vscode-nls-dev" 9 | ], 10 | "topLevelPatterns": [ 11 | "isexe@1.0.0", 12 | "which@1.3.1" 13 | ], 14 | "lockfileEntries": { 15 | "isexe@1.0.0": "https://registry.yarnpkg.com/isexe/-/isexe-1.0.0.tgz#9aa37a1d11d27b523bec4f8791b72af1ead44ee3", 16 | "isexe@^2.0.0": "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10", 17 | "which@1.3.1": "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" 18 | }, 19 | "files": [], 20 | "artifacts": {} 21 | } -------------------------------------------------------------------------------- /src/test/fixtures/packagedDependencies/node_modules/isexe/.npmignore: -------------------------------------------------------------------------------- 1 | .nyc_output/ 2 | coverage/ 3 | -------------------------------------------------------------------------------- /src/test/fixtures/packagedDependencies/node_modules/isexe/README.md: -------------------------------------------------------------------------------- 1 | # isexe 2 | 3 | Minimal module to check if a file is executable. 4 | 5 | Uses `fs.access` if available, and tests against the `PATHEXT` 6 | environment variable on Windows. 7 | 8 | ## USAGE 9 | 10 | ```javascript 11 | var isexe = require('isexe') 12 | isexe('some-file-name', function (err, isExe) { 13 | if (err) { 14 | console.error('probably file does not exist or something', err) 15 | } else if (isExe) { 16 | console.error('this thing can be run') 17 | } else { 18 | console.error('cannot be run') 19 | } 20 | }) 21 | 22 | // same thing but synchronous, throws errors 23 | var isExe = isexe.sync('some-file-name') 24 | 25 | // treat errors as just "not executable" 26 | isexe('maybe-missing-file', { ignoreErrors: true }, callback) 27 | var isExe = isexe.sync('maybe-missing-file', { ignoreErrors: true }) 28 | ``` 29 | 30 | ## API 31 | 32 | ### `isexe(path, [options], [callback])` 33 | 34 | Check if the path is executable. If no callback provided, and a 35 | global `Promise` object is available, then a Promise will be returned. 36 | 37 | Will raise whatever errors may be raised by `fs.access` or `fs.stat`, 38 | unless `options.ignoreErrors` is set to true. 39 | 40 | ### `isexe.sync(path, [options])` 41 | 42 | Same as `isexe` but returns the value and throws any errors raised. 43 | -------------------------------------------------------------------------------- /src/test/fixtures/packagedDependencies/node_modules/isexe/access.js: -------------------------------------------------------------------------------- 1 | module.exports = isexe 2 | isexe.sync = sync 3 | 4 | var fs = require('fs') 5 | 6 | function isexe (path, cb) { 7 | fs.access(path, fs.X_OK, function (er) { 8 | cb(er, !er) 9 | }) 10 | } 11 | 12 | function sync (path) { 13 | fs.accessSync(path, fs.X_OK) 14 | return true 15 | } 16 | -------------------------------------------------------------------------------- /src/test/fixtures/packagedDependencies/node_modules/isexe/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var core 3 | if (process.platform === 'win32' || global.TESTING_WINDOWS) { 4 | core = require('./windows.js') 5 | } else if (typeof fs.access === 'function') { 6 | core = require('./access.js') 7 | } else { 8 | core = require('./mode.js') 9 | } 10 | 11 | module.exports = isexe 12 | isexe.sync = sync 13 | 14 | function isexe (path, options, cb) { 15 | if (typeof options === 'function') { 16 | cb = options 17 | options = {} 18 | } 19 | 20 | if (!cb) { 21 | if (typeof Promise !== 'function') { 22 | throw new TypeError('callback not provided') 23 | } 24 | 25 | return new Promise(function (resolve, reject) { 26 | isexe(path, options, function (er, is) { 27 | if (er) { 28 | reject(er) 29 | } else { 30 | resolve(is) 31 | } 32 | }) 33 | }) 34 | } 35 | 36 | core(path, function (er, is) { 37 | // ignore EACCES because that just means we aren't allowed to run it 38 | if (er) { 39 | if (er.code === 'EACCES' || options && options.ignoreErrors) { 40 | er = null 41 | is = false 42 | } 43 | } 44 | cb(er, is) 45 | }) 46 | } 47 | 48 | function sync (path, options) { 49 | // my kingdom for a filtered catch 50 | try { 51 | return core.sync(path) 52 | } catch (er) { 53 | if (options && options.ignoreErrors || er.code === 'EACCES') { 54 | return false 55 | } else { 56 | throw er 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/fixtures/packagedDependencies/node_modules/isexe/mode.js: -------------------------------------------------------------------------------- 1 | module.exports = isexe 2 | isexe.sync = sync 3 | 4 | var fs = require('fs') 5 | 6 | function isexe (path, cb) { 7 | fs.stat(path, function (er, st) { 8 | cb(er, er ? false : checkMode(st)) 9 | }) 10 | } 11 | 12 | function sync (path) { 13 | return checkMode(fs.statSync(path)) 14 | } 15 | 16 | function checkMode (stat) { 17 | var mod = stat.mode 18 | var uid = stat.uid 19 | var gid = stat.gid 20 | var u = parseInt('100', 8) 21 | var g = parseInt('010', 8) 22 | var o = parseInt('001', 8) 23 | var ug = u | g 24 | 25 | var ret = (mod & o) || 26 | (mod & g) && process.getgid && gid === process.getgid() || 27 | (mod & u) && process.getuid && uid === process.getuid() || 28 | (mod & ug) && process.getuid && process.getuid() === 0 29 | 30 | return ret 31 | } 32 | -------------------------------------------------------------------------------- /src/test/fixtures/packagedDependencies/node_modules/isexe/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "isexe", 3 | "version": "1.0.0", 4 | "description": "Minimal module to check if a file is executable.", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "devDependencies": { 10 | "mkdirp": "^0.5.1", 11 | "rimraf": "^2.5.0", 12 | "tap": "^5.0.1" 13 | }, 14 | "scripts": { 15 | "test": "tap test/*.js --cov" 16 | }, 17 | "author": "Isaac Z. Schlueter (http://blog.izs.me/)", 18 | "license": "ISC" 19 | } 20 | -------------------------------------------------------------------------------- /src/test/fixtures/packagedDependencies/node_modules/isexe/test/basic.js: -------------------------------------------------------------------------------- 1 | var t = require('tap') 2 | var fs = require('fs') 3 | var path = require('path') 4 | var fixture = path.resolve(__dirname, 'fixtures') 5 | var meow = fixture + '/meow.cat' 6 | var fail = fixture + '/fail.false' 7 | var noent = fixture + '/enoent.exe' 8 | var mkdirp = require('mkdirp') 9 | var rimraf = require('rimraf') 10 | 11 | var isWindows = process.platform === 'win32' 12 | var hasAccess = typeof fs.access === 'function' 13 | var winSkip = isWindows && 'windows' 14 | var accessSkip = !hasAccess && 'no fs.access function' 15 | var hasPromise = typeof Promise === 'function' 16 | var promiseSkip = !hasPromise && 'no global Promise' 17 | 18 | function reset () { 19 | delete require.cache[require.resolve('../')] 20 | return require('../') 21 | } 22 | 23 | t.test('setup fixtures', function (t) { 24 | rimraf.sync(fixture) 25 | mkdirp.sync(fixture) 26 | fs.writeFileSync(meow, '#!/usr/bin/env cat\nmeow\n') 27 | fs.chmodSync(meow, parseInt('0755', 8)) 28 | fs.writeFileSync(fail, '#!/usr/bin/env false\n') 29 | fs.chmodSync(fail, parseInt('0644', 8)) 30 | t.end() 31 | }) 32 | 33 | t.test('promise', { skip: promiseSkip }, function (t) { 34 | isexe = reset() 35 | t.test('meow async', function (t) { 36 | isexe(meow).then(function (is) { 37 | t.ok(is) 38 | t.end() 39 | }) 40 | }) 41 | t.test('fail async', function (t) { 42 | isexe(fail).then(function (is) { 43 | t.notOk(is) 44 | t.end() 45 | }) 46 | }) 47 | t.test('noent async', function (t) { 48 | isexe(noent).catch(function (er) { 49 | t.ok(er) 50 | t.end() 51 | }) 52 | }) 53 | t.test('noent ignore async', function (t) { 54 | isexe(noent, { ignoreErrors: true }).then(function (is) { 55 | t.notOk(is) 56 | t.end() 57 | }) 58 | }) 59 | t.end() 60 | }) 61 | 62 | t.test('access', { skip: accessSkip || winSkip }, function (t) { 63 | runTest(t) 64 | }) 65 | 66 | t.test('mode', { skip: winSkip }, function (t) { 67 | delete fs.access 68 | delete fs.accessSync 69 | runTest(t) 70 | }) 71 | 72 | t.test('windows', function (t) { 73 | global.TESTING_WINDOWS = true 74 | process.env.PATHEXT = '.EXE;.CAT;.CMD;.COM' 75 | runTest(t) 76 | }) 77 | 78 | t.test('cleanup', function (t) { 79 | rimraf.sync(fixture) 80 | t.end() 81 | }) 82 | 83 | function runTest (t) { 84 | var isexe = reset() 85 | 86 | t.notOk(isexe.sync(fail)) 87 | t.notOk(isexe.sync(noent, { ignoreErrors: true })) 88 | t.ok(isexe.sync(meow)) 89 | t.throws(function () { 90 | isexe.sync(noent) 91 | }) 92 | 93 | t.test('meow async', function (t) { 94 | isexe(meow, function (er, is) { 95 | if (er) { 96 | throw er 97 | } 98 | t.ok(is) 99 | t.end() 100 | }) 101 | }) 102 | 103 | t.test('fail async', function (t) { 104 | isexe(fail, function (er, is) { 105 | if (er) { 106 | throw er 107 | } 108 | t.notOk(is) 109 | t.end() 110 | }) 111 | }) 112 | 113 | t.test('noent async', function (t) { 114 | isexe(noent, function (er, is) { 115 | t.ok(er) 116 | t.notOk(is) 117 | t.end() 118 | }) 119 | }) 120 | 121 | t.test('noent ignore async', function (t) { 122 | isexe(noent, { ignoreErrors: true }, function (er, is) { 123 | if (er) { 124 | throw er 125 | } 126 | t.notOk(is) 127 | t.end() 128 | }) 129 | }) 130 | 131 | t.end() 132 | } 133 | -------------------------------------------------------------------------------- /src/test/fixtures/packagedDependencies/node_modules/isexe/windows.js: -------------------------------------------------------------------------------- 1 | module.exports = isexe 2 | isexe.sync = sync 3 | 4 | var fs = require('fs') 5 | 6 | function checkPathExt (path) { 7 | var pathext = process.env.PATHEXT 8 | if (!pathext) { 9 | return true 10 | } 11 | pathext = pathext.split(';') 12 | for (var i = 0; i < pathext.length; i++) { 13 | var p = pathext[i].toLowerCase() 14 | if (p && path.substr(-p.length).toLowerCase() === p) { 15 | return true 16 | } 17 | } 18 | return false 19 | } 20 | 21 | function isexe (path, cb) { 22 | fs.stat(path, function (er, st) { 23 | cb(er, er ? false : checkPathExt(path)) 24 | }) 25 | } 26 | 27 | function sync (path) { 28 | fs.statSync(path) 29 | return checkPathExt(path) 30 | } 31 | -------------------------------------------------------------------------------- /src/test/fixtures/packagedDependencies/node_modules/which/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | 3 | 4 | ## 1.3.1 5 | 6 | * update deps 7 | * update travis 8 | 9 | ## v1.3.0 10 | 11 | * Add nothrow option to which.sync 12 | * update tap 13 | 14 | ## v1.2.14 15 | 16 | * appveyor: drop node 5 and 0.x 17 | * travis-ci: add node 6, drop 0.x 18 | 19 | ## v1.2.13 20 | 21 | * test: Pass missing option to pass on windows 22 | * update tap 23 | * update isexe to 2.0.0 24 | * neveragain.tech pledge request 25 | 26 | ## v1.2.12 27 | 28 | * Removed unused require 29 | 30 | ## v1.2.11 31 | 32 | * Prevent changelog script from being included in package 33 | 34 | ## v1.2.10 35 | 36 | * Use env.PATH only, not env.Path 37 | 38 | ## v1.2.9 39 | 40 | * fix for paths starting with ../ 41 | * Remove unused `is-absolute` module 42 | 43 | ## v1.2.8 44 | 45 | * bullet items in changelog that contain (but don't start with) # 46 | 47 | ## v1.2.7 48 | 49 | * strip 'update changelog' changelog entries out of changelog 50 | 51 | ## v1.2.6 52 | 53 | * make the changelog bulleted 54 | 55 | ## v1.2.5 56 | 57 | * make a changelog, and keep it up to date 58 | * don't include tests in package 59 | * Properly handle relative-path executables 60 | * appveyor 61 | * Attach error code to Not Found error 62 | * Make tests pass on Windows 63 | 64 | ## v1.2.4 65 | 66 | * Fix typo 67 | 68 | ## v1.2.3 69 | 70 | * update isexe, fix regression in pathExt handling 71 | 72 | ## v1.2.2 73 | 74 | * update deps, use isexe module, test windows 75 | 76 | ## v1.2.1 77 | 78 | * Sometimes windows PATH entries are quoted 79 | * Fixed a bug in the check for group and user mode bits. This bug was introduced during refactoring for supporting strict mode. 80 | * doc cli 81 | 82 | ## v1.2.0 83 | 84 | * Add support for opt.all and -as cli flags 85 | * test the bin 86 | * update travis 87 | * Allow checking for multiple programs in bin/which 88 | * tap 2 89 | 90 | ## v1.1.2 91 | 92 | * travis 93 | * Refactored and fixed undefined error on Windows 94 | * Support strict mode 95 | 96 | ## v1.1.1 97 | 98 | * test +g exes against secondary groups, if available 99 | * Use windows exe semantics on cygwin & msys 100 | * cwd should be first in path on win32, not last 101 | * Handle lower-case 'env.Path' on Windows 102 | * Update docs 103 | * use single-quotes 104 | 105 | ## v1.1.0 106 | 107 | * Add tests, depend on is-absolute 108 | 109 | ## v1.0.9 110 | 111 | * which.js: root is allowed to execute files owned by anyone 112 | 113 | ## v1.0.8 114 | 115 | * don't use graceful-fs 116 | 117 | ## v1.0.7 118 | 119 | * add license to package.json 120 | 121 | ## v1.0.6 122 | 123 | * isc license 124 | 125 | ## 1.0.5 126 | 127 | * Awful typo 128 | 129 | ## 1.0.4 130 | 131 | * Test for path absoluteness properly 132 | * win: Allow '' as a pathext if cmd has a . in it 133 | 134 | ## 1.0.3 135 | 136 | * Remove references to execPath 137 | * Make `which.sync()` work on Windows by honoring the PATHEXT variable. 138 | * Make `isExe()` always return true on Windows. 139 | * MIT 140 | 141 | ## 1.0.2 142 | 143 | * Only files can be exes 144 | 145 | ## 1.0.1 146 | 147 | * Respect the PATHEXT env for win32 support 148 | * should 0755 the bin 149 | * binary 150 | * guts 151 | * package 152 | * 1st 153 | -------------------------------------------------------------------------------- /src/test/fixtures/packagedDependencies/node_modules/which/LICENSE: -------------------------------------------------------------------------------- 1 | The ISC License 2 | 3 | Copyright (c) Isaac Z. Schlueter and Contributors 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 15 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /src/test/fixtures/packagedDependencies/node_modules/which/README.md: -------------------------------------------------------------------------------- 1 | # which 2 | 3 | Like the unix `which` utility. 4 | 5 | Finds the first instance of a specified executable in the PATH 6 | environment variable. Does not cache the results, so `hash -r` is not 7 | needed when the PATH changes. 8 | 9 | ## USAGE 10 | 11 | ```javascript 12 | var which = require('which') 13 | 14 | // async usage 15 | which('node', function (er, resolvedPath) { 16 | // er is returned if no "node" is found on the PATH 17 | // if it is found, then the absolute path to the exec is returned 18 | }) 19 | 20 | // sync usage 21 | // throws if not found 22 | var resolved = which.sync('node') 23 | 24 | // if nothrow option is used, returns null if not found 25 | resolved = which.sync('node', {nothrow: true}) 26 | 27 | // Pass options to override the PATH and PATHEXT environment vars. 28 | which('node', { path: someOtherPath }, function (er, resolved) { 29 | if (er) 30 | throw er 31 | console.log('found at %j', resolved) 32 | }) 33 | ``` 34 | 35 | ## CLI USAGE 36 | 37 | Same as the BSD `which(1)` binary. 38 | 39 | ``` 40 | usage: which [-as] program ... 41 | ``` 42 | 43 | ## OPTIONS 44 | 45 | You may pass an options object as the second argument. 46 | 47 | - `path`: Use instead of the `PATH` environment variable. 48 | - `pathExt`: Use instead of the `PATHEXT` environment variable. 49 | - `all`: Return all matches, instead of just the first one. Note that 50 | this means the function returns an array of strings instead of a 51 | single string. 52 | -------------------------------------------------------------------------------- /src/test/fixtures/packagedDependencies/node_modules/which/bin/which: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var which = require("../") 3 | if (process.argv.length < 3) 4 | usage() 5 | 6 | function usage () { 7 | console.error('usage: which [-as] program ...') 8 | process.exit(1) 9 | } 10 | 11 | var all = false 12 | var silent = false 13 | var dashdash = false 14 | var args = process.argv.slice(2).filter(function (arg) { 15 | if (dashdash || !/^-/.test(arg)) 16 | return true 17 | 18 | if (arg === '--') { 19 | dashdash = true 20 | return false 21 | } 22 | 23 | var flags = arg.substr(1).split('') 24 | for (var f = 0; f < flags.length; f++) { 25 | var flag = flags[f] 26 | switch (flag) { 27 | case 's': 28 | silent = true 29 | break 30 | case 'a': 31 | all = true 32 | break 33 | default: 34 | console.error('which: illegal option -- ' + flag) 35 | usage() 36 | } 37 | } 38 | return false 39 | }) 40 | 41 | process.exit(args.reduce(function (pv, current) { 42 | try { 43 | var f = which.sync(current, { all: all }) 44 | if (all) 45 | f = f.join('\n') 46 | if (!silent) 47 | console.log(f) 48 | return pv; 49 | } catch (e) { 50 | return 1; 51 | } 52 | }, 0)) 53 | -------------------------------------------------------------------------------- /src/test/fixtures/packagedDependencies/node_modules/which/node_modules/isexe/.npmignore: -------------------------------------------------------------------------------- 1 | .nyc_output/ 2 | coverage/ 3 | -------------------------------------------------------------------------------- /src/test/fixtures/packagedDependencies/node_modules/which/node_modules/isexe/LICENSE: -------------------------------------------------------------------------------- 1 | The ISC License 2 | 3 | Copyright (c) Isaac Z. Schlueter and Contributors 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 15 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /src/test/fixtures/packagedDependencies/node_modules/which/node_modules/isexe/README.md: -------------------------------------------------------------------------------- 1 | # isexe 2 | 3 | Minimal module to check if a file is executable, and a normal file. 4 | 5 | Uses `fs.stat` and tests against the `PATHEXT` environment variable on 6 | Windows. 7 | 8 | ## USAGE 9 | 10 | ```javascript 11 | var isexe = require('isexe') 12 | isexe('some-file-name', function (err, isExe) { 13 | if (err) { 14 | console.error('probably file does not exist or something', err) 15 | } else if (isExe) { 16 | console.error('this thing can be run') 17 | } else { 18 | console.error('cannot be run') 19 | } 20 | }) 21 | 22 | // same thing but synchronous, throws errors 23 | var isExe = isexe.sync('some-file-name') 24 | 25 | // treat errors as just "not executable" 26 | isexe('maybe-missing-file', { ignoreErrors: true }, callback) 27 | var isExe = isexe.sync('maybe-missing-file', { ignoreErrors: true }) 28 | ``` 29 | 30 | ## API 31 | 32 | ### `isexe(path, [options], [callback])` 33 | 34 | Check if the path is executable. If no callback provided, and a 35 | global `Promise` object is available, then a Promise will be returned. 36 | 37 | Will raise whatever errors may be raised by `fs.stat`, unless 38 | `options.ignoreErrors` is set to true. 39 | 40 | ### `isexe.sync(path, [options])` 41 | 42 | Same as `isexe` but returns the value and throws any errors raised. 43 | 44 | ### Options 45 | 46 | * `ignoreErrors` Treat all errors as "no, this is not executable", but 47 | don't raise them. 48 | * `uid` Number to use as the user id 49 | * `gid` Number to use as the group id 50 | * `pathExt` List of path extensions to use instead of `PATHEXT` 51 | environment variable on Windows. 52 | -------------------------------------------------------------------------------- /src/test/fixtures/packagedDependencies/node_modules/which/node_modules/isexe/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var core 3 | if (process.platform === 'win32' || global.TESTING_WINDOWS) { 4 | core = require('./windows.js') 5 | } else { 6 | core = require('./mode.js') 7 | } 8 | 9 | module.exports = isexe 10 | isexe.sync = sync 11 | 12 | function isexe (path, options, cb) { 13 | if (typeof options === 'function') { 14 | cb = options 15 | options = {} 16 | } 17 | 18 | if (!cb) { 19 | if (typeof Promise !== 'function') { 20 | throw new TypeError('callback not provided') 21 | } 22 | 23 | return new Promise(function (resolve, reject) { 24 | isexe(path, options || {}, function (er, is) { 25 | if (er) { 26 | reject(er) 27 | } else { 28 | resolve(is) 29 | } 30 | }) 31 | }) 32 | } 33 | 34 | core(path, options || {}, function (er, is) { 35 | // ignore EACCES because that just means we aren't allowed to run it 36 | if (er) { 37 | if (er.code === 'EACCES' || options && options.ignoreErrors) { 38 | er = null 39 | is = false 40 | } 41 | } 42 | cb(er, is) 43 | }) 44 | } 45 | 46 | function sync (path, options) { 47 | // my kingdom for a filtered catch 48 | try { 49 | return core.sync(path, options || {}) 50 | } catch (er) { 51 | if (options && options.ignoreErrors || er.code === 'EACCES') { 52 | return false 53 | } else { 54 | throw er 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/test/fixtures/packagedDependencies/node_modules/which/node_modules/isexe/mode.js: -------------------------------------------------------------------------------- 1 | module.exports = isexe 2 | isexe.sync = sync 3 | 4 | var fs = require('fs') 5 | 6 | function isexe (path, options, cb) { 7 | fs.stat(path, function (er, stat) { 8 | cb(er, er ? false : checkStat(stat, options)) 9 | }) 10 | } 11 | 12 | function sync (path, options) { 13 | return checkStat(fs.statSync(path), options) 14 | } 15 | 16 | function checkStat (stat, options) { 17 | return stat.isFile() && checkMode(stat, options) 18 | } 19 | 20 | function checkMode (stat, options) { 21 | var mod = stat.mode 22 | var uid = stat.uid 23 | var gid = stat.gid 24 | 25 | var myUid = options.uid !== undefined ? 26 | options.uid : process.getuid && process.getuid() 27 | var myGid = options.gid !== undefined ? 28 | options.gid : process.getgid && process.getgid() 29 | 30 | var u = parseInt('100', 8) 31 | var g = parseInt('010', 8) 32 | var o = parseInt('001', 8) 33 | var ug = u | g 34 | 35 | var ret = (mod & o) || 36 | (mod & g) && gid === myGid || 37 | (mod & u) && uid === myUid || 38 | (mod & ug) && myUid === 0 39 | 40 | return ret 41 | } 42 | -------------------------------------------------------------------------------- /src/test/fixtures/packagedDependencies/node_modules/which/node_modules/isexe/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "isexe", 3 | "version": "2.0.0", 4 | "description": "Minimal module to check if a file is executable.", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "devDependencies": { 10 | "mkdirp": "^0.5.1", 11 | "rimraf": "^2.5.0", 12 | "tap": "^10.3.0" 13 | }, 14 | "scripts": { 15 | "test": "tap test/*.js --100", 16 | "preversion": "npm test", 17 | "postversion": "npm publish", 18 | "postpublish": "git push origin --all; git push origin --tags" 19 | }, 20 | "author": "Isaac Z. Schlueter (http://blog.izs.me/)", 21 | "license": "ISC", 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/isaacs/isexe.git" 25 | }, 26 | "keywords": [], 27 | "bugs": { 28 | "url": "https://github.com/isaacs/isexe/issues" 29 | }, 30 | "homepage": "https://github.com/isaacs/isexe#readme" 31 | } 32 | -------------------------------------------------------------------------------- /src/test/fixtures/packagedDependencies/node_modules/which/node_modules/isexe/test/basic.js: -------------------------------------------------------------------------------- 1 | var t = require('tap') 2 | var fs = require('fs') 3 | var path = require('path') 4 | var fixture = path.resolve(__dirname, 'fixtures') 5 | var meow = fixture + '/meow.cat' 6 | var mine = fixture + '/mine.cat' 7 | var ours = fixture + '/ours.cat' 8 | var fail = fixture + '/fail.false' 9 | var noent = fixture + '/enoent.exe' 10 | var mkdirp = require('mkdirp') 11 | var rimraf = require('rimraf') 12 | 13 | var isWindows = process.platform === 'win32' 14 | var hasAccess = typeof fs.access === 'function' 15 | var winSkip = isWindows && 'windows' 16 | var accessSkip = !hasAccess && 'no fs.access function' 17 | var hasPromise = typeof Promise === 'function' 18 | var promiseSkip = !hasPromise && 'no global Promise' 19 | 20 | function reset () { 21 | delete require.cache[require.resolve('../')] 22 | return require('../') 23 | } 24 | 25 | t.test('setup fixtures', function (t) { 26 | rimraf.sync(fixture) 27 | mkdirp.sync(fixture) 28 | fs.writeFileSync(meow, '#!/usr/bin/env cat\nmeow\n') 29 | fs.chmodSync(meow, parseInt('0755', 8)) 30 | fs.writeFileSync(fail, '#!/usr/bin/env false\n') 31 | fs.chmodSync(fail, parseInt('0644', 8)) 32 | fs.writeFileSync(mine, '#!/usr/bin/env cat\nmine\n') 33 | fs.chmodSync(mine, parseInt('0744', 8)) 34 | fs.writeFileSync(ours, '#!/usr/bin/env cat\nours\n') 35 | fs.chmodSync(ours, parseInt('0754', 8)) 36 | t.end() 37 | }) 38 | 39 | t.test('promise', { skip: promiseSkip }, function (t) { 40 | var isexe = reset() 41 | t.test('meow async', function (t) { 42 | isexe(meow).then(function (is) { 43 | t.ok(is) 44 | t.end() 45 | }) 46 | }) 47 | t.test('fail async', function (t) { 48 | isexe(fail).then(function (is) { 49 | t.notOk(is) 50 | t.end() 51 | }) 52 | }) 53 | t.test('noent async', function (t) { 54 | isexe(noent).catch(function (er) { 55 | t.ok(er) 56 | t.end() 57 | }) 58 | }) 59 | t.test('noent ignore async', function (t) { 60 | isexe(noent, { ignoreErrors: true }).then(function (is) { 61 | t.notOk(is) 62 | t.end() 63 | }) 64 | }) 65 | t.end() 66 | }) 67 | 68 | t.test('no promise', function (t) { 69 | global.Promise = null 70 | var isexe = reset() 71 | t.throws('try to meow a promise', function () { 72 | isexe(meow) 73 | }) 74 | t.end() 75 | }) 76 | 77 | t.test('access', { skip: accessSkip || winSkip }, function (t) { 78 | runTest(t) 79 | }) 80 | 81 | t.test('mode', { skip: winSkip }, function (t) { 82 | delete fs.access 83 | delete fs.accessSync 84 | var isexe = reset() 85 | t.ok(isexe.sync(ours, { uid: 0, gid: 0 })) 86 | t.ok(isexe.sync(mine, { uid: 0, gid: 0 })) 87 | runTest(t) 88 | }) 89 | 90 | t.test('windows', function (t) { 91 | global.TESTING_WINDOWS = true 92 | var pathExt = '.EXE;.CAT;.CMD;.COM' 93 | t.test('pathExt option', function (t) { 94 | runTest(t, { pathExt: '.EXE;.CAT;.CMD;.COM' }) 95 | }) 96 | t.test('pathExt env', function (t) { 97 | process.env.PATHEXT = pathExt 98 | runTest(t) 99 | }) 100 | t.test('no pathExt', function (t) { 101 | // with a pathExt of '', any filename is fine. 102 | // so the "fail" one would still pass. 103 | runTest(t, { pathExt: '', skipFail: true }) 104 | }) 105 | t.test('pathext with empty entry', function (t) { 106 | // with a pathExt of '', any filename is fine. 107 | // so the "fail" one would still pass. 108 | runTest(t, { pathExt: ';' + pathExt, skipFail: true }) 109 | }) 110 | t.end() 111 | }) 112 | 113 | t.test('cleanup', function (t) { 114 | rimraf.sync(fixture) 115 | t.end() 116 | }) 117 | 118 | function runTest (t, options) { 119 | var isexe = reset() 120 | 121 | var optionsIgnore = Object.create(options || {}) 122 | optionsIgnore.ignoreErrors = true 123 | 124 | if (!options || !options.skipFail) { 125 | t.notOk(isexe.sync(fail, options)) 126 | } 127 | t.notOk(isexe.sync(noent, optionsIgnore)) 128 | if (!options) { 129 | t.ok(isexe.sync(meow)) 130 | } else { 131 | t.ok(isexe.sync(meow, options)) 132 | } 133 | 134 | t.ok(isexe.sync(mine, options)) 135 | t.ok(isexe.sync(ours, options)) 136 | t.throws(function () { 137 | isexe.sync(noent, options) 138 | }) 139 | 140 | t.test('meow async', function (t) { 141 | if (!options) { 142 | isexe(meow, function (er, is) { 143 | if (er) { 144 | throw er 145 | } 146 | t.ok(is) 147 | t.end() 148 | }) 149 | } else { 150 | isexe(meow, options, function (er, is) { 151 | if (er) { 152 | throw er 153 | } 154 | t.ok(is) 155 | t.end() 156 | }) 157 | } 158 | }) 159 | 160 | t.test('mine async', function (t) { 161 | isexe(mine, options, function (er, is) { 162 | if (er) { 163 | throw er 164 | } 165 | t.ok(is) 166 | t.end() 167 | }) 168 | }) 169 | 170 | t.test('ours async', function (t) { 171 | isexe(ours, options, function (er, is) { 172 | if (er) { 173 | throw er 174 | } 175 | t.ok(is) 176 | t.end() 177 | }) 178 | }) 179 | 180 | if (!options || !options.skipFail) { 181 | t.test('fail async', function (t) { 182 | isexe(fail, options, function (er, is) { 183 | if (er) { 184 | throw er 185 | } 186 | t.notOk(is) 187 | t.end() 188 | }) 189 | }) 190 | } 191 | 192 | t.test('noent async', function (t) { 193 | isexe(noent, options, function (er, is) { 194 | t.ok(er) 195 | t.notOk(is) 196 | t.end() 197 | }) 198 | }) 199 | 200 | t.test('noent ignore async', function (t) { 201 | isexe(noent, optionsIgnore, function (er, is) { 202 | if (er) { 203 | throw er 204 | } 205 | t.notOk(is) 206 | t.end() 207 | }) 208 | }) 209 | 210 | t.test('directory is not executable', function (t) { 211 | isexe(__dirname, options, function (er, is) { 212 | if (er) { 213 | throw er 214 | } 215 | t.notOk(is) 216 | t.end() 217 | }) 218 | }) 219 | 220 | t.end() 221 | } 222 | -------------------------------------------------------------------------------- /src/test/fixtures/packagedDependencies/node_modules/which/node_modules/isexe/windows.js: -------------------------------------------------------------------------------- 1 | module.exports = isexe 2 | isexe.sync = sync 3 | 4 | var fs = require('fs') 5 | 6 | function checkPathExt (path, options) { 7 | var pathext = options.pathExt !== undefined ? 8 | options.pathExt : process.env.PATHEXT 9 | 10 | if (!pathext) { 11 | return true 12 | } 13 | 14 | pathext = pathext.split(';') 15 | if (pathext.indexOf('') !== -1) { 16 | return true 17 | } 18 | for (var i = 0; i < pathext.length; i++) { 19 | var p = pathext[i].toLowerCase() 20 | if (p && path.substr(-p.length).toLowerCase() === p) { 21 | return true 22 | } 23 | } 24 | return false 25 | } 26 | 27 | function checkStat (stat, path, options) { 28 | if (!stat.isSymbolicLink() && !stat.isFile()) { 29 | return false 30 | } 31 | return checkPathExt(path, options) 32 | } 33 | 34 | function isexe (path, options, cb) { 35 | fs.stat(path, function (er, stat) { 36 | cb(er, er ? false : checkStat(stat, path, options)) 37 | }) 38 | } 39 | 40 | function sync (path, options) { 41 | return checkStat(fs.statSync(path), path, options) 42 | } 43 | -------------------------------------------------------------------------------- /src/test/fixtures/packagedDependencies/node_modules/which/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Isaac Z. Schlueter (http://blog.izs.me)", 3 | "name": "which", 4 | "description": "Like which(1) unix command. Find the first instance of an executable in the PATH.", 5 | "version": "1.3.1", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/isaacs/node-which.git" 9 | }, 10 | "main": "which.js", 11 | "bin": "./bin/which", 12 | "license": "ISC", 13 | "dependencies": { 14 | "isexe": "^2.0.0" 15 | }, 16 | "devDependencies": { 17 | "mkdirp": "^0.5.0", 18 | "rimraf": "^2.6.2", 19 | "tap": "^12.0.1" 20 | }, 21 | "scripts": { 22 | "test": "tap test/*.js --cov", 23 | "changelog": "bash gen-changelog.sh", 24 | "postversion": "npm run changelog && git add CHANGELOG.md && git commit -m 'update changelog - '${npm_package_version}" 25 | }, 26 | "files": [ 27 | "which.js", 28 | "bin/which" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /src/test/fixtures/packagedDependencies/node_modules/which/which.js: -------------------------------------------------------------------------------- 1 | module.exports = which 2 | which.sync = whichSync 3 | 4 | var isWindows = process.platform === 'win32' || 5 | process.env.OSTYPE === 'cygwin' || 6 | process.env.OSTYPE === 'msys' 7 | 8 | var path = require('path') 9 | var COLON = isWindows ? ';' : ':' 10 | var isexe = require('isexe') 11 | 12 | function getNotFoundError (cmd) { 13 | var er = new Error('not found: ' + cmd) 14 | er.code = 'ENOENT' 15 | 16 | return er 17 | } 18 | 19 | function getPathInfo (cmd, opt) { 20 | var colon = opt.colon || COLON 21 | var pathEnv = opt.path || process.env.PATH || '' 22 | var pathExt = [''] 23 | 24 | pathEnv = pathEnv.split(colon) 25 | 26 | var pathExtExe = '' 27 | if (isWindows) { 28 | pathEnv.unshift(process.cwd()) 29 | pathExtExe = (opt.pathExt || process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM') 30 | pathExt = pathExtExe.split(colon) 31 | 32 | 33 | // Always test the cmd itself first. isexe will check to make sure 34 | // it's found in the pathExt set. 35 | if (cmd.indexOf('.') !== -1 && pathExt[0] !== '') 36 | pathExt.unshift('') 37 | } 38 | 39 | // If it has a slash, then we don't bother searching the pathenv. 40 | // just check the file itself, and that's it. 41 | if (cmd.match(/\//) || isWindows && cmd.match(/\\/)) 42 | pathEnv = [''] 43 | 44 | return { 45 | env: pathEnv, 46 | ext: pathExt, 47 | extExe: pathExtExe 48 | } 49 | } 50 | 51 | function which (cmd, opt, cb) { 52 | if (typeof opt === 'function') { 53 | cb = opt 54 | opt = {} 55 | } 56 | 57 | var info = getPathInfo(cmd, opt) 58 | var pathEnv = info.env 59 | var pathExt = info.ext 60 | var pathExtExe = info.extExe 61 | var found = [] 62 | 63 | ;(function F (i, l) { 64 | if (i === l) { 65 | if (opt.all && found.length) 66 | return cb(null, found) 67 | else 68 | return cb(getNotFoundError(cmd)) 69 | } 70 | 71 | var pathPart = pathEnv[i] 72 | if (pathPart.charAt(0) === '"' && pathPart.slice(-1) === '"') 73 | pathPart = pathPart.slice(1, -1) 74 | 75 | var p = path.join(pathPart, cmd) 76 | if (!pathPart && (/^\.[\\\/]/).test(cmd)) { 77 | p = cmd.slice(0, 2) + p 78 | } 79 | ;(function E (ii, ll) { 80 | if (ii === ll) return F(i + 1, l) 81 | var ext = pathExt[ii] 82 | isexe(p + ext, { pathExt: pathExtExe }, function (er, is) { 83 | if (!er && is) { 84 | if (opt.all) 85 | found.push(p + ext) 86 | else 87 | return cb(null, p + ext) 88 | } 89 | return E(ii + 1, ll) 90 | }) 91 | })(0, pathExt.length) 92 | })(0, pathEnv.length) 93 | } 94 | 95 | function whichSync (cmd, opt) { 96 | opt = opt || {} 97 | 98 | var info = getPathInfo(cmd, opt) 99 | var pathEnv = info.env 100 | var pathExt = info.ext 101 | var pathExtExe = info.extExe 102 | var found = [] 103 | 104 | for (var i = 0, l = pathEnv.length; i < l; i ++) { 105 | var pathPart = pathEnv[i] 106 | if (pathPart.charAt(0) === '"' && pathPart.slice(-1) === '"') 107 | pathPart = pathPart.slice(1, -1) 108 | 109 | var p = path.join(pathPart, cmd) 110 | if (!pathPart && /^\.[\\\/]/.test(cmd)) { 111 | p = cmd.slice(0, 2) + p 112 | } 113 | for (var j = 0, ll = pathExt.length; j < ll; j ++) { 114 | var cur = p + pathExt[j] 115 | var is 116 | try { 117 | is = isexe.sync(cur, { pathExt: pathExtExe }) 118 | if (is) { 119 | if (opt.all) 120 | found.push(cur) 121 | else 122 | return cur 123 | } 124 | } catch (ex) {} 125 | } 126 | } 127 | 128 | if (opt.all && found.length) 129 | return found 130 | 131 | if (opt.nothrow) 132 | return null 133 | 134 | throw getNotFoundError(cmd) 135 | } 136 | -------------------------------------------------------------------------------- /src/test/fixtures/packagedDependencies/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root-test-package", 3 | "publisher": "jrieken", 4 | "version": "1.0.0", 5 | "engines": { 6 | "vscode": "*" 7 | }, 8 | "dependencies": { 9 | "isexe": "1.0.0", 10 | "which": "1.3.1" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/test/fixtures/packagedDependencies/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | isexe@1.0.0: 6 | version "1.0.0" 7 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-1.0.0.tgz#9aa37a1d11d27b523bec4f8791b72af1ead44ee3" 8 | 9 | isexe@^2.0.0: 10 | version "2.0.0" 11 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 12 | 13 | which@1.3.1: 14 | version "1.3.1" 15 | resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" 16 | dependencies: 17 | isexe "^2.0.0" 18 | -------------------------------------------------------------------------------- /src/test/fixtures/readme/readme.branch.main.expected.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | >**Important:** Once installed the checker will only update if you add the setting `"spellMD.enable": true` to your `.vscode\settings.json` file. 4 | 5 | This README covers off: 6 | * [Functionality](#functionality) 7 | * [Install](#install) 8 | * [Run and Configure](#run-and-configure) 9 | * [Known Issues/Bugs](#known-issuesbugs) 10 | * [Backlog](#backlog) 11 | * [How to Debug](#how-to-debug) 12 | 13 | # Functionality 14 | 15 | Load up a Markdown file and get highlights and hovers for existing issues. Checking will occur as you type in the document. 16 | 17 | ![Underscores and hovers](https://github.com/username/repository/raw/main/images/SpellMDDemo1.gif) 18 | 19 | The status bar lets you quickly navigate to any issue and you can see all positions in the gutter. 20 | 21 | [![Jump to issues](https://github.com/username/repository/raw/main/images/SpellMDDemo2.gif)](http://shouldnottouchthis/) 22 | [![Jump to issues](https://github.com/username/repository/raw/main/images/SpellMDDemo2.gif)](https://github.com/username/repository/blob/main/monkey) 23 | ![](https://github.com/username/repository/raw/main/images/SpellMDDemo2.gif) 24 | ![](https://github.com/username/repository/raw/main/SpellMDDemo2.gif) 25 | ![](https://github.com/username/repository/raw/main/SpellMDDemo2.gif#gh-light-mode-only) 26 | 27 | 28 | 29 | 30 | 31 | The `spellMD.json` config file is watched so you can add more ignores or change mappings at will. 32 | 33 | ![Add to dictionary](https://github.com/username/repository/raw/main/images/SpellMDDemo3.gif) 34 | 35 | ![issue](https://github.com/username/repository/raw/main/issue) 36 | 37 | [mono](https://github.com/username/repository/blob/main/monkey) 38 | [not](http://shouldnottouchthis/) 39 | [Email me](mailto:example@example.com) 40 | 41 | # Install 42 | This extension is published in the VS Code Gallery. So simply hit 'F1' and type 'ext inst' from there select `SpellMD` and follow instructions. 43 | 44 | 45 | To clone the extension and load locally... 46 | 47 | ``` 48 | git clone https://github.com/Microsoft/vscode-SpellMD.git 49 | npm install 50 | tsc 51 | ``` 52 | 53 | >**Note:** TypeScript 1.6 or higher is required you can check with `tsc -v` and if you need to upgrade then run `npm install -g typescript`. 54 | -------------------------------------------------------------------------------- /src/test/fixtures/readme/readme.branch.override.content.expected.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | >**Important:** Once installed the checker will only update if you add the setting `"spellMD.enable": true` to your `.vscode\settings.json` file. 4 | 5 | This README covers off: 6 | * [Functionality](#functionality) 7 | * [Install](#install) 8 | * [Run and Configure](#run-and-configure) 9 | * [Known Issues/Bugs](#known-issuesbugs) 10 | * [Backlog](#backlog) 11 | * [How to Debug](#how-to-debug) 12 | 13 | # Functionality 14 | 15 | Load up a Markdown file and get highlights and hovers for existing issues. Checking will occur as you type in the document. 16 | 17 | ![Underscores and hovers](https://github.com/base/images/SpellMDDemo1.gif) 18 | 19 | The status bar lets you quickly navigate to any issue and you can see all positions in the gutter. 20 | 21 | [![Jump to issues](https://github.com/base/images/SpellMDDemo2.gif)](http://shouldnottouchthis/) 22 | [![Jump to issues](https://github.com/base/images/SpellMDDemo2.gif)](https://github.com/base/monkey) 23 | ![](https://github.com/base/images/SpellMDDemo2.gif) 24 | ![](https://github.com/base/SpellMDDemo2.gif) 25 | ![](https://github.com/base/SpellMDDemo2.gif#gh-light-mode-only) 26 | 27 | 28 | 29 | 30 | 31 | The `spellMD.json` config file is watched so you can add more ignores or change mappings at will. 32 | 33 | ![Add to dictionary](https://github.com/base/images/SpellMDDemo3.gif) 34 | 35 | ![issue](https://github.com/base/issue) 36 | 37 | [mono](https://github.com/base/monkey) 38 | [not](http://shouldnottouchthis/) 39 | [Email me](mailto:example@example.com) 40 | 41 | # Install 42 | This extension is published in the VS Code Gallery. So simply hit 'F1' and type 'ext inst' from there select `SpellMD` and follow instructions. 43 | 44 | 45 | To clone the extension and load locally... 46 | 47 | ``` 48 | git clone https://github.com/Microsoft/vscode-SpellMD.git 49 | npm install 50 | tsc 51 | ``` 52 | 53 | >**Note:** TypeScript 1.6 or higher is required you can check with `tsc -v` and if you need to upgrade then run `npm install -g typescript`. 54 | -------------------------------------------------------------------------------- /src/test/fixtures/readme/readme.branch.override.images.expected.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | >**Important:** Once installed the checker will only update if you add the setting `"spellMD.enable": true` to your `.vscode\settings.json` file. 4 | 5 | This README covers off: 6 | * [Functionality](#functionality) 7 | * [Install](#install) 8 | * [Run and Configure](#run-and-configure) 9 | * [Known Issues/Bugs](#known-issuesbugs) 10 | * [Backlog](#backlog) 11 | * [How to Debug](#how-to-debug) 12 | 13 | # Functionality 14 | 15 | Load up a Markdown file and get highlights and hovers for existing issues. Checking will occur as you type in the document. 16 | 17 | ![Underscores and hovers](https://github.com/base/images/SpellMDDemo1.gif) 18 | 19 | The status bar lets you quickly navigate to any issue and you can see all positions in the gutter. 20 | 21 | [![Jump to issues](https://github.com/base/images/SpellMDDemo2.gif)](http://shouldnottouchthis/) 22 | [![Jump to issues](https://github.com/base/images/SpellMDDemo2.gif)](https://github.com/username/repository/blob/main/monkey) 23 | ![](https://github.com/base/images/SpellMDDemo2.gif) 24 | ![](https://github.com/base/SpellMDDemo2.gif) 25 | ![](https://github.com/base/SpellMDDemo2.gif#gh-light-mode-only) 26 | 27 | 28 | 29 | 30 | 31 | The `spellMD.json` config file is watched so you can add more ignores or change mappings at will. 32 | 33 | ![Add to dictionary](https://github.com/base/images/SpellMDDemo3.gif) 34 | 35 | ![issue](https://github.com/base/issue) 36 | 37 | [mono](https://github.com/username/repository/blob/main/monkey) 38 | [not](http://shouldnottouchthis/) 39 | [Email me](mailto:example@example.com) 40 | 41 | # Install 42 | This extension is published in the VS Code Gallery. So simply hit 'F1' and type 'ext inst' from there select `SpellMD` and follow instructions. 43 | 44 | 45 | To clone the extension and load locally... 46 | 47 | ``` 48 | git clone https://github.com/Microsoft/vscode-SpellMD.git 49 | npm install 50 | tsc 51 | ``` 52 | 53 | >**Note:** TypeScript 1.6 or higher is required you can check with `tsc -v` and if you need to upgrade then run `npm install -g typescript`. 54 | -------------------------------------------------------------------------------- /src/test/fixtures/readme/readme.default.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | >**Important:** Once installed the checker will only update if you add the setting `"spellMD.enable": true` to your `.vscode\settings.json` file. 4 | 5 | This README covers off: 6 | * [Functionality](#functionality) 7 | * [Install](#install) 8 | * [Run and Configure](#run-and-configure) 9 | * [Known Issues/Bugs](#known-issuesbugs) 10 | * [Backlog](#backlog) 11 | * [How to Debug](#how-to-debug) 12 | 13 | # Functionality 14 | 15 | Load up a Markdown file and get highlights and hovers for existing issues. Checking will occur as you type in the document. 16 | 17 | ![Underscores and hovers](https://github.com/username/repository/raw/HEAD/images/SpellMDDemo1.gif) 18 | 19 | The status bar lets you quickly navigate to any issue and you can see all positions in the gutter. 20 | 21 | [![Jump to issues](https://github.com/username/repository/raw/HEAD/images/SpellMDDemo2.gif)](http://shouldnottouchthis/) 22 | [![Jump to issues](https://github.com/username/repository/raw/HEAD/images/SpellMDDemo2.gif)](https://github.com/username/repository/blob/HEAD/monkey) 23 | ![](https://github.com/username/repository/raw/HEAD/images/SpellMDDemo2.gif) 24 | ![](https://github.com/username/repository/raw/HEAD/SpellMDDemo2.gif) 25 | ![](https://github.com/username/repository/raw/HEAD/SpellMDDemo2.gif#gh-light-mode-only) 26 | 27 | 28 | 29 | 30 | 31 | The `spellMD.json` config file is watched so you can add more ignores or change mappings at will. 32 | 33 | ![Add to dictionary](https://github.com/username/repository/raw/HEAD/images/SpellMDDemo3.gif) 34 | 35 | ![issue](https://github.com/username/repository/raw/HEAD/issue) 36 | 37 | [mono](https://github.com/username/repository/blob/HEAD/monkey) 38 | [not](http://shouldnottouchthis/) 39 | [Email me](mailto:example@example.com) 40 | 41 | # Install 42 | This extension is published in the VS Code Gallery. So simply hit 'F1' and type 'ext inst' from there select `SpellMD` and follow instructions. 43 | 44 | 45 | To clone the extension and load locally... 46 | 47 | ``` 48 | git clone https://github.com/Microsoft/vscode-SpellMD.git 49 | npm install 50 | tsc 51 | ``` 52 | 53 | >**Note:** TypeScript 1.6 or higher is required you can check with `tsc -v` and if you need to upgrade then run `npm install -g typescript`. 54 | -------------------------------------------------------------------------------- /src/test/fixtures/readme/readme.expected.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | >**Important:** Once installed the checker will only update if you add the setting `"spellMD.enable": true` to your `.vscode\settings.json` file. 4 | 5 | This README covers off: 6 | * [Functionality](#functionality) 7 | * [Install](#install) 8 | * [Run and Configure](#run-and-configure) 9 | * [Known Issues/Bugs](#known-issuesbugs) 10 | * [Backlog](#backlog) 11 | * [How to Debug](#how-to-debug) 12 | 13 | # Functionality 14 | 15 | Load up a Markdown file and get highlights and hovers for existing issues. Checking will occur as you type in the document. 16 | 17 | ![Underscores and hovers](https://github.com/username/repository/raw/master/images/SpellMDDemo1.gif) 18 | 19 | The status bar lets you quickly navigate to any issue and you can see all positions in the gutter. 20 | 21 | [![Jump to issues](https://github.com/username/repository/raw/master/images/SpellMDDemo2.gif)](http://shouldnottouchthis/) 22 | [![Jump to issues](https://github.com/username/repository/raw/master/images/SpellMDDemo2.gif)](https://github.com/username/repository/blob/master/monkey) 23 | ![](https://github.com/username/repository/raw/master/images/SpellMDDemo2.gif) 24 | ![](https://github.com/username/repository/raw/master/SpellMDDemo2.gif) 25 | ![](https://github.com/username/repository/raw/master/SpellMDDemo2.gif#gh-light-mode-only) 26 | 27 | 28 | 29 | 30 | 31 | The `spellMD.json` config file is watched so you can add more ignores or change mappings at will. 32 | 33 | ![Add to dictionary](https://github.com/username/repository/raw/master/images/SpellMDDemo3.gif) 34 | 35 | ![issue](https://github.com/username/repository/raw/master/issue) 36 | 37 | [mono](https://github.com/username/repository/blob/master/monkey) 38 | [not](http://shouldnottouchthis/) 39 | [Email me](mailto:example@example.com) 40 | 41 | # Install 42 | This extension is published in the VS Code Gallery. So simply hit 'F1' and type 'ext inst' from there select `SpellMD` and follow instructions. 43 | 44 | 45 | To clone the extension and load locally... 46 | 47 | ``` 48 | git clone https://github.com/Microsoft/vscode-SpellMD.git 49 | npm install 50 | tsc 51 | ``` 52 | 53 | >**Note:** TypeScript 1.6 or higher is required you can check with `tsc -v` and if you need to upgrade then run `npm install -g typescript`. 54 | -------------------------------------------------------------------------------- /src/test/fixtures/readme/readme.github.expected.md: -------------------------------------------------------------------------------- 1 | # Replace 2 | 3 | [#8](https://github.com/username/repository/issues/8) 4 | 5 | * Some issue in same repository: [#7](https://github.com/username/repository/issues/7) 6 | * Some issue in other repository: [other/repositoryName#8](https://github.com/other/repositoryName/issues/8) 7 | * Some issue in other repository with fancy name: [my_user-name/my-rep_o12#6](https://github.com/my_user-name/my-rep_o12/issues/6) 8 | 9 | # Do not touch this: 10 | * username#4 (no valid github link) 11 | * /#7 12 | * foo/$234/#7 13 | * [#7](http://shouldnottouchthis/) 14 | * [other/repositoryName#8](http://shouldnottouchthis/) 15 | * [Email me](MAILTO:example@example.com) 16 | -------------------------------------------------------------------------------- /src/test/fixtures/readme/readme.github.md: -------------------------------------------------------------------------------- 1 | # Replace 2 | 3 | #8 4 | 5 | * Some issue in same repository: #7 6 | * Some issue in other repository: other/repositoryName#8 7 | * Some issue in other repository with fancy name: my_user-name/my-rep_o12#6 8 | 9 | # Do not touch this: 10 | * username#4 (no valid github link) 11 | * /#7 12 | * foo/$234/#7 13 | * [#7](http://shouldnottouchthis/) 14 | * [other/repositoryName#8](http://shouldnottouchthis/) 15 | * [Email me](MAILTO:example@example.com) 16 | -------------------------------------------------------------------------------- /src/test/fixtures/readme/readme.gitlab.branch.main.expected.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | >**Important:** Once installed the checker will only update if you add the setting `"spellMD.enable": true` to your `.vscode\settings.json` file. 4 | 5 | This README covers off: 6 | * [Functionality](#functionality) 7 | * [Install](#install) 8 | * [Run and Configure](#run-and-configure) 9 | * [Known Issues/Bugs](#known-issuesbugs) 10 | * [Backlog](#backlog) 11 | * [How to Debug](#how-to-debug) 12 | 13 | # Functionality 14 | 15 | Load up a Markdown file and get highlights and hovers for existing issues. Checking will occur as you type in the document. 16 | 17 | ![Underscores and hovers](https://gitlab.com/username/repository/-/raw/main/images/SpellMDDemo1.gif) 18 | 19 | The status bar lets you quickly navigate to any issue and you can see all positions in the gutter. 20 | 21 | [![Jump to issues](https://gitlab.com/username/repository/-/raw/main/images/SpellMDDemo2.gif)](http://shouldnottouchthis/) 22 | [![Jump to issues](https://gitlab.com/username/repository/-/raw/main/images/SpellMDDemo2.gif)](https://gitlab.com/username/repository/-/blob/main/monkey) 23 | ![](https://gitlab.com/username/repository/-/raw/main/images/SpellMDDemo2.gif) 24 | ![](https://gitlab.com/username/repository/-/raw/main/SpellMDDemo2.gif) 25 | ![](https://gitlab.com/username/repository/-/raw/main/SpellMDDemo2.gif#gh-light-mode-only) 26 | 27 | 28 | 29 | 30 | 31 | The `spellMD.json` config file is watched so you can add more ignores or change mappings at will. 32 | 33 | ![Add to dictionary](https://gitlab.com/username/repository/-/raw/main/images/SpellMDDemo3.gif) 34 | 35 | ![issue](https://gitlab.com/username/repository/-/raw/main/issue) 36 | 37 | [mono](https://gitlab.com/username/repository/-/blob/main/monkey) 38 | [not](http://shouldnottouchthis/) 39 | [Email me](mailto:example@example.com) 40 | 41 | # Install 42 | This extension is published in the VS Code Gallery. So simply hit 'F1' and type 'ext inst' from there select `SpellMD` and follow instructions. 43 | 44 | 45 | To clone the extension and load locally... 46 | 47 | ``` 48 | git clone https://github.com/Microsoft/vscode-SpellMD.git 49 | npm install 50 | tsc 51 | ``` 52 | 53 | >**Note:** TypeScript 1.6 or higher is required you can check with `tsc -v` and if you need to upgrade then run `npm install -g typescript`. 54 | -------------------------------------------------------------------------------- /src/test/fixtures/readme/readme.gitlab.branch.override.content.expected.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | >**Important:** Once installed the checker will only update if you add the setting `"spellMD.enable": true` to your `.vscode\settings.json` file. 4 | 5 | This README covers off: 6 | * [Functionality](#functionality) 7 | * [Install](#install) 8 | * [Run and Configure](#run-and-configure) 9 | * [Known Issues/Bugs](#known-issuesbugs) 10 | * [Backlog](#backlog) 11 | * [How to Debug](#how-to-debug) 12 | 13 | # Functionality 14 | 15 | Load up a Markdown file and get highlights and hovers for existing issues. Checking will occur as you type in the document. 16 | 17 | ![Underscores and hovers](https://gitlab.com/base/images/SpellMDDemo1.gif) 18 | 19 | The status bar lets you quickly navigate to any issue and you can see all positions in the gutter. 20 | 21 | [![Jump to issues](https://gitlab.com/base/images/SpellMDDemo2.gif)](http://shouldnottouchthis/) 22 | [![Jump to issues](https://gitlab.com/base/images/SpellMDDemo2.gif)](https://gitlab.com/base/monkey) 23 | ![](https://gitlab.com/base/images/SpellMDDemo2.gif) 24 | ![](https://gitlab.com/base/SpellMDDemo2.gif) 25 | ![](https://gitlab.com/base/SpellMDDemo2.gif#gh-light-mode-only) 26 | 27 | 28 | 29 | 30 | 31 | The `spellMD.json` config file is watched so you can add more ignores or change mappings at will. 32 | 33 | ![Add to dictionary](https://gitlab.com/base/images/SpellMDDemo3.gif) 34 | 35 | ![issue](https://gitlab.com/base/issue) 36 | 37 | [mono](https://gitlab.com/base/monkey) 38 | [not](http://shouldnottouchthis/) 39 | [Email me](mailto:example@example.com) 40 | 41 | # Install 42 | This extension is published in the VS Code Gallery. So simply hit 'F1' and type 'ext inst' from there select `SpellMD` and follow instructions. 43 | 44 | 45 | To clone the extension and load locally... 46 | 47 | ``` 48 | git clone https://github.com/Microsoft/vscode-SpellMD.git 49 | npm install 50 | tsc 51 | ``` 52 | 53 | >**Note:** TypeScript 1.6 or higher is required you can check with `tsc -v` and if you need to upgrade then run `npm install -g typescript`. 54 | -------------------------------------------------------------------------------- /src/test/fixtures/readme/readme.gitlab.branch.override.images.expected.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | >**Important:** Once installed the checker will only update if you add the setting `"spellMD.enable": true` to your `.vscode\settings.json` file. 4 | 5 | This README covers off: 6 | * [Functionality](#functionality) 7 | * [Install](#install) 8 | * [Run and Configure](#run-and-configure) 9 | * [Known Issues/Bugs](#known-issuesbugs) 10 | * [Backlog](#backlog) 11 | * [How to Debug](#how-to-debug) 12 | 13 | # Functionality 14 | 15 | Load up a Markdown file and get highlights and hovers for existing issues. Checking will occur as you type in the document. 16 | 17 | ![Underscores and hovers](https://gitlab.com/base/images/SpellMDDemo1.gif) 18 | 19 | The status bar lets you quickly navigate to any issue and you can see all positions in the gutter. 20 | 21 | [![Jump to issues](https://gitlab.com/base/images/SpellMDDemo2.gif)](http://shouldnottouchthis/) 22 | [![Jump to issues](https://gitlab.com/base/images/SpellMDDemo2.gif)](https://gitlab.com/username/repository/-/blob/main/monkey) 23 | ![](https://gitlab.com/base/images/SpellMDDemo2.gif) 24 | ![](https://gitlab.com/base/SpellMDDemo2.gif) 25 | ![](https://gitlab.com/base/SpellMDDemo2.gif#gh-light-mode-only) 26 | 27 | 28 | 29 | 30 | 31 | The `spellMD.json` config file is watched so you can add more ignores or change mappings at will. 32 | 33 | ![Add to dictionary](https://gitlab.com/base/images/SpellMDDemo3.gif) 34 | 35 | ![issue](https://gitlab.com/base/issue) 36 | 37 | [mono](https://gitlab.com/username/repository/-/blob/main/monkey) 38 | [not](http://shouldnottouchthis/) 39 | [Email me](mailto:example@example.com) 40 | 41 | # Install 42 | This extension is published in the VS Code Gallery. So simply hit 'F1' and type 'ext inst' from there select `SpellMD` and follow instructions. 43 | 44 | 45 | To clone the extension and load locally... 46 | 47 | ``` 48 | git clone https://github.com/Microsoft/vscode-SpellMD.git 49 | npm install 50 | tsc 51 | ``` 52 | 53 | >**Note:** TypeScript 1.6 or higher is required you can check with `tsc -v` and if you need to upgrade then run `npm install -g typescript`. 54 | -------------------------------------------------------------------------------- /src/test/fixtures/readme/readme.gitlab.default.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | >**Important:** Once installed the checker will only update if you add the setting `"spellMD.enable": true` to your `.vscode\settings.json` file. 4 | 5 | This README covers off: 6 | * [Functionality](#functionality) 7 | * [Install](#install) 8 | * [Run and Configure](#run-and-configure) 9 | * [Known Issues/Bugs](#known-issuesbugs) 10 | * [Backlog](#backlog) 11 | * [How to Debug](#how-to-debug) 12 | 13 | # Functionality 14 | 15 | Load up a Markdown file and get highlights and hovers for existing issues. Checking will occur as you type in the document. 16 | 17 | ![Underscores and hovers](https://gitlab.com/username/repository/-/raw/HEAD/images/SpellMDDemo1.gif) 18 | 19 | The status bar lets you quickly navigate to any issue and you can see all positions in the gutter. 20 | 21 | [![Jump to issues](https://gitlab.com/username/repository/-/raw/HEAD/images/SpellMDDemo2.gif)](http://shouldnottouchthis/) 22 | [![Jump to issues](https://gitlab.com/username/repository/-/raw/HEAD/images/SpellMDDemo2.gif)](https://gitlab.com/username/repository/-/blob/HEAD/monkey) 23 | ![](https://gitlab.com/username/repository/-/raw/HEAD/images/SpellMDDemo2.gif) 24 | ![](https://gitlab.com/username/repository/-/raw/HEAD/SpellMDDemo2.gif) 25 | ![](https://gitlab.com/username/repository/-/raw/HEAD/SpellMDDemo2.gif#gh-light-mode-only) 26 | 27 | 28 | 29 | 30 | 31 | The `spellMD.json` config file is watched so you can add more ignores or change mappings at will. 32 | 33 | ![Add to dictionary](https://gitlab.com/username/repository/-/raw/HEAD/images/SpellMDDemo3.gif) 34 | 35 | ![issue](https://gitlab.com/username/repository/-/raw/HEAD/issue) 36 | 37 | [mono](https://gitlab.com/username/repository/-/blob/HEAD/monkey) 38 | [not](http://shouldnottouchthis/) 39 | [Email me](mailto:example@example.com) 40 | 41 | # Install 42 | This extension is published in the VS Code Gallery. So simply hit 'F1' and type 'ext inst' from there select `SpellMD` and follow instructions. 43 | 44 | 45 | To clone the extension and load locally... 46 | 47 | ``` 48 | git clone https://github.com/Microsoft/vscode-SpellMD.git 49 | npm install 50 | tsc 51 | ``` 52 | 53 | >**Note:** TypeScript 1.6 or higher is required you can check with `tsc -v` and if you need to upgrade then run `npm install -g typescript`. 54 | -------------------------------------------------------------------------------- /src/test/fixtures/readme/readme.gitlab.expected.md: -------------------------------------------------------------------------------- 1 | # Replace 2 | 3 | [#8](https://gitlab.com/username/repository/-/issues/8) 4 | 5 | * Some issue in same repository: [#7](https://gitlab.com/username/repository/-/issues/7) 6 | * Some issue in other repository: [other/repositoryName#8](https://gitlab.com/other/repositoryName/-/issues/8) 7 | * Some issue in other repository with fancy name: [my_user-name/my-rep_o12#6](https://gitlab.com/my_user-name/my-rep_o12/-/issues/6) 8 | 9 | # Do not touch this: 10 | * username#4 (no valid gitlab link) 11 | * /#7 12 | * foo/$234/#7 13 | * [#7](http://shouldnottouchthis/) 14 | * [other/repositoryName#8](http://shouldnottouchthis/) 15 | * [Email me](MAILTO:example@example.com) 16 | -------------------------------------------------------------------------------- /src/test/fixtures/readme/readme.gitlab.md: -------------------------------------------------------------------------------- 1 | # Replace 2 | 3 | #8 4 | 5 | * Some issue in same repository: #7 6 | * Some issue in other repository: other/repositoryName#8 7 | * Some issue in other repository with fancy name: my_user-name/my-rep_o12#6 8 | 9 | # Do not touch this: 10 | * username#4 (no valid gitlab link) 11 | * /#7 12 | * foo/$234/#7 13 | * [#7](http://shouldnottouchthis/) 14 | * [other/repositoryName#8](http://shouldnottouchthis/) 15 | * [Email me](MAILTO:example@example.com) 16 | -------------------------------------------------------------------------------- /src/test/fixtures/readme/readme.images.expected.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | >**Important:** Once installed the checker will only update if you add the setting `"spellMD.enable": true` to your `.vscode\settings.json` file. 4 | 5 | This README covers off: 6 | * [Functionality](#functionality) 7 | * [Install](#install) 8 | * [Run and Configure](#run-and-configure) 9 | * [Known Issues/Bugs](#known-issuesbugs) 10 | * [Backlog](#backlog) 11 | * [How to Debug](#how-to-debug) 12 | 13 | # Functionality 14 | 15 | Load up a Markdown file and get highlights and hovers for existing issues. Checking will occur as you type in the document. 16 | 17 | ![Underscores and hovers](https://github.com/username/repository/path/to/images/SpellMDDemo1.gif) 18 | 19 | The status bar lets you quickly navigate to any issue and you can see all positions in the gutter. 20 | 21 | [![Jump to issues](https://github.com/username/repository/path/to/images/SpellMDDemo2.gif)](http://shouldnottouchthis/) 22 | [![Jump to issues](https://github.com/username/repository/path/to/images/SpellMDDemo2.gif)](https://github.com/username/repository/blob/HEAD/monkey) 23 | ![](https://github.com/username/repository/path/to/images/SpellMDDemo2.gif) 24 | ![](https://github.com/username/repository/path/to/SpellMDDemo2.gif) 25 | ![](https://github.com/username/repository/path/to/SpellMDDemo2.gif#gh-light-mode-only) 26 | 27 | 28 | 29 | 30 | 31 | The `spellMD.json` config file is watched so you can add more ignores or change mappings at will. 32 | 33 | ![Add to dictionary](https://github.com/username/repository/path/to/images/SpellMDDemo3.gif) 34 | 35 | ![issue](https://github.com/username/repository/path/to/issue) 36 | 37 | [mono](https://github.com/username/repository/blob/HEAD/monkey) 38 | [not](http://shouldnottouchthis/) 39 | [Email me](mailto:example@example.com) 40 | 41 | # Install 42 | This extension is published in the VS Code Gallery. So simply hit 'F1' and type 'ext inst' from there select `SpellMD` and follow instructions. 43 | 44 | 45 | To clone the extension and load locally... 46 | 47 | ``` 48 | git clone https://github.com/Microsoft/vscode-SpellMD.git 49 | npm install 50 | tsc 51 | ``` 52 | 53 | >**Note:** TypeScript 1.6 or higher is required you can check with `tsc -v` and if you need to upgrade then run `npm install -g typescript`. 54 | -------------------------------------------------------------------------------- /src/test/fixtures/readme/readme.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | >**Important:** Once installed the checker will only update if you add the setting `"spellMD.enable": true` to your `.vscode\settings.json` file. 4 | 5 | This README covers off: 6 | * [Functionality](#functionality) 7 | * [Install](#install) 8 | * [Run and Configure](#run-and-configure) 9 | * [Known Issues/Bugs](#known-issuesbugs) 10 | * [Backlog](#backlog) 11 | * [How to Debug](#how-to-debug) 12 | 13 | # Functionality 14 | 15 | Load up a Markdown file and get highlights and hovers for existing issues. Checking will occur as you type in the document. 16 | 17 | ![Underscores and hovers](/images/SpellMDDemo1.gif) 18 | 19 | The status bar lets you quickly navigate to any issue and you can see all positions in the gutter. 20 | 21 | [![Jump to issues](images/SpellMDDemo2.gif)](http://shouldnottouchthis/) 22 | [![Jump to issues](images/SpellMDDemo2.gif)](monkey) 23 | ![](images/SpellMDDemo2.gif) 24 | ![](./SpellMDDemo2.gif) 25 | ![](./SpellMDDemo2.gif#gh-light-mode-only) 26 | 27 | 28 | 29 | 30 | 31 | The `spellMD.json` config file is watched so you can add more ignores or change mappings at will. 32 | 33 | ![Add to dictionary](/images/SpellMDDemo3.gif) 34 | 35 | ![issue](issue) 36 | 37 | [mono](monkey) 38 | [not](http://shouldnottouchthis/) 39 | [Email me](mailto:example@example.com) 40 | 41 | # Install 42 | This extension is published in the VS Code Gallery. So simply hit 'F1' and type 'ext inst' from there select `SpellMD` and follow instructions. 43 | 44 | 45 | To clone the extension and load locally... 46 | 47 | ``` 48 | git clone https://github.com/Microsoft/vscode-SpellMD.git 49 | npm install 50 | tsc 51 | ``` 52 | 53 | >**Note:** TypeScript 1.6 or higher is required you can check with `tsc -v` and if you need to upgrade then run `npm install -g typescript`. 54 | -------------------------------------------------------------------------------- /src/test/fixtures/secrets/LICENSE: -------------------------------------------------------------------------------- 1 | LICENSE... -------------------------------------------------------------------------------- /src/test/fixtures/secrets/README.md: -------------------------------------------------------------------------------- 1 | Test -------------------------------------------------------------------------------- /src/test/fixtures/secrets/noSecret1.ts: -------------------------------------------------------------------------------- 1 | export const k = { 2 | 'type': 'service_account', 3 | 'project_id': 'my-gcp-project', 4 | 'private_key_id': 'abcdef1234567890abcdef1234567890abcdef12', 5 | 'private_key': '-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkq...\n-----END PRIVATE KEY-----\n', 6 | 'client_email': 'my-service-account@my-gcp-project.iam.gserviceaccount.com', 7 | 'client_id': '123456789012345678901', 8 | 'auth_uri': 'https://accounts.google.com/o/oauth2/auth', 9 | 'token_uri': 'https://oauth2.googleapis.com/token', 10 | 'auth_provider_x509_cert_url': 'https://www.googleapis.com/oauth2/v1/certs', 11 | 'client_x509_cert_url': 'https://www.googleapis.com/robot/v1/metadata/x509/my-service-account%40my-gcp-project.iam.gserviceaccount.com' 12 | }; 13 | 14 | // Here a Fibonacci sequence function 15 | export function fib(n: number): number { 16 | if (n <= 1) return n; 17 | return fib(n - 1) + fib(n - 2); 18 | } -------------------------------------------------------------------------------- /src/test/fixtures/secrets/noSecret1Ignore: -------------------------------------------------------------------------------- 1 | **secret** 2 | **noSecret** 3 | !noSecret1.ts -------------------------------------------------------------------------------- /src/test/fixtures/secrets/noSecret2.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/microsoft/vscode-vsce/issues/1147 2 | let privateB64 = '-----BEGIN PRIVATE KEY-----\n'; 3 | privateB64 += 'ABC\n'; 4 | privateB64 += '-----END PRIVATE KEY-----\n'; 5 | 6 | const description = ` This is some description test 7 | 8 | \`\`\`ini 9 | key="-----BEGIN PRIVATE KEY-----\\nXXXX\\nXXXX\\n-----END PRIVATE KEY-----" 10 | \`\`\` 11 | 12 | some other text. 13 | `; 14 | 15 | -------------------------------------------------------------------------------- /src/test/fixtures/secrets/noSecret2Ignore: -------------------------------------------------------------------------------- 1 | **secret** 2 | **noSecret** 3 | !noSecret2.ts -------------------------------------------------------------------------------- /src/test/fixtures/secrets/noSecret3.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/microsoft/vscode-vsce/issues/1153 2 | function npm_i_save_dev_types_Slashjest_or_npm_i_(){} 3 | function npm_i_save_dev_types_Slash_1_if_it_exists(){} 4 | function Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_jQuery_Try_npm_i_save_dev_types_Slashjquery_and_then_add_jquery_to_the_types_field_in_your_tsconfig(){} -------------------------------------------------------------------------------- /src/test/fixtures/secrets/noSecret3Ignore: -------------------------------------------------------------------------------- 1 | **secret** 2 | **noSecret** 3 | !noSecret3.ts -------------------------------------------------------------------------------- /src/test/fixtures/secrets/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uuid", 3 | "publisher": "joaomoreno", 4 | "version": "1.0.0", 5 | "engines": { 6 | "vscode": "*" 7 | } 8 | } -------------------------------------------------------------------------------- /src/test/fixtures/secrets/secret1.ts: -------------------------------------------------------------------------------- 1 | export const k = ` 2 | -----BEGIN OPENSSH PRIVATE KEY----- 3 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS 4 | 1zaGEyLW5pc3RwMjU2A564CG5pc3RwMjU2AAAAQQR+598gRY+O8LM7Jk80+etTh+Hi4zUW 5 | Pj7jrQoOPbvvkLKhPMHPXaVsXScxbFe87++o9Qn0h9AKtp+Rvf4mHSqwAAAAoKDtkvGg7Z 6 | LxAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAA123bmlzdHAyNTYAAABBBH7n3yBFj47wszsm 7 | TzT561OH4eLjNRY+PuOtCg49u++QsqE8wc9dpWxdJzFsV7zv76j1CfSH0Aq2n5G9/iYdKr 8 | AAAAAgNbbtcAGWxT7sR5Rbth6D/4MPQd+LO5ljjbjHQlu9KdUAAAAGbm9uYW1lAQI= 9 | -----END OPENSSH PRIVATE KEY----- 10 | ` -------------------------------------------------------------------------------- /src/test/fixtures/secrets/secret1Ignore: -------------------------------------------------------------------------------- 1 | **secret** 2 | **noSecret** 3 | !secret1.ts -------------------------------------------------------------------------------- /src/test/fixtures/secrets/secret2.ts: -------------------------------------------------------------------------------- 1 | export const k = `npm_Ab3kZy0X9QpLmN4tUvW7aBcDeFgHiJkLmNoPqRsTu` -------------------------------------------------------------------------------- /src/test/fixtures/secrets/secret2Ignore: -------------------------------------------------------------------------------- 1 | **secret** 2 | **noSecret** 3 | !secret2.ts -------------------------------------------------------------------------------- /src/test/fixtures/target/darwin-arm64/file.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-vsce/fafad8a63e9cf31179f918eb7a4eeb376834c904/src/test/fixtures/target/darwin-arm64/file.txt -------------------------------------------------------------------------------- /src/test/fixtures/target/deep/darwin-arm64/file.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-vsce/fafad8a63e9cf31179f918eb7a4eeb376834c904/src/test/fixtures/target/deep/darwin-arm64/file.txt -------------------------------------------------------------------------------- /src/test/fixtures/target/deep/file.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-vsce/fafad8a63e9cf31179f918eb7a4eeb376834c904/src/test/fixtures/target/deep/file.txt -------------------------------------------------------------------------------- /src/test/fixtures/target/deep/linux-x64/file.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-vsce/fafad8a63e9cf31179f918eb7a4eeb376834c904/src/test/fixtures/target/deep/linux-x64/file.txt -------------------------------------------------------------------------------- /src/test/fixtures/target/deep/random/file.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-vsce/fafad8a63e9cf31179f918eb7a4eeb376834c904/src/test/fixtures/target/deep/random/file.txt -------------------------------------------------------------------------------- /src/test/fixtures/target/deep/web/file.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-vsce/fafad8a63e9cf31179f918eb7a4eeb376834c904/src/test/fixtures/target/deep/web/file.txt -------------------------------------------------------------------------------- /src/test/fixtures/target/file.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-vsce/fafad8a63e9cf31179f918eb7a4eeb376834c904/src/test/fixtures/target/file.txt -------------------------------------------------------------------------------- /src/test/fixtures/target/linux-x64/file.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-vsce/fafad8a63e9cf31179f918eb7a4eeb376834c904/src/test/fixtures/target/linux-x64/file.txt -------------------------------------------------------------------------------- /src/test/fixtures/target/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "target", 3 | "publisher": "joaomoreno", 4 | "version": "1.0.0", 5 | "engines": { "vscode": "*" } 6 | } -------------------------------------------------------------------------------- /src/test/fixtures/target/random/file.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-vsce/fafad8a63e9cf31179f918eb7a4eeb376834c904/src/test/fixtures/target/random/file.txt -------------------------------------------------------------------------------- /src/test/fixtures/target/web/file.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-vsce/fafad8a63e9cf31179f918eb7a4eeb376834c904/src/test/fixtures/target/web/file.txt -------------------------------------------------------------------------------- /src/test/fixtures/uuid/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uuid", 3 | "publisher": "joaomoreno", 4 | "version": "1.0.0", 5 | "engines": { "vscode": "*" } 6 | } -------------------------------------------------------------------------------- /src/test/fixtures/version/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "version", 3 | "publisher": "felipecrs", 4 | "version": "1.0.0", 5 | "engines": { 6 | "vscode": "*" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/test/fixtures/vscodeignore/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": [], 3 | } -------------------------------------------------------------------------------- /src/test/fixtures/vscodeignore/.vscodeignore: -------------------------------------------------------------------------------- 1 | # pattern of dotted directory without backslash 2 | .vscode 3 | 4 | # pattern of directory with trailing backslash 5 | out/ 6 | 7 | # pattern of file name 8 | *.log 9 | 10 | # https://github.com/microsoft/vscode-vsce/issues/588 11 | foo 12 | !foo/bar/hello.txt -------------------------------------------------------------------------------- /src/test/fixtures/vscodeignore/foo/bar/hello.txt: -------------------------------------------------------------------------------- 1 | hi there -------------------------------------------------------------------------------- /src/test/fixtures/vscodeignore/logger.log: -------------------------------------------------------------------------------- 1 | log -------------------------------------------------------------------------------- /src/test/fixtures/vscodeignore/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uuid", 3 | "publisher": "joaomoreno", 4 | "version": "1.0.0", 5 | "engines": { "vscode": "*" }, 6 | "files": [] 7 | } -------------------------------------------------------------------------------- /src/test/fixtures/vsixmanifest/ohno.vsixmanifest: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/fixtures/vsixmanifest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uuid", 3 | "publisher": "joaomoreno", 4 | "version": "1.0.0", 5 | "engines": { "vscode": "*" } 6 | } -------------------------------------------------------------------------------- /src/test/store.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import * as tmp from 'tmp'; 3 | import * as fs from 'fs'; 4 | import { FileStore } from '../store'; 5 | 6 | describe('FileStore', () => { 7 | it('works', async () => { 8 | const name = tmp.tmpNameSync(); 9 | const store = await FileStore.open(name); 10 | 11 | assert.deepStrictEqual(store.get('joe'), undefined); 12 | assert.deepStrictEqual([...store], []); 13 | assert.ok(!fs.existsSync(name)); 14 | 15 | await store.add({ name: 'joe', pat: 'abc' }); 16 | assert.deepStrictEqual(store.get('joe'), { name: 'joe', pat: 'abc' }); 17 | assert.deepStrictEqual([...store], [{ name: 'joe', pat: 'abc' }]); 18 | assert.deepStrictEqual(JSON.parse(fs.readFileSync(name, 'utf8')), { 19 | publishers: [{ name: 'joe', pat: 'abc' }], 20 | }); 21 | 22 | await store.add({ name: 'joe', pat: 'what' }); 23 | assert.deepStrictEqual(store.get('joe'), { name: 'joe', pat: 'what' }); 24 | assert.deepStrictEqual([...store], [{ name: 'joe', pat: 'what' }]); 25 | assert.deepStrictEqual(JSON.parse(fs.readFileSync(name, 'utf8')), { 26 | publishers: [{ name: 'joe', pat: 'what' }], 27 | }); 28 | 29 | await store.add({ name: 'jane', pat: 'oh' }); 30 | assert.deepStrictEqual(store.get('joe'), { name: 'joe', pat: 'what' }); 31 | assert.deepStrictEqual(store.get('jane'), { name: 'jane', pat: 'oh' }); 32 | assert.deepStrictEqual( 33 | [...store], 34 | [ 35 | { name: 'joe', pat: 'what' }, 36 | { name: 'jane', pat: 'oh' }, 37 | ] 38 | ); 39 | assert.deepStrictEqual(JSON.parse(fs.readFileSync(name, 'utf8')), { 40 | publishers: [ 41 | { name: 'joe', pat: 'what' }, 42 | { name: 'jane', pat: 'oh' }, 43 | ], 44 | }); 45 | 46 | await store.delete('joe'); 47 | assert.deepStrictEqual(store.get('joe'), undefined); 48 | assert.deepStrictEqual(store.get('jane'), { name: 'jane', pat: 'oh' }); 49 | assert.deepStrictEqual([...store], [{ name: 'jane', pat: 'oh' }]); 50 | assert.deepStrictEqual(JSON.parse(fs.readFileSync(name, 'utf8')), { 51 | publishers: [{ name: 'jane', pat: 'oh' }], 52 | }); 53 | 54 | await store.delete('joe'); 55 | assert.deepStrictEqual(store.get('joe'), undefined); 56 | assert.deepStrictEqual(store.get('jane'), { name: 'jane', pat: 'oh' }); 57 | assert.deepStrictEqual([...store], [{ name: 'jane', pat: 'oh' }]); 58 | assert.deepStrictEqual(JSON.parse(fs.readFileSync(name, 'utf8')), { 59 | publishers: [{ name: 'jane', pat: 'oh' }], 60 | }); 61 | 62 | await store.delete('jane'); 63 | assert.deepStrictEqual(store.get('joe'), undefined); 64 | assert.deepStrictEqual(store.get('jane'), undefined); 65 | assert.deepStrictEqual([...store], []); 66 | assert.deepStrictEqual(JSON.parse(fs.readFileSync(name, 'utf8')), { 67 | publishers: [], 68 | }); 69 | 70 | fs.unlinkSync(name); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /src/test/validation.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import { 3 | validatePublisher, 4 | validateExtensionName, 5 | validateVersion, 6 | validateEngineCompatibility, 7 | validateVSCodeTypesCompatibility, 8 | } from '../validation'; 9 | 10 | describe('validatePublisher', () => { 11 | it('should throw with empty', () => { 12 | assert.throws(() => validatePublisher(null!)); 13 | assert.throws(() => validatePublisher(undefined!)); 14 | assert.throws(() => validatePublisher('')); 15 | }); 16 | 17 | it('should validate', () => { 18 | validatePublisher('hello'); 19 | validatePublisher('Hello'); 20 | validatePublisher('HelloWorld'); 21 | validatePublisher('Hello-World'); 22 | validatePublisher('Hell0-World'); 23 | 24 | assert.throws(() => validatePublisher('hello.')); 25 | assert.throws(() => validatePublisher('.hello')); 26 | assert.throws(() => validatePublisher('h ello')); 27 | assert.throws(() => validatePublisher('hello world')); 28 | assert.throws(() => validatePublisher('-hello')); 29 | assert.throws(() => validatePublisher('-')); 30 | }); 31 | }); 32 | 33 | describe('validateExtensionName', () => { 34 | it('should throw with empty', () => { 35 | assert.throws(() => validateExtensionName(null!)); 36 | assert.throws(() => validateExtensionName(undefined!)); 37 | assert.throws(() => validateExtensionName('')); 38 | }); 39 | 40 | it('should validate', () => { 41 | validateExtensionName('hello'); 42 | validateExtensionName('Hello'); 43 | validateExtensionName('HelloWorld'); 44 | validateExtensionName('Hello-World'); 45 | validateExtensionName('Hell0-World'); 46 | 47 | assert.throws(() => validateExtensionName('hello.')); 48 | assert.throws(() => validateExtensionName('.hello')); 49 | assert.throws(() => validateExtensionName('h ello')); 50 | assert.throws(() => validateExtensionName('hello world')); 51 | assert.throws(() => validateExtensionName('-hello')); 52 | assert.throws(() => validateExtensionName('-')); 53 | }); 54 | }); 55 | 56 | describe('validateVersion', () => { 57 | it('should throw with empty', () => { 58 | assert.throws(() => validateVersion(null!)); 59 | assert.throws(() => validateVersion(undefined!)); 60 | assert.throws(() => validateVersion('')); 61 | }); 62 | 63 | it('should validate', () => { 64 | validateVersion('1.0.0'); 65 | validateVersion('0.1.1'); 66 | validateVersion('0.1.1-pre'); 67 | 68 | assert.throws(() => validateVersion('.')); 69 | assert.throws(() => validateVersion('..')); 70 | assert.throws(() => validateVersion('0')); 71 | assert.throws(() => validateVersion('0.1')); 72 | assert.throws(() => validateVersion('.0.1')); 73 | assert.throws(() => validateVersion('0.1.')); 74 | assert.throws(() => validateVersion('0.0.0.1')); 75 | assert.throws(() => validateVersion('1.0-pre')); 76 | assert.throws(() => validateVersion('pre')); 77 | }); 78 | }); 79 | 80 | describe('validateEngineCompatibility', () => { 81 | it('should throw with empty', () => { 82 | assert.throws(() => validateEngineCompatibility(null!)); 83 | assert.throws(() => validateEngineCompatibility(undefined!)); 84 | assert.throws(() => validateEngineCompatibility('')); 85 | }); 86 | 87 | it('should validate', () => { 88 | validateEngineCompatibility('*'); 89 | 90 | validateEngineCompatibility('1.0.0'); 91 | validateEngineCompatibility('1.0.x'); 92 | validateEngineCompatibility('1.x.x'); 93 | 94 | validateEngineCompatibility('^1.0.0'); 95 | validateEngineCompatibility('^1.0.x'); 96 | validateEngineCompatibility('^1.x.x'); 97 | 98 | validateEngineCompatibility('>=1.0.0'); 99 | validateEngineCompatibility('>=1.0.x'); 100 | validateEngineCompatibility('>=1.x.x'); 101 | 102 | assert.throws(() => validateVersion('0.0.0.1')); 103 | assert.throws(() => validateVersion('^0.0.0.1')); 104 | assert.throws(() => validateVersion('^1')); 105 | assert.throws(() => validateVersion('^1.0')); 106 | assert.throws(() => validateVersion('>=1')); 107 | assert.throws(() => validateVersion('>=1.0')); 108 | }); 109 | }); 110 | 111 | describe('validateVSCodeTypesCompatibility', () => { 112 | it('should validate', () => { 113 | validateVSCodeTypesCompatibility('*', '1.30.0'); 114 | validateVSCodeTypesCompatibility('*', '^1.30.0'); 115 | validateVSCodeTypesCompatibility('*', '~1.30.0'); 116 | 117 | validateVSCodeTypesCompatibility('1.30.0', '1.30.0'); 118 | validateVSCodeTypesCompatibility('1.30.0', '1.20.0'); 119 | validateVSCodeTypesCompatibility('1.46.0', '1.45.1'); 120 | validateVSCodeTypesCompatibility('1.45.0', '1.45.1'); 121 | 122 | assert.throws(() => validateVSCodeTypesCompatibility('1.30.0', '1.40.0')); 123 | assert.throws(() => validateVSCodeTypesCompatibility('1.30.0', '^1.40.0')); 124 | assert.throws(() => validateVSCodeTypesCompatibility('1.30.0', '~1.40.0')); 125 | 126 | assert.throws(() => validateVSCodeTypesCompatibility('1.30.0', '1.40.0')); 127 | assert.throws(() => validateVSCodeTypesCompatibility('^1.30.0', '1.40.0')); 128 | assert.throws(() => validateVSCodeTypesCompatibility('~1.30.0', '1.40.0')); 129 | 130 | assert.throws(() => validateVSCodeTypesCompatibility('1.x.x', '1.30.0')); 131 | assert.throws(() => validateVSCodeTypesCompatibility('1.x.0', '1.30.0')); 132 | 133 | assert.throws(() => validateVSCodeTypesCompatibility('1.5.0', '1.30.0')); 134 | assert.throws(() => validateVSCodeTypesCompatibility('1.5', '1.30.0')); 135 | assert.throws(() => validateVSCodeTypesCompatibility('1.5', '1.30')); 136 | }); 137 | }); 138 | -------------------------------------------------------------------------------- /src/typings/parse-semver.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'parse-semver' { 2 | interface Result { 3 | readonly name: string; 4 | readonly range: string; 5 | readonly version: string; 6 | } 7 | module parseSemver {} 8 | function parseSemver(input: string): Result; 9 | export = parseSemver; 10 | } 11 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | import { promisify } from 'util'; 2 | import * as fs from 'fs'; 3 | import _read from 'read'; 4 | import { WebApi, getBasicHandler } from 'azure-devops-node-api/WebApi'; 5 | import { IGalleryApi, GalleryApi } from 'azure-devops-node-api/GalleryApi'; 6 | import chalk from 'chalk'; 7 | import { PublicGalleryAPI } from './publicgalleryapi'; 8 | import { ISecurityRolesApi } from 'azure-devops-node-api/SecurityRolesApi'; 9 | import { ManifestPackage } from './manifest'; 10 | import { EOL } from 'os'; 11 | 12 | const __read = promisify<_read.Options, string>(_read); 13 | export function read(prompt: string, options: _read.Options = {}): Promise { 14 | if (process.env['VSCE_TESTS'] || !process.stdout.isTTY) { 15 | return Promise.resolve('y'); 16 | } 17 | 18 | return __read({ prompt, ...options }); 19 | } 20 | 21 | const marketplaceUrl = process.env['VSCE_MARKETPLACE_URL'] || 'https://marketplace.visualstudio.com'; 22 | 23 | export function getPublishedUrl(extension: string): string { 24 | return `${marketplaceUrl}/items?itemName=${extension}`; 25 | } 26 | 27 | export function getMarketplaceUrl(): string { 28 | return marketplaceUrl; 29 | } 30 | 31 | export function getHubUrl(publisher: string, name: string): string { 32 | return `${marketplaceUrl}/manage/publishers/${publisher}/extensions/${name}/hub`; 33 | } 34 | 35 | export async function getGalleryAPI(pat: string): Promise { 36 | // from https://github.com/Microsoft/tfs-cli/blob/master/app/exec/extension/default.ts#L287-L292 37 | const authHandler = getBasicHandler('OAuth', pat); 38 | return new GalleryApi(marketplaceUrl, [authHandler]); 39 | 40 | // const vsoapi = new WebApi(marketplaceUrl, authHandler); 41 | // return await vsoapi.getGalleryApi(); 42 | } 43 | 44 | export async function getSecurityRolesAPI(pat: string): Promise { 45 | const authHandler = getBasicHandler('OAuth', pat); 46 | const vsoapi = new WebApi(marketplaceUrl, authHandler); 47 | return await vsoapi.getSecurityRolesApi(); 48 | } 49 | 50 | export function getPublicGalleryAPI() { 51 | return new PublicGalleryAPI(marketplaceUrl, '3.0-preview.1'); 52 | } 53 | 54 | export function normalize(path: string): string { 55 | return path.replace(/\\/g, '/'); 56 | } 57 | 58 | function chain2(a: A, b: B[], fn: (a: A, b: B) => Promise, index = 0): Promise { 59 | if (index >= b.length) { 60 | return Promise.resolve(a); 61 | } 62 | 63 | return fn(a, b[index]).then(a => chain2(a, b, fn, index + 1)); 64 | } 65 | 66 | export function chain(initial: T, processors: P[], process: (a: T, b: P) => Promise): Promise { 67 | return chain2(initial, processors, process); 68 | } 69 | 70 | export function flatten(arr: T[][]): T[] { 71 | return ([] as T[]).concat.apply([], arr) as T[]; 72 | } 73 | 74 | export function nonnull(arg: T | null | undefined): arg is T { 75 | return !!arg; 76 | } 77 | 78 | const CancelledError = 'Cancelled'; 79 | 80 | export function isCancelledError(error: any) { 81 | return error === CancelledError; 82 | } 83 | 84 | export class CancellationToken { 85 | private listeners: Function[] = []; 86 | private _cancelled: boolean = false; 87 | get isCancelled(): boolean { 88 | return this._cancelled; 89 | } 90 | 91 | subscribe(fn: Function): Function { 92 | this.listeners.push(fn); 93 | 94 | return () => { 95 | const index = this.listeners.indexOf(fn); 96 | 97 | if (index > -1) { 98 | this.listeners.splice(index, 1); 99 | } 100 | }; 101 | } 102 | 103 | cancel(): void { 104 | const emit = !this._cancelled; 105 | this._cancelled = true; 106 | 107 | if (emit) { 108 | this.listeners.forEach(l => l(CancelledError)); 109 | this.listeners = []; 110 | } 111 | } 112 | } 113 | 114 | export async function sequence(promiseFactories: { (): Promise }[]): Promise { 115 | for (const factory of promiseFactories) { 116 | await factory(); 117 | } 118 | } 119 | 120 | enum LogMessageType { 121 | DONE, 122 | INFO, 123 | WARNING, 124 | ERROR, 125 | } 126 | 127 | const LogPrefix = { 128 | [LogMessageType.DONE]: chalk.bgGreen.black(' DONE '), 129 | [LogMessageType.INFO]: chalk.bgBlueBright.black(' INFO '), 130 | [LogMessageType.WARNING]: chalk.bgYellow.black(' WARNING '), 131 | [LogMessageType.ERROR]: chalk.bgRed.black(' ERROR '), 132 | }; 133 | 134 | function _log(type: LogMessageType, msg: any, ...args: any[]): void { 135 | args = [LogPrefix[type], msg, ...args]; 136 | 137 | if (type === LogMessageType.WARNING) { 138 | process.env['GITHUB_ACTIONS'] ? logToGitHubActions('warning', msg) : console.warn(...args); 139 | } else if (type === LogMessageType.ERROR) { 140 | process.env['GITHUB_ACTIONS'] ? logToGitHubActions('error', msg) : console.error(...args); 141 | } else { 142 | process.env['GITHUB_ACTIONS'] ? logToGitHubActions('info', msg) : console.log(...args); 143 | } 144 | } 145 | 146 | const EscapeCharacters = new Map([ 147 | ['%', '%25'], 148 | ['\r', '%0D'], 149 | ['\n', '%0A'], 150 | ]); 151 | 152 | const EscapeRegex = new RegExp(`[${[...EscapeCharacters.keys()].join('')}]`, 'g'); 153 | 154 | function escapeGitHubActionsMessage(message: string): string { 155 | return message.replace(EscapeRegex, c => EscapeCharacters.get(c) ?? c); 156 | } 157 | 158 | function logToGitHubActions(type: string, message: string): void { 159 | const command = type === 'info' ? message : `::${type}::${escapeGitHubActionsMessage(message)}`; 160 | process.stdout.write(command + EOL); 161 | } 162 | 163 | export interface LogFn { 164 | (msg: any, ...args: any[]): void; 165 | } 166 | 167 | export const log = { 168 | done: _log.bind(null, LogMessageType.DONE) as LogFn, 169 | info: _log.bind(null, LogMessageType.INFO) as LogFn, 170 | warn: _log.bind(null, LogMessageType.WARNING) as LogFn, 171 | error: _log.bind(null, LogMessageType.ERROR) as LogFn, 172 | }; 173 | 174 | export function patchOptionsWithManifest(options: any, manifest: ManifestPackage): void { 175 | if (!manifest.vsce) { 176 | return; 177 | } 178 | 179 | for (const key of Object.keys(manifest.vsce)) { 180 | const optionsKey = key === 'yarn' ? 'useYarn' : key; 181 | 182 | if (options[optionsKey] === undefined) { 183 | options[optionsKey] = manifest.vsce[key]; 184 | } 185 | } 186 | } 187 | 188 | export function bytesToString(bytes: number): string { 189 | let size = 0; 190 | let unit = ''; 191 | 192 | if (bytes > 1048576) { 193 | size = Math.round(bytes / 10485.76) / 100; 194 | unit = 'MB'; 195 | } else { 196 | size = Math.round(bytes / 10.24) / 100; 197 | unit = 'KB'; 198 | } 199 | return `${size} ${unit}`; 200 | } 201 | 202 | export function filePathToVsixPath(originalFilePath: string): string { 203 | return `extension/${originalFilePath}`; 204 | } 205 | 206 | export function vsixPathToFilePath(extensionFilePath: string): string { 207 | return extensionFilePath.startsWith('extension/') ? extensionFilePath.substring('extension/'.length) : extensionFilePath; 208 | } 209 | 210 | const FOLDER_SIZE_KEY = "/__FOlDER_SIZE__\\"; 211 | const FOLDER_FILES_TOTAL_KEY = "/__FOLDER_CHILDREN__\\"; 212 | const FILE_SIZE_WARNING_THRESHOLD = 0.85; 213 | const FILE_SIZE_LARGE_THRESHOLD = 0.2; 214 | 215 | export async function generateFileStructureTree(rootFolder: string, filePaths: { origin: string, tree: string }[], printLinesLimit: number = Number.MAX_VALUE): Promise { 216 | const folderTree: any = {}; 217 | const depthCounts: number[] = []; 218 | 219 | // Build a tree structure from the file paths 220 | // Store the file size in the leaf node and the folder size in the folder node 221 | // Store the number of children in the folder node 222 | for (const filePath of filePaths) { 223 | const parts = filePath.tree.split('/'); 224 | let currentLevel = folderTree; 225 | 226 | parts.forEach((part, depth) => { 227 | const isFile = depth === parts.length - 1; 228 | 229 | // Create the node if it doesn't exist 230 | if (!currentLevel[part]) { 231 | if (isFile) { 232 | // The file size is stored in the leaf node, 233 | currentLevel[part] = 0; 234 | } else { 235 | // The folder size is stored in the folder node 236 | currentLevel[part] = {}; 237 | currentLevel[part][FOLDER_SIZE_KEY] = 0; 238 | currentLevel[part][FOLDER_FILES_TOTAL_KEY] = 0; 239 | } 240 | 241 | // Count the number of items at each depth 242 | if (depthCounts.length <= depth) { 243 | depthCounts.push(0); 244 | } 245 | depthCounts[depth]++; 246 | } 247 | 248 | currentLevel = currentLevel[part]; 249 | 250 | // Count the total number of children in the nested folders 251 | if (!isFile) { 252 | currentLevel[FOLDER_FILES_TOTAL_KEY]++; 253 | } 254 | }); 255 | }; 256 | 257 | // Get max depth depending on the maximum number of lines allowed to print 258 | let currentDepth = 0; 259 | let countUpToCurrentDepth = depthCounts[0] + 1 /* root folder */; 260 | for (let i = 1; i < depthCounts.length; i++) { 261 | if (countUpToCurrentDepth + depthCounts[i] > printLinesLimit) { 262 | break; 263 | } 264 | currentDepth++; 265 | countUpToCurrentDepth += depthCounts[i]; 266 | } 267 | const maxDepth = currentDepth; 268 | 269 | // Get all file sizes 270 | const fileSizes: [number, string][] = await Promise.all(filePaths.map(async (filePath) => { 271 | try { 272 | const stats = await fs.promises.stat(filePath.origin); 273 | return [stats.size, filePath.tree]; 274 | } catch (error) { 275 | return [0, filePath.origin]; 276 | } 277 | })); 278 | 279 | // Store all file sizes in the tree 280 | let totalFileSizes = 0; 281 | fileSizes.forEach(([size, filePath]) => { 282 | totalFileSizes += size; 283 | 284 | const parts = filePath.split('/'); 285 | let currentLevel = folderTree; 286 | parts.forEach(part => { 287 | if (currentLevel === undefined) { 288 | throw new Error(`currentLevel is undefined for ${part} in ${filePath}`); 289 | } 290 | 291 | if (typeof currentLevel[part] === 'number') { 292 | currentLevel[part] = size; 293 | } else if (currentLevel[part]) { 294 | currentLevel[part][FOLDER_SIZE_KEY] += size; 295 | } 296 | currentLevel = currentLevel[part]; 297 | }); 298 | }); 299 | 300 | let output: string[] = []; 301 | output.push(chalk.bold(rootFolder)); 302 | output.push(...createTreeOutput(folderTree, maxDepth, totalFileSizes)); 303 | 304 | for (const [size, filePath] of fileSizes) { 305 | if (size > FILE_SIZE_WARNING_THRESHOLD * totalFileSizes) { 306 | output.push(`\nThe file ${filePath} is ${chalk.red('large')} (${bytesToString(size)})`); 307 | break; 308 | } 309 | } 310 | 311 | return output; 312 | } 313 | 314 | function createTreeOutput(fileSystem: any, maxDepth: number, totalFileSizes: number): string[] { 315 | 316 | const getColorFromSize = (size: number) => { 317 | if (size > FILE_SIZE_WARNING_THRESHOLD * totalFileSizes) { 318 | return chalk.red; 319 | } else if (size > FILE_SIZE_LARGE_THRESHOLD * totalFileSizes) { 320 | return chalk.yellow; 321 | } else { 322 | return chalk.grey; 323 | } 324 | }; 325 | 326 | const createFileOutput = (prefix: string, fileName: string, fileSize: number) => { 327 | let fileSizeColored = ''; 328 | if (fileSize > 0) { 329 | const fileSizeString = `[${bytesToString(fileSize)}]`; 330 | fileSizeColored = getColorFromSize(fileSize)(fileSizeString); 331 | } 332 | return `${prefix}${fileName} ${fileSizeColored}`; 333 | } 334 | 335 | const createFolderOutput = (prefix: string, filesCount: number, folderSize: number, folderName: string, depth: number) => { 336 | if (depth < maxDepth) { 337 | // Max depth is not reached, print only the folder 338 | // as children will be printed 339 | return prefix + chalk.bold(`${folderName}/`); 340 | } 341 | 342 | // Max depth is reached, print the folder name and additional metadata 343 | // as children will not be printed 344 | const folderSizeString = bytesToString(folderSize); 345 | const folder = chalk.bold(`${folderName}/`); 346 | const numFilesString = chalk.green(`(${filesCount} ${filesCount === 1 ? 'file' : 'files'})`); 347 | const folderSizeColored = getColorFromSize(folderSize)(`[${folderSizeString}]`); 348 | return `${prefix}${folder} ${numFilesString} ${folderSizeColored}`; 349 | } 350 | 351 | const createTreeLayerOutput = (tree: any, depth: number, prefix: string, path: string) => { 352 | // Print all files before folders 353 | const sortedFolderKeys = Object.keys(tree).filter(key => typeof tree[key] !== 'number').sort(); 354 | const sortedFileKeys = Object.keys(tree).filter(key => typeof tree[key] === 'number').sort(); 355 | const sortedKeys = [...sortedFileKeys, ...sortedFolderKeys].filter(key => key !== FOLDER_SIZE_KEY && key !== FOLDER_FILES_TOTAL_KEY); 356 | 357 | const output: string[] = []; 358 | for (let i = 0; i < sortedKeys.length; i++) { 359 | const key = sortedKeys[i]; 360 | const isLast = i === sortedKeys.length - 1; 361 | const localPrefix = prefix + (isLast ? '└─ ' : '├─ '); 362 | const childPrefix = prefix + (isLast ? ' ' : '│ '); 363 | 364 | if (typeof tree[key] === 'number') { 365 | // It's a file 366 | output.push(createFileOutput(localPrefix, key, tree[key])); 367 | } else { 368 | // It's a folder 369 | output.push(createFolderOutput(localPrefix, tree[key][FOLDER_FILES_TOTAL_KEY], tree[key][FOLDER_SIZE_KEY], key, depth)); 370 | if (depth < maxDepth) { 371 | output.push(...createTreeLayerOutput(tree[key], depth + 1, childPrefix, path + key + '/')); 372 | } 373 | } 374 | } 375 | return output; 376 | }; 377 | 378 | return createTreeLayerOutput(fileSystem, 0, '', ''); 379 | } 380 | -------------------------------------------------------------------------------- /src/validation.ts: -------------------------------------------------------------------------------- 1 | import * as semver from 'semver'; 2 | import parseSemver from 'parse-semver'; 3 | 4 | const nameRegex = /^[a-z0-9][a-z0-9\-]*$/i; 5 | 6 | export function validatePublisher(publisher: string | undefined): string { 7 | if (!publisher) { 8 | throw new Error( 9 | `Missing extension "publisher": "" in package.json. Learn more: https://code.visualstudio.com/api/working-with-extensions/publishing-extension#create-a-publisher` 10 | ); 11 | } 12 | 13 | if (!nameRegex.test(publisher)) { 14 | throw new Error( 15 | `Invalid extension "publisher": "${publisher}" in package.json. Expected the identifier of a publisher, not its human-friendly name. Learn more: https://code.visualstudio.com/api/working-with-extensions/publishing-extension#create-a-publisher` 16 | ); 17 | } 18 | 19 | return publisher; 20 | } 21 | 22 | export function validateExtensionName(name: string | undefined): string { 23 | if (!name) { 24 | throw new Error(`Missing extension "name": "" in package.json. Learn more: https://code.visualstudio.com/api/references/extension-manifest`); 25 | } 26 | 27 | if (!nameRegex.test(name)) { 28 | throw new Error(`Invalid extension "name": "${name}" in package.json. Learn more: https://code.visualstudio.com/api/references/extension-manifest`); 29 | } 30 | 31 | return name; 32 | } 33 | 34 | export function validateVersion(version: string | undefined): string { 35 | if (!version) { 36 | throw new Error(`Missing extension "version": "" in package.json. Learn more: https://code.visualstudio.com/api/references/extension-manifest`); 37 | } 38 | 39 | if (!semver.valid(version)) { 40 | throw new Error(`Invalid extension "version": "${version}" in package.json. Learn more: https://code.visualstudio.com/api/references/extension-manifest`); 41 | } 42 | 43 | return version; 44 | } 45 | 46 | export function validateEngineCompatibility(version: string | undefined): string { 47 | if (!version) { 48 | throw new Error(`Missing vscode engine compatibility version. ("engines": { "vscode": "" } in package.json) Learn more: https://code.visualstudio.com/api/working-with-extensions/publishing-extension#visual-studio-code-compatibility`); 49 | } 50 | 51 | if (!/^\*$|^(\^|>=)?((\d+)|x)\.((\d+)|x)\.((\d+)|x)(\-.*)?$/.test(version)) { 52 | throw new Error(`Invalid vscode engine compatibility version '${version}'. ("engines": { "vscode": "${version}" } in package.json) Learn more: https://code.visualstudio.com/api/working-with-extensions/publishing-extension#visual-studio-code-compatibility`); 53 | } 54 | 55 | return version; 56 | } 57 | 58 | /** 59 | * User shouldn't use a newer version of @types/vscode than the one specified in engines.vscode 60 | * 61 | * NOTE: This is enforced at the major and minor level. Since we don't have control over the patch 62 | * version (it's auto-incremented by DefinitelyTyped), we don't look at the patch version at all. 63 | */ 64 | export function validateVSCodeTypesCompatibility(engineVersion: string, typeVersion: string): void { 65 | if (engineVersion === '*') { 66 | return; 67 | } 68 | 69 | if (!typeVersion) { 70 | throw new Error(`Missing @types/vscode version`); 71 | } 72 | 73 | let plainEngineVersion: string, plainTypeVersion: string; 74 | 75 | try { 76 | const engineSemver = parseSemver(`vscode@${engineVersion}`); 77 | plainEngineVersion = engineSemver.version; 78 | } catch (err) { 79 | throw new Error('Failed to parse semver of engines.vscode'); 80 | } 81 | 82 | try { 83 | const typeSemver = parseSemver(`@types/vscode@${typeVersion}`); 84 | plainTypeVersion = typeSemver.version; 85 | } catch (err) { 86 | throw new Error('Failed to parse semver of @types/vscode'); 87 | } 88 | 89 | // For all `x`, use smallest version for comparison 90 | plainEngineVersion = plainEngineVersion.replace(/x/g, '0'); 91 | 92 | const [typeMajor, typeMinor] = plainTypeVersion.split('.').map(x => { 93 | try { 94 | return parseInt(x); 95 | } catch (err) { 96 | return 0; 97 | } 98 | }); 99 | const [engineMajor, engineMinor] = plainEngineVersion.split('.').map(x => { 100 | try { 101 | return parseInt(x); 102 | } catch (err) { 103 | return 0; 104 | } 105 | }); 106 | 107 | const error = new Error( 108 | `@types/vscode ${typeVersion} greater than engines.vscode ${engineVersion}. Either upgrade engines.vscode or use an older @types/vscode version` 109 | ); 110 | 111 | if (typeMajor > engineMajor) { 112 | throw error; 113 | } 114 | if (typeMajor === engineMajor && typeMinor > engineMinor) { 115 | throw error; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/viewutils.ts: -------------------------------------------------------------------------------- 1 | export type ViewTableRow = string[]; 2 | export type ViewTable = ViewTableRow[]; 3 | 4 | const fixedLocale = 'en-us'; 5 | const format = { 6 | date: { month: 'long', day: 'numeric', year: 'numeric' } as Intl.DateTimeFormatOptions, 7 | time: { hour: 'numeric', minute: 'numeric', second: 'numeric' } as Intl.DateTimeFormatOptions, 8 | }; 9 | 10 | const columns = process.stdout.columns ? process.stdout.columns : 80; 11 | 12 | // xxx: Windows cmd + powershell standard fonts currently don't support the full 13 | // unicode charset. For now we use fallback icons when on windows. 14 | const useFallbackIcons = process.platform === 'win32'; 15 | 16 | export const icons = useFallbackIcons 17 | ? { download: '\u{2193}', star: '\u{2665}', emptyStar: '\u{2022}' } 18 | : { download: '\u{2913}', star: '\u{2605}', emptyStar: '\u{2606}' }; 19 | 20 | export function formatDate(date: Date) { 21 | return date.toLocaleString(fixedLocale, format.date); 22 | } 23 | export function formatTime(date: Date) { 24 | return date.toLocaleString(fixedLocale, format.time); 25 | } 26 | export function formatDateTime(date: Date) { 27 | return date.toLocaleString(fixedLocale, { ...format.date, ...format.time }); 28 | } 29 | 30 | export function repeatString(text: string, count: number): string { 31 | let result: string = ''; 32 | for (let i = 0; i < count; i++) { 33 | result += text; 34 | } 35 | return result; 36 | } 37 | 38 | export function ratingStars(rating: number, total = 5): string { 39 | const c = Math.min(Math.round(rating), total); 40 | return `${repeatString(icons.star + ' ', c)}${repeatString(icons.emptyStar + ' ', total - c)}`; 41 | } 42 | 43 | export function tableView(table: ViewTable, spacing: number = 2): string[] { 44 | const maxLen: Record = {}; 45 | table.forEach(row => row.forEach((cell, i) => (maxLen[i] = Math.max(maxLen[i] || 0, cell.length)))); 46 | return table.map(row => 47 | row.map((cell, i) => `${cell}${repeatString(' ', maxLen[i] - cell.length + spacing)}`).join('') 48 | ); 49 | } 50 | 51 | export function wordWrap(text: string, width: number = columns): string { 52 | const [indent = ''] = text.match(/^\s+/) || []; 53 | const maxWidth = width - indent.length; 54 | return text 55 | .replace(/^\s+/, '') 56 | .split('') 57 | .reduce( 58 | ([out, buffer, pos], ch) => { 59 | const nl = pos === maxWidth ? `\n${indent}` : ''; 60 | const newPos: number = nl ? 0 : +pos + 1; 61 | return / |-|,|\./.test(ch) ? [`${out}${buffer}${ch}${nl}`, '', newPos] : [`${out}${nl}`, buffer + ch, newPos]; 62 | }, 63 | [indent, '', 0] 64 | ) 65 | .slice(0, 2) 66 | .join(''); 67 | } 68 | 69 | export function indentRow(row: string) { 70 | return ` ${row}`; 71 | } 72 | 73 | export function wordTrim(text: string, width: number = columns, indicator = '...') { 74 | if (text.length > width) { 75 | return text.substr(0, width - indicator.length) + indicator; 76 | } 77 | return text; 78 | } 79 | -------------------------------------------------------------------------------- /src/xml.ts: -------------------------------------------------------------------------------- 1 | import { promisify } from 'util'; 2 | import { parseString } from 'xml2js'; 3 | 4 | function createXMLParser(): (raw: string) => Promise { 5 | return promisify(parseString); 6 | } 7 | 8 | export type XMLManifest = { 9 | PackageManifest: { 10 | $: { Version: string; xmlns: string; 'xmlns:d': string }; 11 | Metadata: { 12 | Description: { _: string }[]; 13 | DisplayName: string[]; 14 | Identity: { $: { Id: string; Version: string; Publisher: string; TargetPlatform?: string } }[]; 15 | Tags: string[]; 16 | GalleryFlags: string[]; 17 | License: string[]; 18 | Icon: string[]; 19 | Properties: { Property: { $: { Id: string; Value: string } }[] }[]; 20 | Categories: string[]; 21 | Badges: { Badge: { $: { Link: string; ImgUri: string; Description: string } }[] }[]; 22 | }[]; 23 | Installation: { InstallationTarget: { $: { Id: string } }[] }[]; 24 | Dependencies: string[]; 25 | Assets: { Asset: { $: { Type: string; Path: string } }[] }[]; 26 | }; 27 | }; 28 | 29 | export type ContentTypes = { 30 | Types: { 31 | Default: { $: { Extension: string; ContentType: string } }[]; 32 | }; 33 | }; 34 | 35 | export const parseXmlManifest = createXMLParser(); 36 | export const parseContentTypes = createXMLParser(); 37 | -------------------------------------------------------------------------------- /src/zip.ts: -------------------------------------------------------------------------------- 1 | import { Entry, open, ZipFile } from 'yauzl'; 2 | import { ManifestPackage, UnverifiedManifest } from './manifest'; 3 | import { parseXmlManifest, XMLManifest } from './xml'; 4 | import { Readable } from 'stream'; 5 | import { filePathToVsixPath } from './util'; 6 | import { validateManifestForPackaging } from './package'; 7 | 8 | async function bufferStream(stream: Readable): Promise { 9 | return await new Promise((c, e) => { 10 | const buffers: Buffer[] = []; 11 | stream.on('data', buffer => buffers.push(buffer)); 12 | stream.once('error', e); 13 | stream.once('end', () => c(Buffer.concat(buffers))); 14 | }); 15 | } 16 | 17 | export async function readZip(packagePath: string, filter: (name: string) => boolean): Promise> { 18 | const zipfile = await new Promise((c, e) => 19 | open(packagePath, { lazyEntries: true }, (err, zipfile) => (err ? e(err) : c(zipfile!))) 20 | ); 21 | 22 | return await new Promise((c, e) => { 23 | const result = new Map(); 24 | 25 | zipfile.once('close', () => c(result)); 26 | 27 | zipfile.readEntry(); 28 | zipfile.on('entry', (entry: Entry) => { 29 | const name = entry.fileName.toLowerCase(); 30 | 31 | if (filter(name)) { 32 | zipfile.openReadStream(entry, (err, stream) => { 33 | if (err) { 34 | zipfile.close(); 35 | return e(err); 36 | } 37 | 38 | bufferStream(stream!).then(buffer => { 39 | result.set(name, buffer); 40 | zipfile.readEntry(); 41 | }); 42 | }); 43 | } else { 44 | zipfile.readEntry(); 45 | } 46 | }); 47 | }); 48 | } 49 | 50 | export async function readVSIXPackage(packagePath: string): Promise<{ manifest: ManifestPackage; xmlManifest: XMLManifest }> { 51 | const map = await readZip(packagePath, name => /^extension\/package\.json$|^extension\.vsixmanifest$/i.test(name)); 52 | const rawManifest = map.get(filePathToVsixPath('package.json')); 53 | 54 | if (!rawManifest) { 55 | throw new Error('Manifest not found'); 56 | } 57 | 58 | const rawXmlManifest = map.get('extension.vsixmanifest'); 59 | 60 | if (!rawXmlManifest) { 61 | throw new Error('VSIX manifest not found'); 62 | } 63 | 64 | const manifest = JSON.parse(rawManifest.toString('utf8')) as UnverifiedManifest; 65 | let manifestValidated; 66 | try { 67 | manifestValidated = validateManifestForPackaging(manifest); 68 | } catch (error) { 69 | throw new Error(`Invalid extension VSIX manifest: ${error}`); 70 | } 71 | 72 | return { 73 | manifest: manifestValidated, 74 | xmlManifest: await parseXmlManifest(rawXmlManifest.toString('utf8')), 75 | }; 76 | } 77 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "lib": ["es2020"], 6 | "esModuleInterop": true, 7 | "outDir": "out", 8 | "allowJs": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "noImplicitReturns": true, 14 | "declaration": true, 15 | "sourceMap": true 16 | }, 17 | "include": ["src"], 18 | "ts-node": { 19 | "files": true 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /vsce: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('./out/main')(process.argv); 3 | --------------------------------------------------------------------------------