rebase
@@ -1,6 +1,6 @@
|
||||
# Code - OSS Development Container
|
||||
|
||||
[](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/microsoft/vscode)
|
||||
[](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/microsoft/vscode)
|
||||
|
||||
This repository includes configuration for a development container for working with Code - OSS in a local container or using [GitHub Codespaces](https://github.com/features/codespaces).
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
**/extensions/markdown-language-features/notebook-out/**
|
||||
**/extensions/markdown-math/notebook-out/**
|
||||
**/extensions/notebook-renderers/renderer-out/index.js
|
||||
**/extensions/open-remote-ssh/out/extension.js
|
||||
**/extensions/simple-browser/media/index.js
|
||||
**/extensions/terminal-suggest/src/completions/upstream/**
|
||||
**/extensions/terminal-suggest/src/shell/zshBuiltinsCache.ts
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# ensure the API police is aware of changes to the vscode-dts file
|
||||
# this is only about the final API, not about proposed API changes
|
||||
src/vscode-dts/vscode.d.ts @jrieken @mjbvz
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
---
|
||||
<!-- ⚠️⚠️ Do Not Delete This! bug_report_template ⚠️⚠️ -->
|
||||
<!-- Please read our Rules of Conduct: https://opensource.microsoft.com/codeofconduct/ -->
|
||||
<!-- 🕮 Read our guide about submitting issues: https://github.com/microsoft/vscode/wiki/Submitting-Bugs-and-Suggestions -->
|
||||
<!-- 🔎 Search existing issues to avoid creating duplicates. -->
|
||||
<!-- 🧪 Test using the latest Insiders build to see if your issue has already been fixed: https://code.visualstudio.com/insiders/ -->
|
||||
<!-- 💡 Instead of creating your report here, use 'Report Issue' from the 'Help' menu in VS Code to pre-fill useful information. -->
|
||||
<!-- 🔧 Launch with `code --disable-extensions` to check. -->
|
||||
Does this issue occur when all extensions are disabled?: Yes/No
|
||||
|
||||
<!-- 🪓 If you answered No above, use 'Help: Start Extension Bisect' from Command Palette to try to identify the cause. -->
|
||||
<!-- 📣 Issues caused by an extension need to be reported directly to the extension publisher. The 'Help > Report Issue' dialog can assist with this. -->
|
||||
- VS Code Version:
|
||||
- OS Version:
|
||||
|
||||
Steps to Reproduce:
|
||||
|
||||
1.
|
||||
2.
|
||||
@@ -1,8 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Question
|
||||
url: https://stackoverflow.com/questions/tagged/visual-studio-code
|
||||
about: Please ask and answer questions here.
|
||||
- name: Extension Development
|
||||
url: https://github.com/microsoft/vscode-discussions/discussions
|
||||
about: Please use this for extension development questions and ideas.
|
||||
@@ -1,11 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
<!-- ⚠️⚠️ Do Not Delete This! feature_request_template ⚠️⚠️ -->
|
||||
<!-- Please read our Rules of Conduct: https://opensource.microsoft.com/codeofconduct/ -->
|
||||
<!-- Please search existing issues to avoid creating duplicates. -->
|
||||
|
||||
<!-- Describe the feature you'd like. -->
|
||||
@@ -1,311 +0,0 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/microsoft/vscode-github-triage-actions/stable/classifier-deep/apply/apply-labels/deep-classifier-config.schema.json",
|
||||
"vacation": [],
|
||||
"assignees": {
|
||||
"nameToOverrideAccuracyOf": {"accuracy": 0.8}
|
||||
},
|
||||
"labels": {
|
||||
"accessibility": { "assign": ["meganrogge"]},
|
||||
"api": {"assign": ["jrieken"]},
|
||||
"api-finalization": {"assign": []},
|
||||
"api-proposal": {"assign": ["jrieken"]},
|
||||
"authentication": {"assign": ["TylerLeonhardt"]},
|
||||
"bisect-ext": {"assign": ["jrieken"]},
|
||||
"bot-proposal": {"assign": ["lramos15"]},
|
||||
"bracket-pair-colorization": {"assign": ["hediet"]},
|
||||
"bracket-pair-guides": {"assign": ["hediet"]},
|
||||
"breadcrumbs": {"assign": ["jrieken"]},
|
||||
"callhierarchy": {"assign": ["jrieken"]},
|
||||
"chrome-devtools": {"assign": ["deepak1556"]},
|
||||
"cloud-changes": {"assign": ["joyceerhl"]},
|
||||
"code-cli": {"assign": ["connor4312"]},
|
||||
"code-lens": {"assign": ["jrieken"]},
|
||||
"code-server-web": {"assign": ["aeschli"]},
|
||||
"command-center": {"assign": ["jrieken"]},
|
||||
"comments": {"assign": ["alexr00"]},
|
||||
"config": {"assign": ["sandy081"]},
|
||||
"containers": {"assign": ["chrmarti"]},
|
||||
"context-keys": {"assign": ["ulugbekna"]},
|
||||
"continue-working-on": {"assign": ["joyceerhl"]},
|
||||
"css-less-scss": {"assign": ["aeschli"]},
|
||||
"custom-editors": {"assign": ["mjbvz"]},
|
||||
"debug": {"assign": ["roblourens"]},
|
||||
"debug-disassembly": {"assign": []},
|
||||
"dialogs": {"assign": ["sbatten"]},
|
||||
"diff-editor": {"assign": ["hediet"]},
|
||||
"dropdown": {"assign": ["lramos15"]},
|
||||
"editor-api": {"assign": ["alexdima"]},
|
||||
"editor-autoclosing": {"assign": ["alexdima"]},
|
||||
"editor-autoindent": {"assign": ["rebornix"]},
|
||||
"editor-bracket-matching": {"assign": ["hediet"]},
|
||||
"editor-clipboard": {"assign": ["alexdima", "rebornix"]},
|
||||
"editor-code-actions": {"assign": ["mjbvz", "justschen"]},
|
||||
"editor-color-picker": {"assign": ["aiday-mar"]},
|
||||
"editor-columnselect": {"assign": ["alexdima"]},
|
||||
"editor-commands": {"assign": ["alexdima"]},
|
||||
"editor-comments": {"assign": ["alexdima"]},
|
||||
"editor-contrib": {"assign": ["alexdima"]},
|
||||
"editor-core": {"assign": ["alexdima"]},
|
||||
"editor-drag-and-drop": {"assign": ["rebornix"]},
|
||||
"editor-error-widget": {"assign": ["sandy081"]},
|
||||
"editor-find": {"assign": ["rebornix"]},
|
||||
"editor-folding": {"assign": ["aeschli"]},
|
||||
"editor-highlight": {"assign": ["alexdima"]},
|
||||
"editor-hover": {"assign": ["aiday-mar"]},
|
||||
"editor-indent-detection": {"assign": ["alexdima"]},
|
||||
"editor-indent-guides": {"assign": ["hediet"]},
|
||||
"editor-input": {"assign": ["alexdima"]},
|
||||
"editor-input-IME": {"assign": ["alexdima"]},
|
||||
"editor-insets": {"assign": ["jrieken"]},
|
||||
"editor-minimap": {"assign": ["alexdima"]},
|
||||
"editor-multicursor": {"assign": ["alexdima"]},
|
||||
"editor-parameter-hints": {"assign": ["mjbvz"]},
|
||||
"editor-render-whitespace": {"assign": ["alexdima"]},
|
||||
"editor-rendering": {"assign": ["alexdima"]},
|
||||
"editor-RTL": {"assign": ["alexdima"]},
|
||||
"editor-scrollbar": {"assign": ["alexdima"]},
|
||||
"editor-sorting": {"assign": ["alexdima"]},
|
||||
"editor-sticky-scroll": {"assign": ["aiday-mar"]},
|
||||
"editor-symbols": {"assign": ["jrieken"]},
|
||||
"editor-synced-region": {"assign": ["aeschli"]},
|
||||
"editor-textbuffer": {"assign": ["alexdima", "rebornix"]},
|
||||
"editor-theming": {"assign": ["alexdima"]},
|
||||
"editor-wordnav": {"assign": ["alexdima"]},
|
||||
"editor-wrapping": {"assign": ["alexdima"]},
|
||||
"emmet": {"assign": ["rzhao271"]},
|
||||
"emmet-parse": {"assign": ["rzhao271"]},
|
||||
"error-list": {"assign": ["sandy081"]},
|
||||
"extension-activation": {"assign": ["joyceerhl", "alexdima"]},
|
||||
"extension-host": {"assign": ["alexdima"]},
|
||||
"extension-prerelease": {"assign": ["sandy081"]},
|
||||
"extension-recommendations": {"assign": ["sandy081"]},
|
||||
"extensions": {"assign": ["sandy081"]},
|
||||
"extensions-development": {"assign": []},
|
||||
"file-decorations": {"assign": ["jrieken"]},
|
||||
"file-encoding": {"assign": ["bpasero"]},
|
||||
"file-explorer": {"assign": ["lramos15"]},
|
||||
"file-glob": {"assign": ["bpasero"]},
|
||||
"file-io": {"assign": ["bpasero"]},
|
||||
"file-nesting": {"assign": ["lramos15"]},
|
||||
"file-watcher": {"assign": ["bpasero"]},
|
||||
"font-rendering": {"assign": ["rzhao271"]},
|
||||
"formatting": {"assign": ["jrieken"]},
|
||||
"getting-started": {"assign": ["bhavyaus"]},
|
||||
"ghost-text": {"assign": ["hediet"]},
|
||||
"git": {"assign": ["lszomoru"]},
|
||||
"github": {"assign": ["lszomoru"]},
|
||||
"github-authentication": {"assign": ["TylerLeonhardt"]},
|
||||
"github-repositories": {"assign": ["lszomoru"]},
|
||||
"gpu": {"assign": ["deepak1556"]},
|
||||
"grammar": {"assign": ["mjbvz"]},
|
||||
"grid-widget": {"assign": ["joaomoreno"]},
|
||||
"html": {"assign": ["aeschli"]},
|
||||
"icon-brand": {"assign": ["daviddossett"]},
|
||||
"icons-product": {"assign": ["daviddossett"]},
|
||||
"image-preview": {"assign": ["mjbvz"]},
|
||||
"inlay-hints": {"assign": ["jrieken", "hediet"]},
|
||||
"inline-completions": {"assign": ["hediet"]},
|
||||
"install-update": {"assign": ["joaomoreno"], "accuracy": 0.85},
|
||||
"intellisense-config": {"assign": ["rzhao271"]},
|
||||
"interactive-playground": {"assign": ["chrmarti"]},
|
||||
"interactive-window": {"assign": ["amunger", "rebornix"]},
|
||||
"ipc": {"assign": ["joaomoreno"]},
|
||||
"issue-bot": {"assign": ["chrmarti"]},
|
||||
"issue-reporter": {"assign": ["justschen"]},
|
||||
"javascript": {"assign": ["mjbvz"]},
|
||||
"json": {"assign": ["aeschli"]},
|
||||
"json-sorting": {"assign": ["aiday-mar"]},
|
||||
"keybindings": {"assign": ["ulugbekna"]},
|
||||
"keybindings-editor": {"assign": ["ulugbekna"]},
|
||||
"keyboard-layout": {"assign": ["ulugbekna"]},
|
||||
"L10N": {"assign": ["TylerLeonhardt", "csigs"]},
|
||||
"l10n-platform": {"assign": ["TylerLeonhardt"]},
|
||||
"label-provider": {"assign": ["lramos15"]},
|
||||
"languages-basic": {"assign": ["aeschli"]},
|
||||
"languages-diagnostics": {"assign": ["jrieken"]},
|
||||
"languages-guessing": {"assign": ["TylerLeonhardt"]},
|
||||
"layout": {"assign": ["benibenj"]},
|
||||
"lcd-text-rendering": {"assign": []},
|
||||
"list-widget": {"assign": ["joaomoreno"]},
|
||||
"live-preview": {"assign": []},
|
||||
"log": {"assign": ["sandy081"]},
|
||||
"markdown": {"assign": ["mjbvz"]},
|
||||
"marketplace": {"assign": ["isidorn"]},
|
||||
"menus": {"assign": ["sbatten"]},
|
||||
"merge-conflict": {"assign": ["chrmarti"]},
|
||||
"merge-editor": {"assign": ["hediet"]},
|
||||
"merge-editor-workbench": {"assign": ["jrieken"]},
|
||||
"monaco-editor": {"assign": []},
|
||||
"native-file-dialog": {"assign": ["deepak1556"]},
|
||||
"network": {"assign": ["deepak1556"]},
|
||||
"notebook": {"assign": ["rebornix"]},
|
||||
"notebook-api": {"assign": []},
|
||||
"notebook-builtin-renderers": {"assign": []},
|
||||
"notebook-cell-editor": {"assign": []},
|
||||
"notebook-celltoolbar": {"assign": []},
|
||||
"notebook-clipboard": {"assign": []},
|
||||
"notebook-commenting": {"assign": []},
|
||||
"notebook-debugging": {"assign": []},
|
||||
"notebook-diff": {"assign": []},
|
||||
"notebook-dnd": {"assign": []},
|
||||
"notebook-execution": {"assign": []},
|
||||
"notebook-find": {"assign": []},
|
||||
"notebook-folding": {"assign": []},
|
||||
"notebook-getting-started": {"assign": []},
|
||||
"notebook-getting-globaltoolbar": {"assign": []},
|
||||
"notebook-ipynb": {"assign": []},
|
||||
"notebook-kernel": {"assign": []},
|
||||
"notebook-kernel-picker": {"assign": []},
|
||||
"notebook-keybinding": {"assign": []},
|
||||
"notebook-language": {"assign": []},
|
||||
"notebook-layout": {"assign": []},
|
||||
"notebook-markdown": {"assign": []},
|
||||
"notebook-math": {"assign": []},
|
||||
"notebook-minimap": {"assign": []},
|
||||
"notebook-multiselect": {"assign": []},
|
||||
"notebook-output": {"assign": []},
|
||||
"notebook-perf": {"assign": []},
|
||||
"notebook-remote": {"assign": []},
|
||||
"notebook-rendering": {"assign": []},
|
||||
"notebook-serialization": {"assign": []},
|
||||
"notebook-serverless-web": {"assign": []},
|
||||
"notebook-statusbar": {"assign": []},
|
||||
"notebook-toc-outline": {"assign": []},
|
||||
"notebook-undo-redo": {"assign": []},
|
||||
"notebook-variables": {"assign": []},
|
||||
"notebook-workbench-integration": {"assign": []},
|
||||
"notebook-workflow": {"assign": []},
|
||||
"open-editors": {"assign": ["lramos15"]},
|
||||
"opener": {"assign": ["mjbvz"]},
|
||||
"outline": {"assign": ["jrieken"]},
|
||||
"output": {"assign": ["sandy081"]},
|
||||
"perf": {"assign": []},
|
||||
"perf-bloat": {"assign": []},
|
||||
"perf-startup": {"assign": []},
|
||||
"php": {"assign": ["roblourens"]},
|
||||
"portable-mode": {"assign": ["joaomoreno"]},
|
||||
"proxy": {"assign": ["chrmarti"]},
|
||||
"quick-open": {"assign": ["TylerLeonhardt"]},
|
||||
"quick-pick": {"assign": ["TylerLeonhardt"]},
|
||||
"references-viewlet": {"assign": ["jrieken"]},
|
||||
"release-notes": {"assign": []},
|
||||
"remote": {"assign": []},
|
||||
"remote-connection": {"assign": ["alexdima"]},
|
||||
"remote-explorer": {"assign": ["alexr00"]},
|
||||
"remote-tunnel": {"assign": ["aeschli", "connor4312"]},
|
||||
"rename": {"assign": ["jrieken"]},
|
||||
"runCommands": {"assign": ["ulugbekna"]},
|
||||
"sandbox": {"assign": ["deepak1556"]},
|
||||
"sash-widget": {"assign": ["joaomoreno"]},
|
||||
"scm": {"assign": ["lszomoru"]},
|
||||
"screencast-mode": {"assign": ["joaomoreno"]},
|
||||
"search": {"assign": ["osortega"]},
|
||||
"search-api": {"assign": ["osortega"]},
|
||||
"search-editor": {"assign": ["osortega"]},
|
||||
"search-replace": {"assign": ["sandy081"]},
|
||||
"semantic-tokens": {"assign": ["alexdima", "aeschli"]},
|
||||
"server": {"assign": ["alexdima"]},
|
||||
"settings-editor": {"assign": ["rzhao271"]},
|
||||
"settings-search": {"assign": ["rzhao271"]},
|
||||
"settings-sync": {"assign": ["sandy081"]},
|
||||
"settings-sync-server": {"assign": ["Tyriar", "lszomoru"]},
|
||||
"shared-process": {"assign": []},
|
||||
"simple-file-dialog": {"assign": ["alexr00"]},
|
||||
"smart-select": {"assign": ["jrieken"]},
|
||||
"snap": {"assign": ["deepak1556"]},
|
||||
"snippets": {"assign": ["jrieken"]},
|
||||
"splitview-widget": {"assign": ["joaomoreno"]},
|
||||
"suggest": {"assign": ["jrieken"]},
|
||||
"table-widget": {"assign": ["joaomoreno"]},
|
||||
"tasks": {"assign": ["meganrogge"], "accuracy": 0.85},
|
||||
"telemetry": {"assign": ["lramos15"]},
|
||||
"terminal": {"assign": ["meganrogge"]},
|
||||
"terminal-conpty": {"assign": ["meganrogge"]},
|
||||
"terminal-editors": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-external": {"assign": ["meganrogge"]},
|
||||
"terminal-find": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-input": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-layout": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-links": {"assign": ["Tyriar"]},
|
||||
"terminal-local-echo": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-persistence": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-process": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-profiles": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-quick-fix": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-rendering": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-search": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-shell-bash": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-shell-cmd": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-shell-fish": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-shell-git-bash": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-shell-integration": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-shell-pwsh": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-shell-zsh": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-tabs": {"assign": ["meganrogge"]},
|
||||
"terminal-winpty": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"testing": {"assign": ["connor4312"]},
|
||||
"themes": {"assign": ["aeschli"]},
|
||||
"timeline": {"assign": ["lramos15"]},
|
||||
"timeline-git": {"assign": ["lszomoru"]},
|
||||
"timeline-local-history": {"assign": ["bpasero"]},
|
||||
"titlebar": {"assign": ["benibenj"]},
|
||||
"tokenization": {"assign": ["alexdima"]},
|
||||
"touch/pointer": {"assign": []},
|
||||
"trackpad/scroll": {"assign": []},
|
||||
"tree-sticky-scroll": {"assign": ["benibenj"]},
|
||||
"tree-views": {"assign": ["alexr00"]},
|
||||
"tree-widget": {"assign": ["joaomoreno"]},
|
||||
"typehierarchy": {"assign": ["jrieken"]},
|
||||
"typescript": {"assign": ["mjbvz"]},
|
||||
"undo-redo": {"assign": ["alexdima"]},
|
||||
"unicode-highlight": {"assign": ["hediet"]},
|
||||
"uri": {"assign": ["jrieken"]},
|
||||
"user-profiles": {"assign": ["sandy081"]},
|
||||
"ux": {"assign": ["daviddossett"]},
|
||||
"variable-resolving": {"assign": ["alexr00"]},
|
||||
"VIM": {"assign": ["alexdima", "rebornix"]},
|
||||
"virtual-workspaces": {"assign": []},
|
||||
"vscode.dev": {"assign": []},
|
||||
"vscode-build": {"assign": []},
|
||||
"vscode-website": {"assign": ["rzhao271"]},
|
||||
"web": {"assign": []},
|
||||
"webview": {"assign": ["mjbvz"]},
|
||||
"webview-views": {"assign": ["mjbvz"]},
|
||||
"workbench-actions": {"assign": ["bpasero"]},
|
||||
"workbench-auxwindow": {"assign": ["bpasero"]},
|
||||
"workbench-banner": {"assign": ["lszomoru", "sbatten"]},
|
||||
"workbench-cli": {"assign": ["bpasero"]},
|
||||
"workbench-diagnostics": {"assign": ["Tyriar"]},
|
||||
"workbench-dnd": {"assign": ["bpasero"]},
|
||||
"workbench-editor-grid": {"assign": ["benibenj"]},
|
||||
"workbench-editor-groups": {"assign": ["bpasero"]},
|
||||
"workbench-editor-resolver": {"assign": ["lramos15"]},
|
||||
"workbench-editors": {"assign": ["bpasero"]},
|
||||
"workbench-electron": {"assign": ["deepak1556"]},
|
||||
"workbench-fonts": {"assign": []},
|
||||
"workbench-history": {"assign": ["bpasero"]},
|
||||
"workbench-hot-exit": {"assign": ["bpasero"]},
|
||||
"workbench-hover": {"assign": ["Tyriar"]},
|
||||
"workbench-launch": {"assign": []},
|
||||
"workbench-link": {"assign": []},
|
||||
"workbench-multiroot": {"assign": ["bpasero"]},
|
||||
"workbench-notifications": {"assign": ["bpasero"]},
|
||||
"workbench-os-integration": {"assign": ["bpasero"]},
|
||||
"workbench-rapid-render": {"assign": ["jrieken"]},
|
||||
"workbench-run-as-admin": {"assign": ["bpasero"]},
|
||||
"workbench-state": {"assign": ["bpasero"]},
|
||||
"workbench-status": {"assign": ["bpasero"]},
|
||||
"workbench-tabs": {"assign": ["benibenj"]},
|
||||
"workbench-touchbar": {"assign": ["bpasero"]},
|
||||
"workbench-untitled-editors": {"assign": ["bpasero"]},
|
||||
"workbench-views": {"assign": ["benibenj"]},
|
||||
"workbench-welcome": {"assign": ["lramos15"]},
|
||||
"workbench-window": {"assign": ["bpasero"]},
|
||||
"workbench-workspace": {"assign": []},
|
||||
"workbench-zen": {"assign": ["benibenj"]},
|
||||
"workspace-edit": {"assign": ["jrieken"]},
|
||||
"workspace-symbols": {"assign": []},
|
||||
"workspace-trust": {"assign": ["lszomoru", "sbatten"]},
|
||||
"zoom": {"assign": ["alexdima"] }
|
||||
}
|
||||
}
|
||||
@@ -1,542 +0,0 @@
|
||||
[
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "question",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "updateLabels",
|
||||
"addLabel": "*question"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "dev-question",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "updateLabels",
|
||||
"addLabel": "*dev-question"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "*question",
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"comment": "We closed this issue because it is a question about using VS Code rather than an issue or feature request. Please search for help on [StackOverflow](https://aka.ms/vscodestackoverflow), where the community has already answered thousands of similar questions. You may find their [guide on asking a new question](https://aka.ms/vscodestackoverflowquestion) helpful if your question has not already been asked. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "*dev-question",
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"comment": "We have a great extension developer community over on [GitHub discussions](https://github.com/microsoft/vscode-discussions/discussions) and [Slack](https://vscode-dev-community.slack.com/) where extension authors help each other. This is a great place for you to ask questions and find support.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "*extension-candidate",
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"comment": "We try to keep VS Code lean and we think the functionality you're asking for is great for a VS Code extension. Maybe you can already find one that suits you in the [VS Code Marketplace](https://aka.ms/vscodemarketplace). Just in case, in a few simple steps you can get started [writing your own extension](https://aka.ms/vscodewritingextensions). See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "*not-reproducible",
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"comment": "We closed this issue because we are unable to reproduce the problem with the steps you describe. Chances are we've already fixed your problem in a recent version of VS Code. If not, please ask us to reopen the issue and provide us with more detail. Our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) might help you with that.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "*out-of-scope",
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"comment": "We closed this issue because we [don't plan to address it](https://aka.ms/vscode-out-of-scope) in the foreseeable future. If you disagree and feel that this issue is crucial: we are happy to listen and to reconsider.\n\nIf you wonder what we are up to, please see our [roadmap](https://aka.ms/vscoderoadmap) and [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nThanks for your understanding, and happy coding!"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "wont-fix",
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"comment": "We closed this issue because we [don't plan to address it](https://github.com/microsoft/vscode/wiki/Issue-Grooming#wont-fix-bugs).\n\nThanks for your understanding, and happy coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "causedByExtension",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "updateLabels",
|
||||
"addLabel": "*caused-by-extension"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "*caused-by-extension",
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"comment": "This issue is caused by an extension, please file it with the repository (or contact) the extension has linked in its overview in VS Code or the [marketplace](https://aka.ms/vscodemarketplace) for VS Code. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting). If you don't know which extension is causing the problem, you can run `Help: Start extension bisect` from the command palette (F1) to help identify the problem extension.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "*as-designed",
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"comment": "The described behavior is how it is expected to work. If you disagree, please explain what is expected and what is not in more detail. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "L10N",
|
||||
"assign": [
|
||||
"csigs",
|
||||
"TylerLeonhardt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "duplicate",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "updateLabels",
|
||||
"addLabel": "*duplicate"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "*duplicate",
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"comment": "Thanks for creating this issue! We figured it's covering the same as another one we already have. Thus, we closed this one as a duplicate. You can search for [similar existing issues](${duplicateQuery}). See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "verified",
|
||||
"allowUsers": [
|
||||
"@author"
|
||||
],
|
||||
"action": "updateLabels",
|
||||
"addLabel": "verified",
|
||||
"removeLabel": "author-verification-requested",
|
||||
"requireLabel": "author-verification-requested",
|
||||
"disallowLabel": "unreleased"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "confirm",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "updateLabels",
|
||||
"addLabel": "confirmed",
|
||||
"removeLabel": "confirmation-pending"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "confirmationPending",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "updateLabels",
|
||||
"addLabel": "confirmation-pending",
|
||||
"removeLabel": "confirmed"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "needsMoreInfo",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "updateLabels",
|
||||
"addLabel": "~info-needed"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "needsPerfInfo",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"addLabel": "info-needed",
|
||||
"comment": "Thanks for creating this issue regarding performance! Please follow this guide to help us diagnose performance issues: https://github.com/microsoft/vscode/wiki/Performance-Issues \n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "jsDebugLogs",
|
||||
"action": "updateLabels",
|
||||
"addLabel": "info-needed",
|
||||
"comment": "Please collect trace logs using the following instructions:\n\n> If you're able to, add `\"trace\": true` to your `launch.json` and reproduce the issue. The location of the log file on your disk will be written to the Debug Console. Share that with us.\n>\n> ⚠️ This log file will not contain source code, but will contain file paths. You can drop it into https://microsoft.github.io/vscode-pwa-analyzer/index.html to see what it contains. If you'd rather not share the log publicly, you can email it to connor@xbox.com"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "closedWith",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "completed",
|
||||
"addLabel": "unreleased"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "spam",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "invalid"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "a11ymas",
|
||||
"allowUsers": [
|
||||
"AccessibilityTestingTeam-TCS",
|
||||
"dixitsonali95",
|
||||
"Mohini78",
|
||||
"ChitrarupaSharma",
|
||||
"mspatil110",
|
||||
"umasarath52",
|
||||
"v-umnaik"
|
||||
],
|
||||
"action": "updateLabels",
|
||||
"addLabel": "a11ymas"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "*off-topic",
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"comment": "Thanks for creating this issue. We think this issue is unactionable or unrelated to the goals of this project. Please follow our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extPython",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the Python extension. Please file the issue to the [Python extension repository](https://github.com/microsoft/vscode-python). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extJupyter",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the Jupyter extension. Please file the issue to the [Jupyter extension repository](https://github.com/microsoft/vscode-jupyter). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extC",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the C extension. Please file the issue to the [C extension repository](https://github.com/microsoft/vscode-cpptools). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extC++",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the C++ extension. Please file the issue to the [C++ extension repository](https://github.com/microsoft/vscode-cpptools). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extCpp",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the C++ extension. Please file the issue to the [C++ extension repository](https://github.com/microsoft/vscode-cpptools). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extTS",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the TypeScript language service. Please file the issue to the [TypeScript repository](https://github.com/microsoft/TypeScript/). Make sure to check their [contributing guidelines](https://github.com/microsoft/TypeScript/blob/master/CONTRIBUTING.md) and provide relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extJS",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the TypeScript/JavaScript language service. Please file the issue to the [TypeScript repository](https://github.com/microsoft/TypeScript/). Make sure to check their [contributing guidelines](https://github.com/microsoft/TypeScript/blob/master/CONTRIBUTING.md) and provide relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extC#",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the C# extension. Please file the issue to the [C# extension repository](https://github.com/OmniSharp/omnisharp-vscode.git). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extGo",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the Go extension. Please file the issue to the [Go extension repository](https://github.com/golang/vscode-go). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extPowershell",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the PowerShell extension. Please file the issue to the [PowerShell extension repository](https://github.com/PowerShell/vscode-powershell). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extLiveShare",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the LiveShare extension. Please file the issue to the [LiveShare repository](https://github.com/MicrosoftDocs/live-share). Make sure to check their [contributing guidelines](https://github.com/MicrosoftDocs/live-share/blob/master/CONTRIBUTING.md) and provide relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extDocker",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the Docker extension. Please file the issue to the [Docker extension repository](https://github.com/microsoft/vscode-docker). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extJava",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the Java extension. Please file the issue to the [Java extension repository](https://github.com/redhat-developer/vscode-java). Make sure to check their [troubleshooting instructions](https://github.com/redhat-developer/vscode-java/wiki/Troubleshooting) and provide relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extJavaDebug",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the Java Debugger extension. Please file the issue to the [Java Debugger repository](https://github.com/microsoft/vscode-java-debug). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extCodespaces",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the Codespaces extension. Please file the issue in the [Codespaces Discussion Forum](http://aka.ms/ghcs-feedback). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extCopilot",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the Copilot extension. Please file the issue in the [Copilot Discussion Forum](https://github.com/community/community/discussions/categories/copilot). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "gifPlease",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "comment",
|
||||
"addLabel": "info-needed",
|
||||
"comment": "Thanks for reporting this issue! Unfortunately, it's hard for us to understand what issue you're seeing. Please help us out by providing a screen recording showing exactly what isn't working as expected. While we can work with most standard formats, `.gif` files are preferred as they are displayed inline on GitHub. You may find https://gifcap.dev helpful as a browser-based gif recording tool.\n\nIf the issue depends on keyboard input, you can help us by enabling screencast mode for the recording (`Developer: Toggle Screencast Mode` in the command palette). Lastly, please attach this file via the GitHub web interface as emailed responses will strip files out from the issue.\n\nHappy coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "confirmPlease",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "comment",
|
||||
"addLabel": "info-needed",
|
||||
"comment": "Please diagnose the root cause of the issue by running the command `F1 > Help: Troubleshoot Issue` and following the instructions. Once you have done that, please update the issue with the results.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"__comment__": "Allows folks on the team to label issues by commenting: `\\label My-Label` ",
|
||||
"type": "comment",
|
||||
"name": "label",
|
||||
"allowUsers": []
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "assign",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "*workspace-trust-docs",
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"comment": "This issue appears to be the result of the new workspace trust feature shipped in June 2021. This security-focused feature has major impact on the functionality of VS Code. Due to the volume of issues, we ask that you take some time to review our [comprehensive documentation](https://aka.ms/vscode-workspace-trust) on the feature. If your issue is still not resolved, please let us know."
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "~verification-steps-needed",
|
||||
"action": "updateLabels",
|
||||
"addLabel": "verification-steps-needed",
|
||||
"removeLabel": "~verification-steps-needed",
|
||||
"comment": "Friendly ping! Looks like this issue requires some further steps to be verified. Please provide us with the steps necessary to verify this issue."
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "~info-needed",
|
||||
"action": "updateLabels",
|
||||
"addLabel": "info-needed",
|
||||
"removeLabel": "~info-needed",
|
||||
"comment": "Thanks for creating this issue! We figured it's missing some basic information or in some other way doesn't follow our [issue reporting guidelines](https://aka.ms/vscodeissuereporting). Please take the time to review these and update the issue.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "~version-info-needed",
|
||||
"action": "updateLabels",
|
||||
"addLabel": "info-needed",
|
||||
"removeLabel": "~version-info-needed",
|
||||
"comment": "Thanks for creating this issue! We figured it's missing some basic information, such as a version number, or in some other way doesn't follow our [issue reporting guidelines](https://aka.ms/vscodeissuereporting). Please take the time to review these and update the issue.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "~confirmation-needed",
|
||||
"action": "updateLabels",
|
||||
"addLabel": "info-needed",
|
||||
"removeLabel": "~confirmation-needed",
|
||||
"comment": "Please diagnose the root cause of the issue by running the command `F1 > Help: Troubleshoot Issue` and following the instructions. Once you have done that, please update the issue with the results.\n\nHappy Coding!"
|
||||
}
|
||||
]
|
||||
@@ -1,11 +0,0 @@
|
||||
# Learn more about the syntax here:
|
||||
# https://docs.github.com/en/early-access/github/save-time-with-slash-commands/syntax-for-user-defined-slash-commands
|
||||
---
|
||||
trigger: codespaces_issue
|
||||
title: Codespaces Issue
|
||||
description: Report downstream
|
||||
|
||||
steps:
|
||||
- type: fill
|
||||
template: |-
|
||||
This looks like an issue with the Codespaces service which we don't track in this repository. You can report this to the Codespaces team at https://github.com/orgs/community/discussions/categories/codespaces
|
||||
@@ -1,57 +0,0 @@
|
||||
# Coding Guidelines
|
||||
|
||||
## Introduction
|
||||
|
||||
These are VS Code coding guidelines. Please also review our [Source Code Organisation](https://github.com/microsoft/vscode/wiki/Source-Code-Organization) page.
|
||||
|
||||
## Indentation
|
||||
|
||||
We use tabs, not spaces.
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
* Use PascalCase for `type` names
|
||||
* Use PascalCase for `enum` values
|
||||
* Use camelCase for `function` and `method` names
|
||||
* Use camelCase for `property` names and `local variables`
|
||||
* Use whole words in names when possible
|
||||
|
||||
## Types
|
||||
|
||||
* Do not export `types` or `functions` unless you need to share it across multiple components
|
||||
* Do not introduce new `types` or `values` to the global namespace
|
||||
|
||||
## Comments
|
||||
|
||||
* When there are comments for `functions`, `interfaces`, `enums`, and `classes` use JSDoc style comments
|
||||
|
||||
## Strings
|
||||
|
||||
* Use "double quotes" for strings shown to the user that need to be externalized (localized)
|
||||
* Use 'single quotes' otherwise
|
||||
* All strings visible to the user need to be externalized
|
||||
|
||||
## Style
|
||||
|
||||
* Use arrow functions `=>` over anonymous function expressions
|
||||
* Only surround arrow function parameters when necessary. For example, `(x) => x + x` is wrong but the following are correct:
|
||||
|
||||
```javascript
|
||||
x => x + x
|
||||
(x, y) => x + y
|
||||
<T>(x: T, y: T) => x === y
|
||||
```
|
||||
|
||||
* Always surround loop and conditional bodies with curly braces
|
||||
* Open curly braces always go on the same line as whatever necessitates them
|
||||
* Parenthesized constructs should have no surrounding whitespace. A single space follows commas, colons, and semicolons in those constructs. For example:
|
||||
|
||||
```javascript
|
||||
for (let i = 0, n = str.length; i < 10; i++) {
|
||||
if (x < 10) {
|
||||
foo();
|
||||
}
|
||||
}
|
||||
|
||||
function f(x: number, y: string): void { }
|
||||
```
|
||||
@@ -1,6 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
insidersLabel: "insiders",
|
||||
insidersColor: "006b75",
|
||||
action: "add",
|
||||
perform: true,
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
insidersLabel: "insiders",
|
||||
insidersColor: "006b75",
|
||||
action: "remove",
|
||||
perform: true,
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
<!-- Thank you for submitting a Pull Request. Please:
|
||||
* Read our Pull Request guidelines:
|
||||
https://github.com/microsoft/vscode/wiki/How-to-Contribute#pull-requests
|
||||
* Associate an issue with the Pull Request.
|
||||
* Ensure that the code is up-to-date with the `main` branch.
|
||||
* Include a description of the proposed changes and how to test them.
|
||||
-->
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
perform: true,
|
||||
whenCreatedByTeam: false,
|
||||
comment: "(Experimental duplicate detection)\nThanks for submitting this issue. Please also check if it is already covered by an existing one, like:\n${potentialDuplicates}",
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
name: Basic checks
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
# on:
|
||||
# push:
|
||||
# branches:
|
||||
# - main
|
||||
# pull_request:
|
||||
# branches:
|
||||
# - main
|
||||
|
||||
jobs:
|
||||
main:
|
||||
if: github.ref != 'refs/heads/main'
|
||||
name: Compilation, Unit and Integration Tests
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# TODO: rename azure-pipelines/linux/xvfb.init to github-actions
|
||||
- name: Setup Build Environment
|
||||
run: |
|
||||
sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb
|
||||
sudo chmod +x /etc/init.d/xvfb
|
||||
sudo update-rc.d xvfb defaults
|
||||
sudo service xvfb start
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
|
||||
- name: Compute node modules cache key
|
||||
id: nodeModulesCacheKey
|
||||
run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT
|
||||
- name: Cache node modules
|
||||
id: cacheNodeModules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.os }}-cacheNodeModulesLinux-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
- name: Get npm cache directory path
|
||||
id: npmCacheDirPath
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
|
||||
- name: Cache npm directory
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.npmCacheDirPath.outputs.dir }}
|
||||
key: ${{ runner.os }}-npmCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
restore-keys: ${{ runner.os }}-npmCacheDir-
|
||||
- name: Execute npm
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
env:
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
|
||||
run: npm ci
|
||||
|
||||
- name: Compile and Download
|
||||
run: npm exec -- npm-run-all -lp compile "electron x64"
|
||||
|
||||
- name: Run Unit Tests
|
||||
id: electron-unit-tests
|
||||
run: DISPLAY=:10 ./scripts/test.sh
|
||||
|
||||
- name: Run Integration Tests (Electron)
|
||||
id: electron-integration-tests
|
||||
run: DISPLAY=:10 ./scripts/test-integration.sh
|
||||
|
||||
hygiene:
|
||||
if: github.ref != 'refs/heads/main'
|
||||
name: Hygiene and Layering
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
|
||||
- name: Compute node modules cache key
|
||||
id: nodeModulesCacheKey
|
||||
run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT
|
||||
- name: Cache node modules
|
||||
id: cacheNodeModules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.os }}-cacheNodeModulesLinux-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
- name: Get npm cache directory path
|
||||
id: npmCacheDirPath
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
|
||||
- name: Cache npm directory
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.npmCacheDirPath.outputs.dir }}
|
||||
key: ${{ runner.os }}-npmCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
restore-keys: ${{ runner.os }}-npmCacheDir-
|
||||
- name: Execute npm
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
env:
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
|
||||
run: npm ci
|
||||
|
||||
- name: Run Hygiene Checks
|
||||
run: npm run gulp hygiene
|
||||
|
||||
- name: Run Valid Layers Checks
|
||||
run: npm run valid-layers-check
|
||||
|
||||
- name: Compile /build/
|
||||
run: npm run compile
|
||||
working-directory: build
|
||||
|
||||
- name: Check clean git state
|
||||
run: ./.github/workflows/check-clean-git-state.sh
|
||||
|
||||
- name: Run eslint
|
||||
run: npm run eslint
|
||||
|
||||
- name: Run vscode-dts Compile Checks
|
||||
run: npm run vscode-dts-compile-check
|
||||
|
||||
- name: Run Trusted Types Checks
|
||||
run: npm run tsec-compile-check
|
||||
|
||||
warm-cache:
|
||||
name: Warm up node modules cache
|
||||
if: github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
|
||||
- name: Compute node modules cache key
|
||||
id: nodeModulesCacheKey
|
||||
run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT
|
||||
- name: Cache node modules
|
||||
id: cacheNodeModules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.os }}-cacheNodeModulesLinux-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
- name: Get npm cache directory path
|
||||
id: npmCacheDirPath
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
|
||||
- name: Cache npm directory
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.npmCacheDirPath.outputs.dir }}
|
||||
key: ${{ runner.os }}-npmCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
restore-keys: ${{ runner.os }}-npmCacheDir-
|
||||
- name: Execute npm
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
env:
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
|
||||
run: npm ci
|
||||
@@ -1,6 +0,0 @@
|
||||
R=`git status --porcelain | wc -l`
|
||||
if [ "$R" -ne "0" ]; then
|
||||
echo "The git repo is not clean after compiling the /build/ folder. Did you forget to commit .js output for .ts files?";
|
||||
git status --porcelain
|
||||
exit 1;
|
||||
fi
|
||||
@@ -1,316 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
# on:
|
||||
# push:
|
||||
# branches:
|
||||
# - main
|
||||
# - release/*
|
||||
# pull_request:
|
||||
# branches:
|
||||
# - main
|
||||
# - release/*
|
||||
|
||||
jobs:
|
||||
windows:
|
||||
name: Windows
|
||||
runs-on: windows-2022
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
- name: Compute node modules cache key
|
||||
id: nodeModulesCacheKey
|
||||
run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT
|
||||
- name: Cache node_modules archive
|
||||
id: cacheNodeModules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ".build/node_modules_cache"
|
||||
key: "${{ runner.os }}-cacheNodeModulesArchive-${{ steps.nodeModulesCacheKey.outputs.value }}"
|
||||
- name: Extract node_modules archive
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit == 'true' }}
|
||||
run: 7z.exe x .build/node_modules_cache/cache.7z -aos
|
||||
- name: Get npm cache directory path
|
||||
id: npmCacheDirPath
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
|
||||
- name: Cache npm directory
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.npmCacheDirPath.outputs.dir }}
|
||||
key: ${{ runner.os }}-npmCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
restore-keys: ${{ runner.os }}-npmCacheDir-
|
||||
- name: Execute npm
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
env:
|
||||
npm_config_foreground_scripts: "true"
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
|
||||
run: npm ci
|
||||
- name: Create node_modules archive
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
run: |
|
||||
mkdir -Force .build
|
||||
node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt
|
||||
mkdir -Force .build/node_modules_cache
|
||||
7z.exe a .build/node_modules_cache/cache.7z -mx3 `@.build/node_modules_list.txt
|
||||
|
||||
- name: Compile and Download
|
||||
run: npm exec -- npm-run-all -lp compile "electron x64" playwright-install download-builtin-extensions
|
||||
|
||||
- name: Compile Integration Tests
|
||||
run: npm run compile
|
||||
working-directory: test/integration/browser
|
||||
|
||||
- name: Run Unit Tests (Electron)
|
||||
run: .\scripts\test.bat
|
||||
|
||||
- name: Run Unit Tests (node.js)
|
||||
run: npm run test-node
|
||||
|
||||
- name: Run Unit Tests (Browser, Chromium)
|
||||
run: npm run test-browser-no-install -- --browser chromium
|
||||
|
||||
- name: Run Integration Tests (Electron)
|
||||
run: .\scripts\test-integration.bat
|
||||
|
||||
- name: Run Integration Tests (Browser, Firefox)
|
||||
timeout-minutes: 20
|
||||
run: .\scripts\test-web-integration.bat --browser firefox
|
||||
|
||||
- name: Run Integration Tests (Remote)
|
||||
timeout-minutes: 20
|
||||
run: .\scripts\test-remote-integration.bat
|
||||
|
||||
linux:
|
||||
name: Linux
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# TODO: rename azure-pipelines/linux/xvfb.init to github-actions
|
||||
- name: Setup Build Environment
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libxkbfile-dev pkg-config libkrb5-dev libxss1 dbus xvfb libgtk-3-0 libgbm1
|
||||
sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb
|
||||
sudo chmod +x /etc/init.d/xvfb
|
||||
sudo update-rc.d xvfb defaults
|
||||
sudo service xvfb start
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
|
||||
- name: Compute node modules cache key
|
||||
id: nodeModulesCacheKey
|
||||
run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT
|
||||
- name: Cache node modules
|
||||
id: cacheNodeModules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.os }}-cacheNodeModulesLinux-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
- name: Get npm cache directory path
|
||||
id: npmCacheDirPath
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
|
||||
- name: Cache npm directory
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.npmCacheDirPath.outputs.dir }}
|
||||
key: ${{ runner.os }}-npmCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
restore-keys: ${{ runner.os }}-npmCacheDir-
|
||||
- name: Execute npm
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
env:
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
|
||||
run: npm ci
|
||||
|
||||
- name: Compile and Download
|
||||
run: npm exec -- npm-run-all -lp compile "electron x64" playwright-install download-builtin-extensions
|
||||
|
||||
- name: Compile Integration Tests
|
||||
run: npm run compile
|
||||
working-directory: test/integration/browser
|
||||
|
||||
- name: Run Unit Tests (Electron)
|
||||
id: electron-unit-tests
|
||||
run: DISPLAY=:10 ./scripts/test.sh
|
||||
|
||||
- name: Run Unit Tests (node.js)
|
||||
id: nodejs-unit-tests
|
||||
run: npm run test-node
|
||||
|
||||
- name: Run Unit Tests (Browser, Chromium)
|
||||
id: browser-unit-tests
|
||||
run: DISPLAY=:10 npm run test-browser-no-install -- --browser chromium
|
||||
|
||||
- name: Run Integration Tests (Electron)
|
||||
id: electron-integration-tests
|
||||
run: DISPLAY=:10 ./scripts/test-integration.sh
|
||||
|
||||
- name: Run Integration Tests (Browser, Chromium)
|
||||
id: browser-integration-tests
|
||||
run: DISPLAY=:10 ./scripts/test-web-integration.sh --browser chromium
|
||||
|
||||
- name: Run Integration Tests (Remote)
|
||||
id: electron-remote-integration-tests
|
||||
timeout-minutes: 15
|
||||
run: DISPLAY=:10 ./scripts/test-remote-integration.sh
|
||||
|
||||
darwin:
|
||||
name: macOS
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 40
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
|
||||
- name: Compute node modules cache key
|
||||
id: nodeModulesCacheKey
|
||||
run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT
|
||||
- name: Cache node modules
|
||||
id: cacheNodeModules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.os }}-cacheNodeModulesMacOS-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
- name: Get npm cache directory path
|
||||
id: npmCacheDirPath
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
|
||||
- name: Cache npm directory
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.npmCacheDirPath.outputs.dir }}
|
||||
key: ${{ runner.os }}-npmCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
restore-keys: ${{ runner.os }}-npmCacheDir-
|
||||
- name: Execute npm
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
env:
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
|
||||
run: npm ci
|
||||
|
||||
- name: Compile and Download
|
||||
run: npm exec -- npm-run-all -lp compile "electron x64" playwright-install download-builtin-extensions
|
||||
|
||||
- name: Compile Integration Tests
|
||||
run: npm run compile
|
||||
working-directory: test/integration/browser
|
||||
|
||||
# This is required for SecretStorage unittests
|
||||
- name: Create temporary keychain
|
||||
run: |
|
||||
security create-keychain -p pwd $RUNNER_TEMP/buildagent.keychain
|
||||
security default-keychain -s $RUNNER_TEMP/buildagent.keychain
|
||||
security unlock-keychain -p pwd $RUNNER_TEMP/buildagent.keychain
|
||||
|
||||
- name: Run Unit Tests (Electron)
|
||||
run: DISPLAY=:10 ./scripts/test.sh
|
||||
|
||||
- name: Run Unit Tests (node.js)
|
||||
run: npm run test-node
|
||||
|
||||
- name: Run Unit Tests (Browser, Chromium)
|
||||
run: DISPLAY=:10 npm run test-browser-no-install -- --browser chromium
|
||||
|
||||
- name: Run Integration Tests (Electron)
|
||||
run: DISPLAY=:10 ./scripts/test-integration.sh
|
||||
|
||||
- name: Run Integration Tests (Browser, Webkit)
|
||||
run: DISPLAY=:10 ./scripts/test-web-integration.sh --browser webkit
|
||||
|
||||
- name: Run Integration Tests (Remote)
|
||||
timeout-minutes: 15
|
||||
run: DISPLAY=:10 ./scripts/test-remote-integration.sh
|
||||
|
||||
hygiene:
|
||||
name: Hygiene and Layering
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
|
||||
- name: Compute node modules cache key
|
||||
id: nodeModulesCacheKey
|
||||
run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT
|
||||
- name: Cache node modules
|
||||
id: cacheNodeModules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.os }}-cacheNodeModulesLinux-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
- name: Get npm cache directory path
|
||||
id: npmCacheDirPath
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
|
||||
- name: Cache npm directory
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.npmCacheDirPath.outputs.dir }}
|
||||
key: ${{ runner.os }}-npmCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
restore-keys: ${{ runner.os }}-npmCacheDir-
|
||||
- name: Execute npm
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
env:
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
|
||||
run: npm ci
|
||||
|
||||
- name: Download Playwright
|
||||
run: npm run playwright-install
|
||||
|
||||
- name: Run Hygiene Checks
|
||||
run: npm run gulp hygiene
|
||||
|
||||
- name: Run Valid Layers Checks
|
||||
run: npm run valid-layers-check
|
||||
|
||||
- name: Compile /build/
|
||||
run: npm run compile
|
||||
working-directory: build
|
||||
|
||||
- name: Check clean git state
|
||||
run: ./.github/workflows/check-clean-git-state.sh
|
||||
|
||||
- name: Run eslint
|
||||
run: npm run eslint
|
||||
|
||||
- name: Run vscode-dts Compile Checks
|
||||
run: npm run vscode-dts-compile-check
|
||||
|
||||
- name: Run Trusted Types Checks
|
||||
run: npm run tsec-compile-check
|
||||
@@ -1,97 +0,0 @@
|
||||
name: Monaco Editor checks
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- release/*
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- release/*
|
||||
|
||||
jobs:
|
||||
main:
|
||||
name: Monaco Editor checks
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
|
||||
- name: Compute node modules cache key
|
||||
id: nodeModulesCacheKey
|
||||
run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT
|
||||
- name: Cache node modules
|
||||
id: cacheNodeModules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.os }}-cacheNodeModules20-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
restore-keys: ${{ runner.os }}-cacheNodeModules20-
|
||||
- name: Get npm cache directory path
|
||||
id: npmCacheDirPath
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
|
||||
- name: Cache npm directory
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.npmCacheDirPath.outputs.dir }}
|
||||
key: ${{ runner.os }}-npmCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
restore-keys: ${{ runner.os }}-npmCacheDir-
|
||||
- name: Install system dependencies
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y libxkbfile-dev pkg-config libkrb5-dev libxss1
|
||||
- name: Execute npm
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
env:
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
|
||||
run: |
|
||||
npm ci
|
||||
|
||||
- name: Download Playwright
|
||||
run: npm run playwright-install
|
||||
|
||||
- name: Run Monaco Editor Checks
|
||||
run: npm run monaco-compile-check
|
||||
|
||||
- name: Editor Distro & ESM
|
||||
run: npm run gulp editor-esm
|
||||
|
||||
- name: Editor ESM sources check
|
||||
working-directory: ./test/monaco
|
||||
run: npm run esm-check
|
||||
|
||||
- name: Typings validation prep
|
||||
run: |
|
||||
mkdir typings-test
|
||||
|
||||
- name: Typings validation
|
||||
working-directory: ./typings-test
|
||||
run: |
|
||||
npm init -yp
|
||||
../node_modules/.bin/tsc --init
|
||||
echo "import '../out-monaco-editor-core';" > a.ts
|
||||
../node_modules/.bin/tsc --noEmit
|
||||
|
||||
- name: Package Editor with Webpack
|
||||
working-directory: ./test/monaco
|
||||
run: npm run bundle-webpack
|
||||
|
||||
- name: Compile Editor Tests
|
||||
working-directory: ./test/monaco
|
||||
run: npm run compile
|
||||
|
||||
- name: Run Editor Tests
|
||||
timeout-minutes: 5
|
||||
working-directory: ./test/monaco
|
||||
run: npm run test
|
||||
@@ -1,31 +0,0 @@
|
||||
name: Prevent package-lock.json changes in PRs
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
main:
|
||||
name: Prevent package-lock.json changes in PRs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: octokit/request-action@v2.x
|
||||
id: get_permissions
|
||||
with:
|
||||
route: GET /repos/microsoft/vscode/collaborators/{username}/permission
|
||||
username: ${{ github.event.pull_request.user.login }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Set control output variable
|
||||
id: control
|
||||
run: |
|
||||
echo "user: ${{ github.event.pull_request.user.login }}"
|
||||
echo "role: ${{ fromJson(steps.get_permissions.outputs.data).permission }}"
|
||||
echo "is dependabot: ${{ github.event.pull_request.user.login == 'dependabot[bot]' }}"
|
||||
echo "should_run: ${{ !contains(fromJson('["admin", "maintain", "write"]'), fromJson(steps.get_permissions.outputs.data).permission) }}"
|
||||
echo "should_run=${{ !contains(fromJson('["admin", "maintain", "write"]'), fromJson(steps.get_permissions.outputs.data).permission) && github.event.pull_request.user.login != 'dependabot[bot]' }}" >> $GITHUB_OUTPUT
|
||||
- name: Get file changes
|
||||
uses: trilom/file-changes-action@ce38c8ce2459ca3c303415eec8cb0409857b4272
|
||||
if: ${{ steps.control.outputs.should_run == 'true' }}
|
||||
- name: Check for lockfile changes
|
||||
if: ${{ steps.control.outputs.should_run == 'true' }}
|
||||
run: |
|
||||
cat $HOME/files.json | jq -e 'any(test("package-lock\\.json$|Cargo\\.lock$")) | not' \
|
||||
|| (echo "Changes to package-lock.json/Cargo.lock files aren't allowed in PRs." && exit 1)
|
||||
@@ -1,31 +0,0 @@
|
||||
name: Prevent yarn.lock changes in PRs
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
main:
|
||||
name: Prevent yarn.lock changes in PRs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: octokit/request-action@v2.x
|
||||
id: get_permissions
|
||||
with:
|
||||
route: GET /repos/microsoft/vscode/collaborators/{username}/permission
|
||||
username: ${{ github.event.pull_request.user.login }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Set control output variable
|
||||
id: control
|
||||
run: |
|
||||
echo "user: ${{ github.event.pull_request.user.login }}"
|
||||
echo "role: ${{ fromJson(steps.get_permissions.outputs.data).permission }}"
|
||||
echo "is dependabot: ${{ github.event.pull_request.user.login == 'dependabot[bot]' }}"
|
||||
echo "should_run: ${{ !contains(fromJson('["admin", "maintain", "write"]'), fromJson(steps.get_permissions.outputs.data).permission) }}"
|
||||
echo "should_run=${{ !contains(fromJson('["admin", "maintain", "write"]'), fromJson(steps.get_permissions.outputs.data).permission) && github.event.pull_request.user.login != 'dependabot[bot]' }}" >> $GITHUB_OUTPUT
|
||||
- name: Get file changes
|
||||
uses: trilom/file-changes-action@ce38c8ce2459ca3c303415eec8cb0409857b4272
|
||||
if: ${{ steps.control.outputs.should_run == 'true' }}
|
||||
- name: Check for lockfile changes
|
||||
if: ${{ steps.control.outputs.should_run == 'true' }}
|
||||
run: |
|
||||
cat $HOME/files.json | jq -e 'any(test("yarn\\.lock$|Cargo\\.lock$")) | not' \
|
||||
|| (echo "Changes to yarn.lock/Cargo.lock files aren't allowed in PRs." && exit 1)
|
||||
@@ -1,39 +0,0 @@
|
||||
name: "Rich Navigation Indexing"
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- '[0-9]+.[0-9]+.[0-9]+'
|
||||
|
||||
jobs:
|
||||
richnav:
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/cache@v3
|
||||
id: caching-stage
|
||||
name: Cache VS Code dependencies
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-dependencies-${{ hashfiles('package-lock.json') }}
|
||||
restore-keys: ${{ runner.os }}-dependencies-
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.caching-stage.outputs.cache-hit != 'true'
|
||||
run: npm ci
|
||||
env:
|
||||
npm_config_foreground_scripts: "true"
|
||||
|
||||
- uses: microsoft/RichCodeNavIndexer@v0.1
|
||||
with:
|
||||
languages: typescript
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
configFiles: .lsifrc.json
|
||||
continue-on-error: true
|
||||
@@ -1,19 +0,0 @@
|
||||
name: 'Telemetry'
|
||||
on:
|
||||
pull_request:
|
||||
jobs:
|
||||
check-metdata:
|
||||
name: 'Check metadata'
|
||||
runs-on: 'ubuntu-latest'
|
||||
|
||||
steps:
|
||||
- uses: 'actions/checkout@v4'
|
||||
|
||||
- uses: 'actions/setup-node@v4'
|
||||
with:
|
||||
node-version: 'lts/*'
|
||||
|
||||
- name: 'Run vscode-telemetry-extractor'
|
||||
run: 'npx --package=@vscode/telemetry-extractor@1.14.0 --yes vscode-telemetry-extractor -s .'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -21,3 +21,6 @@ vscode.db
|
||||
product.overrides.json
|
||||
*.snap.actual
|
||||
.vscode-test
|
||||
.tmp/
|
||||
.tmp2/
|
||||
.tool-versions
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# Created for Void
|
||||
# To learn more about how to use Nix to configure your environment
|
||||
# see: https://developers.google.com/idx/guides/customize-idx-env
|
||||
{pkgs}: {
|
||||
# Which nixpkgs channel to use.
|
||||
channel = "stable-23.11"; # or "unstable"
|
||||
# Use https://search.nixos.org/packages to find packages
|
||||
packages = [
|
||||
pkgs.nodejs_20
|
||||
pkgs.yarn
|
||||
pkgs.nodePackages.pnpm
|
||||
pkgs.bun
|
||||
pkgs.gh
|
||||
];
|
||||
# Sets environment variables in the workspace
|
||||
env = {};
|
||||
idx = {
|
||||
# Search for the extensions you want on https://open-vsx.org/ and use "publisher.id"
|
||||
extensions = [
|
||||
# "vscodevim.vim"
|
||||
];
|
||||
workspace = {
|
||||
# Runs when a workspace is first created with this `dev.nix` file
|
||||
onCreate = {
|
||||
npm-install = "npm ci --no-audit --prefer-offline --no-progress --timing";
|
||||
# Open editors for the following files by default, if they exist:
|
||||
default.openFiles = [
|
||||
# Cover all the variations of language, src-dir, router (app/pages)
|
||||
"pages/index.tsx" "pages/index.jsx"
|
||||
"src/pages/index.tsx" "src/pages/index.jsx"
|
||||
"app/page.tsx" "app/page.jsx"
|
||||
"src/app/page.tsx" "src/app/page.jsx"
|
||||
];
|
||||
};
|
||||
# To run something each time the workspace is (re)started, use the `onStart` hook
|
||||
};
|
||||
# Enable previews and customize configuration
|
||||
previews = {
|
||||
enable = true;
|
||||
previews = {
|
||||
web = {
|
||||
command = ["npm" "run" "dev" "--" "--port" "$PORT" "--hostname" "0.0.0.0"];
|
||||
manager = "web";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -29,6 +29,34 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "watchreactd",
|
||||
"label": "React - Build",
|
||||
"isBackground": true,
|
||||
"presentation": {
|
||||
"reveal": "never",
|
||||
"group": "buildWatchers",
|
||||
"close": false
|
||||
},
|
||||
"problemMatcher": {
|
||||
"owner": "typescript",
|
||||
"applyTo": "closedDocuments",
|
||||
"fileLocation": [
|
||||
"absolute"
|
||||
],
|
||||
"pattern": {
|
||||
"regexp": "Error: ([^(]+)\\((\\d+|\\d+,\\d+|\\d+,\\d+,\\d+,\\d+)\\): (.*)$",
|
||||
"file": 1,
|
||||
"location": 2,
|
||||
"message": 3
|
||||
},
|
||||
"background": {
|
||||
"beginsPattern": "Starting compilation",
|
||||
"endsPattern": "Finished compilation"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "watch-extensionsd",
|
||||
@@ -61,7 +89,8 @@
|
||||
"label": "VS Code - Build",
|
||||
"dependsOn": [
|
||||
"Core - Build",
|
||||
"Ext - Build"
|
||||
"Ext - Build",
|
||||
"React - Build"
|
||||
],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
|
||||
@@ -1,99 +1,164 @@
|
||||
# Contributing to VS Code
|
||||
# Contributing to Void
|
||||
### Welcome! 👋
|
||||
This is the official guide on how to contribute to Void. We want to make it as easy as possible to contribute, so if you have any questions or comments, reach out via email or discord!
|
||||
|
||||
Welcome, and thank you for your interest in contributing to VS Code!
|
||||
There are a few ways to contribute:
|
||||
|
||||
There are several ways in which you can contribute, beyond writing code. The goal of this document is to provide a high-level overview of how you can get involved.
|
||||
|
||||
## Asking Questions
|
||||
- 💫 Complete items on the [Roadmap](https://github.com/orgs/voideditor/projects/2).
|
||||
- 💡 Make suggestions in our [Discord](https://discord.gg/RSNjgaugJs).
|
||||
- 🪴 Start new Issues - see [Issues](https://github.com/voideditor/void/issues).
|
||||
|
||||
|
||||
Have a question? Instead of opening an issue, please ask on [Stack Overflow](https://stackoverflow.com/questions/tagged/visual-studio-code) using the tag `visual-studio-code`.
|
||||
|
||||
The active community will be eager to assist you. Your well-worded question will serve as a resource to others searching for help.
|
||||
### Codebase Guide
|
||||
|
||||
## Providing Feedback
|
||||
We highly recommend reading [this](https://github.com/microsoft/vscode/wiki/Source-Code-Organization) article on VSCode's sourcecode organization.
|
||||
|
||||
Your comments and feedback are welcome, and the development team is available via a handful of different channels.
|
||||
<!-- ADD BLOG HERE
|
||||
We wrote a [guide to working in VSCode].
|
||||
-->
|
||||
|
||||
See the [Feedback Channels](https://github.com/microsoft/vscode/wiki/Feedback-Channels) wiki page for details on how to share your thoughts.
|
||||
Most of Void's code lives in the folder `src/vs/workbench/contrib/void/`.
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
Have you identified a reproducible problem in VS Code? Do you have a feature request? We want to hear about it! Here's how you can report your issue as effectively as possible.
|
||||
|
||||
### Identify Where to Report
|
||||
|
||||
The VS Code project is distributed across multiple repositories. Try to file the issue against the correct repository. Check the list of [Related Projects](https://github.com/microsoft/vscode/wiki/Related-Projects) if you aren't sure which repo is correct.
|
||||
## Building Void
|
||||
|
||||
Can you recreate the issue even after [disabling all extensions](https://code.visualstudio.com/docs/editor/extension-gallery#_disable-an-extension)? If you find the issue is caused by an extension you have installed, please file an issue on the extension's repo directly.
|
||||
### a. Build Prerequisites - Mac
|
||||
|
||||
### Look For an Existing Issue
|
||||
If you're using a Mac, you need Python and XCode. You probably have these by default.
|
||||
|
||||
Before you create a new issue, please do a search in [open issues](https://github.com/microsoft/vscode/issues) to see if the issue or feature request has already been filed.
|
||||
### b. Build Prerequisites - Windows
|
||||
|
||||
Be sure to scan through the [most popular](https://github.com/microsoft/vscode/issues?q=is%3Aopen+is%3Aissue+label%3Afeature-request+sort%3Areactions-%2B1-desc) feature requests.
|
||||
If you're using a Windows computer, first get [Visual Studio 2022](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community) (recommended) or [VS Build Tools](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools) (not recommended). If you already have both, you might need to run the next few steps on both of them.
|
||||
|
||||
If you find your issue already exists, make relevant comments and add your [reaction](https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments). Use a reaction in place of a "+1" comment:
|
||||
Go to the "Workloads" tab and select:
|
||||
- `Desktop development with C++`
|
||||
- `Node.js build tools`
|
||||
|
||||
* 👍 - upvote
|
||||
* 👎 - downvote
|
||||
Go to the "Individual Components" tab and select:
|
||||
- `MSVC v143 - VS 2022 C++ x64/x86 Spectre-mitigated libs (Latest)`
|
||||
- `C++ ATL for latest build tools with Spectre Mitigations`
|
||||
- `C++ MFC for latest build tools with Spectre Mitigations`
|
||||
|
||||
If you cannot find an existing issue that describes your bug or feature, create a new issue using the guidelines below.
|
||||
Finally, click Install.
|
||||
|
||||
### Writing Good Bug Reports and Feature Requests
|
||||
### c. Build Prerequisites - Linux
|
||||
|
||||
File a single issue per problem and feature request. Do not enumerate multiple bugs or feature requests in the same issue.
|
||||
First, run `npm install -g node-gyp`. Then:
|
||||
|
||||
Do not add your issue as a comment to an existing issue unless it's for the identical input. Many issues look similar but have different causes.
|
||||
- Debian (Ubuntu, etc): `sudo apt-get install build-essential g++ libx11-dev libxkbfile-dev libsecret-1-dev libkrb5-dev python-is-python3`.
|
||||
- Red Hat (Fedora, etc): `sudo dnf install @development-tools gcc gcc-c++ make libsecret-devel krb5-devel libX11-devel libxkbfile-devel`.
|
||||
- Others: see [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute).
|
||||
|
||||
The more information you can provide, the more likely someone will be successful at reproducing the issue and finding a fix.
|
||||
### d. Building Void
|
||||
|
||||
The built-in tool for reporting an issue, which you can access by using `Report Issue` in VS Code's Help menu, can help streamline this process by automatically providing the version of VS Code, all your installed extensions, and your system info. Additionally, the tool will search among existing issues to see if a similar issue already exists.
|
||||
To build Void, open `void/` inside VSCode. Then open your terminal and run:
|
||||
|
||||
Please include the following with each issue:
|
||||
1. `npm install` to install all dependencies.
|
||||
2. `npm run watchreact` to build Void's browser dependencies like React. (If this doesn't work, try `npm run buildreact`).
|
||||
3. Build Void.
|
||||
- Press <kbd>Cmd+Shift+B</kbd> (Mac).
|
||||
- Press <kbd>Ctrl+Shift+B</kbd> (Windows/Linux).
|
||||
- This step can take ~5 min. The build is done when you see two check marks.
|
||||
4. Run Void.
|
||||
- Run `./scripts/code.sh` (Mac/Linux).
|
||||
- Run `./scripts/code.bat` (Windows).
|
||||
6. Nice-to-knows.
|
||||
- You can always press <kbd>Ctrl+R</kbd> (<kbd>Cmd+R</kbd>) inside the new window to reload and see your new changes. It's faster than <kbd>Ctrl+Shift+P</kbd> and `Reload Window`.
|
||||
- You might want to add the flags `--user-data-dir ./.tmp/user-data --extensions-dir ./.tmp/extensions` to the above run command, which lets you delete the `.tmp` folder to reset any IDE changes you made when testing.
|
||||
|
||||
* Version of VS Code
|
||||
* Your operating system
|
||||
* List of extensions that you have installed
|
||||
* Reproducible steps (1... 2... 3...) that cause the issue
|
||||
* What you expected to see, versus what you actually saw
|
||||
* Images, animations, or a link to a video showing the issue occurring
|
||||
* A code snippet that demonstrates the issue or a link to a code repository the developers can easily pull down to recreate the issue locally
|
||||
* **Note:** Because the developers need to copy and paste the code snippet, including a code snippet as a media file (i.e. .gif) is not sufficient.
|
||||
* Errors from the Dev Tools Console (open from the menu: Help > Toggle Developer Tools)
|
||||
#### Building Void from Terminal
|
||||
|
||||
### Creating Pull Requests
|
||||
Alternatively, if you want to build Void from the terminal, instead of pressing <kbd>Cmd+Shift+B</kbd> you can run `npm run watch`. The build is done when you see something like this:
|
||||
|
||||
* Please refer to the article on [creating pull requests](https://github.com/microsoft/vscode/wiki/How-to-Contribute#pull-requests) and contributing to this project.
|
||||
```
|
||||
[watch-extensions] [00:37:39] Finished compilation extensions with 0 errors after 19303 ms
|
||||
[watch-client ] [00:38:06] Finished compilation with 0 errors after 46248 ms
|
||||
[watch-client ] [00:38:07] Starting compilation...
|
||||
[watch-client ] [00:38:07] Finished compilation with 0 errors after 5 ms
|
||||
```
|
||||
|
||||
### Final Checklist
|
||||
|
||||
Please remember to do the following:
|
||||
|
||||
* [ ] Search the issue repository to ensure your report is a new issue
|
||||
* [ ] Recreate the issue after disabling all extensions
|
||||
* [ ] Simplify your code around the issue to better isolate the problem
|
||||
#### Common Fixes
|
||||
|
||||
Don't feel bad if the developers can't reproduce the issue right away. They will simply ask for more information!
|
||||
- Make sure you followed the prerequisite steps.
|
||||
- Make sure you have Node version `20.16.0` (the version in `.nvmrc`)!
|
||||
- If you get `"TypeError: Failed to fetch dynamically imported module"`, make sure all imports end with `.js`.
|
||||
- If you see missing styles, wait a few seconds and then reload.
|
||||
- If you have any questions, feel free to [submit an issue](https://github.com/voideditor/void/issues/new). You can also refer to VSCode's complete [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute) page.
|
||||
|
||||
### Follow Your Issue
|
||||
|
||||
Once submitted, your report will go into the [issue tracking](https://github.com/microsoft/vscode/wiki/Issue-Tracking) workflow. Be sure to understand what will happen next, so you know what to expect and how to continue to assist throughout the process.
|
||||
|
||||
## Automated Issue Management
|
||||
## Packaging
|
||||
|
||||
We use GitHub Actions to help us manage issues. These Actions and their descriptions can be [viewed here](https://github.com/microsoft/vscode-github-triage-actions). Some examples of what these Actions do are:
|
||||
We don't usually recommend packaging. Instead, you should probably just build. If you're sure you want to package Void into an executable app, make sure you've built first, then run one of the following commands. This will create a folder named `VSCode-darwin-arm64` or similar outside of the void/ repo (see below). Be patient - packaging can take ~25 minutes.
|
||||
|
||||
* Automatically close any issue marked `info-needed` if there has been no response in the past 7 days.
|
||||
* Automatically lock issues 45 days after they are closed.
|
||||
* Automatically implement the VS Code [feature request pipeline](https://github.com/microsoft/vscode/wiki/Issues-Triaging#managing-feature-requests).
|
||||
|
||||
If you believe the bot got something wrong, please open a new issue and let us know.
|
||||
### Mac
|
||||
- `npm run gulp vscode-darwin-arm64` - most common (Apple Silicon)
|
||||
- `npm run gulp vscode-darwin-x64` (Intel)
|
||||
|
||||
## Contributing Fixes
|
||||
### Windows
|
||||
- `npm run gulp vscode-win32-x64` - most common
|
||||
- `npm run gulp vscode-win32-ia32`
|
||||
|
||||
If you are interested in writing code to fix issues, please see [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute) in the wiki.
|
||||
### Linux
|
||||
- `npm run gulp vscode-linux-x64` - most common
|
||||
- `npm run gulp vscode-linux-arm`
|
||||
- `npm run gulp vscode-linux-ia32`
|
||||
|
||||
## Thank You
|
||||
|
||||
Your contributions to open source, large or small, make great projects like this possible. Thank you for taking the time to contribute.
|
||||
### Output
|
||||
|
||||
This will generate a folder outside of `void/`:
|
||||
```bash
|
||||
workspace/
|
||||
├── void/ # Your Void fork
|
||||
└── VSCode-darwin-arm64/ # Generated output
|
||||
```
|
||||
|
||||
### Distributing
|
||||
Void's maintainers distribute Void on our website and in releases. If you'd like to see the scripts to convert `Mac .app -> .dmg`, `Windows folder -> .exe`, and `Linux folder -> appimage` for distribution, feel free to reach out.
|
||||
|
||||
## Pull Request Guidelines
|
||||
|
||||
|
||||
- Please submit a pull request once you've made a change.
|
||||
- No need to submit an Issue unless you're creating a new feature that might involve multiple PRs.
|
||||
- Please don't use AI to write your PR 🙂
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!--
|
||||
# Relevant files
|
||||
|
||||
We keep track of all the files we've changed with Void so it's easy to rebase:
|
||||
|
||||
Edit: far too many changes to track... this is old
|
||||
|
||||
- README.md
|
||||
- CONTRIBUTING.md
|
||||
- VOID_USEFUL_LINKS.md
|
||||
- product.json
|
||||
- package.json
|
||||
|
||||
- src/vs/workbench/api/common/{extHost.api.impl.ts | extHostApiCommands.ts}
|
||||
- src/vs/workbench/workbench.common.main.ts
|
||||
- src/vs/workbench/contrib/void/\*
|
||||
- extensions/void/\*
|
||||
|
||||
- .github/\*
|
||||
- .vscode/settings/\*
|
||||
- .eslintrc.json
|
||||
- build/hygiene.js
|
||||
- build/lib/i18n.resources.json
|
||||
- build/npm/dirs.js
|
||||
|
||||
- vscode.proposed.editorInsets.d.ts - not modified, but code copied
|
||||
|
||||
-->
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
Void is a fork of VS Code, which is licensed under the MIT License (below).
|
||||
Void's additions and modifications are licensed under the Apache 2.0 License (see LICENSE.txt).
|
||||
|
||||
--------------------
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2015 - present Microsoft Corporation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,21 +1,201 @@
|
||||
MIT License
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
Copyright (c) 2015 - present Microsoft Corporation
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
1. Definitions.
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2025 Glass Devtools, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
@@ -1,79 +1,39 @@
|
||||
# Visual Studio Code - Open Source ("Code - OSS")
|
||||
# Welcome to Void.
|
||||
|
||||
[](https://github.com/microsoft/vscode/issues?q=is%3Aopen+is%3Aissue+label%3Afeature-request+sort%3Areactions-%2B1-desc)
|
||||
[](https://github.com/microsoft/vscode/issues?utf8=✓&q=is%3Aissue+is%3Aopen+label%3Abug)
|
||||
[](https://gitter.im/Microsoft/vscode)
|
||||
<div align="center">
|
||||
<img
|
||||
src="./src/vs/workbench/browser/parts/editor/media/slice_of_void.png"
|
||||
alt="Void Welcome"
|
||||
width="300"
|
||||
height="300"
|
||||
/>
|
||||
</div>
|
||||
|
||||
## The Repository
|
||||
Void is the open-source Cursor alternative.
|
||||
|
||||
This repository ("`Code - OSS`") is where we (Microsoft) develop the [Visual Studio Code](https://code.visualstudio.com) product together with the community. Not only do we work on code and issues here, we also publish our [roadmap](https://github.com/microsoft/vscode/wiki/Roadmap), [monthly iteration plans](https://github.com/microsoft/vscode/wiki/Iteration-Plans), and our [endgame plans](https://github.com/microsoft/vscode/wiki/Running-the-Endgame). This source code is available to everyone under the standard [MIT license](https://github.com/microsoft/vscode/blob/main/LICENSE.txt).
|
||||
This repo contains the full sourcecode for Void. We are currently in [open beta](https://voideditor.com/email) for Discord members (see the `announcements` channel), with a waitlist for our official release. If you're new, welcome!
|
||||
|
||||
## Visual Studio Code
|
||||
- 👋 [Discord](https://discord.gg/RSNjgaugJs)
|
||||
|
||||
<p align="center">
|
||||
<img alt="VS Code in action" src="https://user-images.githubusercontent.com/35271042/118224532-3842c400-b438-11eb-923d-a5f66fa6785a.png">
|
||||
</p>
|
||||
- 🔨 [Contribute](https://github.com/voideditor/void/blob/main/CONTRIBUTING.md)
|
||||
|
||||
[Visual Studio Code](https://code.visualstudio.com) is a distribution of the `Code - OSS` repository with Microsoft-specific customizations released under a traditional [Microsoft product license](https://code.visualstudio.com/License/).
|
||||
- 🚙 [Roadmap](https://github.com/orgs/voideditor/projects/2)
|
||||
|
||||
[Visual Studio Code](https://code.visualstudio.com) combines the simplicity of a code editor with what developers need for their core edit-build-debug cycle. It provides comprehensive code editing, navigation, and understanding support along with lightweight debugging, a rich extensibility model, and lightweight integration with existing tools.
|
||||
- 📝 [Changelog](https://voideditor.com/changelog)
|
||||
|
||||
Visual Studio Code is updated monthly with new features and bug fixes. You can download it for Windows, macOS, and Linux on [Visual Studio Code's website](https://code.visualstudio.com/Download). To get the latest releases every day, install the [Insiders build](https://code.visualstudio.com/insiders).
|
||||
|
||||
## Contributing
|
||||
|
||||
There are many ways in which you can participate in this project, for example:
|
||||
1. Feel free to attend a weekly meeting in our Discord channel if you'd like to contribute!
|
||||
|
||||
* [Submit bugs and feature requests](https://github.com/microsoft/vscode/issues), and help us verify as they are checked in
|
||||
* Review [source code changes](https://github.com/microsoft/vscode/pulls)
|
||||
* Review the [documentation](https://github.com/microsoft/vscode-docs) and make pull requests for anything from typos to additional and new content
|
||||
2. To get started working on Void, see [Contributing](https://github.com/voideditor/void/blob/main/CONTRIBUTING.md).
|
||||
|
||||
If you are interested in fixing issues and contributing directly to the code base,
|
||||
please see the document [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute), which covers the following:
|
||||
3. We're open to collaborations and suggestions of all types - just reach out.
|
||||
|
||||
* [How to build and run from source](https://github.com/microsoft/vscode/wiki/How-to-Contribute)
|
||||
* [The development workflow, including debugging and running tests](https://github.com/microsoft/vscode/wiki/How-to-Contribute#debugging)
|
||||
* [Coding guidelines](https://github.com/microsoft/vscode/wiki/Coding-Guidelines)
|
||||
* [Submitting pull requests](https://github.com/microsoft/vscode/wiki/How-to-Contribute#pull-requests)
|
||||
* [Finding an issue to work on](https://github.com/microsoft/vscode/wiki/How-to-Contribute#where-to-contribute)
|
||||
* [Contributing to translations](https://aka.ms/vscodeloc)
|
||||
|
||||
## Feedback
|
||||
## Reference
|
||||
|
||||
* Ask a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/vscode)
|
||||
* [Request a new feature](CONTRIBUTING.md)
|
||||
* Upvote [popular feature requests](https://github.com/microsoft/vscode/issues?q=is%3Aopen+is%3Aissue+label%3Afeature-request+sort%3Areactions-%2B1-desc)
|
||||
* [File an issue](https://github.com/microsoft/vscode/issues)
|
||||
* Connect with the extension author community on [GitHub Discussions](https://github.com/microsoft/vscode-discussions/discussions) or [Slack](https://aka.ms/vscode-dev-community)
|
||||
* Follow [@code](https://twitter.com/code) and let us know what you think!
|
||||
Void is a fork of the [vscode](https://github.com/microsoft/vscode) repository. For some useful links on VSCode, see [`VOID_USEFUL_LINKS.md`](https://github.com/voideditor/void/blob/main/VOID_USEFUL_LINKS.md).
|
||||
|
||||
See our [wiki](https://github.com/microsoft/vscode/wiki/Feedback-Channels) for a description of each of these channels and information on some other available community-driven channels.
|
||||
|
||||
## Related Projects
|
||||
|
||||
Many of the core components and extensions to VS Code live in their own repositories on GitHub. For example, the [node debug adapter](https://github.com/microsoft/vscode-node-debug) and the [mono debug adapter](https://github.com/microsoft/vscode-mono-debug) repositories are separate from each other. For a complete list, please visit the [Related Projects](https://github.com/microsoft/vscode/wiki/Related-Projects) page on our [wiki](https://github.com/microsoft/vscode/wiki).
|
||||
|
||||
## Bundled Extensions
|
||||
|
||||
VS Code includes a set of built-in extensions located in the [extensions](extensions) folder, including grammars and snippets for many languages. Extensions that provide rich language support (code completion, Go to Definition) for a language have the suffix `language-features`. For example, the `json` extension provides coloring for `JSON` and the `json-language-features` extension provides rich language support for `JSON`.
|
||||
|
||||
## Development Container
|
||||
|
||||
This repository includes a Visual Studio Code Dev Containers / GitHub Codespaces development container.
|
||||
|
||||
* For [Dev Containers](https://aka.ms/vscode-remote/download/containers), use the **Dev Containers: Clone Repository in Container Volume...** command which creates a Docker volume for better disk I/O on macOS and Windows.
|
||||
* If you already have VS Code and Docker installed, you can also click [here](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/microsoft/vscode) to get started. This will cause VS Code to automatically install the Dev Containers extension if needed, clone the source code into a container volume, and spin up a dev container for use.
|
||||
|
||||
* For Codespaces, install the [GitHub Codespaces](https://marketplace.visualstudio.com/items?itemName=GitHub.codespaces) extension in VS Code, and use the **Codespaces: Create New Codespace** command.
|
||||
|
||||
Docker / the Codespace should have at least **4 Cores and 6 GB of RAM (8 GB recommended)** to run full build. See the [development container README](.devcontainer/README.md) for more information.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
Licensed under the [MIT](LICENSE.txt) license.
|
||||
## Support
|
||||
Feel free to reach out in our Discord or contact us via email: hello@voideditor.com.
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
<!-- BEGIN MICROSOFT SECURITY.MD V0.0.9 BLOCK -->
|
||||
|
||||
## Security
|
||||
|
||||
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) and [Xamarin](https://github.com/xamarin).
|
||||
|
||||
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/security.md/definition), please report it to us as described below.
|
||||
|
||||
## Reporting Security Issues
|
||||
|
||||
**Please do not report security vulnerabilities through public GitHub issues.**
|
||||
|
||||
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report).
|
||||
|
||||
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://aka.ms/security.md/msrc/pgp).
|
||||
|
||||
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).
|
||||
|
||||
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:
|
||||
|
||||
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
|
||||
* Full paths of source file(s) related to the manifestation of the issue
|
||||
* The location of the affected source code (tag/branch/commit or direct URL)
|
||||
* Any special configuration required to reproduce the issue
|
||||
* Step-by-step instructions to reproduce the issue
|
||||
* Proof-of-concept or exploit code (if possible)
|
||||
* Impact of the issue, including how an attacker might exploit the issue
|
||||
|
||||
This information will help us triage your report more quickly.
|
||||
|
||||
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://aka.ms/security.md/msrc/bounty) page for more details about our active programs.
|
||||
|
||||
## Preferred Languages
|
||||
|
||||
We prefer all communications to be in English.
|
||||
|
||||
## Policy
|
||||
|
||||
Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd).
|
||||
|
||||
<!-- END MICROSOFT SECURITY.MD BLOCK -->
|
||||
@@ -0,0 +1,37 @@
|
||||
# Useful links
|
||||
|
||||
The Void team put together this list of links to get up and running with VSCode's sourcecode. We hope it's helpful!
|
||||
|
||||
## Contributing
|
||||
|
||||
- [How VSCode's sourcecode is organized](https://github.com/microsoft/vscode/wiki/Source-Code-Organization) - this explains where the entry point files are, what `browser/` and `common/` mean, etc. This is the most important read on this whole list! We recommend reading the whole thing.
|
||||
|
||||
- [Built-in VSCode styles](https://code.visualstudio.com/api/references/theme-color) - CSS variables that are built into VSCode. Use `var(--vscode-{theme but replacing . with -})`. You can also see their [Webview theming guide](https://code.visualstudio.com/api/extension-guides/webview#theming-webview-content).
|
||||
|
||||
## Beginners / Getting started
|
||||
|
||||
- [VSCode UI guide](https://code.visualstudio.com/docs/getstarted/userinterface) - covers auxbar, panels, etc.
|
||||
|
||||
- [UX guide](https://code.visualstudio.com/api/ux-guidelines/overview) - covers Containers, Views, Items, etc.
|
||||
|
||||
|
||||
## Misc
|
||||
|
||||
- [Every command](https://code.visualstudio.com/api/references/commands) built-in to VSCode - not used often, but here for reference.
|
||||
|
||||
|
||||
## VSCode's Extension API
|
||||
|
||||
Void is no longer an extension, so these links are no longer required, but they might be useful if we ever build an extension again.
|
||||
|
||||
- [Files you need in an extension](https://code.visualstudio.com/api/get-started/extension-anatomy).
|
||||
|
||||
- [An extension's `package.json` schema](https://code.visualstudio.com/api/references/extension-manifest).
|
||||
|
||||
- ["Contributes" Guide](https://code.visualstudio.com/api/references/contribution-points) - the `"contributes"` part of `package.json` is how an extension mounts.
|
||||
|
||||
- [The Full VSCode Extension API](https://code.visualstudio.com/api/references/vscode-api) - look on the right side for organization. The [bottom](https://code.visualstudio.com/api/references/vscode-api#api-patterns) of the page is easy to miss but is useful - cancellation tokens, events, disposables.
|
||||
|
||||
- [Activation events](https://code.visualstudio.com/api/references/activation-events) you can define in `package.json` (not the most useful).
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ async function main(buildDir) {
|
||||
// universal will get its copy from the x64 build.
|
||||
if (arch !== 'universal') {
|
||||
await (0, cross_spawn_promise_1.spawn)('plutil', [
|
||||
'-insert',
|
||||
'-replace', // Void changed this to replace
|
||||
'NSAppleEventsUsageDescription',
|
||||
'-string',
|
||||
'An application in Visual Studio Code wants to use AppleScript.',
|
||||
|
||||
@@ -89,7 +89,7 @@ async function main(buildDir?: string): Promise<void> {
|
||||
// universal will get its copy from the x64 build.
|
||||
if (arch !== 'universal') {
|
||||
await spawn('plutil', [
|
||||
'-insert',
|
||||
'-replace', // Void changed this to replace
|
||||
'NSAppleEventsUsageDescription',
|
||||
'-string',
|
||||
'An application in Visual Studio Code wants to use AppleScript.',
|
||||
|
||||
@@ -137,6 +137,8 @@ module.exports.indentationFilter = [
|
||||
'!extensions/ipynb/notebook-out/**',
|
||||
'!extensions/notebook-renderers/renderer-out/*.js',
|
||||
'!extensions/simple-browser/media/*.js',
|
||||
|
||||
'!extensions/open-remote-ssh/out/*.js', // Void added this
|
||||
];
|
||||
|
||||
module.exports.copyrightFilter = [
|
||||
|
||||
@@ -71,6 +71,9 @@ const compilations = [
|
||||
|
||||
'.vscode/extensions/vscode-selfhost-test-provider/tsconfig.json',
|
||||
'.vscode/extensions/vscode-selfhost-import-aid/tsconfig.json',
|
||||
|
||||
'extensions/open-remote-ssh/tsconfig.json', // Void added this
|
||||
|
||||
];
|
||||
|
||||
const getBaseUrl = out => `https://main.vscode-cdn.net/sourcemaps/${commit}/${out}`;
|
||||
|
||||
@@ -281,6 +281,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op
|
||||
this.emit('data', file);
|
||||
}));
|
||||
|
||||
// Void - this is important, creates the product.json in .app
|
||||
let productJsonContents;
|
||||
const productJsonStream = gulp.src(['product.json'], { base: '.' })
|
||||
.pipe(json({ commit, date: readISODate('out-build'), checksums, version }))
|
||||
|
||||
@@ -261,53 +261,54 @@ function createGitIndexVinyls(paths) {
|
||||
return pall(fns, { concurrency: 4 }).then((r) => r.filter((p) => !!p));
|
||||
}
|
||||
|
||||
// this allows us to run hygiene as a git pre-commit hook
|
||||
if (require.main === module) {
|
||||
const cp = require('child_process');
|
||||
// Void - NO PRE COMMIT HOOKS!!!! for now... - Void team
|
||||
// // this allows us to run hygiene as a git pre-commit hook
|
||||
// if (require.main === module) {
|
||||
// const cp = require('child_process');
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
process.exit(1);
|
||||
});
|
||||
// process.on('unhandledRejection', (reason, p) => {
|
||||
// console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
// process.exit(1);
|
||||
// });
|
||||
|
||||
if (process.argv.length > 2) {
|
||||
hygiene(process.argv.slice(2)).on('error', (err) => {
|
||||
console.error();
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
} else {
|
||||
cp.exec(
|
||||
'git diff --cached --name-only',
|
||||
{ maxBuffer: 2000 * 1024 },
|
||||
(err, out) => {
|
||||
if (err) {
|
||||
console.error();
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
// if (process.argv.length > 2) {
|
||||
// hygiene(process.argv.slice(2)).on('error', (err) => {
|
||||
// console.error();
|
||||
// console.error(err);
|
||||
// process.exit(1);
|
||||
// });
|
||||
// } else {
|
||||
// cp.exec(
|
||||
// 'git diff --cached --name-only',
|
||||
// { maxBuffer: 2000 * 1024 },
|
||||
// (err, out) => {
|
||||
// if (err) {
|
||||
// console.error();
|
||||
// console.error(err);
|
||||
// process.exit(1);
|
||||
// }
|
||||
|
||||
const some = out.split(/\r?\n/).filter((l) => !!l);
|
||||
// const some = out.split(/\r?\n/).filter((l) => !!l);
|
||||
|
||||
if (some.length > 0) {
|
||||
console.log('Reading git index versions...');
|
||||
// if (some.length > 0) {
|
||||
// console.log('Reading git index versions...');
|
||||
|
||||
createGitIndexVinyls(some)
|
||||
.then(
|
||||
(vinyls) =>
|
||||
new Promise((c, e) =>
|
||||
hygiene(es.readArray(vinyls).pipe(filter(all)))
|
||||
.on('end', () => c())
|
||||
.on('error', e)
|
||||
)
|
||||
)
|
||||
.catch((err) => {
|
||||
console.error();
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
// createGitIndexVinyls(some)
|
||||
// .then(
|
||||
// (vinyls) =>
|
||||
// new Promise((c, e) =>
|
||||
// hygiene(es.readArray(vinyls).pipe(filter(all)))
|
||||
// .on('end', () => c())
|
||||
// .on('error', e)
|
||||
// )
|
||||
// )
|
||||
// .catch((err) => {
|
||||
// console.error();
|
||||
// console.error(err);
|
||||
// process.exit(1);
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -90,7 +90,7 @@ function darwinBundleDocumentType(extensions, icon, nameOrSuffix, utis) {
|
||||
role: 'Editor',
|
||||
ostypes: ['TEXT', 'utxt', 'TUTX', '****'],
|
||||
extensions,
|
||||
iconFile: 'resources/darwin/' + icon.toLowerCase() + '.icns',
|
||||
iconFile: 'resources/darwin/' + icon.toLowerCase() + '.icns', // <-- Void icon code.icns
|
||||
utis
|
||||
};
|
||||
}
|
||||
@@ -215,7 +215,7 @@ exports.config = {
|
||||
darwinForceDarkModeSupport: true,
|
||||
darwinCredits: darwinCreditsTemplate ? Buffer.from(darwinCreditsTemplate({ commit: commit, date: new Date().toISOString() })) : undefined,
|
||||
linuxExecutableName: product.applicationName,
|
||||
winIcon: 'resources/win32/code.ico',
|
||||
winIcon: 'resources/win32/code.ico', // <-- Void icon
|
||||
token: process.env['GITHUB_TOKEN'],
|
||||
repo: product.electronRepository || undefined,
|
||||
validateChecksum: true,
|
||||
|
||||
@@ -68,7 +68,7 @@ function darwinBundleDocumentType(extensions: string[], icon: string, nameOrSuff
|
||||
role: 'Editor',
|
||||
ostypes: ['TEXT', 'utxt', 'TUTX', '****'],
|
||||
extensions,
|
||||
iconFile: 'resources/darwin/' + icon.toLowerCase() + '.icns',
|
||||
iconFile: 'resources/darwin/' + icon.toLowerCase() + '.icns', // <-- Void icon code.icns
|
||||
utis
|
||||
};
|
||||
}
|
||||
@@ -196,7 +196,7 @@ export const config = {
|
||||
darwinForceDarkModeSupport: true,
|
||||
darwinCredits: darwinCreditsTemplate ? Buffer.from(darwinCreditsTemplate({ commit: commit, date: new Date().toISOString() })) : undefined,
|
||||
linuxExecutableName: product.applicationName,
|
||||
winIcon: 'resources/win32/code.ico',
|
||||
winIcon: 'resources/win32/code.ico', // <-- Void icon
|
||||
token: process.env['GITHUB_TOKEN'],
|
||||
repo: product.electronRepository || undefined,
|
||||
validateChecksum: true,
|
||||
|
||||
@@ -54,6 +54,8 @@ const dirs = [
|
||||
'test/smoke',
|
||||
'.vscode/extensions/vscode-selfhost-import-aid',
|
||||
'.vscode/extensions/vscode-selfhost-test-provider',
|
||||
'extensions/open-remote-ssh', // Void added this
|
||||
|
||||
];
|
||||
|
||||
if (fs.existsSync(`${__dirname}/../../.build/distro/npm`)) {
|
||||
|
||||
@@ -20,8 +20,10 @@ Compression=lzma
|
||||
SolidCompression=yes
|
||||
AppMutex={code:GetAppMutex}
|
||||
SetupMutex={#AppMutex}setup
|
||||
WizardImageFile="{#RepoDir}\resources\win32\inno-big-100.bmp,{#RepoDir}\resources\win32\inno-big-125.bmp,{#RepoDir}\resources\win32\inno-big-150.bmp,{#RepoDir}\resources\win32\inno-big-175.bmp,{#RepoDir}\resources\win32\inno-big-200.bmp,{#RepoDir}\resources\win32\inno-big-225.bmp,{#RepoDir}\resources\win32\inno-big-250.bmp"
|
||||
WizardSmallImageFile="{#RepoDir}\resources\win32\inno-small-100.bmp,{#RepoDir}\resources\win32\inno-small-125.bmp,{#RepoDir}\resources\win32\inno-small-150.bmp,{#RepoDir}\resources\win32\inno-small-175.bmp,{#RepoDir}\resources\win32\inno-small-200.bmp,{#RepoDir}\resources\win32\inno-small-225.bmp,{#RepoDir}\resources\win32\inno-small-250.bmp"
|
||||
; this is a // Void icon comment. Old: WizardImageFile="{#RepoDir}\resources\win32\inno-big-100.bmp,{#RepoDir}\resources\win32\inno-big-125.bmp,{#RepoDir}\resources\win32\inno-big-150.bmp,{#RepoDir}\resources\win32\inno-big-175.bmp,{#RepoDir}\resources\win32\inno-big-200.bmp,{#RepoDir}\resources\win32\inno-big-225.bmp,{#RepoDir}\resources\win32\inno-big-250.bmp"
|
||||
; this is a // Void icon comment. Old: WizardSmallImageFile="{#RepoDir}\resources\win32\inno-small-100.bmp,{#RepoDir}\resources\win32\inno-small-125.bmp,{#RepoDir}\resources\win32\inno-small-150.bmp,{#RepoDir}\resources\win32\inno-small-175.bmp,{#RepoDir}\resources\win32\inno-small-200.bmp,{#RepoDir}\resources\win32\inno-small-225.bmp,{#RepoDir}\resources\win32\inno-small-250.bmp"
|
||||
; COMMENTED OUT WizardImageFile=""
|
||||
WizardSmallImageFile="{#RepoDir}\resources\win32\inno-void.bmp,{#RepoDir}\resources\win32\inno-void.bmp,{#RepoDir}\resources\win32\inno-void.bmp,{#RepoDir}\resources\win32\inno-void.bmp,{#RepoDir}\resources\win32\inno-void.bmp,{#RepoDir}\resources\win32\inno-void.bmp,{#RepoDir}\resources\win32\inno-void.bmp"
|
||||
SetupIconFile={#RepoDir}\resources\win32\code.ico
|
||||
UninstallDisplayIcon={app}\{#ExeBasename}.exe
|
||||
ChangesEnvironment=true
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
#!/bin/bash
|
||||
set -e # Exit on error
|
||||
set -x # Print commands as they are executed
|
||||
|
||||
# Configuration
|
||||
APP_NAME="void"
|
||||
APP_VERSION="1.0.0"
|
||||
ARCH="x86_64"
|
||||
|
||||
export ARCH
|
||||
|
||||
# Check if void binary exists in current directory
|
||||
if [ ! -f "./void" ]; then
|
||||
echo "Error: void binary not found in current directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if icon exists
|
||||
if [ ! -f "./void.png" ]; then
|
||||
echo "Error: void.png icon not found in current directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create temporary directory
|
||||
TEMP_DIR="$(mktemp -d)"
|
||||
echo "Created temporary directory: $TEMP_DIR"
|
||||
APP_DIR="$TEMP_DIR/$APP_NAME.AppDir"
|
||||
|
||||
# Create basic AppDir structure
|
||||
mkdir -pv "$APP_DIR/usr/bin"
|
||||
mkdir -pv "$APP_DIR/usr/lib"
|
||||
mkdir -pv "$APP_DIR/usr/share/applications"
|
||||
mkdir -pv "$APP_DIR/usr/share/icons/hicolor/256x256/apps"
|
||||
|
||||
# Exclude create-appimage.sh and appimagetool-x86_64.AppImage from being copied
|
||||
echo "Copying files excluding create-appimage.sh and appimagetool-x86_64.AppImage..."
|
||||
for file in ./*; do
|
||||
if [[ "$file" != "./create-appimage.sh" && "$file" != "./appimagetool-x86_64.AppImage" ]]; then
|
||||
cp -rv "$file" "$APP_DIR/usr/bin/"
|
||||
fi
|
||||
done
|
||||
|
||||
# Copy the icon to required locations
|
||||
cp -v ./void.png "$APP_DIR/void.png"
|
||||
cp -v ./void.png "$APP_DIR/usr/share/icons/hicolor/256x256/apps/void.png"
|
||||
|
||||
# Copy dependencies with error checking
|
||||
echo "Copying dependencies..."
|
||||
for lib in $(ldd ./void | grep "=> /" | awk '{print $3}'); do
|
||||
if [ -f "$lib" ]; then
|
||||
cp -v "$lib" "$APP_DIR/usr/lib/" || echo "Failed to copy $lib"
|
||||
else
|
||||
echo "Warning: Library $lib not found"
|
||||
fi
|
||||
done
|
||||
|
||||
# Create desktop file with error checking
|
||||
echo "Creating desktop file..."
|
||||
if ! cat > "$APP_DIR/$APP_NAME.desktop" <<EOF
|
||||
[Desktop Entry]
|
||||
Name=$APP_NAME
|
||||
Exec=void
|
||||
Icon=void
|
||||
Type=Application
|
||||
Categories=Utility;
|
||||
Comment=Void Linux Application
|
||||
EOF
|
||||
then
|
||||
echo "Error creating desktop file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Make desktop file executable
|
||||
chmod +x "$APP_DIR/$APP_NAME.desktop"
|
||||
|
||||
# Copy the desktop file to the applications directory
|
||||
cp -v "$APP_DIR/$APP_NAME.desktop" "$APP_DIR/usr/share/applications/"
|
||||
|
||||
# Create AppRun with error checking
|
||||
echo "Creating AppRun..."
|
||||
if ! cat > "$APP_DIR/AppRun" <<EOF
|
||||
#!/bin/bash
|
||||
cd "\$(dirname "\$0")/usr/bin"
|
||||
export LD_LIBRARY_PATH="\$APPDIR/usr/lib:\$LD_LIBRARY_PATH"
|
||||
exec ./void "\$@"
|
||||
EOF
|
||||
then
|
||||
echo "Error creating AppRun"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Make AppRun executable
|
||||
chmod +x "$APP_DIR/AppRun"
|
||||
|
||||
# Download appimagetool if not present in the current directory
|
||||
if [ ! -f "./appimagetool-x86_64.AppImage" ]; then
|
||||
echo "Downloading appimagetool-x86_64.AppImage..."
|
||||
wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||
chmod +x appimagetool-x86_64.AppImage
|
||||
else
|
||||
echo "appimagetool-x86_64.AppImage is already present."
|
||||
fi
|
||||
|
||||
# Create the AppImage
|
||||
echo "Creating AppImage..."
|
||||
ARCH=x86_64 ./appimagetool-x86_64.AppImage "$APP_DIR" "${APP_NAME}-${APP_VERSION}-${ARCH}.AppImage"
|
||||
|
||||
# Cleanup
|
||||
echo "Cleaning up..."
|
||||
rm -rf "$TEMP_DIR"
|
||||
|
||||
echo "AppImage creation complete!"
|
||||
@@ -11,6 +11,7 @@ import { fileURLToPath } from 'url';
|
||||
import stylisticTs from '@stylistic/eslint-plugin-ts';
|
||||
import pluginLocal from 'eslint-plugin-local';
|
||||
import pluginJsdoc from 'eslint-plugin-jsdoc';
|
||||
// import pluginReact from 'eslint-plugin-react'; // Void
|
||||
|
||||
import pluginHeader from 'eslint-plugin-header';
|
||||
pluginHeader.rules.header.meta.schema = false;
|
||||
@@ -37,7 +38,7 @@ export default tseslint.config(
|
||||
},
|
||||
rules: {
|
||||
'constructor-super': 'warn',
|
||||
'curly': 'warn',
|
||||
'curly': 'off', // <-- Void
|
||||
'eqeqeq': 'warn',
|
||||
'prefer-const': [
|
||||
'warn',
|
||||
@@ -80,7 +81,7 @@ export default tseslint.config(
|
||||
'local/code-parameter-properties-must-have-explicit-accessibility': 'warn',
|
||||
'local/code-no-nls-in-standalone-editor': 'warn',
|
||||
'local/code-no-potentially-unsafe-disposables': 'warn',
|
||||
'local/code-no-dangerous-type-assertions': 'warn',
|
||||
'local/code-no-dangerous-type-assertions': 'off',
|
||||
'local/code-no-standalone-editor': 'warn',
|
||||
'local/code-no-unexternalized-strings': 'warn',
|
||||
'local/code-must-use-super-dispose': 'warn',
|
||||
@@ -110,16 +111,17 @@ export default tseslint.config(
|
||||
]
|
||||
}
|
||||
],
|
||||
'header/header': [
|
||||
2,
|
||||
'block',
|
||||
[
|
||||
'---------------------------------------------------------------------------------------------',
|
||||
' * Copyright (c) Microsoft Corporation. All rights reserved.',
|
||||
' * Licensed under the MIT License. See License.txt in the project root for license information.',
|
||||
' *--------------------------------------------------------------------------------------------'
|
||||
]
|
||||
]
|
||||
// Void - this should only apply to workbench/void/
|
||||
// 'header/header': [
|
||||
// 2,
|
||||
// 'block',
|
||||
// [
|
||||
// '/*--------------------------------------------------------------------------------------',
|
||||
// '* Copyright 2025 Glass Devtools, Inc. All rights reserved.',
|
||||
// '* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.',
|
||||
// '*--------------------------------------------------------------------------------------*/',
|
||||
// ]
|
||||
// ]
|
||||
},
|
||||
},
|
||||
// TS
|
||||
@@ -137,7 +139,7 @@ export default tseslint.config(
|
||||
'jsdoc': pluginJsdoc,
|
||||
},
|
||||
rules: {
|
||||
'@stylistic/ts/semi': 'warn',
|
||||
'@stylistic/ts/semi': 'off', // <-- Void
|
||||
'@stylistic/ts/member-delimiter-style': 'warn',
|
||||
'local/code-no-unused-expressions': [
|
||||
'warn',
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
## 0.0.48
|
||||
- Support `%n` in ProxyCommand
|
||||
- fix: add missing direct @types/ssh2-stream dependency (#177)
|
||||
- fix Win32 internal error (#178)
|
||||
|
||||
## 0.0.47
|
||||
- Add support for loong64 (#175)
|
||||
- Add s390x support (#174)
|
||||
- Support vscodium alpine reh (#142)
|
||||
|
||||
## 0.0.46
|
||||
- Add riscv64 support (#147)
|
||||
|
||||
## 0.0.45
|
||||
- Use windows-x64 server on windows-arm64
|
||||
|
||||
## 0.0.44
|
||||
- Update ssh2 lib
|
||||
- Properly set extensionHost env variables
|
||||
|
||||
## 0.0.43
|
||||
- Fix parsing multiple include directives
|
||||
|
||||
## 0.0.42
|
||||
- Fix remote label to show port when connecting to a port other than 22
|
||||
|
||||
## 0.0.41
|
||||
- Take into account parsed port from ssh destination. Fixes (#110)
|
||||
|
||||
## 0.0.40
|
||||
- Update ssh-config package
|
||||
|
||||
## 0.0.39
|
||||
|
||||
- output error messages when downloading vscode server (#39)
|
||||
- Add PreferredAuthentications support (#97)
|
||||
|
||||
## 0.0.38
|
||||
|
||||
- Enable remote support for ppc64le (#93)
|
||||
|
||||
## 0.0.37
|
||||
|
||||
- Default to Current OS User in Connection String if No User Provided (#91)
|
||||
- Add support for (unofficial) DragonFly reh (#86)
|
||||
|
||||
## 0.0.36
|
||||
|
||||
- Make wget support continue download (#85)
|
||||
|
||||
## 0.0.35
|
||||
|
||||
- Fixes hardcoded agentsock for windows breaks pageant compatibility (#81)
|
||||
|
||||
## 0.0.34
|
||||
|
||||
- Add remote.SSH.connectTimeout setting
|
||||
- adding %r username replacement to proxycommand (#77)
|
||||
|
||||
## 0.0.33
|
||||
|
||||
- feat: support %r user substitution in proxycommand
|
||||
|
||||
## 0.0.32
|
||||
|
||||
- feat: use serverDownloadUrlTemplate from product.json (#59)
|
||||
|
||||
## 0.0.31
|
||||
|
||||
- feat: support glob patterns in SSH include directives
|
||||
|
||||
## 0.0.30
|
||||
|
||||
- feat: support file patterns in SSH include directives
|
||||
@@ -0,0 +1,48 @@
|
||||
# Open Remote - SSH
|
||||
|
||||
## SSH Host Requirements
|
||||
|
||||
You can connect to a running SSH server on the following platforms.
|
||||
|
||||
**Supported**:
|
||||
|
||||
- x86_64 Debian 8+, Ubuntu 16.04+, CentOS / RHEL 7+ Linux.
|
||||
- ARMv7l (AArch32) Raspbian Stretch/9+ (32-bit).
|
||||
- ARMv8l (AArch64) Ubuntu 18.04+ (64-bit).
|
||||
- macOS 10.14+ (Mojave)
|
||||
- Windows 10+
|
||||
- FreeBSD 13 (Requires manual remote-extension-host installation)
|
||||
- DragonFlyBSD (Requires manual remote-extension-host installation)
|
||||
|
||||
## Requirements
|
||||
|
||||
**Activation**
|
||||
|
||||
Enable the extension in your `argv.json`
|
||||
|
||||
```json
|
||||
{
|
||||
...
|
||||
"enable-proposed-api": [
|
||||
...,
|
||||
"jeanp413.open-remote-ssh",
|
||||
]
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
which you can open by running the `Preferences: Configure Runtime Arguments` command.
|
||||
The file is located in `~/.vscode-oss/argv.json`.
|
||||
|
||||
**Alpine linux**
|
||||
|
||||
When running on alpine linux, the packages `libstdc++` and `bash` are necessary and can be installed via
|
||||
running
|
||||
|
||||
```bash
|
||||
sudo apk add bash libstdc++
|
||||
```
|
||||
|
||||
## SSH configuration file
|
||||
|
||||
[OpenSSH](https://www.openssh.com/) supports using a [configuration file](https://linuxize.com/post/using-the-ssh-config-file/) to store all your different SSH connections. To use an SSH config file, run the `Remote-SSH: Open SSH Configuration File...` command.
|
||||
@@ -0,0 +1,17 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
//@ts-check
|
||||
|
||||
'use strict';
|
||||
|
||||
const withBrowserDefaults = require('../shared.webpack.config').browser;
|
||||
|
||||
module.exports = withBrowserDefaults({
|
||||
context: __dirname,
|
||||
entry: {
|
||||
extension: './src/extension.ts'
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
//@ts-check
|
||||
|
||||
'use strict';
|
||||
|
||||
const withDefaults = require('../shared.webpack.config');
|
||||
const { IgnorePlugin } = require('webpack');
|
||||
|
||||
module.exports = withDefaults({
|
||||
context: __dirname,
|
||||
resolve: {
|
||||
mainFields: ['module', 'main']
|
||||
},
|
||||
entry: {
|
||||
extension: './src/extension.ts',
|
||||
},
|
||||
externals: {
|
||||
vscode: "commonjs vscode",
|
||||
bufferutil: "commonjs bufferutil",
|
||||
"utf-8-validate": "commonjs utf-8-validate",
|
||||
},
|
||||
plugins: [
|
||||
new IgnorePlugin({
|
||||
resourceRegExp: /crypto\/build\/Release\/sshcrypto\.node$/,
|
||||
}),
|
||||
new IgnorePlugin({
|
||||
resourceRegExp: /cpu-features/,
|
||||
})
|
||||
]
|
||||
});
|
||||
@@ -0,0 +1,370 @@
|
||||
{
|
||||
"name": "open-remote-ssh",
|
||||
"version": "0.0.48",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "open-remote-ssh",
|
||||
"version": "0.0.48",
|
||||
"dependencies": {
|
||||
"@jeanp413/ssh-config": "^4.3.1",
|
||||
"glob": "^9.3.1",
|
||||
"simple-socks": "git+https://github.com/jeanp413/simple-socks#main",
|
||||
"socks": "^2.5.0",
|
||||
"ssh2": "git+https://github.com/jeanp413/ssh2#master"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ssh2": "^0.5.52",
|
||||
"@types/ssh2-streams": "0.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"vscode": "^1.70.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime-corejs3": {
|
||||
"version": "7.26.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.26.0.tgz",
|
||||
"integrity": "sha512-YXHu5lN8kJCb1LOb9PgV6pvak43X2h4HvRApcN5SdWeaItQOzfn1hgP6jasD6KWQyJDBxrVmA9o9OivlnNJK/w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"core-js-pure": "^3.30.2",
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jeanp413/ssh-config": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@jeanp413/ssh-config/-/ssh-config-4.3.1.tgz",
|
||||
"integrity": "sha512-x0EaWRdjs5sPDNmYr11wVB1GdwWQgRekc7SbueuO5FK7YZUav98qZKtZZU5iSDKyxJkooCs3rgVizB1wIWrF7g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.10.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz",
|
||||
"integrity": "sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ssh2": {
|
||||
"version": "0.5.52",
|
||||
"resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-0.5.52.tgz",
|
||||
"integrity": "sha512-lbLLlXxdCZOSJMCInKH2+9V/77ET2J6NPQHpFI0kda61Dd1KglJs+fPQBchizmzYSOJBgdTajhPqBO1xxLywvg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"@types/ssh2-streams": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ssh2-streams": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/ssh2-streams/-/ssh2-streams-0.1.12.tgz",
|
||||
"integrity": "sha512-Sy8tpEmCce4Tq0oSOYdfqaBpA3hDM8SoxoFh5vzFsu2oL+znzGz8oVWW7xb4K920yYMUY+PIG31qZnFMfPWNCg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/asn1": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
|
||||
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": "~2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bcrypt-pbkdf": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
|
||||
"integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"tweetnacl": "^0.14.3"
|
||||
}
|
||||
},
|
||||
"node_modules/binary": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz",
|
||||
"integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffers": "~0.1.1",
|
||||
"chainsaw": "~0.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/buffers": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz",
|
||||
"integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==",
|
||||
"engines": {
|
||||
"node": ">=0.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/buildcheck": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz",
|
||||
"integrity": "sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/chainsaw": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz",
|
||||
"integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==",
|
||||
"license": "MIT/X11",
|
||||
"dependencies": {
|
||||
"traverse": ">=0.3.0 <0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/core-js-pure": {
|
||||
"version": "3.40.0",
|
||||
"resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.40.0.tgz",
|
||||
"integrity": "sha512-AtDzVIgRrmRKQai62yuSIN5vNiQjcJakJb4fbhVw3ehxx7Lohphvw9SGNWKhLFqSxC4ilD0g/L1huAYFQU3Q6A==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/core-js"
|
||||
}
|
||||
},
|
||||
"node_modules/cpu-features": {
|
||||
"version": "0.0.10",
|
||||
"resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz",
|
||||
"integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"buildcheck": "~0.0.6",
|
||||
"nan": "^2.19.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "9.3.5",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz",
|
||||
"integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"minimatch": "^8.0.2",
|
||||
"minipass": "^4.2.4",
|
||||
"path-scurry": "^1.6.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/ip-address": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
|
||||
"integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jsbn": "1.1.0",
|
||||
"sprintf-js": "^1.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/jsbn": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
|
||||
"integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "10.4.3",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
|
||||
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "8.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz",
|
||||
"integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/minipass": {
|
||||
"version": "4.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz",
|
||||
"integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/nan": {
|
||||
"version": "2.22.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz",
|
||||
"integrity": "sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/path-scurry": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
|
||||
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"lru-cache": "^10.2.0",
|
||||
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/path-scurry/node_modules/minipass": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
||||
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/simple-socks": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "git+ssh://git@github.com/jeanp413/simple-socks.git#2ac739301a82d6baff04804ed494436a026acb60",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime-corejs3": "^7.16.8",
|
||||
"binary": "^0.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/smart-buffer": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
|
||||
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6.0.0",
|
||||
"npm": ">= 3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socks": {
|
||||
"version": "2.8.3",
|
||||
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz",
|
||||
"integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ip-address": "^9.0.5",
|
||||
"smart-buffer": "^4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.0.0",
|
||||
"npm": ">= 3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sprintf-js": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
|
||||
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/ssh2": {
|
||||
"version": "1.14.0",
|
||||
"resolved": "git+ssh://git@github.com/jeanp413/ssh2.git#a169f627213aa663e0aa2fd2f0ef5c8931890c26",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"asn1": "^0.2.6",
|
||||
"bcrypt-pbkdf": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.16.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"cpu-features": "~0.0.9",
|
||||
"nan": "^2.17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/traverse": {
|
||||
"version": "0.3.9",
|
||||
"resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz",
|
||||
"integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==",
|
||||
"license": "MIT/X11",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/tweetnacl": {
|
||||
"version": "0.14.5",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||
"integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==",
|
||||
"license": "Unlicense"
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.20.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
|
||||
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
{
|
||||
"name": "open-remote-ssh",
|
||||
"displayName": "Open Remote - SSH",
|
||||
"publisher": "voideditor",
|
||||
"description": "Use any remote machine with a SSH server as your development environment.",
|
||||
"version": "0.0.48",
|
||||
"icon": "resources/icon.png",
|
||||
"engines": {
|
||||
"vscode": "^1.70.2"
|
||||
},
|
||||
"extensionKind": [
|
||||
"ui"
|
||||
],
|
||||
"enabledApiProposals": [
|
||||
"resolvers",
|
||||
"contribViewsRemote"
|
||||
],
|
||||
"keywords": [
|
||||
"remote development",
|
||||
"remote",
|
||||
"ssh"
|
||||
],
|
||||
"api": "none",
|
||||
"activationEvents": [
|
||||
"onCommand:openremotessh.openEmptyWindow",
|
||||
"onCommand:openremotessh.openEmptyWindowInCurrentWindow",
|
||||
"onCommand:openremotessh.openConfigFile",
|
||||
"onCommand:openremotessh.showLog",
|
||||
"onResolveRemoteAuthority:ssh-remote",
|
||||
"onView:sshHosts"
|
||||
],
|
||||
"main": "./out/extension.js",
|
||||
"contributes": {
|
||||
"configuration": {
|
||||
"title": "Remote - SSH",
|
||||
"properties": {
|
||||
"remote.SSH.configFile": {
|
||||
"type": "string",
|
||||
"description": "The absolute file path to a custom SSH config file.",
|
||||
"default": "",
|
||||
"scope": "application"
|
||||
},
|
||||
"remote.SSH.connectTimeout": {
|
||||
"type": "number",
|
||||
"description": "Specifies the timeout in seconds used for the SSH command that connects to the remote.",
|
||||
"default": 60,
|
||||
"scope": "application",
|
||||
"minimum": 1
|
||||
},
|
||||
"remote.SSH.defaultExtensions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "List of extensions that should be installed automatically on all SSH hosts.",
|
||||
"scope": "application"
|
||||
},
|
||||
"remote.SSH.enableDynamicForwarding": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to use SSH dynamic forwarding to allow setting up new port tunnels over an existing SSH connection.",
|
||||
"scope": "application",
|
||||
"default": true
|
||||
},
|
||||
"remote.SSH.enableAgentForwarding": {
|
||||
"type": "boolean",
|
||||
"markdownDescription": "Enable fixing the remote environment so that the SSH config option `ForwardAgent` will take effect as expected from VS Code's remote extension host.",
|
||||
"scope": "application",
|
||||
"default": true
|
||||
},
|
||||
"remote.SSH.serverDownloadUrlTemplate": {
|
||||
"type": "string",
|
||||
"description": "The URL from where the vscode server will be downloaded. You can use the following variables and they will be replaced dynamically:\n- ${quality}: vscode server quality, e.g. stable or insiders\n- ${version}: vscode server version, e.g. 1.69.0\n- ${commit}: vscode server release commit\n- ${arch}: vscode server arch, e.g. x64, armhf, arm64\n- ${release}: release number",
|
||||
"scope": "application",
|
||||
"default": "https://github.com/voideditor/${NAME_OF_REPO}/releases/download/${version}.${release}/void-server-${os}-${arch}-${version}.${release}.tar.gz"
|
||||
},
|
||||
"remote.SSH.remotePlatform": {
|
||||
"type": "object",
|
||||
"description": "A map of the remote hostname to the platform for that remote. Valid values: linux, macos, windows.",
|
||||
"scope": "application",
|
||||
"default": {},
|
||||
"additionalProperties": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"linux",
|
||||
"macos",
|
||||
"windows"
|
||||
]
|
||||
}
|
||||
},
|
||||
"remote.SSH.remoteServerListenOnSocket": {
|
||||
"type": "boolean",
|
||||
"description": "When true, the remote vscode server will listen on a socket path instead of opening a port. Only valid for Linux and macOS remotes. Requires `AllowStreamLocalForwarding` to be enabled for the SSH server.",
|
||||
"default": false
|
||||
},
|
||||
"remote.SSH.experimental.serverBinaryName": {
|
||||
"type": "string",
|
||||
"description": "**Experimental:** The name of the server binary, use this **only if** you are using a client without a corresponding server release",
|
||||
"scope": "application",
|
||||
"default": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"views": {
|
||||
"remote": [
|
||||
{
|
||||
"id": "sshHosts",
|
||||
"name": "SSH Targets",
|
||||
"group": "targets@1",
|
||||
"remoteName": "ssh-remote"
|
||||
}
|
||||
]
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"command": "openremotessh.openEmptyWindow",
|
||||
"title": "Connect to Host...",
|
||||
"category": "Remote-SSH"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.openEmptyWindowInCurrentWindow",
|
||||
"title": "Connect Current Window to Host...",
|
||||
"category": "Remote-SSH"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.openConfigFile",
|
||||
"title": "Open SSH Configuration File...",
|
||||
"category": "Remote-SSH"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.showLog",
|
||||
"title": "Show Log",
|
||||
"category": "Remote-SSH"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.explorer.emptyWindowInNewWindow",
|
||||
"title": "Connect to Host in New Window",
|
||||
"icon": "$(empty-window)"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.explorer.emptyWindowInCurrentWindow",
|
||||
"title": "Connect to Host in Current Window"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.explorer.reopenFolderInCurrentWindow",
|
||||
"title": "Open on SSH Host in Current Window"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.explorer.reopenFolderInNewWindow",
|
||||
"title": "Open on SSH Host in New Window",
|
||||
"icon": "$(folder-opened)"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.explorer.deleteFolderHistoryItem",
|
||||
"title": "Remove From Recent List",
|
||||
"icon": "$(x)"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.explorer.refresh",
|
||||
"title": "Refresh",
|
||||
"icon": "$(refresh)"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.explorer.configure",
|
||||
"title": "Configure",
|
||||
"icon": "$(gear)"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.explorer.add",
|
||||
"title": "Add New",
|
||||
"icon": "$(plus)"
|
||||
}
|
||||
],
|
||||
"resourceLabelFormatters": [
|
||||
{
|
||||
"scheme": "vscode-remote",
|
||||
"authority": "ssh-remote+*",
|
||||
"formatting": {
|
||||
"label": "${path}",
|
||||
"separator": "/",
|
||||
"tildify": true,
|
||||
"workspaceSuffix": "SSH"
|
||||
}
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"statusBar/remoteIndicator": [
|
||||
{
|
||||
"command": "openremotessh.openEmptyWindow",
|
||||
"when": "remoteName =~ /^ssh-remote$/ && remoteConnectionState == connected",
|
||||
"group": "remote_20_ssh_1general@1"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.openEmptyWindowInCurrentWindow",
|
||||
"when": "remoteName =~ /^ssh-remote$/ && remoteConnectionState == connected",
|
||||
"group": "remote_20_ssh_1general@2"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.openConfigFile",
|
||||
"when": "remoteName =~ /^ssh-remote$/ && remoteConnectionState == connected",
|
||||
"group": "remote_20_ssh_1general@3"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.showLog",
|
||||
"when": "remoteName =~ /^ssh-remote$/ && remoteConnectionState == connected",
|
||||
"group": "remote_20_ssh_1general@4"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.openEmptyWindow",
|
||||
"when": "remoteConnectionState == disconnected",
|
||||
"group": "remote_20_ssh_3local@1"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.openEmptyWindowInCurrentWindow",
|
||||
"when": "remoteConnectionState == disconnected",
|
||||
"group": "remote_20_ssh_3local@2"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.openConfigFile",
|
||||
"when": "remoteConnectionState == disconnected",
|
||||
"group": "remote_20_ssh_3local@3"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.openEmptyWindow",
|
||||
"when": "!remoteName && !virtualWorkspace",
|
||||
"group": "remote_20_ssh_3local@5"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.openEmptyWindowInCurrentWindow",
|
||||
"when": "!remoteName && !virtualWorkspace",
|
||||
"group": "remote_20_ssh_3local@6"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.openConfigFile",
|
||||
"when": "!remoteName && !virtualWorkspace",
|
||||
"group": "remote_20_ssh_3local@7"
|
||||
}
|
||||
],
|
||||
"commandPalette": [
|
||||
{
|
||||
"command": "openremotessh.explorer.refresh",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.explorer.configure",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.explorer.add",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.explorer.emptyWindowInNewWindow",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.explorer.emptyWindowInCurrentWindow",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.explorer.reopenFolderInCurrentWindow",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.explorer.reopenFolderInNewWindow",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.explorer.deleteFolderHistoryItem",
|
||||
"when": "false"
|
||||
}
|
||||
],
|
||||
"view/title": [
|
||||
{
|
||||
"command": "openremotessh.explorer.add",
|
||||
"when": "view == sshHosts",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.explorer.configure",
|
||||
"when": "view == sshHosts",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.explorer.refresh",
|
||||
"when": "view == sshHosts",
|
||||
"group": "navigation"
|
||||
}
|
||||
],
|
||||
"view/item/context": [
|
||||
{
|
||||
"command": "openremotessh.explorer.emptyWindowInNewWindow",
|
||||
"when": "viewItem =~ /^openremotessh.explorer.host$/",
|
||||
"group": "inline@1"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.explorer.emptyWindowInNewWindow",
|
||||
"when": "viewItem =~ /^openremotessh.explorer.host$/",
|
||||
"group": "navigation@2"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.explorer.emptyWindowInCurrentWindow",
|
||||
"when": "viewItem =~ /^openremotessh.explorer.host$/",
|
||||
"group": "navigation@1"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.explorer.reopenFolderInNewWindow",
|
||||
"when": "viewItem == openremotessh.explorer.folder",
|
||||
"group": "inline@1"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.explorer.reopenFolderInNewWindow",
|
||||
"when": "viewItem == openremotessh.explorer.folder",
|
||||
"group": "navigation@2"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.explorer.reopenFolderInCurrentWindow",
|
||||
"when": "viewItem == openremotessh.explorer.folder",
|
||||
"group": "navigation@1"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.explorer.deleteFolderHistoryItem",
|
||||
"when": "viewItem =~ /^openremotessh.explorer.folder/",
|
||||
"group": "navigation@3"
|
||||
},
|
||||
{
|
||||
"command": "openremotessh.explorer.deleteFolderHistoryItem",
|
||||
"when": "viewItem =~ /^openremotessh.explorer.folder/",
|
||||
"group": "inline@2"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "npm run compile",
|
||||
"compile": "gulp compile-extension:open-remote-ssh",
|
||||
"compile-web": "npx webpack-cli --config extension-browser.webpack.config --mode none",
|
||||
"watch": "gulp watch-extension:open-remote-ssh",
|
||||
"watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ssh2": "^0.5.52",
|
||||
"@types/ssh2-streams": "0.1.12"
|
||||
},
|
||||
"dependencies": {
|
||||
"glob": "^9.3.1",
|
||||
"simple-socks": "git+https://github.com/jeanp413/simple-socks#main",
|
||||
"socks": "^2.5.0",
|
||||
"@jeanp413/ssh-config": "^4.3.1",
|
||||
"ssh2": "git+https://github.com/jeanp413/ssh2#master"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,464 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as cp from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as net from 'net';
|
||||
import * as stream from 'stream';
|
||||
import { SocksClient, SocksClientOptions } from 'socks';
|
||||
import * as vscode from 'vscode';
|
||||
import * as ssh2 from 'ssh2';
|
||||
import type { ParsedKey } from 'ssh2-streams';
|
||||
import Log from './common/logger';
|
||||
import SSHDestination from './ssh/sshDestination';
|
||||
import SSHConnection, { SSHTunnelConfig } from './ssh/sshConnection';
|
||||
import SSHConfiguration from './ssh/sshConfig';
|
||||
import { gatherIdentityFiles } from './ssh/identityFiles';
|
||||
import { untildify, exists as fileExists } from './common/files';
|
||||
import { findRandomPort } from './common/ports';
|
||||
import { disposeAll } from './common/disposable';
|
||||
import { installCodeServer, ServerInstallError } from './serverSetup';
|
||||
import { isWindows } from './common/platform';
|
||||
import * as os from 'os';
|
||||
|
||||
const PASSWORD_RETRY_COUNT = 3;
|
||||
const PASSPHRASE_RETRY_COUNT = 3;
|
||||
|
||||
export const REMOTE_SSH_AUTHORITY = 'ssh-remote';
|
||||
|
||||
export function getRemoteAuthority(host: string) {
|
||||
return `${REMOTE_SSH_AUTHORITY}+${host}`;
|
||||
}
|
||||
|
||||
class TunnelInfo implements vscode.Disposable {
|
||||
constructor(
|
||||
readonly localPort: number,
|
||||
readonly remotePortOrSocketPath: number | string,
|
||||
private disposables: vscode.Disposable[]
|
||||
) {
|
||||
}
|
||||
|
||||
dispose() {
|
||||
disposeAll(this.disposables);
|
||||
}
|
||||
}
|
||||
|
||||
interface SSHKey {
|
||||
filename: string;
|
||||
parsedKey: ParsedKey;
|
||||
fingerprint: string;
|
||||
agentSupport?: boolean;
|
||||
isPrivate?: boolean;
|
||||
}
|
||||
|
||||
export class RemoteSSHResolver implements vscode.RemoteAuthorityResolver, vscode.Disposable {
|
||||
|
||||
private proxyConnections: SSHConnection[] = [];
|
||||
private sshConnection: SSHConnection | undefined;
|
||||
private sshAgentSock: string | undefined;
|
||||
private proxyCommandProcess: cp.ChildProcessWithoutNullStreams | undefined;
|
||||
|
||||
private socksTunnel: SSHTunnelConfig | undefined;
|
||||
private tunnels: TunnelInfo[] = [];
|
||||
|
||||
private labelFormatterDisposable: vscode.Disposable | undefined;
|
||||
|
||||
constructor(
|
||||
readonly context: vscode.ExtensionContext,
|
||||
readonly logger: Log
|
||||
) {
|
||||
}
|
||||
|
||||
resolve(authority: string, context: vscode.RemoteAuthorityResolverContext): Thenable<vscode.ResolverResult> {
|
||||
const [type, dest] = authority.split('+');
|
||||
if (type !== REMOTE_SSH_AUTHORITY) {
|
||||
throw new Error(`Invalid authority type for SSH resolver: ${type}`);
|
||||
}
|
||||
|
||||
this.logger.info(`Resolving ssh remote authority '${authority}' (attemp #${context.resolveAttempt})`);
|
||||
|
||||
const sshDest = SSHDestination.parseEncoded(dest);
|
||||
|
||||
// It looks like default values are not loaded yet when resolving a remote,
|
||||
// so let's hardcode the default values here
|
||||
const remoteSSHconfig = vscode.workspace.getConfiguration('remote.SSH');
|
||||
const enableDynamicForwarding = remoteSSHconfig.get<boolean>('enableDynamicForwarding', true)!;
|
||||
const enableAgentForwarding = remoteSSHconfig.get<boolean>('enableAgentForwarding', true)!;
|
||||
const serverDownloadUrlTemplate = remoteSSHconfig.get<string>('serverDownloadUrlTemplate');
|
||||
const defaultExtensions = remoteSSHconfig.get<string[]>('defaultExtensions', []);
|
||||
const remotePlatformMap = remoteSSHconfig.get<Record<string, string>>('remotePlatform', {});
|
||||
const remoteServerListenOnSocket = remoteSSHconfig.get<boolean>('remoteServerListenOnSocket', false)!;
|
||||
const connectTimeout = remoteSSHconfig.get<number>('connectTimeout', 60)!;
|
||||
|
||||
return vscode.window.withProgress({
|
||||
title: `Setting up SSH Host ${sshDest.hostname}`,
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
cancellable: false
|
||||
}, async () => {
|
||||
try {
|
||||
const sshconfig = await SSHConfiguration.loadFromFS();
|
||||
const sshHostConfig = sshconfig.getHostConfiguration(sshDest.hostname);
|
||||
const sshHostName = sshHostConfig['HostName'] ? sshHostConfig['HostName'].replace('%h', sshDest.hostname) : sshDest.hostname;
|
||||
const sshUser = sshHostConfig['User'] || sshDest.user || os.userInfo().username || ''; // https://github.com/openssh/openssh-portable/blob/5ec5504f1d328d5bfa64280cd617c3efec4f78f3/sshconnect.c#L1561-L1562
|
||||
const sshPort = sshHostConfig['Port'] ? parseInt(sshHostConfig['Port'], 10) : (sshDest.port || 22);
|
||||
|
||||
this.sshAgentSock = sshHostConfig['IdentityAgent'] || process.env['SSH_AUTH_SOCK'] || (isWindows ? '\\\\.\\pipe\\openssh-ssh-agent' : undefined);
|
||||
this.sshAgentSock = this.sshAgentSock ? untildify(this.sshAgentSock) : undefined;
|
||||
const agentForward = enableAgentForwarding && (sshHostConfig['ForwardAgent'] || 'no').toLowerCase() === 'yes';
|
||||
const agent = agentForward && this.sshAgentSock ? new ssh2.OpenSSHAgent(this.sshAgentSock) : undefined;
|
||||
|
||||
const preferredAuthentications = sshHostConfig['PreferredAuthentications'] ? sshHostConfig['PreferredAuthentications'].split(',').map(s => s.trim()) : ['publickey', 'password', 'keyboard-interactive'];
|
||||
|
||||
const identityFiles: string[] = (sshHostConfig['IdentityFile'] as unknown as string[]) || [];
|
||||
const identitiesOnly = (sshHostConfig['IdentitiesOnly'] || 'no').toLowerCase() === 'yes';
|
||||
const identityKeys = await gatherIdentityFiles(identityFiles, this.sshAgentSock, identitiesOnly, this.logger);
|
||||
|
||||
// Create proxy jump connections if any
|
||||
let proxyStream: ssh2.ClientChannel | stream.Duplex | undefined;
|
||||
if (sshHostConfig['ProxyJump']) {
|
||||
const proxyJumps = sshHostConfig['ProxyJump'].split(',').filter(i => !!i.trim())
|
||||
.map(i => {
|
||||
const proxy = SSHDestination.parse(i);
|
||||
const proxyHostConfig = sshconfig.getHostConfiguration(proxy.hostname);
|
||||
return [proxy, proxyHostConfig] as [SSHDestination, Record<string, string>];
|
||||
});
|
||||
for (let i = 0; i < proxyJumps.length; i++) {
|
||||
const [proxy, proxyHostConfig] = proxyJumps[i];
|
||||
const proxyHostName = proxyHostConfig['HostName'] || proxy.hostname;
|
||||
const proxyUser = proxyHostConfig['User'] || proxy.user || sshUser;
|
||||
const proxyPort = proxyHostConfig['Port'] ? parseInt(proxyHostConfig['Port'], 10) : (proxy.port || sshPort);
|
||||
|
||||
const proxyAgentForward = enableAgentForwarding && (proxyHostConfig['ForwardAgent'] || 'no').toLowerCase() === 'yes';
|
||||
const proxyAgent = proxyAgentForward && this.sshAgentSock ? new ssh2.OpenSSHAgent(this.sshAgentSock) : undefined;
|
||||
|
||||
const proxyIdentityFiles: string[] = (proxyHostConfig['IdentityFile'] as unknown as string[]) || [];
|
||||
const proxyIdentitiesOnly = (proxyHostConfig['IdentitiesOnly'] || 'no').toLowerCase() === 'yes';
|
||||
const proxyIdentityKeys = await gatherIdentityFiles(proxyIdentityFiles, this.sshAgentSock, proxyIdentitiesOnly, this.logger);
|
||||
|
||||
const proxyAuthHandler = this.getSSHAuthHandler(proxyUser, proxyHostName, proxyIdentityKeys, preferredAuthentications);
|
||||
const proxyConnection = new SSHConnection({
|
||||
host: !proxyStream ? proxyHostName : undefined,
|
||||
port: !proxyStream ? proxyPort : undefined,
|
||||
sock: proxyStream,
|
||||
username: proxyUser,
|
||||
readyTimeout: connectTimeout * 1000,
|
||||
strictVendor: false,
|
||||
agentForward: proxyAgentForward,
|
||||
agent: proxyAgent,
|
||||
authHandler: (arg0, arg1, arg2) => (proxyAuthHandler(arg0, arg1, arg2), undefined)
|
||||
});
|
||||
this.proxyConnections.push(proxyConnection);
|
||||
|
||||
const nextProxyJump = i < proxyJumps.length - 1 ? proxyJumps[i + 1] : undefined;
|
||||
const destIP = nextProxyJump ? (nextProxyJump[1]['HostName'] || nextProxyJump[0].hostname) : sshHostName;
|
||||
const destPort = nextProxyJump ? ((nextProxyJump[1]['Port'] && parseInt(nextProxyJump[1]['Port'], 10)) || nextProxyJump[0].port || 22) : sshPort;
|
||||
proxyStream = await proxyConnection.forwardOut('127.0.0.1', 0, destIP, destPort);
|
||||
}
|
||||
} else if (sshHostConfig['ProxyCommand']) {
|
||||
let proxyArgs = (sshHostConfig['ProxyCommand'] as unknown as string[])
|
||||
.map((arg) => arg.replace('%h', sshHostName).replace('%n', sshDest.hostname).replace('%p', sshPort.toString()).replace('%r', sshUser));
|
||||
let proxyCommand = proxyArgs.shift()!;
|
||||
|
||||
let options = {};
|
||||
if (isWindows && /\.(bat|cmd)$/.test(proxyCommand)) {
|
||||
proxyCommand = `"${proxyCommand}"`;
|
||||
proxyArgs = proxyArgs.map((arg) => arg.includes(' ') ? `"${arg}"` : arg);
|
||||
options = { shell: true, windowsHide: true, windowsVerbatimArguments: true };
|
||||
}
|
||||
|
||||
this.logger.trace(`Spawning ProxyCommand: ${proxyCommand} ${proxyArgs.join(' ')}`);
|
||||
|
||||
const child = cp.spawn(proxyCommand, proxyArgs, options);
|
||||
proxyStream = stream.Duplex.from({ readable: child.stdout, writable: child.stdin });
|
||||
this.proxyCommandProcess = child;
|
||||
}
|
||||
|
||||
// Create final shh connection
|
||||
const sshAuthHandler = this.getSSHAuthHandler(sshUser, sshHostName, identityKeys, preferredAuthentications);
|
||||
|
||||
this.sshConnection = new SSHConnection({
|
||||
host: !proxyStream ? sshHostName : undefined,
|
||||
port: !proxyStream ? sshPort : undefined,
|
||||
sock: proxyStream,
|
||||
username: sshUser,
|
||||
readyTimeout: connectTimeout * 1000,
|
||||
strictVendor: false,
|
||||
agentForward,
|
||||
agent,
|
||||
authHandler: (arg0, arg1, arg2) => (sshAuthHandler(arg0, arg1, arg2), undefined),
|
||||
});
|
||||
await this.sshConnection.connect();
|
||||
|
||||
const envVariables: Record<string, string | null> = {};
|
||||
if (agentForward) {
|
||||
envVariables['SSH_AUTH_SOCK'] = null;
|
||||
}
|
||||
|
||||
const installResult = await installCodeServer(this.sshConnection, serverDownloadUrlTemplate, defaultExtensions, Object.keys(envVariables), remotePlatformMap[sshDest.hostname], remoteServerListenOnSocket, this.logger);
|
||||
|
||||
for (const key of Object.keys(envVariables)) {
|
||||
if (installResult[key] !== undefined) {
|
||||
envVariables[key] = installResult[key];
|
||||
}
|
||||
}
|
||||
|
||||
// Update terminal env variables
|
||||
this.context.environmentVariableCollection.persistent = false;
|
||||
for (const [key, value] of Object.entries(envVariables)) {
|
||||
if (value) {
|
||||
this.context.environmentVariableCollection.replace(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (enableDynamicForwarding) {
|
||||
const socksPort = await findRandomPort();
|
||||
this.socksTunnel = await this.sshConnection!.addTunnel({
|
||||
name: `ssh_tunnel_socks_${socksPort}`,
|
||||
localPort: socksPort,
|
||||
socks: true
|
||||
});
|
||||
}
|
||||
|
||||
const tunnelConfig = await this.openTunnel(0, installResult.listeningOn);
|
||||
this.tunnels.push(tunnelConfig);
|
||||
|
||||
// Enable ports view
|
||||
vscode.commands.executeCommand('setContext', 'forwardedPortsViewEnabled', true);
|
||||
|
||||
this.labelFormatterDisposable?.dispose();
|
||||
this.labelFormatterDisposable = vscode.workspace.registerResourceLabelFormatter({
|
||||
scheme: 'vscode-remote',
|
||||
authority: `${REMOTE_SSH_AUTHORITY}+*`,
|
||||
formatting: {
|
||||
label: '${path}',
|
||||
separator: '/',
|
||||
tildify: true,
|
||||
workspaceSuffix: `SSH: ${sshDest.hostname}` + (sshDest.port && sshDest.port !== 22 ? `:${sshDest.port}` : '')
|
||||
}
|
||||
});
|
||||
|
||||
const resolvedResult: vscode.ResolverResult = new vscode.ResolvedAuthority('127.0.0.1', tunnelConfig.localPort, installResult.connectionToken);
|
||||
resolvedResult.extensionHostEnv = envVariables;
|
||||
return resolvedResult;
|
||||
} catch (e: unknown) {
|
||||
this.logger.error(`Error resolving authority`, e);
|
||||
|
||||
// Initial connection
|
||||
if (context.resolveAttempt === 1) {
|
||||
this.logger.show();
|
||||
|
||||
const closeRemote = 'Close Remote';
|
||||
const retry = 'Retry';
|
||||
const result = await vscode.window.showErrorMessage(`Could not establish connection to "${sshDest.hostname}"`, { modal: true }, closeRemote, retry);
|
||||
if (result === closeRemote) {
|
||||
await vscode.commands.executeCommand('workbench.action.remote.close');
|
||||
} else if (result === retry) {
|
||||
await vscode.commands.executeCommand('workbench.action.reloadWindow');
|
||||
}
|
||||
}
|
||||
|
||||
if (e instanceof ServerInstallError || !(e instanceof Error)) {
|
||||
throw vscode.RemoteAuthorityResolverError.NotAvailable(e instanceof Error ? e.message : String(e));
|
||||
} else {
|
||||
throw vscode.RemoteAuthorityResolverError.TemporarilyNotAvailable(e.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async openTunnel(localPort: number, remotePortOrSocketPath: number | string) {
|
||||
localPort = localPort > 0 ? localPort : await findRandomPort();
|
||||
|
||||
const disposables: vscode.Disposable[] = [];
|
||||
const remotePort = typeof remotePortOrSocketPath === 'number' ? remotePortOrSocketPath : undefined;
|
||||
const remoteSocketPath = typeof remotePortOrSocketPath === 'string' ? remotePortOrSocketPath : undefined;
|
||||
if (this.socksTunnel && remotePort) {
|
||||
const forwardingServer = await new Promise<net.Server>((resolve, reject) => {
|
||||
this.logger.trace(`Creating forwarding server ${localPort}(local) => ${this.socksTunnel!.localPort!}(socks) => ${remotePort}(remote)`);
|
||||
const socksOptions: SocksClientOptions = {
|
||||
proxy: {
|
||||
host: '127.0.0.1',
|
||||
port: this.socksTunnel!.localPort!,
|
||||
type: 5
|
||||
},
|
||||
command: 'connect',
|
||||
destination: {
|
||||
host: '127.0.0.1',
|
||||
port: remotePort
|
||||
}
|
||||
};
|
||||
const server: net.Server = net.createServer()
|
||||
.on('error', reject)
|
||||
.on('connection', async (socket: net.Socket) => {
|
||||
try {
|
||||
const socksConn = await SocksClient.createConnection(socksOptions);
|
||||
socket.pipe(socksConn.socket);
|
||||
socksConn.socket.pipe(socket);
|
||||
} catch (error) {
|
||||
this.logger.error(`Error while creating SOCKS connection`, error);
|
||||
}
|
||||
})
|
||||
.on('listening', () => resolve(server))
|
||||
.listen(localPort);
|
||||
});
|
||||
disposables.push({
|
||||
dispose: () => forwardingServer.close(() => {
|
||||
this.logger.trace(`SOCKS forwading server closed`);
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
this.logger.trace(`Opening tunnel ${localPort}(local) => ${remotePortOrSocketPath}(remote)`);
|
||||
const tunnelConfig = await this.sshConnection!.addTunnel({
|
||||
name: `ssh_tunnel_${localPort}_${remotePortOrSocketPath}`,
|
||||
remoteAddr: '127.0.0.1',
|
||||
remotePort,
|
||||
remoteSocketPath,
|
||||
localPort
|
||||
});
|
||||
disposables.push({
|
||||
dispose: () => {
|
||||
this.sshConnection?.closeTunnel(tunnelConfig.name);
|
||||
this.logger.trace(`Tunnel ${tunnelConfig.name} closed`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return new TunnelInfo(localPort, remotePortOrSocketPath, disposables);
|
||||
}
|
||||
|
||||
private getSSHAuthHandler(sshUser: string, sshHostName: string, identityKeys: SSHKey[], preferredAuthentications: string[]) {
|
||||
let passwordRetryCount = PASSWORD_RETRY_COUNT;
|
||||
let keyboardRetryCount = PASSWORD_RETRY_COUNT;
|
||||
identityKeys = identityKeys.slice();
|
||||
return async (methodsLeft: string[] | null, _partialSuccess: boolean | null, callback: (nextAuth: ssh2.AuthHandlerResult) => void) => {
|
||||
if (methodsLeft === null) {
|
||||
this.logger.info(`Trying no-auth authentication`);
|
||||
|
||||
return callback({
|
||||
type: 'none',
|
||||
username: sshUser,
|
||||
});
|
||||
}
|
||||
if (methodsLeft.includes('publickey') && identityKeys.length && preferredAuthentications.includes('publickey')) {
|
||||
const identityKey = identityKeys.shift()!;
|
||||
|
||||
this.logger.info(`Trying publickey authentication: ${identityKey.filename} ${identityKey.parsedKey.type} SHA256:${identityKey.fingerprint}`);
|
||||
|
||||
if (identityKey.agentSupport) {
|
||||
return callback({
|
||||
type: 'agent',
|
||||
username: sshUser,
|
||||
agent: new class extends ssh2.OpenSSHAgent {
|
||||
// Only return the current key
|
||||
override getIdentities(callback: (err: Error | undefined, publicKeys?: ParsedKey[]) => void): void {
|
||||
callback(undefined, [identityKey.parsedKey]);
|
||||
}
|
||||
}(this.sshAgentSock!)
|
||||
});
|
||||
}
|
||||
if (identityKey.isPrivate) {
|
||||
return callback({
|
||||
type: 'publickey',
|
||||
username: sshUser,
|
||||
key: identityKey.parsedKey
|
||||
});
|
||||
}
|
||||
if (!await fileExists(identityKey.filename)) {
|
||||
// Try next identity file
|
||||
return callback(null as any);
|
||||
}
|
||||
|
||||
const keyBuffer = await fs.promises.readFile(identityKey.filename);
|
||||
let result = ssh2.utils.parseKey(keyBuffer); // First try without passphrase
|
||||
if (result instanceof Error && result.message === 'Encrypted private OpenSSH key detected, but no passphrase given') {
|
||||
let passphraseRetryCount = PASSPHRASE_RETRY_COUNT;
|
||||
while (result instanceof Error && passphraseRetryCount > 0) {
|
||||
const passphrase = await vscode.window.showInputBox({
|
||||
title: `Enter passphrase for ${identityKey.filename}`,
|
||||
password: true,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
if (!passphrase) {
|
||||
break;
|
||||
}
|
||||
result = ssh2.utils.parseKey(keyBuffer, passphrase);
|
||||
passphraseRetryCount--;
|
||||
}
|
||||
}
|
||||
if (!result || result instanceof Error) {
|
||||
// Try next identity file
|
||||
return callback(null as any);
|
||||
}
|
||||
|
||||
const key = Array.isArray(result) ? result[0] : result;
|
||||
return callback({
|
||||
type: 'publickey',
|
||||
username: sshUser,
|
||||
key
|
||||
});
|
||||
}
|
||||
if (methodsLeft.includes('password') && passwordRetryCount > 0 && preferredAuthentications.includes('password')) {
|
||||
if (passwordRetryCount === PASSWORD_RETRY_COUNT) {
|
||||
this.logger.info(`Trying password authentication`);
|
||||
}
|
||||
|
||||
const password = await vscode.window.showInputBox({
|
||||
title: `Enter password for ${sshUser}@${sshHostName}`,
|
||||
password: true,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
passwordRetryCount--;
|
||||
|
||||
return callback(password
|
||||
? {
|
||||
type: 'password',
|
||||
username: sshUser,
|
||||
password
|
||||
}
|
||||
: false);
|
||||
}
|
||||
if (methodsLeft.includes('keyboard-interactive') && keyboardRetryCount > 0 && preferredAuthentications.includes('keyboard-interactive')) {
|
||||
if (keyboardRetryCount === PASSWORD_RETRY_COUNT) {
|
||||
this.logger.info(`Trying keyboard-interactive authentication`);
|
||||
}
|
||||
|
||||
return callback({
|
||||
type: 'keyboard-interactive',
|
||||
username: sshUser,
|
||||
prompt: async (_name, _instructions, _instructionsLang, prompts, finish) => {
|
||||
const responses: string[] = [];
|
||||
for (const prompt of prompts) {
|
||||
const response = await vscode.window.showInputBox({
|
||||
title: `(${sshUser}@${sshHostName}) ${prompt.prompt}`,
|
||||
password: !prompt.echo,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
if (response === undefined) {
|
||||
keyboardRetryCount = 0;
|
||||
break;
|
||||
}
|
||||
responses.push(response);
|
||||
}
|
||||
keyboardRetryCount--;
|
||||
finish(responses);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
callback(false);
|
||||
};
|
||||
}
|
||||
|
||||
dispose() {
|
||||
disposeAll(this.tunnels);
|
||||
// If there's proxy connections then just close the parent connection
|
||||
if (this.proxyConnections.length) {
|
||||
this.proxyConnections[0].close();
|
||||
} else {
|
||||
this.sshConnection?.close();
|
||||
}
|
||||
this.proxyCommandProcess?.kill();
|
||||
this.labelFormatterDisposable?.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as vscode from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
import { getRemoteAuthority } from './authResolver';
|
||||
import { getSSHConfigPath } from './ssh/sshConfig';
|
||||
import { exists as fileExists } from './common/files';
|
||||
import SSHDestination from './ssh/sshDestination';
|
||||
|
||||
export async function promptOpenRemoteSSHWindow(reuseWindow: boolean) {
|
||||
const host = await vscode.window.showInputBox({
|
||||
title: 'Enter [user@]hostname[:port]'
|
||||
});
|
||||
|
||||
if (!host) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sshDest = new SSHDestination(host);
|
||||
openRemoteSSHWindow(sshDest.toEncodedString(), reuseWindow);
|
||||
}
|
||||
|
||||
export function openRemoteSSHWindow(host: string, reuseWindow: boolean) {
|
||||
vscode.commands.executeCommand('vscode.newWindow', { remoteAuthority: getRemoteAuthority(host), reuseWindow });
|
||||
}
|
||||
|
||||
export function openRemoteSSHLocationWindow(host: string, path: string, reuseWindow: boolean) {
|
||||
vscode.commands.executeCommand('vscode.openFolder', vscode.Uri.from({ scheme: 'vscode-remote', authority: getRemoteAuthority(host), path }), { forceNewWindow: !reuseWindow });
|
||||
}
|
||||
|
||||
export async function addNewHost() {
|
||||
const sshConfigPath = getSSHConfigPath();
|
||||
if (!await fileExists(sshConfigPath)) {
|
||||
await fs.promises.appendFile(sshConfigPath, '');
|
||||
}
|
||||
|
||||
await vscode.commands.executeCommand('vscode.open', vscode.Uri.file(sshConfigPath), { preview: false });
|
||||
|
||||
const textEditor = vscode.window.activeTextEditor;
|
||||
if (textEditor?.document.uri.fsPath !== sshConfigPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
const textDocument = textEditor.document;
|
||||
const lastLine = textDocument.lineAt(textDocument.lineCount - 1);
|
||||
|
||||
if (!lastLine.isEmptyOrWhitespace) {
|
||||
await textEditor.edit((editBuilder: vscode.TextEditorEdit) => {
|
||||
editBuilder.insert(lastLine.range.end, '\n');
|
||||
});
|
||||
}
|
||||
|
||||
const snippet = '\nHost ${1:dev}\n\tHostName ${2:dev.example.com}\n\tUser ${3:john}';
|
||||
await textEditor.insertSnippet(
|
||||
new vscode.SnippetString(snippet),
|
||||
new vscode.Position(textDocument.lineCount, 0)
|
||||
);
|
||||
}
|
||||
|
||||
export async function openSSHConfigFile() {
|
||||
const sshConfigPath = getSSHConfigPath();
|
||||
if (!await fileExists(sshConfigPath)) {
|
||||
await fs.promises.appendFile(sshConfigPath, '');
|
||||
}
|
||||
vscode.commands.executeCommand('vscode.open', vscode.Uri.file(sshConfigPath));
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function disposeAll(disposables: vscode.Disposable[]): void {
|
||||
while (disposables.length) {
|
||||
const item = disposables.pop();
|
||||
if (item) {
|
||||
item.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class Disposable {
|
||||
private _isDisposed = false;
|
||||
|
||||
protected _disposables: vscode.Disposable[] = [];
|
||||
|
||||
public dispose(): any {
|
||||
if (this._isDisposed) {
|
||||
return;
|
||||
}
|
||||
this._isDisposed = true;
|
||||
disposeAll(this._disposables);
|
||||
}
|
||||
|
||||
protected _register<T extends vscode.Disposable>(value: T): T {
|
||||
if (this._isDisposed) {
|
||||
value.dispose();
|
||||
} else {
|
||||
this._disposables.push(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
protected get isDisposed(): boolean {
|
||||
return this._isDisposed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
|
||||
const homeDir = os.homedir();
|
||||
|
||||
export async function exists(path: string) {
|
||||
try {
|
||||
await fs.promises.access(path);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function untildify(path: string) {
|
||||
return path.replace(/^~(?=$|\/|\\)/, homeDir);
|
||||
}
|
||||
|
||||
export function normalizeToSlash(path: string) {
|
||||
return path.replace(/\\/g, '/');
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
type LogLevel = 'Trace' | 'Info' | 'Error';
|
||||
|
||||
export default class Log {
|
||||
private output: vscode.OutputChannel;
|
||||
|
||||
constructor(name: string) {
|
||||
this.output = vscode.window.createOutputChannel(name);
|
||||
}
|
||||
|
||||
private data2String(data: any): string {
|
||||
if (data instanceof Error) {
|
||||
return data.stack || data.message;
|
||||
}
|
||||
if (data.success === false && data.message) {
|
||||
return data.message;
|
||||
}
|
||||
return data.toString();
|
||||
}
|
||||
|
||||
public trace(message: string, data?: any): void {
|
||||
this.logLevel('Trace', message, data);
|
||||
}
|
||||
|
||||
public info(message: string, data?: any): void {
|
||||
this.logLevel('Info', message, data);
|
||||
}
|
||||
|
||||
public error(message: string, data?: any): void {
|
||||
this.logLevel('Error', message, data);
|
||||
}
|
||||
|
||||
public logLevel(level: LogLevel, message: string, data?: any): void {
|
||||
this.output.appendLine(`[${level} - ${this.now()}] ${message}`);
|
||||
if (data) {
|
||||
this.output.appendLine(this.data2String(data));
|
||||
}
|
||||
}
|
||||
|
||||
private now(): string {
|
||||
const now = new Date();
|
||||
return padLeft(now.getUTCHours() + '', 2, '0')
|
||||
+ ':' + padLeft(now.getMinutes() + '', 2, '0')
|
||||
+ ':' + padLeft(now.getUTCSeconds() + '', 2, '0') + '.' + now.getMilliseconds();
|
||||
}
|
||||
|
||||
public show() {
|
||||
this.output.show();
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.output.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
function padLeft(s: string, n: number, pad = ' ') {
|
||||
return pad.repeat(Math.max(0, n - s.length)) + s;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
export const isWindows = process.platform === 'win32';
|
||||
export const isMacintosh = process.platform === 'darwin';
|
||||
export const isLinux = process.platform === 'linux';
|
||||
@@ -0,0 +1,133 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as net from 'net';
|
||||
|
||||
/**
|
||||
* Finds a random unused port assigned by the operating system. Will reject in case no free port can be found.
|
||||
*/
|
||||
export function findRandomPort(): Promise<number> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const server = net.createServer({ pauseOnConnect: true });
|
||||
server.on('error', reject);
|
||||
server.on('listening', () => {
|
||||
const port = (server.address() as net.AddressInfo).port;
|
||||
server.close(() => resolve(port));
|
||||
});
|
||||
server.listen(0, '127.0.0.1');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a start point and a max number of retries, will find a port that
|
||||
* is openable. Will return 0 in case no free port can be found.
|
||||
*/
|
||||
export function findFreePort(startPort: number, giveUpAfter: number, timeout: number, stride = 1): Promise<number> {
|
||||
let done = false;
|
||||
|
||||
return new Promise(resolve => {
|
||||
const timeoutHandle = setTimeout(() => {
|
||||
if (!done) {
|
||||
done = true;
|
||||
return resolve(0);
|
||||
}
|
||||
}, timeout);
|
||||
|
||||
doFindFreePort(startPort, giveUpAfter, stride, (port) => {
|
||||
if (!done) {
|
||||
done = true;
|
||||
clearTimeout(timeoutHandle);
|
||||
return resolve(port);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function doFindFreePort(startPort: number, giveUpAfter: number, stride: number, clb: (port: number) => void): void {
|
||||
if (giveUpAfter === 0) {
|
||||
return clb(0);
|
||||
}
|
||||
|
||||
const client = new net.Socket();
|
||||
|
||||
// If we can connect to the port it means the port is already taken so we continue searching
|
||||
client.once('connect', () => {
|
||||
dispose(client);
|
||||
|
||||
return doFindFreePort(startPort + stride, giveUpAfter - 1, stride, clb);
|
||||
});
|
||||
|
||||
client.once('data', () => {
|
||||
// this listener is required since node.js 8.x
|
||||
});
|
||||
|
||||
client.once('error', (err: Error & { code?: string }) => {
|
||||
dispose(client);
|
||||
|
||||
// If we receive any non ECONNREFUSED error, it means the port is used but we cannot connect
|
||||
if (err.code !== 'ECONNREFUSED') {
|
||||
return doFindFreePort(startPort + stride, giveUpAfter - 1, stride, clb);
|
||||
}
|
||||
|
||||
// Otherwise it means the port is free to use!
|
||||
return clb(startPort);
|
||||
});
|
||||
|
||||
client.connect(startPort, '127.0.0.1');
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses listen instead of connect. Is faster, but if there is another listener on 0.0.0.0 then this will take 127.0.0.1 from that listener.
|
||||
*/
|
||||
export function findFreePortFaster(startPort: number, giveUpAfter: number, timeout: number): Promise<number> {
|
||||
let resolved = false;
|
||||
let timeoutHandle: NodeJS.Timeout | undefined = undefined;
|
||||
let countTried = 1;
|
||||
const server = net.createServer({ pauseOnConnect: true });
|
||||
function doResolve(port: number, resolve: (port: number) => void) {
|
||||
if (!resolved) {
|
||||
resolved = true;
|
||||
server.removeAllListeners();
|
||||
server.close();
|
||||
if (timeoutHandle) {
|
||||
clearTimeout(timeoutHandle);
|
||||
}
|
||||
resolve(port);
|
||||
}
|
||||
}
|
||||
return new Promise<number>(resolve => {
|
||||
timeoutHandle = setTimeout(() => {
|
||||
doResolve(0, resolve);
|
||||
}, timeout);
|
||||
|
||||
server.on('listening', () => {
|
||||
doResolve(startPort, resolve);
|
||||
});
|
||||
server.on('error', err => {
|
||||
if (err && ((<any>err).code === 'EADDRINUSE' || (<any>err).code === 'EACCES') && (countTried < giveUpAfter)) {
|
||||
startPort++;
|
||||
countTried++;
|
||||
server.listen(startPort, '127.0.0.1');
|
||||
} else {
|
||||
doResolve(0, resolve);
|
||||
}
|
||||
});
|
||||
server.on('close', () => {
|
||||
doResolve(0, resolve);
|
||||
});
|
||||
server.listen(startPort, '127.0.0.1');
|
||||
});
|
||||
}
|
||||
|
||||
function dispose(socket: net.Socket): void {
|
||||
try {
|
||||
socket.removeAllListeners('connect');
|
||||
socket.removeAllListeners('error');
|
||||
socket.end();
|
||||
socket.destroy();
|
||||
socket.unref();
|
||||
} catch (error) {
|
||||
console.error(error); // otherwise this error would get lost in the callback chain
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as vscode from 'vscode';
|
||||
import Log from './common/logger';
|
||||
import { RemoteSSHResolver, REMOTE_SSH_AUTHORITY } from './authResolver';
|
||||
import { openSSHConfigFile, promptOpenRemoteSSHWindow } from './commands';
|
||||
import { HostTreeDataProvider } from './hostTreeView';
|
||||
import { getRemoteWorkspaceLocationData, RemoteLocationHistory } from './remoteLocationHistory';
|
||||
|
||||
export async function activate(context: vscode.ExtensionContext) {
|
||||
const logger = new Log('Remote - SSH');
|
||||
context.subscriptions.push(logger);
|
||||
|
||||
const remoteSSHResolver = new RemoteSSHResolver(context, logger);
|
||||
context.subscriptions.push(vscode.workspace.registerRemoteAuthorityResolver(REMOTE_SSH_AUTHORITY, remoteSSHResolver));
|
||||
context.subscriptions.push(remoteSSHResolver);
|
||||
|
||||
const locationHistory = new RemoteLocationHistory(context);
|
||||
const locationData = getRemoteWorkspaceLocationData();
|
||||
if (locationData) {
|
||||
await locationHistory.addLocation(locationData[0], locationData[1]);
|
||||
}
|
||||
|
||||
const hostTreeDataProvider = new HostTreeDataProvider(locationHistory);
|
||||
context.subscriptions.push(vscode.window.createTreeView('sshHosts', { treeDataProvider: hostTreeDataProvider }));
|
||||
context.subscriptions.push(hostTreeDataProvider);
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('openremotessh.openEmptyWindow', () => promptOpenRemoteSSHWindow(false)));
|
||||
context.subscriptions.push(vscode.commands.registerCommand('openremotessh.openEmptyWindowInCurrentWindow', () => promptOpenRemoteSSHWindow(true)));
|
||||
context.subscriptions.push(vscode.commands.registerCommand('openremotessh.openConfigFile', () => openSSHConfigFile()));
|
||||
context.subscriptions.push(vscode.commands.registerCommand('openremotessh.showLog', () => logger.show()));
|
||||
}
|
||||
|
||||
export function deactivate() {
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import SSHConfiguration, { getSSHConfigPath } from './ssh/sshConfig';
|
||||
import { RemoteLocationHistory } from './remoteLocationHistory';
|
||||
import { Disposable } from './common/disposable';
|
||||
import { addNewHost, openRemoteSSHLocationWindow, openRemoteSSHWindow, openSSHConfigFile } from './commands';
|
||||
import SSHDestination from './ssh/sshDestination';
|
||||
|
||||
class HostItem {
|
||||
constructor(
|
||||
public hostname: string,
|
||||
public locations: string[]
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
class HostLocationItem {
|
||||
constructor(
|
||||
public path: string,
|
||||
public hostname: string
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
type DataTreeItem = HostItem | HostLocationItem;
|
||||
|
||||
export class HostTreeDataProvider extends Disposable implements vscode.TreeDataProvider<DataTreeItem> {
|
||||
|
||||
private readonly _onDidChangeTreeData = this._register(new vscode.EventEmitter<DataTreeItem | DataTreeItem[] | void>());
|
||||
public readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
|
||||
|
||||
constructor(
|
||||
private locationHistory: RemoteLocationHistory
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(vscode.commands.registerCommand('openremotessh.explorer.add', () => addNewHost()));
|
||||
this._register(vscode.commands.registerCommand('openremotessh.explorer.configure', () => openSSHConfigFile()));
|
||||
this._register(vscode.commands.registerCommand('openremotessh.explorer.refresh', () => this.refresh()));
|
||||
this._register(vscode.commands.registerCommand('openremotessh.explorer.emptyWindowInNewWindow', e => this.openRemoteSSHWindow(e, false)));
|
||||
this._register(vscode.commands.registerCommand('openremotessh.explorer.emptyWindowInCurrentWindow', e => this.openRemoteSSHWindow(e, true)));
|
||||
this._register(vscode.commands.registerCommand('openremotessh.explorer.reopenFolderInNewWindow', e => this.openRemoteSSHLocationWindow(e, false)));
|
||||
this._register(vscode.commands.registerCommand('openremotessh.explorer.reopenFolderInCurrentWindow', e => this.openRemoteSSHLocationWindow(e, true)));
|
||||
this._register(vscode.commands.registerCommand('openremotessh.explorer.deleteFolderHistoryItem', e => this.deleteHostLocation(e)));
|
||||
|
||||
this._register(vscode.workspace.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('remote.SSH.configFile')) {
|
||||
this.refresh();
|
||||
}
|
||||
}));
|
||||
this._register(vscode.workspace.onDidSaveTextDocument(e => {
|
||||
if (e.uri.fsPath === getSSHConfigPath()) {
|
||||
this.refresh();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
getTreeItem(element: DataTreeItem): vscode.TreeItem {
|
||||
if (element instanceof HostLocationItem) {
|
||||
const label = path.posix.basename(element.path).replace(/\.code-workspace$/, ' (Workspace)');
|
||||
const treeItem = new vscode.TreeItem(label);
|
||||
treeItem.description = path.posix.dirname(element.path);
|
||||
treeItem.iconPath = new vscode.ThemeIcon('folder');
|
||||
treeItem.contextValue = 'openremotessh.explorer.folder';
|
||||
return treeItem;
|
||||
}
|
||||
|
||||
const treeItem = new vscode.TreeItem(element.hostname);
|
||||
treeItem.collapsibleState = element.locations.length ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None;
|
||||
treeItem.iconPath = new vscode.ThemeIcon('vm');
|
||||
treeItem.contextValue = 'openremotessh.explorer.host';
|
||||
return treeItem;
|
||||
}
|
||||
|
||||
async getChildren(element?: HostItem): Promise<DataTreeItem[]> {
|
||||
if (!element) {
|
||||
const sshConfigFile = await SSHConfiguration.loadFromFS();
|
||||
const hosts = sshConfigFile.getAllConfiguredHosts();
|
||||
return hosts.map(hostname => new HostItem(hostname, this.locationHistory.getHistory(hostname)));
|
||||
}
|
||||
if (element instanceof HostItem) {
|
||||
return element.locations.map(location => new HostLocationItem(location, element.hostname));
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
private refresh() {
|
||||
this._onDidChangeTreeData.fire();
|
||||
}
|
||||
|
||||
private async deleteHostLocation(element: HostLocationItem) {
|
||||
await this.locationHistory.removeLocation(element.hostname, element.path);
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
private async openRemoteSSHWindow(element: HostItem, reuseWindow: boolean) {
|
||||
const sshDest = new SSHDestination(element.hostname);
|
||||
openRemoteSSHWindow(sshDest.toEncodedString(), reuseWindow);
|
||||
}
|
||||
|
||||
private async openRemoteSSHLocationWindow(element: HostLocationItem, reuseWindow: boolean) {
|
||||
const sshDest = new SSHDestination(element.hostname);
|
||||
openRemoteSSHLocationWindow(sshDest.toEncodedString(), element.path, reuseWindow);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as vscode from 'vscode';
|
||||
import { REMOTE_SSH_AUTHORITY } from './authResolver';
|
||||
import SSHDestination from './ssh/sshDestination';
|
||||
|
||||
export class RemoteLocationHistory {
|
||||
private static STORAGE_KEY = 'remoteLocationHistory_v0';
|
||||
|
||||
private remoteLocationHistory: Record<string, string[]> = {};
|
||||
|
||||
constructor(private context: vscode.ExtensionContext) {
|
||||
// context.globalState.update(RemoteLocationHistory.STORAGE_KEY, undefined);
|
||||
this.remoteLocationHistory = context.globalState.get(RemoteLocationHistory.STORAGE_KEY) || {};
|
||||
}
|
||||
|
||||
getHistory(host: string): string[] {
|
||||
return this.remoteLocationHistory[host] || [];
|
||||
}
|
||||
|
||||
async addLocation(host: string, path: string) {
|
||||
const hostLocations = this.remoteLocationHistory[host] || [];
|
||||
if (!hostLocations.includes(path)) {
|
||||
hostLocations.unshift(path);
|
||||
this.remoteLocationHistory[host] = hostLocations;
|
||||
|
||||
await this.context.globalState.update(RemoteLocationHistory.STORAGE_KEY, this.remoteLocationHistory);
|
||||
}
|
||||
}
|
||||
|
||||
async removeLocation(host: string, path: string) {
|
||||
let hostLocations = this.remoteLocationHistory[host] || [];
|
||||
hostLocations = hostLocations.filter(l => l !== path);
|
||||
this.remoteLocationHistory[host] = hostLocations;
|
||||
|
||||
await this.context.globalState.update(RemoteLocationHistory.STORAGE_KEY, this.remoteLocationHistory);
|
||||
}
|
||||
}
|
||||
|
||||
export function getRemoteWorkspaceLocationData(): [string, string] | undefined {
|
||||
let location = vscode.workspace.workspaceFile;
|
||||
if (location && location.scheme === 'vscode-remote' && location.authority.startsWith(REMOTE_SSH_AUTHORITY) && location.path.endsWith('.code-workspace')) {
|
||||
const [, host] = location.authority.split('+');
|
||||
const sshDest = SSHDestination.parseEncoded(host);
|
||||
return [sshDest.hostname, location.path];
|
||||
}
|
||||
|
||||
location = vscode.workspace.workspaceFolders?.[0].uri;
|
||||
if (location && location.scheme === 'vscode-remote' && location.authority.startsWith(REMOTE_SSH_AUTHORITY)) {
|
||||
const [, host] = location.authority.split('+');
|
||||
const sshDest = SSHDestination.parseEncoded(host);
|
||||
return [sshDest.hostname, location.path];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as vscode from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
let vscodeProductJson: any;
|
||||
async function getVSCodeProductJson() {
|
||||
if (!vscodeProductJson) {
|
||||
const productJsonStr = await fs.promises.readFile(path.join(vscode.env.appRoot, 'product.json'), 'utf8');
|
||||
vscodeProductJson = JSON.parse(productJsonStr);
|
||||
}
|
||||
|
||||
return vscodeProductJson;
|
||||
}
|
||||
|
||||
export interface IServerConfig {
|
||||
version: string;
|
||||
commit: string;
|
||||
quality: string;
|
||||
release?: string; // void-like specific
|
||||
serverApplicationName: string;
|
||||
serverDataFolderName: string;
|
||||
serverDownloadUrlTemplate?: string; // void-like specific
|
||||
}
|
||||
|
||||
export async function getVSCodeServerConfig(): Promise<IServerConfig> {
|
||||
const productJson = await getVSCodeProductJson();
|
||||
|
||||
const customServerBinaryName = vscode.workspace.getConfiguration('remote.SSH.experimental').get<string>('serverBinaryName', '');
|
||||
|
||||
return {
|
||||
version: vscode.version.replace('-insider', ''),
|
||||
commit: productJson.commit,
|
||||
quality: productJson.quality,
|
||||
release: productJson.release,
|
||||
serverApplicationName: customServerBinaryName || productJson.serverApplicationName,
|
||||
serverDataFolderName: productJson.serverDataFolderName,
|
||||
serverDownloadUrlTemplate: productJson.serverDownloadUrlTemplate
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,626 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as crypto from 'crypto';
|
||||
import Log from './common/logger';
|
||||
import { getVSCodeServerConfig } from './serverConfig';
|
||||
import SSHConnection from './ssh/sshConnection';
|
||||
|
||||
export interface ServerInstallOptions {
|
||||
id: string;
|
||||
quality: string;
|
||||
commit: string;
|
||||
version: string;
|
||||
release?: string; // void specific
|
||||
extensionIds: string[];
|
||||
envVariables: string[];
|
||||
useSocketPath: boolean;
|
||||
serverApplicationName: string;
|
||||
serverDataFolderName: string;
|
||||
serverDownloadUrlTemplate: string;
|
||||
}
|
||||
|
||||
export interface ServerInstallResult {
|
||||
exitCode: number;
|
||||
listeningOn: number | string;
|
||||
connectionToken: string;
|
||||
logFile: string;
|
||||
osReleaseId: string;
|
||||
arch: string;
|
||||
platform: string;
|
||||
tmpDir: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export class ServerInstallError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_DOWNLOAD_URL_TEMPLATE = 'https://github.com/voideditor/void-updates-server/releases/download/test/void-server-${os}-${arch}.tar.gz';
|
||||
|
||||
export async function installCodeServer(conn: SSHConnection, serverDownloadUrlTemplate: string | undefined, extensionIds: string[], envVariables: string[], platform: string | undefined, useSocketPath: boolean, logger: Log): Promise<ServerInstallResult> {
|
||||
let shell = 'powershell';
|
||||
|
||||
// detect platform and shell for windows
|
||||
if (!platform || platform === 'windows') {
|
||||
const result = await conn.exec('uname -s');
|
||||
|
||||
if (result.stdout) {
|
||||
if (result.stdout.includes('windows32')) {
|
||||
platform = 'windows';
|
||||
} else if (result.stdout.includes('MINGW64')) {
|
||||
platform = 'windows';
|
||||
shell = 'bash';
|
||||
}
|
||||
} else if (result.stderr) {
|
||||
if (result.stderr.includes('FullyQualifiedErrorId : CommandNotFoundException')) {
|
||||
platform = 'windows';
|
||||
}
|
||||
|
||||
if (result.stderr.includes('is not recognized as an internal or external command')) {
|
||||
platform = 'windows';
|
||||
shell = 'cmd';
|
||||
}
|
||||
}
|
||||
|
||||
if (platform) {
|
||||
logger.trace(`Detected platform: ${platform}, ${shell}`);
|
||||
}
|
||||
}
|
||||
|
||||
const scriptId = crypto.randomBytes(12).toString('hex');
|
||||
|
||||
const vscodeServerConfig = await getVSCodeServerConfig();
|
||||
const installOptions: ServerInstallOptions = {
|
||||
id: scriptId,
|
||||
version: vscodeServerConfig.version,
|
||||
commit: vscodeServerConfig.commit,
|
||||
quality: vscodeServerConfig.quality,
|
||||
release: vscodeServerConfig.release,
|
||||
extensionIds,
|
||||
envVariables,
|
||||
useSocketPath,
|
||||
serverApplicationName: vscodeServerConfig.serverApplicationName,
|
||||
serverDataFolderName: vscodeServerConfig.serverDataFolderName,
|
||||
serverDownloadUrlTemplate: serverDownloadUrlTemplate ?? vscodeServerConfig.serverDownloadUrlTemplate ?? DEFAULT_DOWNLOAD_URL_TEMPLATE,
|
||||
};
|
||||
|
||||
let commandOutput: { stdout: string; stderr: string };
|
||||
if (platform === 'windows') {
|
||||
const installServerScript = generatePowerShellInstallScript(installOptions);
|
||||
|
||||
logger.trace('Server install command:', installServerScript);
|
||||
|
||||
const installDir = `$HOME\\${vscodeServerConfig.serverDataFolderName}\\install`;
|
||||
const installScript = `${installDir}\\${vscodeServerConfig.commit}.ps1`;
|
||||
const endRegex = new RegExp(`${scriptId}: end`);
|
||||
// investigate if it's possible to use `-EncodedCommand` flag
|
||||
// https://devblogs.microsoft.com/powershell/invoking-powershell-with-complex-expressions-using-scriptblocks/
|
||||
let command = '';
|
||||
if (shell === 'powershell') {
|
||||
command = `md -Force ${installDir}; echo @'\n${installServerScript}\n'@ | Set-Content ${installScript}; powershell -ExecutionPolicy ByPass -File "${installScript}"`;
|
||||
} else if (shell === 'bash') {
|
||||
command = `mkdir -p ${installDir.replace(/\\/g, '/')} && echo '\n${installServerScript.replace(/'/g, '\'"\'"\'')}\n' > ${installScript.replace(/\\/g, '/')} && powershell -ExecutionPolicy ByPass -File "${installScript}"`;
|
||||
} else if (shell === 'cmd') {
|
||||
const script = installServerScript.trim()
|
||||
// remove comments
|
||||
.replace(/^#.*$/gm, '')
|
||||
// remove empty lines
|
||||
.replace(/\n{2,}/gm, '\n')
|
||||
// remove leading spaces
|
||||
.replace(/^\s*/gm, '')
|
||||
// escape double quotes (from powershell/cmd)
|
||||
.replace(/"/g, '"""')
|
||||
// escape single quotes (from cmd)
|
||||
.replace(/'/g, `''`)
|
||||
// escape redirect (from cmd)
|
||||
.replace(/>/g, `^>`)
|
||||
// escape new lines (from powershell/cmd)
|
||||
.replace(/\n/g, '\'`n\'');
|
||||
|
||||
command = `powershell "md -Force ${installDir}" && powershell "echo '${script}'" > ${installScript.replace('$HOME', '%USERPROFILE%')} && powershell -ExecutionPolicy ByPass -File "${installScript.replace('$HOME', '%USERPROFILE%')}"`;
|
||||
|
||||
logger.trace('Command length (8191 max):', command.length);
|
||||
|
||||
if (command.length > 8191) {
|
||||
throw new ServerInstallError(`Command line too long`);
|
||||
}
|
||||
} else {
|
||||
throw new ServerInstallError(`Not supported shell: ${shell}`);
|
||||
}
|
||||
|
||||
commandOutput = await conn.execPartial(command, (stdout: string) => endRegex.test(stdout));
|
||||
} else {
|
||||
const installServerScript = generateBashInstallScript(installOptions);
|
||||
|
||||
logger.trace('Server install command:', installServerScript);
|
||||
// Fish shell does not support heredoc so let's workaround it using -c option,
|
||||
// also replace single quotes (') within the script with ('\'') as there's no quoting within single quotes, see https://unix.stackexchange.com/a/24676
|
||||
commandOutput = await conn.exec(`bash -c '${installServerScript.replace(/'/g, `'\\''`)}'`);
|
||||
}
|
||||
|
||||
if (commandOutput.stderr) {
|
||||
logger.trace('Server install command stderr:', commandOutput.stderr);
|
||||
}
|
||||
logger.trace('Server install command stdout:', commandOutput.stdout);
|
||||
|
||||
const resultMap = parseServerInstallOutput(commandOutput.stdout, scriptId);
|
||||
if (!resultMap) {
|
||||
throw new ServerInstallError(`Failed parsing install script output`);
|
||||
}
|
||||
|
||||
const exitCode = parseInt(resultMap.exitCode, 10);
|
||||
if (exitCode !== 0) {
|
||||
throw new ServerInstallError(`Couldn't install vscode server on remote server, install script returned non-zero exit status`);
|
||||
}
|
||||
|
||||
const listeningOn = resultMap.listeningOn.match(/^\d+$/)
|
||||
? parseInt(resultMap.listeningOn, 10)
|
||||
: resultMap.listeningOn;
|
||||
|
||||
const remoteEnvVars = Object.fromEntries(Object.entries(resultMap).filter(([key,]) => envVariables.includes(key)));
|
||||
|
||||
return {
|
||||
exitCode,
|
||||
listeningOn,
|
||||
connectionToken: resultMap.connectionToken,
|
||||
logFile: resultMap.logFile,
|
||||
osReleaseId: resultMap.osReleaseId,
|
||||
arch: resultMap.arch,
|
||||
platform: resultMap.platform,
|
||||
tmpDir: resultMap.tmpDir,
|
||||
...remoteEnvVars
|
||||
};
|
||||
}
|
||||
|
||||
function parseServerInstallOutput(str: string, scriptId: string): { [k: string]: string } | undefined {
|
||||
const startResultStr = `${scriptId}: start`;
|
||||
const endResultStr = `${scriptId}: end`;
|
||||
|
||||
const startResultIdx = str.indexOf(startResultStr);
|
||||
if (startResultIdx < 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const endResultIdx = str.indexOf(endResultStr, startResultIdx + startResultStr.length);
|
||||
if (endResultIdx < 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const installResult = str.substring(startResultIdx + startResultStr.length, endResultIdx);
|
||||
|
||||
const resultMap: { [k: string]: string } = {};
|
||||
const resultArr = installResult.split(/\r?\n/);
|
||||
for (const line of resultArr) {
|
||||
const [key, value] = line.split('==');
|
||||
resultMap[key] = value;
|
||||
}
|
||||
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
function generateBashInstallScript({ id, quality, version, commit, release, extensionIds, envVariables, useSocketPath, serverApplicationName, serverDataFolderName, serverDownloadUrlTemplate }: ServerInstallOptions) {
|
||||
const extensions = extensionIds.map(id => '--install-extension ' + id).join(' ');
|
||||
return `
|
||||
# Server installation script
|
||||
|
||||
TMP_DIR="\${XDG_RUNTIME_DIR:-"/tmp"}"
|
||||
|
||||
DISTRO_VERSION="${version}"
|
||||
DISTRO_COMMIT="${commit}"
|
||||
DISTRO_QUALITY="${quality}"
|
||||
DISTRO_VOID_RELEASE="${release ?? ''}"
|
||||
|
||||
SERVER_APP_NAME="${serverApplicationName}"
|
||||
SERVER_INITIAL_EXTENSIONS="${extensions}"
|
||||
SERVER_LISTEN_FLAG="${useSocketPath ? `--socket-path="$TMP_DIR/vscode-server-sock-${crypto.randomUUID()}"` : '--port=0'}"
|
||||
SERVER_DATA_DIR="$HOME/${serverDataFolderName}"
|
||||
SERVER_DIR="$SERVER_DATA_DIR/bin/$DISTRO_COMMIT"
|
||||
SERVER_SCRIPT="$SERVER_DIR/bin/$SERVER_APP_NAME"
|
||||
SERVER_LOGFILE="$SERVER_DATA_DIR/.$DISTRO_COMMIT.log"
|
||||
SERVER_PIDFILE="$SERVER_DATA_DIR/.$DISTRO_COMMIT.pid"
|
||||
SERVER_TOKENFILE="$SERVER_DATA_DIR/.$DISTRO_COMMIT.token"
|
||||
SERVER_ARCH=
|
||||
SERVER_CONNECTION_TOKEN=
|
||||
SERVER_DOWNLOAD_URL=
|
||||
|
||||
LISTENING_ON=
|
||||
OS_RELEASE_ID=
|
||||
ARCH=
|
||||
PLATFORM=
|
||||
|
||||
# Mimic output from logs of remote-ssh extension
|
||||
print_install_results_and_exit() {
|
||||
echo "${id}: start"
|
||||
echo "exitCode==$1=="
|
||||
echo "listeningOn==$LISTENING_ON=="
|
||||
echo "connectionToken==$SERVER_CONNECTION_TOKEN=="
|
||||
echo "logFile==$SERVER_LOGFILE=="
|
||||
echo "osReleaseId==$OS_RELEASE_ID=="
|
||||
echo "arch==$ARCH=="
|
||||
echo "platform==$PLATFORM=="
|
||||
echo "tmpDir==$TMP_DIR=="
|
||||
${envVariables.map(envVar => `echo "${envVar}==$${envVar}=="`).join('\n')}
|
||||
echo "${id}: end"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Check if platform is supported
|
||||
KERNEL="$(uname -s)"
|
||||
case $KERNEL in
|
||||
Darwin)
|
||||
PLATFORM="darwin"
|
||||
;;
|
||||
Linux)
|
||||
PLATFORM="linux"
|
||||
;;
|
||||
FreeBSD)
|
||||
PLATFORM="freebsd"
|
||||
;;
|
||||
DragonFly)
|
||||
PLATFORM="dragonfly"
|
||||
;;
|
||||
*)
|
||||
echo "Error platform not supported: $KERNEL"
|
||||
print_install_results_and_exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Check machine architecture
|
||||
ARCH="$(uname -m)"
|
||||
case $ARCH in
|
||||
x86_64 | amd64)
|
||||
SERVER_ARCH="x64"
|
||||
;;
|
||||
armv7l | armv8l)
|
||||
SERVER_ARCH="armhf"
|
||||
;;
|
||||
arm64 | aarch64)
|
||||
SERVER_ARCH="arm64"
|
||||
;;
|
||||
ppc64le)
|
||||
SERVER_ARCH="ppc64le"
|
||||
;;
|
||||
riscv64)
|
||||
SERVER_ARCH="riscv64"
|
||||
;;
|
||||
loongarch64)
|
||||
SERVER_ARCH="loong64"
|
||||
;;
|
||||
s390x)
|
||||
SERVER_ARCH="s390x"
|
||||
;;
|
||||
*)
|
||||
echo "Error architecture not supported: $ARCH"
|
||||
print_install_results_and_exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# https://www.freedesktop.org/software/systemd/man/os-release.html
|
||||
OS_RELEASE_ID="$(grep -i '^ID=' /etc/os-release 2>/dev/null | sed 's/^ID=//gi' | sed 's/"//g')"
|
||||
if [[ -z $OS_RELEASE_ID ]]; then
|
||||
OS_RELEASE_ID="$(grep -i '^ID=' /usr/lib/os-release 2>/dev/null | sed 's/^ID=//gi' | sed 's/"//g')"
|
||||
if [[ -z $OS_RELEASE_ID ]]; then
|
||||
OS_RELEASE_ID="unknown"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create installation folder
|
||||
if [[ ! -d $SERVER_DIR ]]; then
|
||||
mkdir -p $SERVER_DIR
|
||||
if (( $? > 0 )); then
|
||||
echo "Error creating server install directory"
|
||||
print_install_results_and_exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# adjust platform for void download, if needed
|
||||
if [[ $OS_RELEASE_ID = alpine ]]; then
|
||||
PLATFORM=$OS_RELEASE_ID
|
||||
fi
|
||||
|
||||
SERVER_DOWNLOAD_URL="$(echo "${serverDownloadUrlTemplate.replace(/\$\{/g, '\\${')}" | sed "s/\\\${quality}/$DISTRO_QUALITY/g" | sed "s/\\\${version}/$DISTRO_VERSION/g" | sed "s/\\\${commit}/$DISTRO_COMMIT/g" | sed "s/\\\${os}/$PLATFORM/g" | sed "s/\\\${arch}/$SERVER_ARCH/g" | sed "s/\\\${release}/$DISTRO_VOID_RELEASE/g")"
|
||||
|
||||
# Check if server script is already installed
|
||||
if [[ ! -f $SERVER_SCRIPT ]]; then
|
||||
case "$PLATFORM" in
|
||||
darwin | linux | alpine )
|
||||
;;
|
||||
*)
|
||||
echo "Error '$PLATFORM' needs manual installation of remote extension host"
|
||||
print_install_results_and_exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
pushd $SERVER_DIR > /dev/null
|
||||
|
||||
if [[ ! -z $(which wget) ]]; then
|
||||
wget --tries=3 --timeout=10 --continue --no-verbose -O vscode-server.tar.gz $SERVER_DOWNLOAD_URL
|
||||
elif [[ ! -z $(which curl) ]]; then
|
||||
curl --retry 3 --connect-timeout 10 --location --show-error --silent --output vscode-server.tar.gz $SERVER_DOWNLOAD_URL
|
||||
else
|
||||
echo "Error no tool to download server binary"
|
||||
print_install_results_and_exit 1
|
||||
fi
|
||||
|
||||
if (( $? > 0 )); then
|
||||
echo "Error downloading server from $SERVER_DOWNLOAD_URL"
|
||||
print_install_results_and_exit 1
|
||||
fi
|
||||
|
||||
tar -xf vscode-server.tar.gz --strip-components 1
|
||||
if (( $? > 0 )); then
|
||||
echo "Error while extracting server contents"
|
||||
print_install_results_and_exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f $SERVER_SCRIPT ]]; then
|
||||
echo "Error server contents are corrupted"
|
||||
print_install_results_and_exit 1
|
||||
fi
|
||||
|
||||
rm -f vscode-server.tar.gz
|
||||
|
||||
popd > /dev/null
|
||||
else
|
||||
echo "Server script already installed in $SERVER_SCRIPT"
|
||||
fi
|
||||
|
||||
# Try to find if server is already running
|
||||
if [[ -f $SERVER_PIDFILE ]]; then
|
||||
SERVER_PID="$(cat $SERVER_PIDFILE)"
|
||||
SERVER_RUNNING_PROCESS="$(ps -o pid,args -p $SERVER_PID | grep $SERVER_SCRIPT)"
|
||||
else
|
||||
SERVER_RUNNING_PROCESS="$(ps -o pid,args -A | grep $SERVER_SCRIPT | grep -v grep)"
|
||||
fi
|
||||
|
||||
if [[ -z $SERVER_RUNNING_PROCESS ]]; then
|
||||
if [[ -f $SERVER_LOGFILE ]]; then
|
||||
rm $SERVER_LOGFILE
|
||||
fi
|
||||
if [[ -f $SERVER_TOKENFILE ]]; then
|
||||
rm $SERVER_TOKENFILE
|
||||
fi
|
||||
|
||||
touch $SERVER_TOKENFILE
|
||||
chmod 600 $SERVER_TOKENFILE
|
||||
SERVER_CONNECTION_TOKEN="${crypto.randomUUID()}"
|
||||
echo $SERVER_CONNECTION_TOKEN > $SERVER_TOKENFILE
|
||||
|
||||
$SERVER_SCRIPT --start-server --host=127.0.0.1 $SERVER_LISTEN_FLAG $SERVER_INITIAL_EXTENSIONS --connection-token-file $SERVER_TOKENFILE --telemetry-level off --enable-remote-auto-shutdown --accept-server-license-terms &> $SERVER_LOGFILE &
|
||||
echo $! > $SERVER_PIDFILE
|
||||
else
|
||||
echo "Server script is already running $SERVER_SCRIPT"
|
||||
fi
|
||||
|
||||
if [[ -f $SERVER_TOKENFILE ]]; then
|
||||
SERVER_CONNECTION_TOKEN="$(cat $SERVER_TOKENFILE)"
|
||||
else
|
||||
echo "Error server token file not found $SERVER_TOKENFILE"
|
||||
print_install_results_and_exit 1
|
||||
fi
|
||||
|
||||
if [[ -f $SERVER_LOGFILE ]]; then
|
||||
for i in {1..5}; do
|
||||
LISTENING_ON="$(cat $SERVER_LOGFILE | grep -E 'Extension host agent listening on .+' | sed 's/Extension host agent listening on //')"
|
||||
if [[ -n $LISTENING_ON ]]; then
|
||||
break
|
||||
fi
|
||||
sleep 0.5
|
||||
done
|
||||
|
||||
if [[ -z $LISTENING_ON ]]; then
|
||||
echo "Error server did not start successfully"
|
||||
print_install_results_and_exit 1
|
||||
fi
|
||||
else
|
||||
echo "Error server log file not found $SERVER_LOGFILE"
|
||||
print_install_results_and_exit 1
|
||||
fi
|
||||
|
||||
# Finish server setup
|
||||
print_install_results_and_exit 0
|
||||
`;
|
||||
}
|
||||
|
||||
function generatePowerShellInstallScript({ id, quality, version, commit, release, extensionIds, envVariables, useSocketPath, serverApplicationName, serverDataFolderName, serverDownloadUrlTemplate }: ServerInstallOptions) {
|
||||
const extensions = extensionIds.map(id => '--install-extension ' + id).join(' ');
|
||||
const downloadUrl = serverDownloadUrlTemplate
|
||||
.replace(/\$\{quality\}/g, quality)
|
||||
.replace(/\$\{version\}/g, version)
|
||||
.replace(/\$\{commit\}/g, commit)
|
||||
.replace(/\$\{os\}/g, 'win32')
|
||||
.replace(/\$\{arch\}/g, 'x64')
|
||||
.replace(/\$\{release\}/g, release ?? '');
|
||||
|
||||
return `
|
||||
# Server installation script
|
||||
|
||||
$TMP_DIR="$env:TEMP\\$([System.IO.Path]::GetRandomFileName())"
|
||||
$ProgressPreference = "SilentlyContinue"
|
||||
|
||||
$DISTRO_VERSION="${version}"
|
||||
$DISTRO_COMMIT="${commit}"
|
||||
$DISTRO_QUALITY="${quality}"
|
||||
$DISTRO_VOID_RELEASE="${release ?? ''}"
|
||||
|
||||
$SERVER_APP_NAME="${serverApplicationName}"
|
||||
$SERVER_INITIAL_EXTENSIONS="${extensions}"
|
||||
$SERVER_LISTEN_FLAG="${useSocketPath ? `--socket-path="$TMP_DIR/vscode-server-sock-${crypto.randomUUID()}"` : '--port=0'}"
|
||||
$SERVER_DATA_DIR="$(Resolve-Path ~)\\${serverDataFolderName}"
|
||||
$SERVER_DIR="$SERVER_DATA_DIR\\bin\\$DISTRO_COMMIT"
|
||||
$SERVER_SCRIPT="$SERVER_DIR\\bin\\$SERVER_APP_NAME.cmd"
|
||||
$SERVER_LOGFILE="$SERVER_DATA_DIR\\.$DISTRO_COMMIT.log"
|
||||
$SERVER_PIDFILE="$SERVER_DATA_DIR\\.$DISTRO_COMMIT.pid"
|
||||
$SERVER_TOKENFILE="$SERVER_DATA_DIR\\.$DISTRO_COMMIT.token"
|
||||
$SERVER_ARCH=
|
||||
$SERVER_CONNECTION_TOKEN=
|
||||
$SERVER_DOWNLOAD_URL=
|
||||
|
||||
$LISTENING_ON=
|
||||
$OS_RELEASE_ID=
|
||||
$ARCH=
|
||||
$PLATFORM="win32"
|
||||
|
||||
function printInstallResults($code) {
|
||||
"${id}: start"
|
||||
"exitCode==$code=="
|
||||
"listeningOn==$LISTENING_ON=="
|
||||
"connectionToken==$SERVER_CONNECTION_TOKEN=="
|
||||
"logFile==$SERVER_LOGFILE=="
|
||||
"osReleaseId==$OS_RELEASE_ID=="
|
||||
"arch==$ARCH=="
|
||||
"platform==$PLATFORM=="
|
||||
"tmpDir==$TMP_DIR=="
|
||||
${envVariables.map(envVar => `"${envVar}==$${envVar}=="`).join('\n')}
|
||||
"${id}: end"
|
||||
}
|
||||
|
||||
# Check machine architecture
|
||||
$ARCH=$env:PROCESSOR_ARCHITECTURE
|
||||
# Use x64 version for ARM64, as it's not yet available.
|
||||
if(($ARCH -eq "AMD64") -or ($ARCH -eq "IA64") -or ($ARCH -eq "ARM64")) {
|
||||
$SERVER_ARCH="x64"
|
||||
}
|
||||
else {
|
||||
"Error architecture not supported: $ARCH"
|
||||
printInstallResults 1
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Create installation folder
|
||||
if(!(Test-Path $SERVER_DIR)) {
|
||||
try {
|
||||
ni -it d $SERVER_DIR -f -ea si
|
||||
} catch {
|
||||
"Error creating server install directory - $($_.ToString())"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if(!(Test-Path $SERVER_DIR)) {
|
||||
"Error creating server install directory"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
cd $SERVER_DIR
|
||||
|
||||
# Check if server script is already installed
|
||||
if(!(Test-Path $SERVER_SCRIPT)) {
|
||||
del vscode-server.tar.gz
|
||||
|
||||
$REQUEST_ARGUMENTS = @{
|
||||
Uri="${downloadUrl}"
|
||||
TimeoutSec=20
|
||||
OutFile="vscode-server.tar.gz"
|
||||
UseBasicParsing=$True
|
||||
}
|
||||
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
|
||||
Invoke-RestMethod @REQUEST_ARGUMENTS
|
||||
|
||||
if(Test-Path "vscode-server.tar.gz") {
|
||||
tar -xf vscode-server.tar.gz --strip-components 1
|
||||
|
||||
del vscode-server.tar.gz
|
||||
}
|
||||
|
||||
if(!(Test-Path $SERVER_SCRIPT)) {
|
||||
"Error while installing the server binary"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
else {
|
||||
"Server script already installed in $SERVER_SCRIPT"
|
||||
}
|
||||
|
||||
# Try to find if server is already running
|
||||
if(Get-Process node -ErrorAction SilentlyContinue | Where-Object Path -Like "$SERVER_DIR\\*") {
|
||||
echo "Server script is already running $SERVER_SCRIPT"
|
||||
}
|
||||
else {
|
||||
if(Test-Path $SERVER_LOGFILE) {
|
||||
del $SERVER_LOGFILE
|
||||
}
|
||||
if(Test-Path $SERVER_PIDFILE) {
|
||||
del $SERVER_PIDFILE
|
||||
}
|
||||
if(Test-Path $SERVER_TOKENFILE) {
|
||||
del $SERVER_TOKENFILE
|
||||
}
|
||||
|
||||
$SERVER_CONNECTION_TOKEN="${crypto.randomUUID()}"
|
||||
[System.IO.File]::WriteAllLines($SERVER_TOKENFILE, $SERVER_CONNECTION_TOKEN)
|
||||
|
||||
$SCRIPT_ARGUMENTS="--start-server --host=127.0.0.1 $SERVER_LISTEN_FLAG $SERVER_INITIAL_EXTENSIONS --connection-token-file $SERVER_TOKENFILE --telemetry-level off --enable-remote-auto-shutdown --accept-server-license-terms *> '$SERVER_LOGFILE'"
|
||||
|
||||
$START_ARGUMENTS = @{
|
||||
FilePath = "powershell.exe"
|
||||
WindowStyle = "hidden"
|
||||
ArgumentList = @(
|
||||
"-ExecutionPolicy", "Unrestricted", "-NoLogo", "-NoProfile", "-NonInteractive", "-c", "$SERVER_SCRIPT $SCRIPT_ARGUMENTS"
|
||||
)
|
||||
PassThru = $True
|
||||
}
|
||||
|
||||
$SERVER_ID = (start @START_ARGUMENTS).ID
|
||||
|
||||
if($SERVER_ID) {
|
||||
[System.IO.File]::WriteAllLines($SERVER_PIDFILE, $SERVER_ID)
|
||||
}
|
||||
}
|
||||
|
||||
if(Test-Path $SERVER_TOKENFILE) {
|
||||
$SERVER_CONNECTION_TOKEN="$(cat $SERVER_TOKENFILE)"
|
||||
}
|
||||
else {
|
||||
"Error server token file not found $SERVER_TOKENFILE"
|
||||
printInstallResults 1
|
||||
exit 0
|
||||
}
|
||||
|
||||
sleep -Milliseconds 500
|
||||
|
||||
$SELECT_ARGUMENTS = @{
|
||||
Path = $SERVER_LOGFILE
|
||||
Pattern = "Extension host agent listening on (\\d+)"
|
||||
}
|
||||
|
||||
for($I = 1; $I -le 5; $I++) {
|
||||
if(Test-Path $SERVER_LOGFILE) {
|
||||
$GROUPS = (Select-String @SELECT_ARGUMENTS).Matches.Groups
|
||||
|
||||
if($GROUPS) {
|
||||
$LISTENING_ON = $GROUPS[1].Value
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
sleep -Milliseconds 500
|
||||
}
|
||||
|
||||
if(!(Test-Path $SERVER_LOGFILE)) {
|
||||
"Error server log file not found $SERVER_LOGFILE"
|
||||
printInstallResults 1
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Finish server setup
|
||||
printInstallResults 0
|
||||
|
||||
if($SERVER_ID) {
|
||||
while($True) {
|
||||
if(!(gps -Id $SERVER_ID)) {
|
||||
"server died, exit"
|
||||
exit 0
|
||||
}
|
||||
|
||||
sleep 30
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as os from 'os';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as crypto from 'crypto';
|
||||
import { exists as folderExists } from '../common/files';
|
||||
|
||||
const PATH_SSH_USER_DIR = path.join(os.homedir(), '.ssh');
|
||||
const KNOW_HOST_FILE = path.join(PATH_SSH_USER_DIR, 'known_hosts');
|
||||
const HASH_MAGIC = '|1|';
|
||||
const HASH_DELIM = '|';
|
||||
|
||||
export async function checkNewHostInHostkeys(host: string): Promise<boolean> {
|
||||
const fileContent = await fs.promises.readFile(KNOW_HOST_FILE, { encoding: 'utf8' });
|
||||
const lines = fileContent.split(/\r?\n/);
|
||||
for (let line of lines) {
|
||||
line = line.trim();
|
||||
if (!line.startsWith(HASH_MAGIC)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const [hostEncripted_] = line.split(' ');
|
||||
const [salt_, hostHash_] = hostEncripted_.substring(HASH_MAGIC.length).split(HASH_DELIM);
|
||||
const hostHash = crypto.createHmac('sha1', Buffer.from(salt_, 'base64')).update(host).digest();
|
||||
if (hostHash.toString('base64') === hostHash_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function addHostToHostFile(host: string, hostKey: Buffer, type: string): Promise<void> {
|
||||
if (!folderExists(PATH_SSH_USER_DIR)) {
|
||||
await fs.promises.mkdir(PATH_SSH_USER_DIR, 0o700);
|
||||
}
|
||||
|
||||
const salt = crypto.randomBytes(20);
|
||||
const hostHash = crypto.createHmac('sha1', salt).update(host).digest();
|
||||
|
||||
const entry = `${HASH_MAGIC}${salt.toString('base64')}${HASH_DELIM}${hostHash.toString('base64')} ${type} ${hostKey.toString('base64')}\n`;
|
||||
await fs.promises.appendFile(KNOW_HOST_FILE, entry);
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as crypto from 'crypto';
|
||||
import type { ParsedKey } from 'ssh2-streams';
|
||||
import * as ssh2 from 'ssh2';
|
||||
import { untildify, exists as fileExists } from '../common/files';
|
||||
import Log from '../common/logger';
|
||||
|
||||
const homeDir = os.homedir();
|
||||
const PATH_SSH_CLIENT_ID_DSA = path.join(homeDir, '.ssh', '/id_dsa');
|
||||
const PATH_SSH_CLIENT_ID_ECDSA = path.join(homeDir, '.ssh', '/id_ecdsa');
|
||||
const PATH_SSH_CLIENT_ID_RSA = path.join(homeDir, '.ssh', '/id_rsa');
|
||||
const PATH_SSH_CLIENT_ID_ED25519 = path.join(homeDir, '.ssh', '/id_ed25519');
|
||||
const PATH_SSH_CLIENT_ID_XMSS = path.join(homeDir, '.ssh', '/id_xmss');
|
||||
const PATH_SSH_CLIENT_ID_ECDSA_SK = path.join(homeDir, '.ssh', '/id_ecdsa_sk');
|
||||
const PATH_SSH_CLIENT_ID_ED25519_SK = path.join(homeDir, '.ssh', '/id_ed25519_sk');
|
||||
|
||||
const DEFAULT_IDENTITY_FILES: string[] = [
|
||||
PATH_SSH_CLIENT_ID_RSA,
|
||||
PATH_SSH_CLIENT_ID_ECDSA,
|
||||
PATH_SSH_CLIENT_ID_ECDSA_SK,
|
||||
PATH_SSH_CLIENT_ID_ED25519,
|
||||
PATH_SSH_CLIENT_ID_ED25519_SK,
|
||||
PATH_SSH_CLIENT_ID_XMSS,
|
||||
PATH_SSH_CLIENT_ID_DSA,
|
||||
];
|
||||
|
||||
export interface SSHKey {
|
||||
filename: string;
|
||||
parsedKey: ParsedKey;
|
||||
fingerprint: string;
|
||||
agentSupport?: boolean;
|
||||
isPrivate?: boolean;
|
||||
}
|
||||
|
||||
// From https://github.com/openssh/openssh-portable/blob/acb2059febaddd71ee06c2ebf63dcf211d9ab9f2/sshconnect2.c#L1689-L1690
|
||||
export async function gatherIdentityFiles(identityFiles: string[], sshAgentSock: string | undefined, identitiesOnly: boolean, logger: Log) {
|
||||
identityFiles = identityFiles.map(untildify).map(i => i.replace(/\.pub$/, ''));
|
||||
if (identityFiles.length === 0) {
|
||||
identityFiles.push(...DEFAULT_IDENTITY_FILES);
|
||||
}
|
||||
|
||||
const identityFileContentsResult = await Promise.allSettled(identityFiles.map(async keyPath => {
|
||||
keyPath = await fileExists(keyPath + '.pub') ? keyPath + '.pub' : keyPath;
|
||||
return fs.promises.readFile(keyPath);
|
||||
}));
|
||||
const fileKeys: SSHKey[] = identityFileContentsResult.map((result, i) => {
|
||||
if (result.status === 'rejected') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const parsedResult = ssh2.utils.parseKey(result.value);
|
||||
if (parsedResult instanceof Error || !parsedResult) {
|
||||
logger.error(`Error while parsing SSH public key ${identityFiles[i]}:`, parsedResult);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const parsedKey = Array.isArray(parsedResult) ? parsedResult[0] : parsedResult;
|
||||
const fingerprint = crypto.createHash('sha256').update(parsedKey.getPublicSSH()).digest('base64');
|
||||
|
||||
return {
|
||||
filename: identityFiles[i],
|
||||
parsedKey,
|
||||
fingerprint
|
||||
};
|
||||
}).filter(<T>(v: T | undefined): v is T => !!v);
|
||||
|
||||
let sshAgentParsedKeys: ParsedKey[] = [];
|
||||
try {
|
||||
if (!sshAgentSock) {
|
||||
throw new Error(`SSH_AUTH_SOCK environment variable not defined`);
|
||||
}
|
||||
|
||||
sshAgentParsedKeys = await new Promise<ParsedKey[]>((resolve, reject) => {
|
||||
const sshAgent = new ssh2.OpenSSHAgent(sshAgentSock);
|
||||
sshAgent.getIdentities((err, publicKeys) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(publicKeys || []);
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error(`Couldn't get identities from OpenSSH agent`, e);
|
||||
}
|
||||
|
||||
const sshAgentKeys: SSHKey[] = sshAgentParsedKeys.map(parsedKey => {
|
||||
const fingerprint = crypto.createHash('sha256').update(parsedKey.getPublicSSH()).digest('base64');
|
||||
return {
|
||||
filename: parsedKey.comment,
|
||||
parsedKey,
|
||||
fingerprint,
|
||||
agentSupport: true
|
||||
};
|
||||
});
|
||||
|
||||
const agentKeys: SSHKey[] = [];
|
||||
const preferredIdentityKeys: SSHKey[] = [];
|
||||
for (const agentKey of sshAgentKeys) {
|
||||
const foundIdx = fileKeys.findIndex(k => agentKey.parsedKey.type === k.parsedKey.type && agentKey.fingerprint === k.fingerprint);
|
||||
if (foundIdx >= 0) {
|
||||
preferredIdentityKeys.push({ ...fileKeys[foundIdx], agentSupport: true });
|
||||
fileKeys.splice(foundIdx, 1);
|
||||
} else if (!identitiesOnly) {
|
||||
agentKeys.push(agentKey);
|
||||
}
|
||||
}
|
||||
preferredIdentityKeys.push(...agentKeys);
|
||||
preferredIdentityKeys.push(...fileKeys);
|
||||
|
||||
logger.trace(`Identity keys:`, preferredIdentityKeys.length ? preferredIdentityKeys.map(k => `${k.filename} ${k.parsedKey.type} SHA256:${k.fingerprint}`).join('\n') : 'None');
|
||||
|
||||
return preferredIdentityKeys;
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as os from 'os';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import SSHConfig, { Directive, Line, Section } from '@jeanp413/ssh-config';
|
||||
import * as vscode from 'vscode';
|
||||
import { exists as fileExists, normalizeToSlash, untildify } from '../common/files';
|
||||
import { isWindows } from '../common/platform';
|
||||
import { glob } from 'glob';
|
||||
|
||||
const systemSSHConfig = isWindows ? path.resolve(process.env.ALLUSERSPROFILE || 'C:\\ProgramData', 'ssh\\ssh_config') : '/etc/ssh/ssh_config';
|
||||
const defaultSSHConfigPath = path.resolve(os.homedir(), '.ssh/config');
|
||||
|
||||
export function getSSHConfigPath() {
|
||||
const sshConfigPath = vscode.workspace.getConfiguration('remote.SSH').get<string>('configFile');
|
||||
return sshConfigPath ? untildify(sshConfigPath) : defaultSSHConfigPath;
|
||||
}
|
||||
|
||||
function isDirective(line: Line): line is Directive {
|
||||
return line.type === SSHConfig.DIRECTIVE;
|
||||
}
|
||||
|
||||
function isHostSection(line: Line): line is Section {
|
||||
return isDirective(line) && line.param === 'Host' && !!line.value && !!(line as Section).config;
|
||||
}
|
||||
|
||||
function isIncludeDirective(line: Line): line is Section {
|
||||
return isDirective(line) && line.param === 'Include' && !!line.value;
|
||||
}
|
||||
|
||||
const SSH_CONFIG_PROPERTIES: Record<string, string> = {
|
||||
'host': 'Host',
|
||||
'hostname': 'HostName',
|
||||
'user': 'User',
|
||||
'port': 'Port',
|
||||
'identityagent': 'IdentityAgent',
|
||||
'identitiesonly': 'IdentitiesOnly',
|
||||
'identityfile': 'IdentityFile',
|
||||
'forwardagent': 'ForwardAgent',
|
||||
'preferredauthentications': 'PreferredAuthentications',
|
||||
'proxyjump': 'ProxyJump',
|
||||
'proxycommand': 'ProxyCommand',
|
||||
'include': 'Include',
|
||||
};
|
||||
|
||||
function normalizeProp(prop: Directive) {
|
||||
prop.param = SSH_CONFIG_PROPERTIES[prop.param.toLowerCase()] || prop.param;
|
||||
}
|
||||
|
||||
function normalizeSSHConfig(config: SSHConfig) {
|
||||
for (const line of config) {
|
||||
if (isDirective(line)) {
|
||||
normalizeProp(line);
|
||||
}
|
||||
if (isHostSection(line)) {
|
||||
normalizeSSHConfig(line.config);
|
||||
}
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
async function parseSSHConfigFromFile(filePath: string, userConfig: boolean) {
|
||||
let content = '';
|
||||
if (await fileExists(filePath)) {
|
||||
content = (await fs.promises.readFile(filePath, 'utf8')).trim();
|
||||
}
|
||||
const config = normalizeSSHConfig(SSHConfig.parse(content));
|
||||
|
||||
const includedConfigs: [number, SSHConfig[]][] = [];
|
||||
for (let i = 0; i < config.length; i++) {
|
||||
const line = config[i];
|
||||
if (isIncludeDirective(line)) {
|
||||
const values = (line.value as string).split(',').map(s => s.trim());
|
||||
const configs: SSHConfig[] = [];
|
||||
for (const value of values) {
|
||||
const includePaths = await glob(normalizeToSlash(untildify(value)), {
|
||||
absolute: true,
|
||||
cwd: normalizeToSlash(path.dirname(userConfig ? defaultSSHConfigPath : systemSSHConfig))
|
||||
});
|
||||
for (const p of includePaths) {
|
||||
configs.push(await parseSSHConfigFromFile(p, userConfig));
|
||||
}
|
||||
}
|
||||
includedConfigs.push([i, configs]);
|
||||
}
|
||||
}
|
||||
for (const [idx, includeConfigs] of includedConfigs.reverse()) {
|
||||
config.splice(idx, 1, ...includeConfigs.flat());
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
export default class SSHConfiguration {
|
||||
|
||||
static async loadFromFS(): Promise<SSHConfiguration> {
|
||||
const config = await parseSSHConfigFromFile(getSSHConfigPath(), true);
|
||||
config.push(...await parseSSHConfigFromFile(systemSSHConfig, false));
|
||||
|
||||
return new SSHConfiguration(config);
|
||||
}
|
||||
|
||||
constructor(private sshConfig: SSHConfig) {
|
||||
}
|
||||
|
||||
getAllConfiguredHosts(): string[] {
|
||||
const hosts = new Set<string>();
|
||||
for (const line of this.sshConfig) {
|
||||
if (isHostSection(line)) {
|
||||
const value = Array.isArray(line.value) ? line.value[0] : line.value;
|
||||
const isPattern = /^!/.test(value) || /[?*]/.test(value);
|
||||
if (!isPattern) {
|
||||
hosts.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [...hosts.keys()];
|
||||
}
|
||||
|
||||
getHostConfiguration(host: string): Record<string, string> {
|
||||
// Only a few directives return an array
|
||||
// https://github.com/jeanp413/ssh-config/blob/8d187bb8f1d83a51ff2b1d127e6b6269d24092b5/src/ssh-config.ts#L9C1-L9C118
|
||||
return this.sshConfig.compute(host) as Record<string, string>;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import * as net from 'net';
|
||||
import * as fs from 'fs';
|
||||
import * as stream from 'stream';
|
||||
import { Client, ClientChannel, ClientErrorExtensions, ExecOptions, ShellOptions, ConnectConfig } from 'ssh2';
|
||||
import { Server } from 'net';
|
||||
import { SocksConnectionInfo, createServer as createSocksServer } from 'simple-socks';
|
||||
|
||||
export interface SSHConnectConfig extends ConnectConfig {
|
||||
/** Optional Unique ID attached to ssh connection. */
|
||||
uniqueId?: string;
|
||||
/** Automatic retry to connect, after disconnect. Default true */
|
||||
reconnect?: boolean;
|
||||
/** Number of reconnect retry, after disconnect. Default 10 */
|
||||
reconnectTries?: number;
|
||||
/** Delay after which reconnect should be done. Default 5000ms */
|
||||
reconnectDelay?: number;
|
||||
/** Path to private key */
|
||||
identity?: string | Buffer;
|
||||
}
|
||||
|
||||
export interface SSHTunnelConfig {
|
||||
/** Remote Address to connect */
|
||||
remoteAddr?: string;
|
||||
/** Local port to bind to. By default, it will bind to a random port, if not passed */
|
||||
localPort?: number;
|
||||
/** Remote Port to connect */
|
||||
remotePort?: number;
|
||||
/** Remote socket path to connect */
|
||||
remoteSocketPath?: string;
|
||||
socks?: boolean;
|
||||
/** Unique name */
|
||||
name?: string;
|
||||
}
|
||||
|
||||
const defaultOptions: Partial<SSHConnectConfig> = {
|
||||
reconnect: false,
|
||||
port: 22,
|
||||
reconnectTries: 3,
|
||||
reconnectDelay: 5000
|
||||
};
|
||||
|
||||
const SSHConstants = {
|
||||
'CHANNEL': {
|
||||
SSH: 'ssh',
|
||||
TUNNEL: 'tunnel',
|
||||
X11: 'x11'
|
||||
},
|
||||
'STATUS': {
|
||||
BEFORECONNECT: 'beforeconnect',
|
||||
CONNECT: 'connect',
|
||||
BEFOREDISCONNECT: 'beforedisconnect',
|
||||
DISCONNECT: 'disconnect'
|
||||
}
|
||||
};
|
||||
|
||||
export default class SSHConnection extends EventEmitter {
|
||||
public config: SSHConnectConfig;
|
||||
|
||||
private activeTunnels: { [index: string]: SSHTunnelConfig & { server: Server } } = {};
|
||||
private __$connectPromise: Promise<SSHConnection> | null = null;
|
||||
private __retries: number = 0;
|
||||
private __err: Error & ClientErrorExtensions & { code?: string } | null = null;
|
||||
private sshConnection: Client | null = null;
|
||||
|
||||
constructor(options: SSHConnectConfig) {
|
||||
super();
|
||||
this.config = Object.assign({}, defaultOptions, options);
|
||||
this.config.uniqueId = this.config.uniqueId || `${this.config.username}@${this.config.host}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit message on this channel
|
||||
*/
|
||||
override emit(channel: string, status: string, payload?: any): boolean {
|
||||
super.emit(channel, status, this, payload);
|
||||
return super.emit(`${channel}:${status}`, this, payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get shell socket
|
||||
*/
|
||||
shell(options: ShellOptions = {}): Promise<ClientChannel> {
|
||||
return this.connect().then(() => {
|
||||
return new Promise<ClientChannel>((resolve, reject) => {
|
||||
this.sshConnection!.shell(options, (err, stream) => err ? reject(err) : resolve(stream));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Exec a command
|
||||
*/
|
||||
exec(cmd: string, params?: Array<string>, options: ExecOptions = {}): Promise<{ stdout: string; stderr: string }> {
|
||||
cmd += (Array.isArray(params) ? (' ' + params.join(' ')) : '');
|
||||
return this.connect().then(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.sshConnection!.exec(cmd, options, (err, stream) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
stream.on('close', function () {
|
||||
return resolve({ stdout, stderr });
|
||||
}).on('data', function (data: Buffer | string) {
|
||||
stdout += data.toString();
|
||||
}).stderr.on('data', function (data: Buffer | string) {
|
||||
stderr += data.toString();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Exec a command
|
||||
*/
|
||||
execPartial(cmd: string, tester: (stdout: string, stderr: string) => boolean, params?: Array<string>, options: ExecOptions = {}): Promise<{ stdout: string; stderr: string }> {
|
||||
cmd += (Array.isArray(params) ? (' ' + params.join(' ')) : '');
|
||||
return this.connect().then(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.sshConnection!.exec(cmd, options, (err, stream) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
let resolved = false;
|
||||
stream.on('close', function () {
|
||||
if (!resolved) {
|
||||
return resolve({ stdout, stderr });
|
||||
}
|
||||
}).on('data', function (data: Buffer | string) {
|
||||
stdout += data.toString();
|
||||
|
||||
if (tester(stdout, stderr)) {
|
||||
resolved = true;
|
||||
|
||||
return resolve({ stdout, stderr });
|
||||
}
|
||||
}).stderr.on('data', function (data: Buffer | string) {
|
||||
stderr += data.toString();
|
||||
|
||||
if (tester(stdout, stderr)) {
|
||||
resolved = true;
|
||||
|
||||
return resolve({ stdout, stderr });
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward out
|
||||
*/
|
||||
forwardOut(srcIP: string, srcPort: number, destIP: string, destPort: number): Promise<ClientChannel> {
|
||||
return this.connect().then(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.sshConnection!.forwardOut(srcIP, srcPort, destIP, destPort, (err, stream) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(stream);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Socks Port
|
||||
*/
|
||||
getSocksPort(localPort: number): Promise<number> {
|
||||
return this.addTunnel({ name: '__socksServer', socks: true, localPort: localPort }).then((tunnel) => {
|
||||
return tunnel.localPort!;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Close SSH Connection
|
||||
*/
|
||||
close(): Promise<void> {
|
||||
this.emit(SSHConstants.CHANNEL.SSH, SSHConstants.STATUS.BEFOREDISCONNECT);
|
||||
return this.closeTunnel().then(() => {
|
||||
if (this.sshConnection) {
|
||||
this.sshConnection.end();
|
||||
this.emit(SSHConstants.CHANNEL.SSH, SSHConstants.STATUS.DISCONNECT);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect the SSH Connection
|
||||
*/
|
||||
connect(c?: SSHConnectConfig): Promise<SSHConnection> {
|
||||
this.config = Object.assign(this.config, c);
|
||||
++this.__retries;
|
||||
|
||||
if (this.__$connectPromise) {
|
||||
return this.__$connectPromise;
|
||||
}
|
||||
|
||||
this.__$connectPromise = new Promise((resolve, reject) => {
|
||||
this.emit(SSHConstants.CHANNEL.SSH, SSHConstants.STATUS.BEFORECONNECT);
|
||||
if (!this.config || typeof this.config === 'function' || !(this.config.host || this.config.sock) || !this.config.username) {
|
||||
reject(`Invalid SSH connection configuration host/username can't be empty`);
|
||||
this.__$connectPromise = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.config.identity) {
|
||||
if (fs.existsSync(this.config.identity)) {
|
||||
this.config.privateKey = fs.readFileSync(this.config.identity);
|
||||
}
|
||||
delete this.config.identity;
|
||||
}
|
||||
|
||||
//Start ssh server connection
|
||||
this.sshConnection = new Client();
|
||||
this.sshConnection.on('ready', (err: Error & ClientErrorExtensions) => {
|
||||
if (err) {
|
||||
this.emit(SSHConstants.CHANNEL.SSH, SSHConstants.STATUS.DISCONNECT, { err: err });
|
||||
this.__$connectPromise = null;
|
||||
return reject(err);
|
||||
}
|
||||
this.emit(SSHConstants.CHANNEL.SSH, SSHConstants.STATUS.CONNECT);
|
||||
this.__retries = 0;
|
||||
this.__err = null;
|
||||
resolve(this);
|
||||
}).on('error', (err) => {
|
||||
this.emit(SSHConstants.CHANNEL.SSH, SSHConstants.STATUS.DISCONNECT, { err: err });
|
||||
this.__err = err;
|
||||
}).on('close', () => {
|
||||
this.emit(SSHConstants.CHANNEL.SSH, SSHConstants.STATUS.DISCONNECT, { err: this.__err });
|
||||
if (this.config.reconnect && this.__retries <= this.config.reconnectTries! && this.__err && this.__err.level !== 'client-authentication' && this.__err.code !== 'ENOTFOUND') {
|
||||
setTimeout(() => {
|
||||
this.__$connectPromise = null;
|
||||
resolve(this.connect());
|
||||
}, this.config.reconnectDelay);
|
||||
} else {
|
||||
reject(this.__err);
|
||||
}
|
||||
}).connect(this.config);
|
||||
});
|
||||
return this.__$connectPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get existing tunnel by name
|
||||
*/
|
||||
getTunnel(name: string) {
|
||||
return this.activeTunnels[name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new tunnel if not exist
|
||||
*/
|
||||
addTunnel(SSHTunnelConfig: SSHTunnelConfig): Promise<SSHTunnelConfig & { server: Server }> {
|
||||
SSHTunnelConfig.name = SSHTunnelConfig.name || `${SSHTunnelConfig.remoteAddr}@${SSHTunnelConfig.remotePort || SSHTunnelConfig.remoteSocketPath}`;
|
||||
this.emit(SSHConstants.CHANNEL.TUNNEL, SSHConstants.STATUS.BEFORECONNECT, { SSHTunnelConfig: SSHTunnelConfig });
|
||||
if (this.getTunnel(SSHTunnelConfig.name)) {
|
||||
this.emit(SSHConstants.CHANNEL.TUNNEL, SSHConstants.STATUS.CONNECT, { SSHTunnelConfig: SSHTunnelConfig });
|
||||
return Promise.resolve(this.getTunnel(SSHTunnelConfig.name));
|
||||
} else {
|
||||
return new Promise((resolve, reject) => {
|
||||
let server: net.Server;
|
||||
if (SSHTunnelConfig.socks) {
|
||||
server = createSocksServer({
|
||||
connectionFilter: (destination: SocksConnectionInfo, origin: SocksConnectionInfo, callback: (err?: any, dest?: stream.Duplex) => void) => {
|
||||
this.connect().then(() => {
|
||||
this.sshConnection!.forwardOut(
|
||||
origin.address,
|
||||
origin.port,
|
||||
destination.address,
|
||||
destination.port,
|
||||
(err, stream) => {
|
||||
if (err) {
|
||||
this.emit(SSHConstants.CHANNEL.TUNNEL, SSHConstants.STATUS.DISCONNECT, { SSHTunnelConfig: SSHTunnelConfig, err: err });
|
||||
return callback(err);
|
||||
}
|
||||
return callback(null, stream);
|
||||
});
|
||||
});
|
||||
}
|
||||
}).on('proxyError', (err: any) => {
|
||||
this.emit(SSHConstants.CHANNEL.TUNNEL, SSHConstants.STATUS.DISCONNECT, { SSHTunnelConfig: SSHTunnelConfig, err: err });
|
||||
});
|
||||
} else {
|
||||
server = net.createServer()
|
||||
.on('connection', (socket) => {
|
||||
this.connect().then(() => {
|
||||
if (SSHTunnelConfig.remotePort) {
|
||||
this.sshConnection!.forwardOut('127.0.0.1', 0, SSHTunnelConfig.remoteAddr!, SSHTunnelConfig.remotePort!, (err, stream) => {
|
||||
if (err) {
|
||||
this.emit(SSHConstants.CHANNEL.TUNNEL, SSHConstants.STATUS.DISCONNECT, { SSHTunnelConfig: SSHTunnelConfig, err: err });
|
||||
return;
|
||||
}
|
||||
stream.pipe(socket);
|
||||
socket.pipe(stream);
|
||||
});
|
||||
} else {
|
||||
this.sshConnection!.openssh_forwardOutStreamLocal(SSHTunnelConfig.remoteSocketPath!, (err, stream) => {
|
||||
if (err) {
|
||||
this.emit(SSHConstants.CHANNEL.TUNNEL, SSHConstants.STATUS.DISCONNECT, { SSHTunnelConfig: SSHTunnelConfig, err: err });
|
||||
return;
|
||||
}
|
||||
stream.pipe(socket);
|
||||
socket.pipe(stream);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
SSHTunnelConfig.localPort = SSHTunnelConfig.localPort || 0;
|
||||
server.on('listening', () => {
|
||||
SSHTunnelConfig.localPort = (server.address() as net.AddressInfo).port;
|
||||
this.activeTunnels[SSHTunnelConfig.name!] = Object.assign({}, { server }, SSHTunnelConfig);
|
||||
this.emit(SSHConstants.CHANNEL.TUNNEL, SSHConstants.STATUS.CONNECT, { SSHTunnelConfig: SSHTunnelConfig });
|
||||
resolve(this.activeTunnels[SSHTunnelConfig.name!]);
|
||||
}).on('error', (err: any) => {
|
||||
this.emit(SSHConstants.CHANNEL.TUNNEL, SSHConstants.STATUS.DISCONNECT, { SSHTunnelConfig: SSHTunnelConfig, err: err });
|
||||
server.close();
|
||||
reject(err);
|
||||
delete this.activeTunnels[SSHTunnelConfig.name!];
|
||||
}).listen(SSHTunnelConfig.localPort);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the tunnel
|
||||
*/
|
||||
closeTunnel(name?: string): Promise<void> {
|
||||
if (name && this.activeTunnels[name]) {
|
||||
return new Promise((resolve) => {
|
||||
const tunnel = this.activeTunnels[name];
|
||||
this.emit(
|
||||
SSHConstants.CHANNEL.TUNNEL,
|
||||
SSHConstants.STATUS.BEFOREDISCONNECT,
|
||||
{ SSHTunnelConfig: tunnel }
|
||||
);
|
||||
tunnel.server.close(() => {
|
||||
this.emit(
|
||||
SSHConstants.CHANNEL.TUNNEL,
|
||||
SSHConstants.STATUS.DISCONNECT,
|
||||
{ SSHTunnelConfig: this.activeTunnels[name] }
|
||||
);
|
||||
delete this.activeTunnels[name];
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
} else if (!name) {
|
||||
const tunnels = Object.keys(this.activeTunnels).map((key) => this.closeTunnel(key));
|
||||
return Promise.all(tunnels).then(() => { });
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
export default class SSHDestination {
|
||||
constructor(
|
||||
public readonly hostname: string,
|
||||
public readonly user?: string,
|
||||
public readonly port?: number
|
||||
) {
|
||||
}
|
||||
|
||||
static parse(dest: string): SSHDestination {
|
||||
let user: string | undefined;
|
||||
const atPos = dest.lastIndexOf('@');
|
||||
if (atPos !== -1) {
|
||||
user = dest.substring(0, atPos);
|
||||
}
|
||||
|
||||
let port: number | undefined;
|
||||
const colonPos = dest.lastIndexOf(':');
|
||||
if (colonPos !== -1) {
|
||||
port = parseInt(dest.substring(colonPos + 1), 10);
|
||||
}
|
||||
|
||||
const start = atPos !== -1 ? atPos + 1 : 0;
|
||||
const end = colonPos !== -1 ? colonPos : dest.length;
|
||||
const hostname = dest.substring(start, end);
|
||||
|
||||
return new SSHDestination(hostname, user, port);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
let result = this.hostname;
|
||||
if (this.user) {
|
||||
result = `${this.user}@` + result;
|
||||
}
|
||||
if (this.port) {
|
||||
result = result + `:${this.port}`;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// vscode.uri implementation lowercases the authority, so when reopen or restore
|
||||
// a remote session from the recently openend list the connection fails
|
||||
static parseEncoded(dest: string): SSHDestination {
|
||||
try {
|
||||
const data = JSON.parse(Buffer.from(dest, 'hex').toString());
|
||||
return new SSHDestination(data.hostName, data.user, data.port);
|
||||
} catch {
|
||||
}
|
||||
return SSHDestination.parse(dest.replace(/\\x([0-9a-f]{2})/g, (_, charCode) => String.fromCharCode(parseInt(charCode, 16))));
|
||||
}
|
||||
|
||||
toEncodedString(): string {
|
||||
return this.toString().replace(/[A-Z]/g, (ch) => `\\x${ch.charCodeAt(0).toString(16).toLowerCase()}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out",
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"../../src/vscode-dts/vscode.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.resolvers.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.contribViewsRemote.d.ts",
|
||||
]
|
||||
}
|
||||
@@ -10,6 +10,9 @@
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"buildreact": "cd ./src/vs/workbench/contrib/void/browser/react/ && node build.js && cd ../../../../../../../",
|
||||
"watchreact": "cd ./src/vs/workbench/contrib/void/browser/react/ && node build.js --watch && cd ../../../../../../../",
|
||||
"watchreactd": "deemon npm run watchreact",
|
||||
"test": "echo Please run any of the test scripts from the scripts folder.",
|
||||
"test-browser": "npx playwright install && node test/unit/browser/index.js",
|
||||
"test-browser-no-install": "node test/unit/browser/index.js",
|
||||
@@ -68,8 +71,12 @@
|
||||
"update-build-ts-version": "npm install typescript@next && tsc -p ./build/tsconfig.build.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.39.0",
|
||||
"@floating-ui/react": "^0.27.5",
|
||||
"@google/generative-ai": "^0.22.0",
|
||||
"@microsoft/1ds-core-js": "^3.2.13",
|
||||
"@microsoft/1ds-post-js": "^3.2.13",
|
||||
"@mistralai/mistralai": "^1.5.0",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@vscode/deviceid": "^0.1.1",
|
||||
@@ -95,6 +102,9 @@
|
||||
"@xterm/addon-webgl": "^0.19.0-beta.98",
|
||||
"@xterm/headless": "^5.6.0-beta.98",
|
||||
"@xterm/xterm": "^5.6.0-beta.98",
|
||||
"cross-spawn": "^7.0.6",
|
||||
"diff": "^7.0.0",
|
||||
"groq-sdk": "^0.15.0",
|
||||
"http-proxy-agent": "^7.0.0",
|
||||
"https-proxy-agent": "^7.0.2",
|
||||
"jschardet": "3.1.4",
|
||||
@@ -104,20 +114,28 @@
|
||||
"native-keymap": "^3.3.5",
|
||||
"native-watchdog": "^1.4.1",
|
||||
"node-pty": "1.1.0-beta30",
|
||||
"ollama": "^0.5.14",
|
||||
"open": "^8.4.2",
|
||||
"openai": "^4.86.1",
|
||||
"posthog-node": "^4.8.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"tas-client-umd": "0.2.0",
|
||||
"v8-inspect-profiler": "^0.1.1",
|
||||
"vscode-html-languageservice": "^5.3.1",
|
||||
"vscode-oniguruma": "1.7.0",
|
||||
"vscode-regexpp": "^3.1.0",
|
||||
"vscode-textmate": "9.2.0",
|
||||
"yauzl": "^3.0.0",
|
||||
"yazl": "^2.4.3"
|
||||
"yazl": "^2.5.1",
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.50.0",
|
||||
"@stylistic/eslint-plugin-ts": "^2.8.0",
|
||||
"@types/cookie": "^0.3.3",
|
||||
"@types/debug": "^4.1.5",
|
||||
"@types/diff": "^7.0.1",
|
||||
"@types/eslint": "^9.6.1",
|
||||
"@types/gulp-svgmin": "^1.2.1",
|
||||
"@types/http-proxy-agent": "^2.0.1",
|
||||
@@ -125,6 +143,8 @@
|
||||
"@types/minimist": "^1.2.1",
|
||||
"@types/mocha": "^9.1.1",
|
||||
"@types/node": "20.x",
|
||||
"@types/react": "^19.0.10",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"@types/sinon": "^10.0.2",
|
||||
"@types/sinon-test": "^2.4.2",
|
||||
"@types/trusted-types": "^1.0.6",
|
||||
@@ -194,7 +214,10 @@
|
||||
"mocha": "^10.8.2",
|
||||
"mocha-junit-reporter": "^2.2.1",
|
||||
"mocha-multi-reporters": "^1.5.1",
|
||||
"next": "^15.2.0",
|
||||
"nodemon": "^3.1.9",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"original-fs": "^1.2.0",
|
||||
"os-browserify": "^0.3.0",
|
||||
"p-all": "^1.0.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
@@ -202,17 +225,20 @@
|
||||
"postcss-nesting": "^12.0.2",
|
||||
"pump": "^1.0.1",
|
||||
"rcedit": "^1.1.0",
|
||||
"rimraf": "^2.2.8",
|
||||
"rimraf": "^2.7.1",
|
||||
"scope-tailwind": "^1.0.6",
|
||||
"sinon": "^12.0.1",
|
||||
"sinon-test": "^3.1.3",
|
||||
"source-map": "0.6.1",
|
||||
"source-map-support": "^0.3.2",
|
||||
"style-loader": "^3.3.2",
|
||||
"tailwindcss": "^4.0.9",
|
||||
"ts-loader": "^9.5.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsec": "0.2.7",
|
||||
"tslib": "^2.6.3",
|
||||
"typescript": "^5.8.0-dev.20250207",
|
||||
"tsup": "^8.4.0",
|
||||
"typescript": "^5.8.2",
|
||||
"typescript-eslint": "^8.8.0",
|
||||
"util": "^0.12.4",
|
||||
"webpack": "^5.94.0",
|
||||
|
||||
@@ -1,84 +1,38 @@
|
||||
{
|
||||
"nameShort": "Code - OSS",
|
||||
"nameLong": "Code - OSS",
|
||||
"applicationName": "code-oss",
|
||||
"dataFolderName": ".vscode-oss",
|
||||
"win32MutexName": "vscodeoss",
|
||||
"nameShort": "Void",
|
||||
"nameLong": "Void",
|
||||
"applicationName": "void",
|
||||
"dataFolderName": ".void-editor",
|
||||
"win32MutexName": "voideditor",
|
||||
"licenseName": "MIT",
|
||||
"licenseUrl": "https://github.com/microsoft/vscode/blob/main/LICENSE.txt",
|
||||
"serverLicenseUrl": "https://github.com/microsoft/vscode/blob/main/LICENSE.txt",
|
||||
"licenseUrl": "https://github.com/voideditor/void/blob/main/LICENSE.txt",
|
||||
"serverLicenseUrl": "https://github.com/voideditor/void/blob/main/LICENSE.txt",
|
||||
"serverGreeting": [],
|
||||
"serverLicense": [],
|
||||
"serverLicensePrompt": "",
|
||||
"serverApplicationName": "code-server-oss",
|
||||
"serverDataFolderName": ".vscode-server-oss",
|
||||
"tunnelApplicationName": "code-tunnel-oss",
|
||||
"win32DirName": "Microsoft Code OSS",
|
||||
"win32NameVersion": "Microsoft Code OSS",
|
||||
"win32RegValueName": "CodeOSS",
|
||||
"win32x64AppId": "{{D77B7E06-80BA-4137-BCF4-654B95CCEBC5}",
|
||||
"win32arm64AppId": "{{D1ACE434-89C5-48D1-88D3-E2991DF85475}",
|
||||
"win32x64UserAppId": "{{CC6B787D-37A0-49E8-AE24-8559A032BE0C}",
|
||||
"win32arm64UserAppId": "{{3AEBF0C8-F733-4AD4-BADE-FDB816D53D7B}",
|
||||
"win32AppUserModelId": "Microsoft.CodeOSS",
|
||||
"win32ShellNameShort": "C&ode - OSS",
|
||||
"win32TunnelServiceMutex": "vscodeoss-tunnelservice",
|
||||
"win32TunnelMutex": "vscodeoss-tunnel",
|
||||
"darwinBundleIdentifier": "com.visualstudio.code.oss",
|
||||
"linuxIconName": "code-oss",
|
||||
"serverApplicationName": "void-server",
|
||||
"serverDataFolderName": ".void-server",
|
||||
"tunnelApplicationName": "void-tunnel",
|
||||
"win32DirName": "Void",
|
||||
"win32NameVersion": "Void",
|
||||
"win32RegValueName": "VoidEditor",
|
||||
"win32x64AppId": "{{9D394D01-1728-45A7-B997-A6C82C5452C3}",
|
||||
"win32arm64AppId": "{{0668DD58-2BDE-4101-8CDA-40252DF8875D}",
|
||||
"win32x64UserAppId": "{{8BED5DC1-6C55-46E6-9FE6-18F7E6F7C7F1}",
|
||||
"win32arm64UserAppId": "{{F6C87466-BC82-4A8F-B0FF-18CA366BA4D8}",
|
||||
"win32AppUserModelId": "Void.Editor",
|
||||
"win32ShellNameShort": "V&oid",
|
||||
"win32TunnelServiceMutex": "void-tunnelservice",
|
||||
"win32TunnelMutex": "void-tunnel",
|
||||
"darwinBundleIdentifier": "com.voideditor.code",
|
||||
"linuxIconName": "void-editor",
|
||||
"licenseFileName": "LICENSE.txt",
|
||||
"reportIssueUrl": "https://github.com/microsoft/vscode/issues/new",
|
||||
"reportIssueUrl": "https://github.com/voideditor/void/issues/new",
|
||||
"nodejsRepository": "https://nodejs.org",
|
||||
"urlProtocol": "code-oss",
|
||||
"webviewContentExternalBaseUrlTemplate": "https://{{uuid}}.vscode-cdn.net/insider/ef65ac1ba57f57f2a3961bfe94aa20481caca4c6/out/vs/workbench/contrib/webview/browser/pre/",
|
||||
"builtInExtensions": [
|
||||
{
|
||||
"name": "ms-vscode.js-debug-companion",
|
||||
"version": "1.1.3",
|
||||
"sha256": "7380a890787452f14b2db7835dfa94de538caf358ebc263f9d46dd68ac52de93",
|
||||
"repo": "https://github.com/microsoft/vscode-js-debug-companion",
|
||||
"metadata": {
|
||||
"id": "99cb0b7f-7354-4278-b8da-6cc79972169d",
|
||||
"publisherId": {
|
||||
"publisherId": "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee",
|
||||
"publisherName": "ms-vscode",
|
||||
"displayName": "Microsoft",
|
||||
"flags": "verified"
|
||||
},
|
||||
"publisherDisplayName": "Microsoft"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ms-vscode.js-debug",
|
||||
"version": "1.97.1",
|
||||
"sha256": "977dd854805547702e312e176f68a1b142fa123f228258f47f0964560ad32496",
|
||||
"repo": "https://github.com/microsoft/vscode-js-debug",
|
||||
"metadata": {
|
||||
"id": "25629058-ddac-4e17-abba-74678e126c5d",
|
||||
"publisherId": {
|
||||
"publisherId": "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee",
|
||||
"publisherName": "ms-vscode",
|
||||
"displayName": "Microsoft",
|
||||
"flags": "verified"
|
||||
},
|
||||
"publisherDisplayName": "Microsoft"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ms-vscode.vscode-js-profile-table",
|
||||
"version": "1.0.10",
|
||||
"sha256": "7361748ddf9fd09d8a2ed1f2a2d7376a2cf9aae708692820b799708385c38e08",
|
||||
"repo": "https://github.com/microsoft/vscode-js-profile-visualizer",
|
||||
"metadata": {
|
||||
"id": "7e52b41b-71ad-457b-ab7e-0620f1fc4feb",
|
||||
"publisherId": {
|
||||
"publisherId": "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee",
|
||||
"publisherName": "ms-vscode",
|
||||
"displayName": "Microsoft",
|
||||
"flags": "verified"
|
||||
},
|
||||
"publisherDisplayName": "Microsoft"
|
||||
}
|
||||
}
|
||||
]
|
||||
"urlProtocol": "void",
|
||||
"extensionsGallery": {
|
||||
"serviceUrl": "https://open-vsx.org/vscode/gallery",
|
||||
"itemUrl": "https://open-vsx.org/vscode/item"
|
||||
},
|
||||
"builtInExtensions": []
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"@xterm/headless": "^5.6.0-beta.98",
|
||||
"@xterm/xterm": "^5.6.0-beta.98",
|
||||
"cookie": "^0.7.0",
|
||||
"debounced": "1.0.2",
|
||||
"http-proxy-agent": "^7.0.0",
|
||||
"https-proxy-agent": "^7.0.2",
|
||||
"jschardet": "3.1.4",
|
||||
@@ -39,6 +40,7 @@
|
||||
"native-watchdog": "^1.4.1",
|
||||
"node-pty": "1.1.0-beta30",
|
||||
"tas-client-umd": "0.2.0",
|
||||
"tslib": "^2.8.1",
|
||||
"vscode-oniguruma": "1.7.0",
|
||||
"vscode-regexpp": "^3.1.0",
|
||||
"vscode-textmate": "9.2.0",
|
||||
@@ -720,6 +722,12 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/debounced": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/debounced/-/debounced-1.0.2.tgz",
|
||||
"integrity": "sha512-6GPv+l/OOtdb1DKNY70k5ubuJhVjtBjUnujC5vQAHHrMuvBpDXsTc91xEMTdeA3/v4swYHamtdB9XIN7DcKxpw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
@@ -1398,6 +1406,12 @@
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"@xterm/headless": "^5.6.0-beta.98",
|
||||
"@xterm/xterm": "^5.6.0-beta.98",
|
||||
"cookie": "^0.7.0",
|
||||
"debounced": "1.0.2",
|
||||
"http-proxy-agent": "^7.0.0",
|
||||
"https-proxy-agent": "^7.0.2",
|
||||
"jschardet": "3.1.4",
|
||||
@@ -34,6 +35,7 @@
|
||||
"native-watchdog": "^1.4.1",
|
||||
"node-pty": "1.1.0-beta30",
|
||||
"tas-client-umd": "0.2.0",
|
||||
"tslib": "^2.8.1",
|
||||
"vscode-oniguruma": "1.7.0",
|
||||
"vscode-regexpp": "^3.1.0",
|
||||
"vscode-textmate": "9.2.0",
|
||||
|
||||
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 813 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 254 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 254 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 29 KiB |
@@ -1,9 +1,9 @@
|
||||
<Application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<VisualElements
|
||||
BackgroundColor="#2D2D30"
|
||||
BackgroundColor="#FFFFFF"
|
||||
ShowNameOnSquare150x150Logo="on"
|
||||
Square150x150Logo="resources\app\resources\win32\code_150x150.png"
|
||||
Square70x70Logo="resources\app\resources\win32\code_70x70.png"
|
||||
ForegroundText="light"
|
||||
ShortDisplayName="Code - OSS" />
|
||||
ForegroundText="light"
|
||||
ShortDisplayName="Void" />
|
||||
</Application>
|
||||
|
||||
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 173 KiB |
|
Before Width: | Height: | Size: 395 B After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 338 B After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 795 KiB |
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
// "jsx": "react", // <-- Void added this
|
||||
"compilerOptions": {
|
||||
"esModuleInterop": true,
|
||||
"removeComments": false,
|
||||
|
||||
@@ -160,7 +160,7 @@ export class InputBox extends Widget {
|
||||
this.scrollableElement = new ScrollableElement(this.element, { vertical: ScrollbarVisibility.Auto });
|
||||
|
||||
if (this.options.flexibleWidth) {
|
||||
this.input.setAttribute('wrap', 'off');
|
||||
this.input.setAttribute('wrap', 'off'); // Void changed this to 'on' at one point, but we're no longer using InputBox
|
||||
this.mirror.style.whiteSpace = 'pre';
|
||||
this.mirror.style.wordWrap = 'initial';
|
||||
}
|
||||
|
||||
@@ -119,6 +119,14 @@ import { AuxiliaryWindowsMainService } from '../../platform/auxiliaryWindow/elec
|
||||
import { normalizeNFC } from '../../base/common/normalization.js';
|
||||
import { ICSSDevelopmentService, CSSDevelopmentService } from '../../platform/cssDev/node/cssDevService.js';
|
||||
|
||||
// in theory this is not allowed
|
||||
// ignore the eslint errors below
|
||||
import { IMetricsService } from '../../workbench/contrib/void/common/metricsService.js';
|
||||
import { IVoidUpdateService } from '../../workbench/contrib/void/common/voidUpdateService.js';
|
||||
import { MetricsMainService } from '../../workbench/contrib/void/electron-main/metricsMainService.js';
|
||||
import { VoidMainUpdateService } from '../../workbench/contrib/void/electron-main/voidUpdateMainService.js';
|
||||
import { LLMMessageChannel } from '../../workbench/contrib/void/electron-main/llmMessageChannel.js';
|
||||
|
||||
/**
|
||||
* The main VS Code application. There will only ever be one instance,
|
||||
* even if the user starts many instances (e.g. from the command line).
|
||||
@@ -146,7 +154,7 @@ export class CodeApplication extends Disposable {
|
||||
@IStateService private readonly stateService: IStateService,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IUserDataProfilesMainService private readonly userDataProfilesMainService: IUserDataProfilesMainService
|
||||
@IUserDataProfilesMainService private readonly userDataProfilesMainService: IUserDataProfilesMainService,
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -522,6 +530,16 @@ export class CodeApplication extends Disposable {
|
||||
});
|
||||
|
||||
//#endregion
|
||||
|
||||
// //#region Void IPC
|
||||
// validatedIpcMain.handle('vscode:sendLLMMessage', async (event, data) => {
|
||||
// try {
|
||||
// await this.sendLLMMessage(data);
|
||||
// } catch (error) {
|
||||
// console.error('Error sending LLM message:', error);
|
||||
// }
|
||||
// });
|
||||
// //#endregion
|
||||
}
|
||||
|
||||
private onUnexpectedError(error: Error): void {
|
||||
@@ -1109,6 +1127,10 @@ export class CodeApplication extends Disposable {
|
||||
services.set(ITelemetryService, NullTelemetryService);
|
||||
}
|
||||
|
||||
// Void main process services (required for services with a channel for comm between browser and electron-main (node))
|
||||
services.set(IMetricsService, new SyncDescriptor(MetricsMainService, undefined, false));
|
||||
services.set(IVoidUpdateService, new SyncDescriptor(VoidMainUpdateService, undefined, false));
|
||||
|
||||
// Default Extensions Profile Init
|
||||
services.set(IExtensionsProfileScannerService, new SyncDescriptor(ExtensionsProfileScannerService, undefined, true));
|
||||
services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService, undefined, true));
|
||||
@@ -1227,6 +1249,16 @@ export class CodeApplication extends Disposable {
|
||||
mainProcessElectronServer.registerChannel('logger', loggerChannel);
|
||||
sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel));
|
||||
|
||||
// Void - use loggerChannel as reference
|
||||
const metricsChannel = ProxyChannel.fromService(accessor.get(IMetricsService), disposables);
|
||||
mainProcessElectronServer.registerChannel('void-channel-metrics', metricsChannel);
|
||||
|
||||
const voidUpdatesChannel = ProxyChannel.fromService(accessor.get(IVoidUpdateService), disposables);
|
||||
mainProcessElectronServer.registerChannel('void-channel-update', voidUpdatesChannel);
|
||||
|
||||
const llmMessageChannel = new LLMMessageChannel(accessor.get(IMetricsService));
|
||||
mainProcessElectronServer.registerChannel('void-channel-llmMessageService', llmMessageChannel);
|
||||
|
||||
// Extension Host Debug Broadcasting
|
||||
const electronExtensionHostDebugBroadcastChannel = new ElectronExtensionHostDebugBroadcastChannel(accessor.get(IWindowsMainService));
|
||||
mainProcessElectronServer.registerChannel('extensionhostdebugservice', electronExtensionHostDebugBroadcastChannel);
|
||||
|
||||
@@ -21,7 +21,7 @@ export class ExpandLineSelectionAction extends EditorAction {
|
||||
kbOpts: {
|
||||
weight: KeybindingWeight.EditorCore,
|
||||
kbExpr: EditorContextKeys.textInputFocus,
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KeyL
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KeyM // Void changed this to Cmd+M
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { safeStorage as safeStorageElectron, app } from 'electron';
|
||||
import { isMacintosh, isWindows } from '../../../base/common/platform.js';
|
||||
import { isMacintosh, isWindows, isLinux } from '../../../base/common/platform.js';
|
||||
import { KnownStorageProvider, IEncryptionMainService, PasswordStoreCLIOption } from '../common/encryptionService.js';
|
||||
import { ILogService } from '../../log/common/log.js';
|
||||
|
||||
@@ -23,6 +23,13 @@ export class EncryptionMainService implements IEncryptionMainService {
|
||||
constructor(
|
||||
@ILogService private readonly logService: ILogService
|
||||
) {
|
||||
|
||||
// Void added this as a nice default for linux so you don't need to specify encryption provider
|
||||
if (isLinux && !app.commandLine.getSwitchValue('password-store')) {
|
||||
this.logService.trace('[EncryptionMainService] No password-store switch, defaulting to basic...');
|
||||
app.commandLine.appendSwitch('password-store', PasswordStoreCLIOption.basic);
|
||||
}
|
||||
|
||||
// if this commandLine switch is set, the user has opted in to using basic text encryption
|
||||
if (app.commandLine.getSwitchValue('password-store') === PasswordStoreCLIOption.basic) {
|
||||
this.logService.trace('[EncryptionMainService] setting usePlainTextEncryption to true...');
|
||||
|
||||
@@ -410,6 +410,7 @@ function isVersionValid(currentVersion: string, date: ProductDate, requestedVers
|
||||
}
|
||||
|
||||
if (!isValidVersion(currentVersion, date, desiredVersion)) {
|
||||
// Void - this is where the 'not compatible with current version' comes from
|
||||
notices.push(nls.localize('versionMismatch', "Extension is not compatible with Code {0}. Extension requires: {1}.", currentVersion, requestedVersion));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -64,7 +64,8 @@ export const enum KeybindingWeight {
|
||||
EditorContrib = 100,
|
||||
WorkbenchContrib = 200,
|
||||
BuiltinExtension = 300,
|
||||
ExternalExtension = 400
|
||||
ExternalExtension = 400,
|
||||
VoidExtension = 605, // Void - must trump any external extension
|
||||
}
|
||||
|
||||
export interface ICommandAndKeybindingRule extends IKeybindingRule {
|
||||
|
||||
@@ -31,25 +31,29 @@ export class ServerTelemetryService extends TelemetryService implements IServerT
|
||||
}
|
||||
|
||||
override publicLog(eventName: string, data?: ITelemetryData) {
|
||||
if (this._injectedTelemetryLevel < TelemetryLevel.USAGE) {
|
||||
return;
|
||||
}
|
||||
return super.publicLog(eventName, data);
|
||||
// Void commented this out
|
||||
// if (this._injectedTelemetryLevel < TelemetryLevel.USAGE) {
|
||||
// return;
|
||||
// }
|
||||
// return super.publicLog(eventName, data);
|
||||
}
|
||||
|
||||
override publicLog2<E extends ClassifiedEvent<OmitMetadata<T>> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck<T, E>) {
|
||||
return this.publicLog(eventName, data as ITelemetryData | undefined);
|
||||
// Void commented this out
|
||||
// return this.publicLog(eventName, data as ITelemetryData | undefined);
|
||||
}
|
||||
|
||||
override publicLogError(errorEventName: string, data?: ITelemetryData) {
|
||||
if (this._injectedTelemetryLevel < TelemetryLevel.ERROR) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
return super.publicLogError(errorEventName, data);
|
||||
// Void commented this out
|
||||
// if (this._injectedTelemetryLevel < TelemetryLevel.ERROR) {
|
||||
// return Promise.resolve(undefined);
|
||||
// }
|
||||
// return super.publicLogError(errorEventName, data);
|
||||
}
|
||||
|
||||
override publicLogError2<E extends ClassifiedEvent<OmitMetadata<T>> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck<T, E>) {
|
||||
return this.publicLogError(eventName, data as ITelemetryData | undefined);
|
||||
// Void commented this out
|
||||
// return this.publicLogError(eventName, data as ITelemetryData | undefined);
|
||||
}
|
||||
|
||||
async updateInjectedTelemetryLevel(telemetryLevel: TelemetryLevel): Promise<void> {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DisposableStore } from '../../../base/common/lifecycle.js';
|
||||
import { mixin } from '../../../base/common/objects.js';
|
||||
// import { mixin } from '../../../base/common/objects.js';
|
||||
import { isWeb } from '../../../base/common/platform.js';
|
||||
import { escapeRegExpCharacters } from '../../../base/common/strings.js';
|
||||
import { localize } from '../../../nls.js';
|
||||
@@ -15,7 +15,7 @@ import { IProductService } from '../../product/common/productService.js';
|
||||
import { Registry } from '../../registry/common/platform.js';
|
||||
import { ClassifiedEvent, IGDPRProperty, OmitMetadata, StrictPropertyCheck } from './gdprTypings.js';
|
||||
import { ITelemetryData, ITelemetryService, TelemetryConfiguration, TelemetryLevel, TELEMETRY_CRASH_REPORTER_SETTING_ID, TELEMETRY_OLD_SETTING_ID, TELEMETRY_SECTION_ID, TELEMETRY_SETTING_ID, ICommonProperties } from './telemetry.js';
|
||||
import { cleanData, getTelemetryLevel, ITelemetryAppender } from './telemetryUtils.js';
|
||||
import { /*cleanData,*/ getTelemetryLevel, ITelemetryAppender } from './telemetryUtils.js';
|
||||
|
||||
export interface ITelemetryServiceConfig {
|
||||
appenders: ITelemetryAppender[];
|
||||
@@ -38,7 +38,8 @@ export class TelemetryService implements ITelemetryService {
|
||||
readonly firstSessionDate: string;
|
||||
readonly msftInternal: boolean | undefined;
|
||||
|
||||
private _appenders: ITelemetryAppender[];
|
||||
// Void commented this out (no longer used)
|
||||
// private _appenders: ITelemetryAppender[];
|
||||
private _commonProperties: ICommonProperties;
|
||||
private _experimentProperties: { [name: string]: string } = {};
|
||||
private _piiPaths: string[];
|
||||
@@ -53,7 +54,8 @@ export class TelemetryService implements ITelemetryService {
|
||||
@IConfigurationService private _configurationService: IConfigurationService,
|
||||
@IProductService private _productService: IProductService
|
||||
) {
|
||||
this._appenders = config.appenders;
|
||||
// Void commented this out (no longer used)
|
||||
// this._appenders = config.appenders;
|
||||
this._commonProperties = config.commonProperties ?? Object.create(null);
|
||||
|
||||
this.sessionId = this._commonProperties['sessionID'] as string;
|
||||
@@ -121,44 +123,49 @@ export class TelemetryService implements ITelemetryService {
|
||||
this._disposables.dispose();
|
||||
}
|
||||
|
||||
private _log(eventName: string, eventLevel: TelemetryLevel, data?: ITelemetryData) {
|
||||
// don't send events when the user is optout
|
||||
if (this._telemetryLevel < eventLevel) {
|
||||
return;
|
||||
}
|
||||
// Void commented this out
|
||||
// private _log(eventName: string, eventLevel: TelemetryLevel, data?: ITelemetryData) {
|
||||
// // don't send events when the user is optout
|
||||
// if (this._telemetryLevel < eventLevel) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// add experiment properties
|
||||
data = mixin(data, this._experimentProperties);
|
||||
// // add experiment properties
|
||||
// data = mixin(data, this._experimentProperties);
|
||||
|
||||
// remove all PII from data
|
||||
data = cleanData(data as Record<string, any>, this._cleanupPatterns);
|
||||
// // remove all PII from data
|
||||
// data = cleanData(data as Record<string, any>, this._cleanupPatterns);
|
||||
|
||||
// add common properties
|
||||
data = mixin(data, this._commonProperties);
|
||||
// // add common properties
|
||||
// data = mixin(data, this._commonProperties);
|
||||
|
||||
// Log to the appenders of sufficient level
|
||||
this._appenders.forEach(a => a.log(eventName, data));
|
||||
}
|
||||
// // Log to the appenders of sufficient level
|
||||
// this._appenders.forEach(a => a.log(eventName, data));
|
||||
// }
|
||||
|
||||
publicLog(eventName: string, data?: ITelemetryData) {
|
||||
this._log(eventName, TelemetryLevel.USAGE, data);
|
||||
// Void commented this out
|
||||
// this._log(eventName, TelemetryLevel.USAGE, data);
|
||||
}
|
||||
|
||||
publicLog2<E extends ClassifiedEvent<OmitMetadata<T>> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck<T, E>) {
|
||||
this.publicLog(eventName, data as ITelemetryData);
|
||||
// Void commented this out
|
||||
// this.publicLog(eventName, data as ITelemetryData);
|
||||
}
|
||||
|
||||
publicLogError(errorEventName: string, data?: ITelemetryData) {
|
||||
if (!this._sendErrorTelemetry) {
|
||||
return;
|
||||
}
|
||||
// Void commented this out
|
||||
// if (!this._sendErrorTelemetry) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// Send error event and anonymize paths
|
||||
this._log(errorEventName, TelemetryLevel.ERROR, data);
|
||||
// // Send error event and anonymize paths
|
||||
// this._log(errorEventName, TelemetryLevel.ERROR, data);
|
||||
}
|
||||
|
||||
publicLogError2<E extends ClassifiedEvent<OmitMetadata<T>> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck<T, E>) {
|
||||
this.publicLogError(eventName, data as ITelemetryData);
|
||||
// Void commented this out
|
||||
// this.publicLogError(eventName, data as ITelemetryData);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { timeout } from '../../../base/common/async.js';
|
||||
// import { timeout } from '../../../base/common/async.js';
|
||||
import { CancellationToken } from '../../../base/common/cancellation.js';
|
||||
import { Emitter, Event } from '../../../base/common/event.js';
|
||||
import { IConfigurationService } from '../../configuration/common/configuration.js';
|
||||
import { IEnvironmentMainService } from '../../environment/electron-main/environmentMainService.js';
|
||||
// import { IEnvironmentMainService } from '../../environment/electron-main/environmentMainService.js';
|
||||
import { ILifecycleMainService, LifecycleMainPhase } from '../../lifecycle/electron-main/lifecycleMainService.js';
|
||||
import { ILogService } from '../../log/common/log.js';
|
||||
import { IProductService } from '../../product/common/productService.js';
|
||||
@@ -15,7 +16,10 @@ import { IRequestService } from '../../request/common/request.js';
|
||||
import { AvailableForDownload, DisablementReason, IUpdateService, State, StateType, UpdateType } from '../common/update.js';
|
||||
|
||||
export function createUpdateURL(platform: string, quality: string, productService: IProductService): string {
|
||||
return `${productService.updateUrl}/api/update/${platform}/${quality}/${productService.commit}`;
|
||||
// return `https://voideditor.dev/api/update/${platform}/stable`;
|
||||
// return `${productService.updateUrl}/api/update/${platform}/${quality}/${productService.commit}`;
|
||||
// https://github.com/VSCodium/update-api
|
||||
return `https://updates.voideditor.dev/api/update/${platform}/${quality}/${productService.commit}`;
|
||||
}
|
||||
|
||||
export type UpdateErrorClassification = {
|
||||
@@ -51,7 +55,7 @@ export abstract class AbstractUpdateService implements IUpdateService {
|
||||
@IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService,
|
||||
@IRequestService protected requestService: IRequestService,
|
||||
@ILogService protected logService: ILogService,
|
||||
@IProductService protected readonly productService: IProductService
|
||||
@IProductService protected readonly productService: IProductService,
|
||||
) {
|
||||
lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen)
|
||||
.finally(() => this.initialize());
|
||||
@@ -64,75 +68,35 @@ export abstract class AbstractUpdateService implements IUpdateService {
|
||||
*/
|
||||
protected async initialize(): Promise<void> {
|
||||
if (!this.environmentMainService.isBuilt) {
|
||||
console.log('is NOT built, canceling update service')
|
||||
this.setState(State.Disabled(DisablementReason.NotBuilt));
|
||||
return; // updates are never enabled when running out of sources
|
||||
}
|
||||
console.log('is built, continuing with update service')
|
||||
|
||||
if (this.environmentMainService.disableUpdates) {
|
||||
this.setState(State.Disabled(DisablementReason.DisabledByEnvironment));
|
||||
this.logService.info('update#ctor - updates are disabled by the environment');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.productService.updateUrl || !this.productService.commit) {
|
||||
this.setState(State.Disabled(DisablementReason.MissingConfiguration));
|
||||
this.logService.info('update#ctor - updates are disabled as there is no update URL');
|
||||
return;
|
||||
}
|
||||
|
||||
const updateMode = this.configurationService.getValue<'none' | 'manual' | 'start' | 'default'>('update.mode');
|
||||
const quality = this.getProductQuality(updateMode);
|
||||
|
||||
if (!quality) {
|
||||
this.setState(State.Disabled(DisablementReason.ManuallyDisabled));
|
||||
this.logService.info('update#ctor - updates are disabled by user preference');
|
||||
return;
|
||||
}
|
||||
|
||||
this.url = this.buildUpdateFeedUrl(quality);
|
||||
this.url = this.doBuildUpdateFeedUrl('stable');
|
||||
if (!this.url) {
|
||||
this.setState(State.Disabled(DisablementReason.InvalidConfiguration));
|
||||
this.logService.info('update#ctor - updates are disabled as the update URL is badly formed');
|
||||
return;
|
||||
}
|
||||
|
||||
// hidden setting
|
||||
if (this.configurationService.getValue<boolean>('_update.prss')) {
|
||||
const url = new URL(this.url);
|
||||
url.searchParams.set('prss', 'true');
|
||||
this.url = url.toString();
|
||||
}
|
||||
this.setState(State.Disabled(DisablementReason.ManuallyDisabled));
|
||||
|
||||
this.setState(State.Idle(this.getUpdateType()));
|
||||
|
||||
if (updateMode === 'manual') {
|
||||
this.logService.info('update#ctor - manual checks only; automatic updates are disabled by user preference');
|
||||
return;
|
||||
}
|
||||
// Void - temporarily disabled while we figure out how to do this the right way
|
||||
|
||||
if (updateMode === 'start') {
|
||||
this.logService.info('update#ctor - startup checks only; automatic updates are disabled by user preference');
|
||||
// this.setState(State.Idle(this.getUpdateType()));
|
||||
|
||||
// Check for updates only once after 30 seconds
|
||||
setTimeout(() => this.checkForUpdates(false), 30 * 1000);
|
||||
} else {
|
||||
// Start checking for updates after 30 seconds
|
||||
this.scheduleCheckForUpdates(30 * 1000).then(undefined, err => this.logService.error(err));
|
||||
}
|
||||
// start checking for updates after 10 seconds
|
||||
// this.scheduleCheckForUpdates(10 * 1000).then(undefined, err => this.logService.error(err));
|
||||
}
|
||||
|
||||
private getProductQuality(updateMode: string): string | undefined {
|
||||
return updateMode === 'none' ? undefined : this.productService.quality;
|
||||
}
|
||||
|
||||
private scheduleCheckForUpdates(delay = 60 * 60 * 1000): Promise<void> {
|
||||
return timeout(delay)
|
||||
.then(() => this.checkForUpdates(false))
|
||||
.then(() => {
|
||||
// Check again after 1 hour
|
||||
return this.scheduleCheckForUpdates(60 * 60 * 1000);
|
||||
});
|
||||
}
|
||||
// private async scheduleCheckForUpdates(delay = 60 * 60 * 1000): Promise<void> {
|
||||
// await timeout(delay);
|
||||
// await this.checkForUpdates(false);
|
||||
// return await this.scheduleCheckForUpdates(60 * 60 * 1000);
|
||||
// }
|
||||
|
||||
async checkForUpdates(explicit: boolean): Promise<void> {
|
||||
this.logService.trace('update#checkForUpdates, state = ', this.state.type);
|
||||
@@ -154,6 +118,7 @@ export abstract class AbstractUpdateService implements IUpdateService {
|
||||
await this.doDownloadUpdate(this.state);
|
||||
}
|
||||
|
||||
// override implemented by windows and linux
|
||||
protected async doDownloadUpdate(state: AvailableForDownload): Promise<void> {
|
||||
// noop
|
||||
}
|
||||
@@ -168,6 +133,7 @@ export abstract class AbstractUpdateService implements IUpdateService {
|
||||
await this.doApplyUpdate();
|
||||
}
|
||||
|
||||
// windows overrides this
|
||||
protected async doApplyUpdate(): Promise<void> {
|
||||
// noop
|
||||
}
|
||||
@@ -230,6 +196,6 @@ export abstract class AbstractUpdateService implements IUpdateService {
|
||||
// noop
|
||||
}
|
||||
|
||||
protected abstract buildUpdateFeedUrl(quality: string): string | undefined;
|
||||
protected abstract doBuildUpdateFeedUrl(quality: string): string | undefined;
|
||||
protected abstract doCheckForUpdates(context: any): void;
|
||||
}
|
||||
|
||||
@@ -73,7 +73,8 @@ export class DarwinUpdateService extends AbstractUpdateService implements IRelau
|
||||
this.setState(State.Idle(UpdateType.Archive, message));
|
||||
}
|
||||
|
||||
protected buildUpdateFeedUrl(quality: string): string | undefined {
|
||||
// Void: buildUpdateFeedUrl -> doBuildUpdateFeedUrl
|
||||
protected doBuildUpdateFeedUrl(quality: string): string | undefined {
|
||||
let assetID: string;
|
||||
if (!this.productService.darwinUniversalAssetId) {
|
||||
assetID = process.arch === 'x64' ? 'darwin' : 'darwin-arm64';
|
||||
|
||||
@@ -28,7 +28,8 @@ export class LinuxUpdateService extends AbstractUpdateService {
|
||||
super(lifecycleMainService, configurationService, environmentMainService, requestService, logService, productService);
|
||||
}
|
||||
|
||||
protected buildUpdateFeedUrl(quality: string): string {
|
||||
// Void: buildUpdateFeedUrl -> doBuildUpdateFeedUrl
|
||||
protected doBuildUpdateFeedUrl(quality: string): string {
|
||||
return createUpdateURL(`linux-${process.arch}`, quality, this.productService);
|
||||
}
|
||||
|
||||
|
||||
@@ -99,7 +99,8 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun
|
||||
await super.initialize();
|
||||
}
|
||||
|
||||
protected buildUpdateFeedUrl(quality: string): string | undefined {
|
||||
// Void: buildUpdateFeedUrl -> doBuildUpdateFeedUrl
|
||||
protected doBuildUpdateFeedUrl(quality: string): string | undefined {
|
||||
let platform = `win32-${process.arch}`;
|
||||
|
||||
if (getUpdateType() === UpdateType.Archive) {
|
||||
|
||||
@@ -205,7 +205,7 @@ MenuRegistry.appendMenuItems([{
|
||||
group: '3_workbench_layout_move',
|
||||
command: {
|
||||
id: ToggleSidebarPositionAction.ID,
|
||||
title: localize('move second sidebar left', "Move Secondary Side Bar Left")
|
||||
title: localize('move second sidebar left', "Move Void Side Bar Left") // Void renamed "Secondary" Side Bar to "Void" Side Bar
|
||||
},
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.notEquals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.AuxiliaryBar))),
|
||||
order: 1
|
||||
@@ -216,7 +216,7 @@ MenuRegistry.appendMenuItems([{
|
||||
group: '3_workbench_layout_move',
|
||||
command: {
|
||||
id: ToggleSidebarPositionAction.ID,
|
||||
title: localize('move second sidebar right', "Move Secondary Side Bar Right")
|
||||
title: localize('move second sidebar right', "Move Void Side Bar Right")
|
||||
},
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.equals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.AuxiliaryBar))),
|
||||
order: 1
|
||||
@@ -908,7 +908,7 @@ registerAction2(class extends Action2 {
|
||||
if (!hasAddedView) {
|
||||
results.push({
|
||||
type: 'separator',
|
||||
label: localize('secondarySideBarContainer', "Secondary Side Bar / {0}", containerModel.title)
|
||||
label: localize('secondarySideBarContainer', "Void Side Bar / {0}", containerModel.title)
|
||||
});
|
||||
hasAddedView = true;
|
||||
}
|
||||
@@ -1015,7 +1015,7 @@ class MoveFocusedViewAction extends Action2 {
|
||||
if (!(isViewSolo && currentLocation === ViewContainerLocation.AuxiliaryBar)) {
|
||||
items.push({
|
||||
id: '_.auxiliarybar.newcontainer',
|
||||
label: localize('moveFocusedView.newContainerInSidePanel', "New Secondary Side Bar Entry")
|
||||
label: localize('moveFocusedView.newContainerInSidePanel', "New Void Side Bar Entry")
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1063,7 +1063,7 @@ class MoveFocusedViewAction extends Action2 {
|
||||
|
||||
items.push({
|
||||
type: 'separator',
|
||||
label: localize('secondarySideBar', "Secondary Side Bar")
|
||||
label: localize('secondarySideBar', "Void Side Bar")
|
||||
});
|
||||
|
||||
const pinnedAuxPanels = paneCompositePartService.getPinnedPaneCompositeIds(ViewContainerLocation.AuxiliaryBar);
|
||||
@@ -1381,7 +1381,7 @@ if (!isMacintosh || !isNative) {
|
||||
ToggleVisibilityActions.push(...[
|
||||
CreateToggleLayoutItem(ToggleActivityBarVisibilityActionId, ContextKeyExpr.notEquals('config.workbench.activityBar.location', 'hidden'), localize('activityBar', "Activity Bar"), { whenA: ContextKeyExpr.equals('config.workbench.sideBar.location', 'left'), iconA: activityBarLeftIcon, iconB: activityBarRightIcon }),
|
||||
CreateToggleLayoutItem(ToggleSidebarVisibilityAction.ID, SideBarVisibleContext, localize('sideBar', "Primary Side Bar"), { whenA: ContextKeyExpr.equals('config.workbench.sideBar.location', 'left'), iconA: panelLeftIcon, iconB: panelRightIcon }),
|
||||
CreateToggleLayoutItem(ToggleAuxiliaryBarAction.ID, AuxiliaryBarVisibleContext, localize('secondarySideBar', "Secondary Side Bar"), { whenA: ContextKeyExpr.equals('config.workbench.sideBar.location', 'left'), iconA: panelRightIcon, iconB: panelLeftIcon }),
|
||||
CreateToggleLayoutItem(ToggleAuxiliaryBarAction.ID, AuxiliaryBarVisibleContext, localize('secondarySideBar', "Void Side Bar"), { whenA: ContextKeyExpr.equals('config.workbench.sideBar.location', 'left'), iconA: panelRightIcon, iconB: panelLeftIcon }),
|
||||
CreateToggleLayoutItem(TogglePanelAction.ID, PanelVisibleContext, localize('panel', "Panel"), panelIcon),
|
||||
CreateToggleLayoutItem(ToggleStatusbarVisibilityAction.ID, ContextKeyExpr.equals('config.workbench.statusBar.visible', true), localize('statusBar', "Status Bar"), statusBarIcon),
|
||||
]);
|
||||
|
||||
@@ -2490,7 +2490,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
comment: 'Information about the layout of the workbench during statup';
|
||||
activityBarVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the activity bar is visible' };
|
||||
sideBarVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the primary side bar is visible' };
|
||||
auxiliaryBarVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the secondary side bar is visible' };
|
||||
auxiliaryBarVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the Void side bar is visible' }; // Void side bar
|
||||
panelVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the panel is visible' };
|
||||
statusbarVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the status bar is visible' };
|
||||
sideBarPosition: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the primary side bar is on the left or right' };
|
||||
@@ -2589,7 +2589,7 @@ const LayoutStateKeys = {
|
||||
|
||||
// Part Sizing
|
||||
SIDEBAR_SIZE: new InitializationStateKey<number>('sideBar.size', StorageScope.PROFILE, StorageTarget.MACHINE, 200),
|
||||
AUXILIARYBAR_SIZE: new InitializationStateKey<number>('auxiliaryBar.size', StorageScope.PROFILE, StorageTarget.MACHINE, 200),
|
||||
AUXILIARYBAR_SIZE: new InitializationStateKey<number>('auxiliaryBar.size', StorageScope.PROFILE, StorageTarget.MACHINE, 800), // Void changed this from 200 to 800
|
||||
PANEL_SIZE: new InitializationStateKey<number>('panel.size', StorageScope.PROFILE, StorageTarget.MACHINE, 300),
|
||||
|
||||
PANEL_LAST_NON_MAXIMIZED_HEIGHT: new RuntimeStateKey<number>('panel.lastNonMaximizedHeight', StorageScope.PROFILE, StorageTarget.MACHINE, 300),
|
||||
|
||||